Refactor scripts
This commit is contained in:
parent
7e25ee6faf
commit
1ddcd4d570
@ -1,5 +1,5 @@
|
||||
import requestIdleCallback from "scripts/Utils/requestIdleCallback"
|
||||
import AnimeNotifier from "../AnimeNotifier"
|
||||
import { requestIdleCallback } from "../Utils"
|
||||
|
||||
// Load
|
||||
export function load(arn: AnimeNotifier, element: HTMLElement) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import emptyPixel from "scripts/Utils/emptyPixel"
|
||||
import findAll from "scripts/Utils/findAll"
|
||||
import AnimeNotifier from "../AnimeNotifier"
|
||||
import { findAll } from "scripts/Utils"
|
||||
|
||||
// Filter anime on explore page
|
||||
export function filterAnime(arn: AnimeNotifier, _: HTMLInputElement) {
|
||||
@ -12,7 +13,7 @@ export function filterAnime(arn: AnimeNotifier, _: HTMLInputElement) {
|
||||
|
||||
for(const element of findAll("anime-grid-image")) {
|
||||
const img = element as HTMLImageElement
|
||||
img.src = arn.emptyPixel()
|
||||
img.src = emptyPixel
|
||||
img.classList.remove("element-found")
|
||||
img.classList.remove("element-color-preview")
|
||||
}
|
||||
|
@ -2,24 +2,24 @@ import AnimeNotifier from "../AnimeNotifier"
|
||||
|
||||
// Enable notifications
|
||||
export async function enableNotifications(arn: AnimeNotifier, _: HTMLElement) {
|
||||
if(!arn.user || !arn.user.dataset.id) {
|
||||
if(!arn.user) {
|
||||
return
|
||||
}
|
||||
|
||||
arn.statusMessage.showInfo("Enabling instant notifications...")
|
||||
await arn.pushManager.subscribe(arn.user.dataset.id)
|
||||
await arn.pushManager.subscribe(arn.user.id)
|
||||
arn.updatePushUI()
|
||||
arn.statusMessage.showInfo("Enabled instant notifications for this device.")
|
||||
}
|
||||
|
||||
// Disable notifications
|
||||
export async function disableNotifications(arn: AnimeNotifier, _: HTMLElement) {
|
||||
if(!arn.user || !arn.user.dataset.id) {
|
||||
if(!arn.user) {
|
||||
return
|
||||
}
|
||||
|
||||
arn.statusMessage.showInfo("Disabling instant notifications...")
|
||||
await arn.pushManager.unsubscribe(arn.user.dataset.id)
|
||||
await arn.pushManager.unsubscribe(arn.user.id)
|
||||
arn.updatePushUI()
|
||||
arn.statusMessage.showInfo("Disabled instant notifications for this device.")
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Diff from "scripts/Diff"
|
||||
import delay from "scripts/Utils/delay"
|
||||
import requestIdleCallback from "scripts/Utils/requestIdleCallback"
|
||||
import AnimeNotifier from "../AnimeNotifier"
|
||||
import { delay, requestIdleCallback } from "../Utils"
|
||||
import Diff from "scripts/Diff";
|
||||
|
||||
// Search page reference
|
||||
let emptySearchHTML = ""
|
||||
@ -111,7 +112,7 @@ export async function search(arn: AnimeNotifier, search: HTMLInputElement, evt?:
|
||||
searchPageTitle.textContent = document.title
|
||||
|
||||
if(!term || term.length < 1) {
|
||||
await arn.innerHTML(searchPage, emptySearchHTML)
|
||||
await Diff.innerHTML(searchPage, emptySearchHTML)
|
||||
arn.app.emit("DOMContentLoaded")
|
||||
return
|
||||
}
|
||||
@ -168,7 +169,7 @@ function showResponseInElement(arn: AnimeNotifier, url: string, typeName: string
|
||||
correctResponseRendered[typeName] = true
|
||||
}
|
||||
|
||||
await arn.innerHTML(element, html)
|
||||
await Diff.innerHTML(element, html)
|
||||
arn.onNewContent(element)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import hexToHSL from "scripts/Utils/hexToHSL"
|
||||
import AnimeNotifier from "../AnimeNotifier"
|
||||
import { hexToHSL } from "scripts/Utils"
|
||||
|
||||
let currentThemeName = "light"
|
||||
let previewTimeoutID: number = 0
|
||||
@ -7,8 +7,8 @@ let previewTimeoutID: number = 0
|
||||
// let themeWheelTimeoutID: number = 0
|
||||
|
||||
const themes = {
|
||||
"light": {},
|
||||
"dark": {
|
||||
light: {},
|
||||
dark: {
|
||||
"link-color-h": "45",
|
||||
"link-color-s": "100%",
|
||||
"link-color-l": "66%",
|
||||
@ -117,7 +117,7 @@ export function applyThemeAndPreview(arn: AnimeNotifier, themeName: string) {
|
||||
clearTimeout(previewTimeoutID)
|
||||
|
||||
// If it's the free light theme or a PRO user, nothing to do here
|
||||
if(currentThemeName === "light" || (arn.user && arn.user.dataset.pro == "true")) {
|
||||
if(currentThemeName === "light" || (arn.user && arn.user.IsPro())) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
import bytesHumanReadable from "scripts/Utils/bytesHumanReadable"
|
||||
import uploadWithProgress from "scripts/Utils/uploadWithProgress"
|
||||
import AnimeNotifier from "../AnimeNotifier"
|
||||
import { bytesHumanReadable, uploadWithProgress } from "../Utils"
|
||||
|
||||
// Select file
|
||||
export function selectFile(arn: AnimeNotifier, button: HTMLButtonElement) {
|
||||
const fileType = button.dataset.type
|
||||
const endpoint = button.dataset.endpoint
|
||||
|
||||
if(endpoint === "/api/upload/user/cover" && arn.user && arn.user.dataset.pro !== "true") {
|
||||
if(endpoint === "/api/upload/user/cover" && arn.user && !arn.user.IsPro()) {
|
||||
alert("Please buy a PRO account to use this feature.")
|
||||
return
|
||||
}
|
||||
|
@ -1,15 +1,14 @@
|
||||
export default class Analytics {
|
||||
push() {
|
||||
export function uploadAnalytics() {
|
||||
const analytics = {
|
||||
general: {
|
||||
timezoneOffset: new Date().getTimezoneOffset()
|
||||
},
|
||||
screen: {
|
||||
width: screen.width,
|
||||
height: screen.height,
|
||||
availableWidth: screen.availWidth,
|
||||
availableHeight: screen.availHeight,
|
||||
pixelRatio: window.devicePixelRatio
|
||||
availableWidth: screen.availWidth,
|
||||
height: screen.height,
|
||||
pixelRatio: window.devicePixelRatio,
|
||||
width: screen.width
|
||||
},
|
||||
system: {
|
||||
cpuCount: navigator.hardwareConcurrency,
|
||||
@ -17,8 +16,8 @@ export default class Analytics {
|
||||
},
|
||||
connection: {
|
||||
downLink: 0,
|
||||
roundTripTime: 0,
|
||||
effectiveType: ""
|
||||
effectiveType: "",
|
||||
roundTripTime: 0
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,5 +36,4 @@ export default class Analytics {
|
||||
credentials: "same-origin",
|
||||
body: JSON.stringify(analytics)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
import Diff from "./Diff"
|
||||
import LoadOptions from "./LoadOptions"
|
||||
import { delay } from "./Utils"
|
||||
import delay from "./Utils/delay"
|
||||
|
||||
export default class Application {
|
||||
public originalPath: string
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { plural } from "./Utils"
|
||||
import plural from "./Utils/plural"
|
||||
|
||||
const oneSecond = 1000
|
||||
const oneMinute = 60 * oneSecond
|
||||
|
@ -1,39 +0,0 @@
|
||||
import Diff from "./Diff"
|
||||
|
||||
export default class InfiniteScroller {
|
||||
container: HTMLElement
|
||||
threshold: number
|
||||
|
||||
constructor(container, threshold) {
|
||||
this.container = container
|
||||
this.threshold = threshold
|
||||
|
||||
const check = () => {
|
||||
if(this.container.scrollTop + this.container.clientHeight >= this.container.scrollHeight - threshold) {
|
||||
this.loadMore()
|
||||
}
|
||||
}
|
||||
|
||||
this.container.addEventListener("scroll", _ => {
|
||||
// Wait for mutations to finish before checking if we need infinite scroll to trigger.
|
||||
if(Diff.mutations.mutations.length > 0) {
|
||||
Diff.mutations.wait(() => check())
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, queue up the check immediately.
|
||||
// Don't call check() directly to make scrolling as smooth as possible.
|
||||
Diff.mutations.queue(check)
|
||||
})
|
||||
}
|
||||
|
||||
loadMore() {
|
||||
const button = document.getElementById("load-more-button")
|
||||
|
||||
if(!button) {
|
||||
return
|
||||
}
|
||||
|
||||
button.click()
|
||||
}
|
||||
}
|
@ -9,15 +9,15 @@ const timeCapacity = 6.5
|
||||
// It checks the time used to process these mutations and if the time is over the
|
||||
// defined time capacity, it will pause and continue the mutations in the next frame.
|
||||
export default class MutationQueue {
|
||||
mutations: Array<() => void>
|
||||
onClearCallBacks: Array<() => void>
|
||||
private mutations: Array<() => void>
|
||||
private onClearCallBacks: Array<() => void>
|
||||
|
||||
constructor() {
|
||||
this.mutations = []
|
||||
this.onClearCallBacks = []
|
||||
}
|
||||
|
||||
queue(mutation: () => void) {
|
||||
public queue(mutation: () => void) {
|
||||
this.mutations.push(mutation)
|
||||
|
||||
if(this.mutations.length === 1) {
|
||||
@ -25,7 +25,20 @@ export default class MutationQueue {
|
||||
}
|
||||
}
|
||||
|
||||
mutateAll() {
|
||||
public wait(callBack: () => void) {
|
||||
if(this.mutations.length === 0) {
|
||||
callBack()
|
||||
return
|
||||
}
|
||||
|
||||
this.onClearCallBacks.push(callBack)
|
||||
}
|
||||
|
||||
public length() {
|
||||
return this.mutations.length
|
||||
}
|
||||
|
||||
private mutateAll() {
|
||||
const start = performance.now()
|
||||
|
||||
for(let i = 0; i < this.mutations.length; i++) {
|
||||
@ -45,7 +58,7 @@ export default class MutationQueue {
|
||||
this.clear()
|
||||
}
|
||||
|
||||
clear() {
|
||||
private clear() {
|
||||
this.mutations.length = 0
|
||||
|
||||
if(this.onClearCallBacks.length > 0) {
|
||||
@ -56,13 +69,4 @@ export default class MutationQueue {
|
||||
this.onClearCallBacks.length = 0
|
||||
}
|
||||
}
|
||||
|
||||
wait(callBack: () => void) {
|
||||
if(this.mutations.length === 0) {
|
||||
callBack()
|
||||
return
|
||||
}
|
||||
|
||||
this.onClearCallBacks.push(callBack)
|
||||
}
|
||||
}
|
||||
|
3
scripts/ServerEvent/ServerEvent.ts
Normal file
3
scripts/ServerEvent/ServerEvent.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export default class ServerEvent {
|
||||
public data: string
|
||||
}
|
95
scripts/ServerEvent/receiveServerEvents.ts
Normal file
95
scripts/ServerEvent/receiveServerEvents.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import AnimeNotifier from "../AnimeNotifier"
|
||||
import plural from "../Utils/plural"
|
||||
import ServerEvent from "./ServerEvent"
|
||||
|
||||
const reconnectDelay = 3000
|
||||
|
||||
let supported: boolean
|
||||
let eventSource: EventSource
|
||||
let arn: AnimeNotifier
|
||||
let etags: Map<string, string>
|
||||
|
||||
export default function receiveServerEvents(animeNotifier: AnimeNotifier) {
|
||||
supported = ("EventSource" in window)
|
||||
|
||||
if(!supported) {
|
||||
return
|
||||
}
|
||||
|
||||
arn = animeNotifier
|
||||
etags = new Map<string, string>()
|
||||
connect()
|
||||
}
|
||||
|
||||
function connect() {
|
||||
if(eventSource) {
|
||||
eventSource.close()
|
||||
}
|
||||
|
||||
eventSource = new EventSource("/api/sse/events", {
|
||||
withCredentials: true
|
||||
})
|
||||
|
||||
eventSource.addEventListener("ping", (e: any) => ping(e))
|
||||
eventSource.addEventListener("etag", (e: any) => etag(e))
|
||||
eventSource.addEventListener("activity", (e: any) => activity(e))
|
||||
eventSource.addEventListener("notificationCount", (e: any) => notificationCount(e))
|
||||
|
||||
eventSource.onerror = () => {
|
||||
setTimeout(() => connect(), reconnectDelay)
|
||||
}
|
||||
}
|
||||
|
||||
function ping(_: ServerEvent) {
|
||||
console.log("ping")
|
||||
}
|
||||
|
||||
function etag(e: ServerEvent) {
|
||||
const data = JSON.parse(e.data)
|
||||
const oldETag = etags.get(data.url)
|
||||
const newETag = data.etag
|
||||
|
||||
if(oldETag && newETag && oldETag !== newETag) {
|
||||
arn.statusMessage.showInfo("A new version of the website is available. Please refresh the page.", -1)
|
||||
}
|
||||
|
||||
etags.set(data.url, newETag)
|
||||
}
|
||||
|
||||
function activity(e: ServerEvent) {
|
||||
if(!location.pathname.startsWith("/activity")) {
|
||||
return
|
||||
}
|
||||
|
||||
const isFollowingUser = JSON.parse(e.data)
|
||||
|
||||
// If we're on the followed only feed and we receive an activity
|
||||
// about a user we don't follow, ignore the message.
|
||||
if(location.pathname.startsWith("/activity/followed") && !isFollowingUser) {
|
||||
return
|
||||
}
|
||||
|
||||
const button = document.getElementById("load-new-activities")
|
||||
|
||||
if(!button || !button.dataset.count) {
|
||||
return
|
||||
}
|
||||
|
||||
const buttonText = document.getElementById("load-new-activities-text")
|
||||
|
||||
if(!buttonText) {
|
||||
return
|
||||
}
|
||||
|
||||
const newCount = parseInt(button.dataset.count, 10) + 1
|
||||
button.dataset.count = newCount.toString()
|
||||
buttonText.textContent = plural(newCount, "new activity")
|
||||
}
|
||||
|
||||
function notificationCount(e: ServerEvent) {
|
||||
if(!arn.notificationManager) {
|
||||
return
|
||||
}
|
||||
|
||||
arn.notificationManager.setCounter(parseInt(e.data, 10))
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
import AnimeNotifier from "./AnimeNotifier"
|
||||
import { plural } from "./Utils"
|
||||
|
||||
const reconnectDelay = 3000
|
||||
|
||||
class ServerEvent {
|
||||
data: string
|
||||
}
|
||||
|
||||
export default class ServerEvents {
|
||||
supported: boolean
|
||||
eventSource: EventSource
|
||||
arn: AnimeNotifier
|
||||
etags: Map<string, string>
|
||||
|
||||
constructor(arn: AnimeNotifier) {
|
||||
this.supported = ("EventSource" in window)
|
||||
|
||||
if(!this.supported) {
|
||||
return
|
||||
}
|
||||
|
||||
this.arn = arn
|
||||
this.etags = new Map<string, string>()
|
||||
this.connect()
|
||||
}
|
||||
|
||||
connect() {
|
||||
if(this.eventSource) {
|
||||
this.eventSource.close()
|
||||
}
|
||||
|
||||
this.eventSource = new EventSource("/api/sse/events", {
|
||||
withCredentials: true
|
||||
})
|
||||
|
||||
this.eventSource.addEventListener("ping", (e: any) => this.ping(e))
|
||||
this.eventSource.addEventListener("etag", (e: any) => this.etag(e))
|
||||
this.eventSource.addEventListener("activity", (e: any) => this.activity(e))
|
||||
this.eventSource.addEventListener("notificationCount", (e: any) => this.notificationCount(e))
|
||||
|
||||
this.eventSource.onerror = () => {
|
||||
setTimeout(() => this.connect(), reconnectDelay)
|
||||
}
|
||||
}
|
||||
|
||||
ping(_: ServerEvent) {
|
||||
console.log("ping")
|
||||
}
|
||||
|
||||
etag(e: ServerEvent) {
|
||||
const data = JSON.parse(e.data)
|
||||
const oldETag = this.etags.get(data.url)
|
||||
const newETag = data.etag
|
||||
|
||||
if(oldETag && newETag && oldETag != newETag) {
|
||||
this.arn.statusMessage.showInfo("A new version of the website is available. Please refresh the page.", -1)
|
||||
}
|
||||
|
||||
this.etags.set(data.url, newETag)
|
||||
}
|
||||
|
||||
activity(e: ServerEvent) {
|
||||
if(!location.pathname.startsWith("/activity")) {
|
||||
return
|
||||
}
|
||||
|
||||
const isFollowingUser = JSON.parse(e.data)
|
||||
|
||||
// If we're on the followed only feed and we receive an activity
|
||||
// about a user we don't follow, ignore the message.
|
||||
if(location.pathname.startsWith("/activity/followed") && !isFollowingUser) {
|
||||
return
|
||||
}
|
||||
|
||||
const button = document.getElementById("load-new-activities")
|
||||
|
||||
if(!button || !button.dataset.count) {
|
||||
return
|
||||
}
|
||||
|
||||
const buttonText = document.getElementById("load-new-activities-text")
|
||||
|
||||
if(!buttonText) {
|
||||
return
|
||||
}
|
||||
|
||||
const newCount = parseInt(button.dataset.count) + 1
|
||||
button.dataset.count = newCount.toString()
|
||||
buttonText.textContent = plural(newCount, "new activity")
|
||||
}
|
||||
|
||||
notificationCount(e: ServerEvent) {
|
||||
if(!this.arn.notificationManager) {
|
||||
return
|
||||
}
|
||||
|
||||
this.arn.notificationManager.setCounter(parseInt(e.data))
|
||||
}
|
||||
}
|
@ -1,66 +1,25 @@
|
||||
import AnimeNotifier from "./AnimeNotifier"
|
||||
|
||||
export default class ServiceWorkerManager {
|
||||
arn: AnimeNotifier
|
||||
uri: string
|
||||
private arn: AnimeNotifier
|
||||
private uri: string
|
||||
|
||||
constructor(arn: AnimeNotifier, uri: string) {
|
||||
this.arn = arn
|
||||
this.uri = uri
|
||||
}
|
||||
|
||||
register() {
|
||||
public register() {
|
||||
if(!("serviceWorker" in navigator)) {
|
||||
console.warn("service worker not supported, skipping registration")
|
||||
return
|
||||
}
|
||||
|
||||
navigator.serviceWorker.register(this.uri)
|
||||
|
||||
navigator.serviceWorker.addEventListener("message", evt => {
|
||||
this.onMessage(evt)
|
||||
})
|
||||
|
||||
// This will send a message to the service worker that the DOM has been loaded
|
||||
const sendContentLoadedEvent = () => {
|
||||
if(!navigator.serviceWorker.controller) {
|
||||
return
|
||||
navigator.serviceWorker.addEventListener("message", evt => this.onMessage(evt))
|
||||
}
|
||||
|
||||
// A reloadContent call should never trigger another reload
|
||||
if(this.arn.app.currentPath === this.arn.lastReloadContentPath) {
|
||||
this.arn.lastReloadContentPath = ""
|
||||
return
|
||||
}
|
||||
|
||||
let url = ""
|
||||
|
||||
// If mainPageLoaded is set, it means every single request is now an AJAX request for the /_/ prefixed page
|
||||
if(this.arn.mainPageLoaded) {
|
||||
url = window.location.origin + "/_" + window.location.pathname
|
||||
} else {
|
||||
this.arn.mainPageLoaded = true
|
||||
url = window.location.href
|
||||
}
|
||||
|
||||
// console.log("checking for updates:", message.url)
|
||||
|
||||
this.postMessage({
|
||||
type: "loaded",
|
||||
url
|
||||
})
|
||||
}
|
||||
|
||||
// For future loaded events
|
||||
document.addEventListener("DOMContentLoaded", sendContentLoadedEvent)
|
||||
|
||||
// If the page is loaded already, send the loaded event right now.
|
||||
if(document.readyState !== "loading") {
|
||||
sendContentLoadedEvent()
|
||||
}
|
||||
}
|
||||
|
||||
postMessage(message: any) {
|
||||
public postMessage(message: any) {
|
||||
const controller = navigator.serviceWorker.controller
|
||||
|
||||
if(!controller) {
|
||||
@ -70,7 +29,7 @@ export default class ServiceWorkerManager {
|
||||
controller.postMessage(JSON.stringify(message))
|
||||
}
|
||||
|
||||
onMessage(evt: MessageEvent) {
|
||||
private onMessage(evt: MessageEvent) {
|
||||
const message = JSON.parse(evt.data)
|
||||
|
||||
switch(message.type) {
|
||||
@ -80,25 +39,6 @@ export default class ServiceWorkerManager {
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
// case "new content":
|
||||
// if(message.url.includes("/_/")) {
|
||||
// // Content reload
|
||||
// this.arn.contentLoadedActions.then(() => {
|
||||
// this.arn.reloadContent(true)
|
||||
// })
|
||||
// } else {
|
||||
// // Full page reload
|
||||
// this.arn.contentLoadedActions.then(() => {
|
||||
// this.arn.reloadPage()
|
||||
// })
|
||||
// }
|
||||
|
||||
// break
|
||||
|
||||
// case "offline":
|
||||
// this.arn.statusMessage.showError("You are viewing an offline version of the site now.")
|
||||
// break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { delay } from "./Utils"
|
||||
import delay from "./Utils/delay"
|
||||
|
||||
export default class StatusMessage {
|
||||
private container: HTMLElement
|
||||
|
19
scripts/User.ts
Normal file
19
scripts/User.ts
Normal file
@ -0,0 +1,19 @@
|
||||
export default class User {
|
||||
public id: string
|
||||
private proExpires: string
|
||||
|
||||
public constructor(id: string) {
|
||||
this.id = id
|
||||
this.sync()
|
||||
}
|
||||
|
||||
public IsPro(): boolean {
|
||||
return new Date() > new Date(this.proExpires)
|
||||
}
|
||||
|
||||
private async sync() {
|
||||
const response = await fetch(`/api/user/${this.id}`)
|
||||
const json = await response.json()
|
||||
Object.assign(this, json)
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
export function bytesHumanReadable(fileSize: number): string {
|
||||
export default function bytesHumanReadable(fileSize: number): string {
|
||||
let unit = "bytes"
|
||||
|
||||
if(fileSize >= 1024) {
|
||||
|
@ -1,3 +1,3 @@
|
||||
export function delay<T>(millis: number, value?: T): Promise<T> {
|
||||
export default function delay<T>(millis: number, value?: T): Promise<T> {
|
||||
return new Promise(resolve => setTimeout(() => resolve(value), millis))
|
||||
}
|
||||
|
1
scripts/Utils/emptyPixel.ts
Normal file
1
scripts/Utils/emptyPixel.ts
Normal file
@ -0,0 +1 @@
|
||||
export default "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="
|
@ -1,15 +1,7 @@
|
||||
export function* findAll(className: string): IterableIterator<HTMLElement> {
|
||||
export default function* findAll(className: string): IterableIterator<HTMLElement> {
|
||||
const elements = document.getElementsByClassName(className)
|
||||
|
||||
for(let i = 0; i < elements.length; ++i) {
|
||||
yield elements[i] as HTMLElement
|
||||
}
|
||||
}
|
||||
|
||||
export function* findAllInside(className: string, root: HTMLElement): IterableIterator<HTMLElement> {
|
||||
const elements = root.getElementsByClassName(className)
|
||||
|
||||
for(let i = 0; i < elements.length; ++i) {
|
||||
yield elements[i] as HTMLElement
|
||||
for(const element of elements) {
|
||||
yield element as HTMLElement
|
||||
}
|
||||
}
|
||||
|
7
scripts/Utils/findAllInside.ts
Normal file
7
scripts/Utils/findAllInside.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export default function* findAllInside(className: string, root: HTMLElement): IterableIterator<HTMLElement> {
|
||||
const elements = root.getElementsByClassName(className)
|
||||
|
||||
for(const element of elements) {
|
||||
yield element as HTMLElement
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
export function hexToHSL(hex: string) {
|
||||
export default function hexToHSL(hex: string) {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
||||
|
||||
if(!result) {
|
||||
@ -20,7 +20,7 @@ export function hexToHSL(hex: string) {
|
||||
let s = 0
|
||||
const l = (max + min) / 2
|
||||
|
||||
if(max == min) {
|
||||
if(max === min) {
|
||||
h = s = 0
|
||||
} else {
|
||||
const d = max - min
|
||||
|
@ -1,9 +0,0 @@
|
||||
export * from "./supportsWebP"
|
||||
export * from "./delay"
|
||||
export * from "./findAll"
|
||||
export * from "./hexToHSL"
|
||||
export * from "./plural"
|
||||
export * from "./requestIdleCallback"
|
||||
export * from "./swapElements"
|
||||
export * from "./uploadWithProgress"
|
||||
export * from "./bytesHumanReadable"
|
@ -2,7 +2,7 @@ const specialized = {
|
||||
"new activity": "new activities"
|
||||
}
|
||||
|
||||
export function plural(count: number, singular: string): string {
|
||||
export default function plural(count: number, singular: string): string {
|
||||
if(count === 1 || count === -1) {
|
||||
return count + " " + singular
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
export function requestIdleCallback(func: Function) {
|
||||
export default function requestIdleCallback(func: Function) {
|
||||
if("requestIdleCallback" in window) {
|
||||
(window["requestIdleCallback"] as Function)(func)
|
||||
} else {
|
||||
|
@ -1,4 +1,4 @@
|
||||
export async function supportsWebP(): Promise<boolean> {
|
||||
export default async function supportsWebP(): Promise<boolean> {
|
||||
if(!window.createImageBitmap) {
|
||||
return false
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
// swapElements assumes that both elements have valid parent nodes.
|
||||
export function swapElements(a: Node, b: Node) {
|
||||
export default function swapElements(a: Node, b: Node) {
|
||||
const bParent = b.parentNode as Node
|
||||
const bNext = b.nextSibling
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
export function uploadWithProgress(url, options: RequestInit, onProgress: ((ev: ProgressEvent) => any) | null): Promise<string> {
|
||||
export default function uploadWithProgress(url, options: RequestInit, onProgress: ((ev: ProgressEvent) => any) | null): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest()
|
||||
|
||||
|
37
scripts/infiniteScroll.ts
Normal file
37
scripts/infiniteScroll.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import Diff from "./Diff"
|
||||
|
||||
let container: HTMLElement
|
||||
let threshold: number
|
||||
|
||||
export default function infiniteScroll(scrollContainer: HTMLElement, scrollThreshold: number) {
|
||||
container = scrollContainer
|
||||
threshold = scrollThreshold
|
||||
|
||||
container.addEventListener("scroll", _ => {
|
||||
// Wait for mutations to finish before checking if we need infinite scroll to trigger.
|
||||
if(Diff.mutations.length() > 0) {
|
||||
Diff.mutations.wait(check)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, queue up the check immediately.
|
||||
// Don't call check() directly to make scrolling as smooth as possible.
|
||||
Diff.mutations.queue(() => check())
|
||||
})
|
||||
}
|
||||
|
||||
function check() {
|
||||
if(container.scrollTop + container.clientHeight >= container.scrollHeight - threshold) {
|
||||
loadMore()
|
||||
}
|
||||
}
|
||||
|
||||
function loadMore() {
|
||||
const button = document.getElementById("load-more-button")
|
||||
|
||||
if(!button) {
|
||||
return
|
||||
}
|
||||
|
||||
button.click()
|
||||
}
|
@ -1,7 +1,3 @@
|
||||
import AnimeNotifier from "./AnimeNotifier"
|
||||
import Application from "./Application"
|
||||
|
||||
const app = new Application()
|
||||
const arn = new AnimeNotifier(app)
|
||||
|
||||
arn.init()
|
||||
new AnimeNotifier().init()
|
||||
|
@ -12,7 +12,12 @@
|
||||
"trailing-comma": false,
|
||||
"prefer-const": true,
|
||||
"no-var-keyword": true,
|
||||
"eofline": true
|
||||
"eofline": true,
|
||||
"no-console": false,
|
||||
"object-literal-sort-keys": false,
|
||||
"no-string-literal": false,
|
||||
"max-line-length": false,
|
||||
"no-string-throw": false
|
||||
},
|
||||
"rulesDirectory": []
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user