Added null checks

This commit is contained in:
Eduard Urbach 2019-04-19 22:12:33 +09:00
parent 912f409688
commit 707233a422
11 changed files with 94 additions and 51 deletions

View File

@ -18,7 +18,7 @@ import * as actions from "./Actions"
export default class AnimeNotifier { export default class AnimeNotifier {
app: Application app: Application
analytics: Analytics analytics: Analytics
user: HTMLElement user: HTMLElement | null
title: string title: string
webpCheck: Promise<boolean> webpCheck: Promise<boolean>
webpEnabled: boolean webpEnabled: boolean
@ -110,12 +110,18 @@ export default class AnimeNotifier {
run() { run() {
// Initiate the elements we need // Initiate the elements we need
this.user = document.getElementById("user") this.user = document.getElementById("user")
this.app.content = document.getElementById("content") this.app.content = document.getElementById("content") as HTMLElement
this.app.loading = document.getElementById("loading") this.app.loading = document.getElementById("loading") as HTMLElement
// Theme // Theme
if(this.user && this.user.dataset.pro === "true" && this.user.dataset.theme !== "light") { if(this.user && this.user.dataset.pro === "true") {
actions.applyTheme(this.user.dataset.theme) const theme = this.user.dataset.theme
// Don't apply light theme on load because
// it's already the standard theme.
if(theme && theme !== "light") {
actions.applyTheme(theme)
}
} }
// Status message // Status message
@ -196,7 +202,7 @@ export default class AnimeNotifier {
if(document.title !== this.title) { if(document.title !== this.title) {
document.title = this.title document.title = this.title
} }
} else { } else if(headers[0].textContent) {
document.title = headers[0].textContent document.title = headers[0].textContent
} }
} }
@ -289,7 +295,7 @@ export default class AnimeNotifier {
} }
async onBeforeUnload(e: BeforeUnloadEvent) { async onBeforeUnload(e: BeforeUnloadEvent) {
let message = undefined let message = ""
// Prevent closing tab on new thread page // Prevent closing tab on new thread page
if(this.app.currentPath === "/new/thread" && document.activeElement.tagName === "TEXTAREA" && (document.activeElement as HTMLTextAreaElement).value.length > 20) { if(this.app.currentPath === "/new/thread" && document.activeElement.tagName === "TEXTAREA" && (document.activeElement as HTMLTextAreaElement).value.length > 20) {
@ -325,7 +331,14 @@ export default class AnimeNotifier {
// Dynamic label assignment to prevent label texts overflowing // Dynamic label assignment to prevent label texts overflowing
// and taking horizontal space at page load. // and taking horizontal space at page load.
element.dataset.label = element.getAttribute("aria-label") let label = element.getAttribute("aria-label")
if(!label) {
console.error("Tooltip without a label:", element)
return
}
element.dataset.label = label
// This is the most expensive call in this whole function, // This is the most expensive call in this whole function,
// it consumes about 2-4 ms every time you call it. // it consumes about 2-4 ms every time you call it.
@ -350,7 +363,7 @@ export default class AnimeNotifier {
let tipChild = document.createElement("div") let tipChild = document.createElement("div")
tipChild.classList.add("tip-offset-child") tipChild.classList.add("tip-offset-child")
tipChild.setAttribute("data-label", element.getAttribute("data-label")) tipChild.setAttribute("data-label", element.dataset.label)
tipChild.style.left = Math.round(leftOffset) + "px" tipChild.style.left = Math.round(leftOffset) + "px"
tipChild.style.width = rect.width + "px" tipChild.style.width = rect.width + "px"
tipChild.style.height = rect.height + "px" tipChild.style.height = rect.height + "px"
@ -373,24 +386,25 @@ export default class AnimeNotifier {
continue continue
} }
element.addEventListener("dragstart", e => { element.addEventListener("dragstart", evt => {
if(!element.draggable) { if(!element.draggable || !evt.dataTransfer) {
return return
} }
let image = element.getElementsByClassName("anime-list-item-image")[0] let image = element.getElementsByClassName("anime-list-item-image")[0]
if(image) { if(image) {
e.dataTransfer.setDragImage(image, 0, 0) evt.dataTransfer.setDragImage(image, 0, 0)
} }
let name = element.getElementsByClassName("anime-list-item-name")[0] let name = element.getElementsByClassName("anime-list-item-name")[0]
e.dataTransfer.setData("text/plain", JSON.stringify({ evt.dataTransfer.setData("text/plain", JSON.stringify({
api: element.dataset.api, api: element.dataset.api,
animeTitle: name.textContent animeTitle: name.textContent
})) }))
e.dataTransfer.effectAllowed = "move"
evt.dataTransfer.effectAllowed = "move"
}, false) }, false)
// Prevent re-attaching the same listeners // Prevent re-attaching the same listeners
@ -404,7 +418,7 @@ export default class AnimeNotifier {
} }
element.addEventListener("drop", async e => { element.addEventListener("drop", async e => {
let toElement = e.toElement as HTMLElement let toElement = e.toElement
// Find tab element // Find tab element
while(toElement && !toElement.classList.contains("tab")) { while(toElement && !toElement.classList.contains("tab")) {
@ -412,12 +426,12 @@ export default class AnimeNotifier {
} }
// Ignore a drop on the current status tab // Ignore a drop on the current status tab
if(!toElement || toElement.classList.contains("active")) { if(!toElement || toElement.classList.contains("active") || !e.dataTransfer) {
return return
} }
let data = e.dataTransfer.getData("text/plain") let data = e.dataTransfer.getData("text/plain")
let json = null let json: any
try { try {
json = JSON.parse(data) json = JSON.parse(data)
@ -433,6 +447,11 @@ export default class AnimeNotifier {
e.preventDefault() e.preventDefault()
let tabText = toElement.textContent let tabText = toElement.textContent
if(!tabText) {
return
}
let newStatus = tabText.toLowerCase() let newStatus = tabText.toLowerCase()
if(newStatus === "on hold") { if(newStatus === "on hold") {
@ -477,7 +496,7 @@ export default class AnimeNotifier {
} }
element.addEventListener("dragstart", e => { element.addEventListener("dragstart", e => {
if(!element.draggable) { if(!element.draggable || !e.dataTransfer) {
return return
} }
@ -1236,8 +1255,10 @@ export default class AnimeNotifier {
return element.dataset.api return element.dataset.api
} }
let apiObject: HTMLElement let apiObject: HTMLElement | undefined
let parent = element let parent: HTMLElement | null
parent = element
while(parent = parent.parentElement) { while(parent = parent.parentElement) {
if(parent.dataset.api !== undefined) { if(parent.dataset.api !== undefined) {
@ -1246,7 +1267,7 @@ export default class AnimeNotifier {
} }
} }
if(!apiObject) { if(!apiObject || !apiObject.dataset.api) {
this.statusMessage.showError("API object not found") this.statusMessage.showError("API object not found")
throw "API object not found" throw "API object not found"
} }
@ -1269,6 +1290,10 @@ export default class AnimeNotifier {
onKeyDown(e: KeyboardEvent) { onKeyDown(e: KeyboardEvent) {
let activeElement = document.activeElement let activeElement = document.activeElement
if(!activeElement) {
return
}
// Ignore hotkeys on input elements // Ignore hotkeys on input elements
switch(activeElement.tagName) { switch(activeElement.tagName) {
case "INPUT": case "INPUT":

View File

@ -17,7 +17,7 @@ export default class AudioPlayer {
playId = 0 playId = 0
// Save last request so that we can cancel it // Save last request so that we can cancel it
lastRequest: XMLHttpRequest lastRequest: XMLHttpRequest | null
// DOM elements // DOM elements
audioPlayer: HTMLElement audioPlayer: HTMLElement
@ -194,7 +194,7 @@ export default class AudioPlayer {
// Stop // Stop
stop() { stop() {
this.arn.currentMediaId = undefined this.arn.currentMediaId = ""
// Remove CSS class "playing" // Remove CSS class "playing"
let playingElements = document.getElementsByClassName("playing") let playingElements = document.getElementsByClassName("playing")

View File

@ -60,7 +60,7 @@ function getRemainingTime(remaining: number): string {
} }
export function displayAiringDate(element: HTMLElement, now: Date) { export function displayAiringDate(element: HTMLElement, now: Date) {
if(element.dataset.startDate === "") { if(!element.dataset.startDate || !element.dataset.endDate) {
element.textContent = "" element.textContent = ""
return return
} }
@ -133,8 +133,8 @@ export function displayDate(element: HTMLElement, now: Date) {
} }
} }
export function displayTime(element: HTMLElement, now: Date) { export function displayTime(element: HTMLElement) {
if(element.dataset.date === "") { if(!element.dataset.date) {
element.textContent = "" element.textContent = ""
return return
} }

View File

@ -5,7 +5,7 @@ export default class PushManager {
this.pushSupported = ("serviceWorker" in navigator) && ("PushManager" in window) this.pushSupported = ("serviceWorker" in navigator) && ("PushManager" in window)
} }
async subscription(): Promise<PushSubscription> { async subscription() {
if(!this.pushSupported) { if(!this.pushSupported) {
return Promise.resolve(null) return Promise.resolve(null)
} }
@ -13,11 +13,7 @@ export default class PushManager {
let registration = await navigator.serviceWorker.ready let registration = await navigator.serviceWorker.ready
let subscription = await registration.pushManager.getSubscription() let subscription = await registration.pushManager.getSubscription()
if(subscription) { return Promise.resolve(subscription)
return Promise.resolve(subscription)
}
return Promise.resolve(null)
} }
async subscribe(userId: string) { async subscribe(userId: string) {

View File

@ -75,11 +75,16 @@ export default class ServerEvents {
let button = document.getElementById("load-new-activities") let button = document.getElementById("load-new-activities")
if(!button) { if(!button || !button.dataset.count) {
return return
} }
let buttonText = document.getElementById("load-new-activities-text") let buttonText = document.getElementById("load-new-activities-text")
if(!buttonText) {
return
}
let newCount = parseInt(button.dataset.count) + 1 let newCount = parseInt(button.dataset.count) + 1
button.dataset.count = newCount.toString() button.dataset.count = newCount.toString()
buttonText.textContent = plural(newCount, "new activity") buttonText.textContent = plural(newCount, "new activity")

View File

@ -99,12 +99,19 @@ function onActivate(evt: any) {
// Only keep current version of the cache and delete old caches // Only keep current version of the cache and delete old caches
let cacheWhitelist = [cache.version] let cacheWhitelist = [cache.version]
// Query existing cache keys
let deleteOldCache = caches.keys().then(keyList => { let deleteOldCache = caches.keys().then(keyList => {
return Promise.all(keyList.map(key => { // Create a deletion for every key that's not whitelisted
let deletions = keyList.map(key => {
if(cacheWhitelist.indexOf(key) === -1) { if(cacheWhitelist.indexOf(key) === -1) {
return caches.delete(key) return caches.delete(key)
} }
}))
return Promise.resolve(false)
})
// Wait for deletions
return Promise.all(deletions)
}) })
// Immediate claim helps us gain control over a new client immediately // Immediate claim helps us gain control over a new client immediately
@ -334,7 +341,7 @@ function broadcast(msg: object) {
} }
// installCache is called when the service worker is installed for the first time. // installCache is called when the service worker is installed for the first time.
async function installCache() { function installCache() {
let urls = [ let urls = [
"/", "/",
"/scripts", "/scripts",
@ -343,19 +350,17 @@ async function installCache() {
"https://media.notify.moe/images/elements/noise-strong.png", "https://media.notify.moe/images/elements/noise-strong.png",
] ]
let promises = [] let requests = urls.map(async url => {
for(let url of urls) {
let request = new Request(url, { let request = new Request(url, {
credentials: "same-origin", credentials: "same-origin",
mode: "cors" mode: "cors"
}) })
let promise = fetch(request).then(response => cache.store(request, response)) const response = await fetch(request)
promises.push(promise) await cache.store(request, response)
} })
return Promise.all(promises) return Promise.all(requests)
} }
// Serve network first. // Serve network first.

View File

@ -63,7 +63,13 @@ export default class ServiceWorkerManager {
} }
postMessage(message: any) { postMessage(message: any) {
navigator.serviceWorker.controller.postMessage(JSON.stringify(message)) const controller = navigator.serviceWorker.controller
if(!controller) {
return
}
controller.postMessage(JSON.stringify(message))
} }
onMessage(evt: MessageEvent) { onMessage(evt: MessageEvent) {

View File

@ -9,8 +9,9 @@ export default class SideBar {
this.element = element this.element = element
document.body.addEventListener("click", e => { document.body.addEventListener("click", e => {
if(document.activeElement.id === "search") if(document.activeElement && document.activeElement.id === "search") {
return; return
}
this.hide() this.hide()
}) })

View File

@ -15,6 +15,8 @@ export default class TouchController {
this.downSwipe = this.upSwipe = this.rightSwipe = this.leftSwipe = () => null this.downSwipe = this.upSwipe = this.rightSwipe = this.leftSwipe = () => null
this.threshold = 3 this.threshold = 3
this.x = -1
this.y = -1
} }
handleTouchStart(evt) { handleTouchStart(evt) {
@ -23,7 +25,7 @@ export default class TouchController {
} }
handleTouchMove(evt) { handleTouchMove(evt) {
if(!this.x || !this.y) { if(this.x === -1 || this.y === -1) {
return return
} }
@ -47,7 +49,7 @@ export default class TouchController {
} }
} }
this.x = undefined this.x = -1
this.y = undefined this.y = -1
} }
} }

View File

@ -20,8 +20,10 @@ export function uploadWithProgress(url, options: RequestInit, onProgress: ((ev:
xhr.open(options.method || "GET", url, true) xhr.open(options.method || "GET", url, true)
for(let k in options.headers || {}) { if(options.headers) {
xhr.setRequestHeader(k, options.headers[k]) for(let key in options.headers) {
xhr.setRequestHeader(key, options.headers[key])
}
} }
xhr.send(options.body) xhr.send(options.body)

View File

@ -6,6 +6,7 @@
"baseUrl": ".", "baseUrl": ".",
"strict": false, "strict": false,
"strictFunctionTypes": false, "strictFunctionTypes": false,
"strictNullChecks": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": false "noUnusedParameters": false
} }