diff --git a/scripts/Actions/Search.ts b/scripts/Actions/Search.ts index 51719334..e25128e5 100644 --- a/scripts/Actions/Search.ts +++ b/scripts/Actions/Search.ts @@ -17,18 +17,14 @@ var correctResponseRendered = { "company": false } +// Search types +var searchTypes = Object.keys(correctResponseRendered) + // Save old term to compare var oldTerm = "" // Containers for all the search results -var animeSearchResults: HTMLElement -var characterSearchResults: HTMLElement -var postsSearchResults: HTMLElement -var threadsSearchResults: HTMLElement -var soundtrackSearchResults: HTMLElement -var userSearchResults: HTMLElement -var amvSearchResults: HTMLElement -var companySearchResults: HTMLElement +var results = new Map() // Delay before a request is sent const searchDelay = 140 @@ -60,14 +56,9 @@ export async function search(arn: AnimeNotifier, search: HTMLInputElement, evt?: oldTerm = term // Reset - correctResponseRendered.anime = false - correctResponseRendered.character = false - correctResponseRendered.posts = false - correctResponseRendered.threads = false - correctResponseRendered.soundtrack = false - correctResponseRendered.user = false - correctResponseRendered.amv = false - correctResponseRendered.company = false + for(let key of searchTypes) { + correctResponseRendered[key] = false + } // Set browser URL let url = "/search/" + term @@ -109,15 +100,11 @@ export async function search(arn: AnimeNotifier, search: HTMLInputElement, evt?: return } - if(!animeSearchResults) { - animeSearchResults = document.getElementById("anime-search-results") - characterSearchResults = document.getElementById("character-search-results") - postsSearchResults = document.getElementById("posts-search-results") - threadsSearchResults = document.getElementById("threads-search-results") - soundtrackSearchResults = document.getElementById("soundtrack-search-results") - userSearchResults = document.getElementById("user-search-results") - amvSearchResults = document.getElementById("amv-search-results") - companySearchResults = document.getElementById("company-search-results") + if(!results["anime"]) { + for(let key of searchTypes) { + results[key] = document.getElementById(`${key}-search-results`) + } + searchPageTitle = document.getElementsByTagName("h1")[0] } @@ -129,9 +116,9 @@ export async function search(arn: AnimeNotifier, search: HTMLInputElement, evt?: return } - // Start searching + // Start searching anime fetch("/_/anime-search/" + term, fetchOptions) - .then(showResponseInElement(arn, url, "anime", animeSearchResults)) + .then(showResponseInElement(arn, url, "anime", results["anime"])) .catch(console.error) requestIdleCallback(() => { @@ -140,33 +127,16 @@ export async function search(arn: AnimeNotifier, search: HTMLInputElement, evt?: return } - fetch("/_/character-search/" + term, fetchOptions) - .then(showResponseInElement(arn, url, "character", characterSearchResults)) - .catch(console.error) + // Search the other types (everything except anime) + for(let key of searchTypes) { + if(key === "anime") { + continue + } - fetch("/_/posts-search/" + term, fetchOptions) - .then(showResponseInElement(arn, url, "posts", postsSearchResults)) - .catch(console.error) - - fetch("/_/threads-search/" + term, fetchOptions) - .then(showResponseInElement(arn, url, "threads", threadsSearchResults)) - .catch(console.error) - - fetch("/_/soundtrack-search/" + term, fetchOptions) - .then(showResponseInElement(arn, url, "soundtrack", soundtrackSearchResults)) - .catch(console.error) - - fetch("/_/user-search/" + term, fetchOptions) - .then(showResponseInElement(arn, url, "user", userSearchResults)) - .catch(console.error) - - fetch("/_/amv-search/" + term, fetchOptions) - .then(showResponseInElement(arn, url, "amv", amvSearchResults)) - .catch(console.error) - - fetch("/_/company-search/" + term, fetchOptions) - .then(showResponseInElement(arn, url, "company", companySearchResults)) - .catch(console.error) + fetch(`/_/${key}-search/` + term, fetchOptions) + .then(showResponseInElement(arn, url, key, results[key])) + .catch(console.error) + } }) } catch(err) { console.error(err) @@ -180,9 +150,9 @@ function showResponseInElement(arn: AnimeNotifier, url: string, typeName: string let html = await response.text() if(html.includes("no-search-results")) { - Diff.mutations.queue(() => element.parentElement.classList.add("search-section-disabled")) + Diff.mutations.queue(() => (element.parentElement as HTMLElement).classList.add("search-section-disabled")) } else { - Diff.mutations.queue(() => element.parentElement.classList.remove("search-section-disabled")) + Diff.mutations.queue(() => (element.parentElement as HTMLElement).classList.remove("search-section-disabled")) } if(arn.app.currentPath !== url) { @@ -236,7 +206,6 @@ export function searchBySpeech(arn: AnimeNotifier, element: HTMLElement) { recognition.onend = e => { searchInput.placeholder = oldPlaceholder element.classList.remove("speech-listening") - recognition = null } // Focus search field diff --git a/scripts/Actions/Serialization.ts b/scripts/Actions/Serialization.ts index cb2a2f64..f212558f 100644 --- a/scripts/Actions/Serialization.ts +++ b/scripts/Actions/Serialization.ts @@ -3,11 +3,16 @@ import { applyTheme } from "./Theme" // Save new data from an input field export async function save(arn: AnimeNotifier, input: HTMLElement) { + if(!input.dataset.field) { + console.error("Input element missing data-field:", input) + return + } + let obj = {} let isContentEditable = input.isContentEditable let value = isContentEditable ? input.textContent : (input as HTMLInputElement).value - if(value === undefined) { + if(value === undefined || value === null) { return } @@ -61,6 +66,11 @@ export async function save(arn: AnimeNotifier, input: HTMLElement) { // Enable (bool field) export async function enable(arn: AnimeNotifier, button: HTMLButtonElement) { + if(!button.dataset.field) { + console.error("Button missing data-field:", button) + return + } + let obj = {} let apiEndpoint = arn.findAPIEndpoint(button) @@ -84,6 +94,11 @@ export async function enable(arn: AnimeNotifier, button: HTMLButtonElement) { // Disable (bool field) export async function disable(arn: AnimeNotifier, button: HTMLButtonElement) { + if(!button.dataset.field) { + console.error("Button missing data-field:", button) + return + } + let obj = {} let apiEndpoint = arn.findAPIEndpoint(button) @@ -106,18 +121,21 @@ export async function disable(arn: AnimeNotifier, button: HTMLButtonElement) { } // Append new element to array -export function arrayAppend(arn: AnimeNotifier, element: HTMLElement) { +export async function arrayAppend(arn: AnimeNotifier, element: HTMLElement) { let field = element.dataset.field let object = element.dataset.object || "" let apiEndpoint = arn.findAPIEndpoint(element) - arn.post(apiEndpoint + "/field/" + field + "/append", object) - .then(() => arn.reloadContent()) - .catch(err => arn.statusMessage.showError(err)) + try { + await arn.post(apiEndpoint + "/field/" + field + "/append", object) + await arn.reloadContent() + } catch(err) { + arn.statusMessage.showError(err) + } } // Remove element from array -export function arrayRemove(arn: AnimeNotifier, element: HTMLElement) { +export async function arrayRemove(arn: AnimeNotifier, element: HTMLElement) { if(!confirm("Are you sure you want to remove this element?")) { return } @@ -126,9 +144,12 @@ export function arrayRemove(arn: AnimeNotifier, element: HTMLElement) { let index = element.dataset.index let apiEndpoint = arn.findAPIEndpoint(element) - arn.post(apiEndpoint + "/field/" + field + "/remove/" + index) - .then(() => arn.reloadContent()) - .catch(err => arn.statusMessage.showError(err)) + try { + await arn.post(apiEndpoint + "/field/" + field + "/remove/" + index) + await arn.reloadContent() + } catch(err) { + arn.statusMessage.showError(err) + } } // Increase episode @@ -137,10 +158,16 @@ export function increaseEpisode(arn: AnimeNotifier, element: HTMLElement) { return } - let prev = element.previousSibling as HTMLElement + let prev = element.previousSibling + + if(prev === null || !(prev instanceof HTMLElement) || prev.textContent === null) { + console.error("Previous sibling is invalid:", element) + return + } + let episodes = parseInt(prev.textContent) prev.textContent = String(episodes + 1) - save(arn, prev) + return save(arn, prev) } // Add number @@ -149,6 +176,11 @@ export function addNumber(arn: AnimeNotifier, element: HTMLElement) { return } + if(!element.dataset.id || !element.dataset.add) { + console.error("Element is missing the data-id or data-add attribute:", element) + return + } + let input = document.getElementById(element.dataset.id) as HTMLInputElement let add = parseInt(element.dataset.add) let num = parseInt(input.value) @@ -167,5 +199,5 @@ export function addNumber(arn: AnimeNotifier, element: HTMLElement) { } input.value = newValue.toString() - save(arn, input) + return save(arn, input) } \ No newline at end of file diff --git a/scripts/Actions/Shop.ts b/scripts/Actions/Shop.ts index 9fa9b4e0..56d46597 100644 --- a/scripts/Actions/Shop.ts +++ b/scripts/Actions/Shop.ts @@ -36,8 +36,19 @@ export function chargeUp(arn: AnimeNotifier, button: HTMLElement) { // Toggle fade export function toggleFade(arn: AnimeNotifier, button: HTMLElement) { let elementId = button.dataset.elementId + + if(!elementId) { + console.error("Missing element ID:", elementId) + return + } + let element = document.getElementById(elementId) + if(!element) { + console.error("Invalid element ID:", elementId) + return + } + if(element.classList.contains("fade-out")) { element.classList.remove("fade-out") } else { diff --git a/scripts/Actions/SideBar.ts b/scripts/Actions/SideBar.ts index 6b542441..8b0746a8 100644 --- a/scripts/Actions/SideBar.ts +++ b/scripts/Actions/SideBar.ts @@ -2,5 +2,6 @@ import AnimeNotifier from "../AnimeNotifier" // Toggle sidebar export function toggleSidebar(arn: AnimeNotifier) { - document.getElementById("sidebar").classList.toggle("sidebar-visible") + let sidebar = document.getElementById("sidebar") as HTMLElement + sidebar.classList.toggle("sidebar-visible") } \ No newline at end of file diff --git a/scripts/Actions/Upload.ts b/scripts/Actions/Upload.ts index b4d40489..36df94b0 100644 --- a/scripts/Actions/Upload.ts +++ b/scripts/Actions/Upload.ts @@ -3,26 +3,31 @@ import { bytesHumanReadable, uploadWithProgress } from "../Utils" // Select file export function selectFile(arn: AnimeNotifier, button: HTMLButtonElement) { - if(button.dataset.endpoint === "/api/upload/cover" && arn.user.dataset.pro !== "true") { + let fileType = button.dataset.type + let endpoint = button.dataset.endpoint + + if(endpoint === "/api/upload/cover" && arn.user && arn.user.dataset.pro !== "true") { alert("Please buy a PRO account to use this feature.") return } - let fileType = button.dataset.type - let endpoint = button.dataset.endpoint - // Click on virtual file input element let input = document.createElement("input") input.setAttribute("type", "file") - input.value = null + input.value = "" input.onchange = async () => { - let file = input.files[0] - - if(!file) { + if(!fileType || !endpoint) { + console.error("Missing data-type or data-endpoint:", button) return } + if(!input.files || input.files.length === 0) { + return + } + + let file = input.files[0] + // Check mime type for images if(fileType === "image" && !file.type.startsWith("image/")) { arn.statusMessage.showError(file.name + " is not an image file!") @@ -171,7 +176,7 @@ function previewImage(dataURL: string, endpoint: string, previews: HTMLCollectio // Update sidebar avatar function updateSideBarAvatar(url: string) { - let sidebar = document.getElementById("sidebar") + let sidebar = document.getElementById("sidebar") as HTMLElement let userImage = sidebar.getElementsByClassName("user-image")[0] as HTMLImageElement let lazyLoad = userImage["became visible"] diff --git a/scripts/Actions/User.ts b/scripts/Actions/User.ts index c1ced1da..86b5bfaa 100644 --- a/scripts/Actions/User.ts +++ b/scripts/Actions/User.ts @@ -3,21 +3,28 @@ import Diff from "scripts/Diff" // Follow user export async function followUser(arn: AnimeNotifier, element: HTMLElement) { - try { - await arn.post(element.dataset.api) - await arn.reloadContent() - arn.statusMessage.showInfo("You are now following " + document.getElementById("nick").textContent + ".") - } catch(err) { - arn.statusMessage.showError(err) - } + return updateFollow(arn, element, "You are now following") } // Unfollow user export async function unfollowUser(arn: AnimeNotifier, element: HTMLElement) { + return updateFollow(arn, element, "You stopped following") +} + +// Update follow +async function updateFollow(arn: AnimeNotifier, element: HTMLElement, message: string) { + let api = element.dataset.api + let nick = document.getElementById("nick") + + if(!api || !nick || !nick.textContent) { + console.error("Missing data-api or invalid nick:", element) + return + } + try { - await arn.post(element.dataset.api) + await arn.post(api) await arn.reloadContent() - arn.statusMessage.showInfo("You stopped following " + document.getElementById("nick").textContent + ".") + arn.statusMessage.showInfo(`${message} ${nick.textContent}.`) } catch(err) { arn.statusMessage.showError(err) } diff --git a/scripts/Actions/Video.ts b/scripts/Actions/Video.ts index bf019be8..7204de15 100644 --- a/scripts/Actions/Video.ts +++ b/scripts/Actions/Video.ts @@ -2,9 +2,21 @@ import AnimeNotifier from "../AnimeNotifier" // Toggle play video export function togglePlayVideo(arn: AnimeNotifier, element: HTMLElement) { - let container = document.getElementById(element.dataset.mediaId) - let video = container.getElementsByTagName("video")[0] + let mediaId = element.dataset.mediaId + if(!mediaId) { + console.error("Missing data-media-id:", element) + return + } + + let container = document.getElementById(mediaId) + + if(!container) { + console.error("Invalid data-media-id:", element) + return + } + + let video = container.getElementsByTagName("video")[0] video.volume = arn.audioPlayer.volume if(video.readyState >= 2) { @@ -30,7 +42,19 @@ function togglePlayVideoElement(video: HTMLVideoElement) { // Toggle fullscreen export function toggleFullscreen(arn: AnimeNotifier, button: HTMLElement) { let elementId = button.dataset.id + + if(!elementId) { + console.error("Missing data-id:", button) + return + } + let element = document.getElementById(elementId) + + if(!element) { + console.error("Invalid data-id:", button) + return + } + let requestFullscreen = element.requestFullscreen || element["mozRequestFullScreen"] || element["webkitRequestFullScreen"] || element["msRequestFullscreen"] let exitFullscreen = document.exitFullscreen || document["mozCancelFullScreen"] || document["webkitExitFullscreen"] || document["msExitFullscreen"] let fullscreen = document.fullscreen || document["webkitIsFullScreen"] || document["mozFullScreen"] diff --git a/scripts/AnimeNotifier.ts b/scripts/AnimeNotifier.ts index cc2b0c96..cef05e8a 100644 --- a/scripts/AnimeNotifier.ts +++ b/scripts/AnimeNotifier.ts @@ -1474,7 +1474,7 @@ export default class AnimeNotifier { } // This is called every time an uncaught JavaScript error is thrown - onError(evt: ErrorEvent) { + async onError(evt: ErrorEvent) { let report = { message: evt.message, stack: evt.error.stack, @@ -1483,8 +1483,11 @@ export default class AnimeNotifier { columnNumber: evt.colno, } - this.post("/api/new/clienterrorreport", report) - .then(() => console.log("Successfully reported the error to the website staff.")) - .catch(() => console.warn("Failed reporting the error to the website staff.")) + try { + await this.post("/api/new/clienterrorreport", report) + console.log("Successfully reported the error to the website staff.") + } catch(err) { + console.warn("Failed reporting the error to the website staff:", err) + } } } \ No newline at end of file