mirror of
https://github.com/hugo-fixit/FixIt.git
synced 2026-08-25 07:48:54 +00:00
17164f42d0
- Replace legacy service-worker.js (imported from DoIt) with a clean rewrite using Hugo's ExecuteAsTemplate for fingerprinted URL injection - Cache-first for static assets (immutable/fingerprinted), network-first for HTML pages with LRU eviction (max 100 entries) - Fix LRU eviction to protect precached URLs using absolute URL comparison - Add Fingerprint param to js-build.html partial to reduce boilerplate - Extract to-css.html partial for reusable SCSS-to-CSS pipeline - Support pre-built Resource param in script.html and style.html - Store mainCSS and mainJS in Site.Store to avoid duplicate builds - Remove explicit scope from serviceWorker.register() (defaults to /) Closes #298
144 lines
5.1 KiB
TypeScript
144 lines
5.1 KiB
TypeScript
import type { CoreService, MiscService } from '../core/tokens'
|
|
import { eventBus } from '../core/event-bus'
|
|
import { getScrollTop, isMobile, isValidDate, scrollIntoView } from '../utils'
|
|
|
|
/**
|
|
* Miscellaneous module — site time, PWA, bookmarks, rewards, comments, and PostChat.
|
|
*
|
|
* Responsibilities:
|
|
* - Display site running time with animated counters.
|
|
* - Register service worker for PWA support.
|
|
* - Auto-bookmark scroll position for page restoration.
|
|
* - Initialize reward QR codes and PostChat AI user info.
|
|
* - Initialize comment section UI and scroll-into-view.
|
|
*/
|
|
export class MiscModule implements MiscService {
|
|
private siteTime: ReturnType<typeof setInterval> | undefined
|
|
|
|
constructor(private readonly core: CoreService) {}
|
|
|
|
/** Calculate and display the elapsed time since site launch. */
|
|
getSiteTime() {
|
|
const now = new Date()
|
|
const run = new Date(this.core.config.siteTime!)
|
|
const $runTimes = document.querySelector<HTMLElement>('.run-times')
|
|
if (!isValidDate(run) || !$runTimes) {
|
|
clearInterval(this.siteTime)
|
|
$runTimes && $runTimes.parentNode!.removeChild($runTimes)
|
|
return
|
|
}
|
|
const totalSeconds = Math.floor((now.getTime() - run.getTime()) / 1000)
|
|
const days = Math.floor(totalSeconds / 86400)
|
|
const hours = Math.floor((totalSeconds % 86400) / 3600)
|
|
const minutes = Math.floor((totalSeconds % 3600) / 60)
|
|
const seconds = totalSeconds % 60
|
|
$runTimes.innerHTML = `${days}, ${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`
|
|
document.querySelector('.site-time .d-none')?.classList.remove('d-none')
|
|
}
|
|
|
|
/** Start the site-time counter with visibility-change pausing. */
|
|
initSiteTime() {
|
|
if (this.core.config.siteTime) {
|
|
this.siteTime = setInterval(() => this.getSiteTime(), 500)
|
|
document.addEventListener('visibilitychange', () => {
|
|
if (document.hidden) {
|
|
return clearInterval(this.siteTime)
|
|
}
|
|
this.siteTime = setInterval(() => this.getSiteTime(), 500)
|
|
}, false)
|
|
}
|
|
}
|
|
|
|
/** Register the service worker for PWA support. */
|
|
initServiceWorker() {
|
|
if (this.core.config.PWA?.enable && 'serviceWorker' in navigator) {
|
|
navigator.serviceWorker
|
|
.register(this.core.config.PWA.serviceWorkerURL)
|
|
.then((_registration) => {
|
|
// console.log('Service Worker Registered');
|
|
})
|
|
.catch((error) => {
|
|
console.error('error: ', error)
|
|
})
|
|
navigator.serviceWorker
|
|
.ready
|
|
.then((_registration) => {
|
|
// console.log('Service Worker Ready');
|
|
})
|
|
}
|
|
}
|
|
|
|
/** Save and restore scroll position as an automatic bookmark. */
|
|
initAutoMark() {
|
|
if (!this.core.config.autoBookmark)
|
|
return
|
|
window.addEventListener('beforeunload', () => {
|
|
window.sessionStorage?.setItem(`fixit-bookmark/#${location.pathname}`, String(getScrollTop()))
|
|
})
|
|
const scrollTop = Number(window.sessionStorage?.getItem(`fixit-bookmark/#${location.pathname}`))
|
|
if (scrollTop && location.hash === '') {
|
|
window.scrollTo({ top: scrollTop, behavior: 'smooth' })
|
|
}
|
|
}
|
|
|
|
/** Initialize reward/donation button exclusive-toggle behaviour. */
|
|
initReward() {
|
|
const $rewards = document.querySelectorAll<HTMLElement>('.post-reward [data-mode="fixed"]')
|
|
if (!$rewards.length)
|
|
return
|
|
if (isMobile()) {
|
|
$rewards.forEach($reward => $reward.removeAttribute('data-mode'))
|
|
return
|
|
}
|
|
const _closeRewardExclude = (id?: string | null) => {
|
|
$rewards.forEach(($reward) => {
|
|
const $rewardInput = $reward.parentElement!.querySelector<HTMLInputElement>('.reward-input')
|
|
if ($rewardInput && $rewardInput.id !== id) {
|
|
$rewardInput.checked = false
|
|
}
|
|
})
|
|
}
|
|
$rewards.forEach(($reward) => {
|
|
$reward.previousElementSibling!.addEventListener('click', function (this: HTMLElement) {
|
|
_closeRewardExclude(this.getAttribute('for'))
|
|
}, false)
|
|
})
|
|
eventBus.on('fixit:scroll', () => _closeRewardExclude())
|
|
}
|
|
|
|
/** Initialize the comment section UI. */
|
|
initComment() {
|
|
if (!this.core.config.comment?.enable)
|
|
return
|
|
|
|
if (document.querySelector('#comments')) {
|
|
const $viewCommentsBtn = document.querySelector<HTMLElement>('.view-comments')!
|
|
$viewCommentsBtn.classList.remove('d-none')
|
|
$viewCommentsBtn.addEventListener('click', () => {
|
|
scrollIntoView('#comments')
|
|
}, false)
|
|
}
|
|
|
|
if (this.core.config.comment.expired)
|
|
document.querySelector('#comments')!.remove()
|
|
}
|
|
|
|
/** Initialize PostChat theme sync if configured. */
|
|
initPostChatUser() {
|
|
if (!window.postChatUser || !window.postChatConfig || window.postChatConfig.userMode === 'magic')
|
|
return
|
|
window.postChat_theme = this.core.isDark ? 'dark' : 'light'
|
|
eventBus.on('fixit:switch-theme', ({ detail }) => {
|
|
if (!detail.isChanged)
|
|
return
|
|
const targetFrame = document.getElementById('postChat_iframeContainer')
|
|
if (targetFrame) {
|
|
window.postChatUser.setPostChatTheme(detail.isDark ? 'dark' : 'light')
|
|
}
|
|
else {
|
|
window.postChat_theme = detail.isDark ? 'dark' : 'light'
|
|
}
|
|
})
|
|
}
|
|
}
|