Added null checks
This commit is contained in:
parent
912f409688
commit
707233a422
@ -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":
|
||||||
|
@ -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")
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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")
|
||||||
|
@ -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.
|
||||||
|
@ -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) {
|
||||||
|
@ -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()
|
||||||
})
|
})
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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)
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"strict": false,
|
"strict": false,
|
||||||
"strictFunctionTypes": false,
|
"strictFunctionTypes": false,
|
||||||
|
"strictNullChecks": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": false
|
"noUnusedParameters": false
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user