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 AnimeNotifier from "../AnimeNotifier"
|
||||||
import { requestIdleCallback } from "../Utils"
|
|
||||||
|
|
||||||
// Load
|
// Load
|
||||||
export function load(arn: AnimeNotifier, element: HTMLElement) {
|
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 AnimeNotifier from "../AnimeNotifier"
|
||||||
import { findAll } from "scripts/Utils"
|
|
||||||
|
|
||||||
// Filter anime on explore page
|
// Filter anime on explore page
|
||||||
export function filterAnime(arn: AnimeNotifier, _: HTMLInputElement) {
|
export function filterAnime(arn: AnimeNotifier, _: HTMLInputElement) {
|
||||||
@ -12,7 +13,7 @@ export function filterAnime(arn: AnimeNotifier, _: HTMLInputElement) {
|
|||||||
|
|
||||||
for(const element of findAll("anime-grid-image")) {
|
for(const element of findAll("anime-grid-image")) {
|
||||||
const img = element as HTMLImageElement
|
const img = element as HTMLImageElement
|
||||||
img.src = arn.emptyPixel()
|
img.src = emptyPixel
|
||||||
img.classList.remove("element-found")
|
img.classList.remove("element-found")
|
||||||
img.classList.remove("element-color-preview")
|
img.classList.remove("element-color-preview")
|
||||||
}
|
}
|
||||||
|
@ -2,24 +2,24 @@ import AnimeNotifier from "../AnimeNotifier"
|
|||||||
|
|
||||||
// Enable notifications
|
// Enable notifications
|
||||||
export async function enableNotifications(arn: AnimeNotifier, _: HTMLElement) {
|
export async function enableNotifications(arn: AnimeNotifier, _: HTMLElement) {
|
||||||
if(!arn.user || !arn.user.dataset.id) {
|
if(!arn.user) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
arn.statusMessage.showInfo("Enabling instant notifications...")
|
arn.statusMessage.showInfo("Enabling instant notifications...")
|
||||||
await arn.pushManager.subscribe(arn.user.dataset.id)
|
await arn.pushManager.subscribe(arn.user.id)
|
||||||
arn.updatePushUI()
|
arn.updatePushUI()
|
||||||
arn.statusMessage.showInfo("Enabled instant notifications for this device.")
|
arn.statusMessage.showInfo("Enabled instant notifications for this device.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable notifications
|
// Disable notifications
|
||||||
export async function disableNotifications(arn: AnimeNotifier, _: HTMLElement) {
|
export async function disableNotifications(arn: AnimeNotifier, _: HTMLElement) {
|
||||||
if(!arn.user || !arn.user.dataset.id) {
|
if(!arn.user) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
arn.statusMessage.showInfo("Disabling instant notifications...")
|
arn.statusMessage.showInfo("Disabling instant notifications...")
|
||||||
await arn.pushManager.unsubscribe(arn.user.dataset.id)
|
await arn.pushManager.unsubscribe(arn.user.id)
|
||||||
arn.updatePushUI()
|
arn.updatePushUI()
|
||||||
arn.statusMessage.showInfo("Disabled instant notifications for this device.")
|
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 AnimeNotifier from "../AnimeNotifier"
|
||||||
import { delay, requestIdleCallback } from "../Utils"
|
|
||||||
import Diff from "scripts/Diff";
|
|
||||||
|
|
||||||
// Search page reference
|
// Search page reference
|
||||||
let emptySearchHTML = ""
|
let emptySearchHTML = ""
|
||||||
@ -111,7 +112,7 @@ export async function search(arn: AnimeNotifier, search: HTMLInputElement, evt?:
|
|||||||
searchPageTitle.textContent = document.title
|
searchPageTitle.textContent = document.title
|
||||||
|
|
||||||
if(!term || term.length < 1) {
|
if(!term || term.length < 1) {
|
||||||
await arn.innerHTML(searchPage, emptySearchHTML)
|
await Diff.innerHTML(searchPage, emptySearchHTML)
|
||||||
arn.app.emit("DOMContentLoaded")
|
arn.app.emit("DOMContentLoaded")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -168,7 +169,7 @@ function showResponseInElement(arn: AnimeNotifier, url: string, typeName: string
|
|||||||
correctResponseRendered[typeName] = true
|
correctResponseRendered[typeName] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
await arn.innerHTML(element, html)
|
await Diff.innerHTML(element, html)
|
||||||
arn.onNewContent(element)
|
arn.onNewContent(element)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
import hexToHSL from "scripts/Utils/hexToHSL"
|
||||||
import AnimeNotifier from "../AnimeNotifier"
|
import AnimeNotifier from "../AnimeNotifier"
|
||||||
import { hexToHSL } from "scripts/Utils"
|
|
||||||
|
|
||||||
let currentThemeName = "light"
|
let currentThemeName = "light"
|
||||||
let previewTimeoutID: number = 0
|
let previewTimeoutID: number = 0
|
||||||
@ -7,8 +7,8 @@ let previewTimeoutID: number = 0
|
|||||||
// let themeWheelTimeoutID: number = 0
|
// let themeWheelTimeoutID: number = 0
|
||||||
|
|
||||||
const themes = {
|
const themes = {
|
||||||
"light": {},
|
light: {},
|
||||||
"dark": {
|
dark: {
|
||||||
"link-color-h": "45",
|
"link-color-h": "45",
|
||||||
"link-color-s": "100%",
|
"link-color-s": "100%",
|
||||||
"link-color-l": "66%",
|
"link-color-l": "66%",
|
||||||
@ -117,7 +117,7 @@ export function applyThemeAndPreview(arn: AnimeNotifier, themeName: string) {
|
|||||||
clearTimeout(previewTimeoutID)
|
clearTimeout(previewTimeoutID)
|
||||||
|
|
||||||
// If it's the free light theme or a PRO user, nothing to do here
|
// 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
|
import bytesHumanReadable from "scripts/Utils/bytesHumanReadable"
|
||||||
|
import uploadWithProgress from "scripts/Utils/uploadWithProgress"
|
||||||
import AnimeNotifier from "../AnimeNotifier"
|
import AnimeNotifier from "../AnimeNotifier"
|
||||||
import { bytesHumanReadable, uploadWithProgress } from "../Utils"
|
|
||||||
|
|
||||||
// Select file
|
// Select file
|
||||||
export function selectFile(arn: AnimeNotifier, button: HTMLButtonElement) {
|
export function selectFile(arn: AnimeNotifier, button: HTMLButtonElement) {
|
||||||
const fileType = button.dataset.type
|
const fileType = button.dataset.type
|
||||||
const endpoint = button.dataset.endpoint
|
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.")
|
alert("Please buy a PRO account to use this feature.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,41 +1,39 @@
|
|||||||
export default class Analytics {
|
export function uploadAnalytics() {
|
||||||
push() {
|
const analytics = {
|
||||||
const analytics = {
|
general: {
|
||||||
general: {
|
timezoneOffset: new Date().getTimezoneOffset()
|
||||||
timezoneOffset: new Date().getTimezoneOffset()
|
},
|
||||||
},
|
screen: {
|
||||||
screen: {
|
availableHeight: screen.availHeight,
|
||||||
width: screen.width,
|
availableWidth: screen.availWidth,
|
||||||
height: screen.height,
|
height: screen.height,
|
||||||
availableWidth: screen.availWidth,
|
pixelRatio: window.devicePixelRatio,
|
||||||
availableHeight: screen.availHeight,
|
width: screen.width
|
||||||
pixelRatio: window.devicePixelRatio
|
},
|
||||||
},
|
system: {
|
||||||
system: {
|
cpuCount: navigator.hardwareConcurrency,
|
||||||
cpuCount: navigator.hardwareConcurrency,
|
platform: navigator.platform
|
||||||
platform: navigator.platform
|
},
|
||||||
},
|
connection: {
|
||||||
connection: {
|
downLink: 0,
|
||||||
downLink: 0,
|
effectiveType: "",
|
||||||
roundTripTime: 0,
|
roundTripTime: 0
|
||||||
effectiveType: ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if("connection" in navigator) {
|
|
||||||
const connection = navigator["connection"] as any
|
|
||||||
|
|
||||||
analytics.connection = {
|
|
||||||
downLink: connection.downlink,
|
|
||||||
roundTripTime: connection.rtt,
|
|
||||||
effectiveType: connection.effectiveType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch("/dark-flame-master", {
|
|
||||||
method: "POST",
|
|
||||||
credentials: "same-origin",
|
|
||||||
body: JSON.stringify(analytics)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if("connection" in navigator) {
|
||||||
|
const connection = navigator["connection"] as any
|
||||||
|
|
||||||
|
analytics.connection = {
|
||||||
|
downLink: connection.downlink,
|
||||||
|
roundTripTime: connection.rtt,
|
||||||
|
effectiveType: connection.effectiveType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch("/dark-flame-master", {
|
||||||
|
method: "POST",
|
||||||
|
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 Diff from "./Diff"
|
||||||
import LoadOptions from "./LoadOptions"
|
import LoadOptions from "./LoadOptions"
|
||||||
import { delay } from "./Utils"
|
import delay from "./Utils/delay"
|
||||||
|
|
||||||
export default class Application {
|
export default class Application {
|
||||||
public originalPath: string
|
public originalPath: string
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { plural } from "./Utils"
|
import plural from "./Utils/plural"
|
||||||
|
|
||||||
const oneSecond = 1000
|
const oneSecond = 1000
|
||||||
const oneMinute = 60 * oneSecond
|
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
|
// 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.
|
// defined time capacity, it will pause and continue the mutations in the next frame.
|
||||||
export default class MutationQueue {
|
export default class MutationQueue {
|
||||||
mutations: Array<() => void>
|
private mutations: Array<() => void>
|
||||||
onClearCallBacks: Array<() => void>
|
private onClearCallBacks: Array<() => void>
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.mutations = []
|
this.mutations = []
|
||||||
this.onClearCallBacks = []
|
this.onClearCallBacks = []
|
||||||
}
|
}
|
||||||
|
|
||||||
queue(mutation: () => void) {
|
public queue(mutation: () => void) {
|
||||||
this.mutations.push(mutation)
|
this.mutations.push(mutation)
|
||||||
|
|
||||||
if(this.mutations.length === 1) {
|
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()
|
const start = performance.now()
|
||||||
|
|
||||||
for(let i = 0; i < this.mutations.length; i++) {
|
for(let i = 0; i < this.mutations.length; i++) {
|
||||||
@ -45,7 +58,7 @@ export default class MutationQueue {
|
|||||||
this.clear()
|
this.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
private clear() {
|
||||||
this.mutations.length = 0
|
this.mutations.length = 0
|
||||||
|
|
||||||
if(this.onClearCallBacks.length > 0) {
|
if(this.onClearCallBacks.length > 0) {
|
||||||
@ -56,13 +69,4 @@ export default class MutationQueue {
|
|||||||
this.onClearCallBacks.length = 0
|
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"
|
import AnimeNotifier from "./AnimeNotifier"
|
||||||
|
|
||||||
export default class ServiceWorkerManager {
|
export default class ServiceWorkerManager {
|
||||||
arn: AnimeNotifier
|
private arn: AnimeNotifier
|
||||||
uri: string
|
private uri: string
|
||||||
|
|
||||||
constructor(arn: AnimeNotifier, uri: string) {
|
constructor(arn: AnimeNotifier, uri: string) {
|
||||||
this.arn = arn
|
this.arn = arn
|
||||||
this.uri = uri
|
this.uri = uri
|
||||||
}
|
}
|
||||||
|
|
||||||
register() {
|
public register() {
|
||||||
if(!("serviceWorker" in navigator)) {
|
if(!("serviceWorker" in navigator)) {
|
||||||
console.warn("service worker not supported, skipping registration")
|
console.warn("service worker not supported, skipping registration")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
navigator.serviceWorker.register(this.uri)
|
navigator.serviceWorker.register(this.uri)
|
||||||
|
navigator.serviceWorker.addEventListener("message", evt => this.onMessage(evt))
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
const controller = navigator.serviceWorker.controller
|
||||||
|
|
||||||
if(!controller) {
|
if(!controller) {
|
||||||
@ -70,7 +29,7 @@ export default class ServiceWorkerManager {
|
|||||||
controller.postMessage(JSON.stringify(message))
|
controller.postMessage(JSON.stringify(message))
|
||||||
}
|
}
|
||||||
|
|
||||||
onMessage(evt: MessageEvent) {
|
private onMessage(evt: MessageEvent) {
|
||||||
const message = JSON.parse(evt.data)
|
const message = JSON.parse(evt.data)
|
||||||
|
|
||||||
switch(message.type) {
|
switch(message.type) {
|
||||||
@ -80,25 +39,6 @@ export default class ServiceWorkerManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
break
|
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 {
|
export default class StatusMessage {
|
||||||
private container: HTMLElement
|
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"
|
let unit = "bytes"
|
||||||
|
|
||||||
if(fileSize >= 1024) {
|
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))
|
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)
|
const elements = document.getElementsByClassName(className)
|
||||||
|
|
||||||
for(let i = 0; i < elements.length; ++i) {
|
for(const element of elements) {
|
||||||
yield elements[i] as HTMLElement
|
yield element 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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)
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
||||||
|
|
||||||
if(!result) {
|
if(!result) {
|
||||||
@ -20,7 +20,7 @@ export function hexToHSL(hex: string) {
|
|||||||
let s = 0
|
let s = 0
|
||||||
const l = (max + min) / 2
|
const l = (max + min) / 2
|
||||||
|
|
||||||
if(max == min) {
|
if(max === min) {
|
||||||
h = s = 0
|
h = s = 0
|
||||||
} else {
|
} else {
|
||||||
const d = max - min
|
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"
|
"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) {
|
if(count === 1 || count === -1) {
|
||||||
return count + " " + singular
|
return count + " " + singular
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export function requestIdleCallback(func: Function) {
|
export default function requestIdleCallback(func: Function) {
|
||||||
if("requestIdleCallback" in window) {
|
if("requestIdleCallback" in window) {
|
||||||
(window["requestIdleCallback"] as Function)(func)
|
(window["requestIdleCallback"] as Function)(func)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export async function supportsWebP(): Promise<boolean> {
|
export default async function supportsWebP(): Promise<boolean> {
|
||||||
if(!window.createImageBitmap) {
|
if(!window.createImageBitmap) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// swapElements assumes that both elements have valid parent nodes.
|
// 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 bParent = b.parentNode as Node
|
||||||
const bNext = b.nextSibling
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
const xhr = new XMLHttpRequest()
|
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 AnimeNotifier from "./AnimeNotifier"
|
||||||
import Application from "./Application"
|
|
||||||
|
|
||||||
const app = new Application()
|
new AnimeNotifier().init()
|
||||||
const arn = new AnimeNotifier(app)
|
|
||||||
|
|
||||||
arn.init()
|
|
||||||
|
@ -12,7 +12,12 @@
|
|||||||
"trailing-comma": false,
|
"trailing-comma": false,
|
||||||
"prefer-const": true,
|
"prefer-const": true,
|
||||||
"no-var-keyword": 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": []
|
"rulesDirectory": []
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user