mirror of
https://github.com/hugo-fixit/FixIt.git
synced 2026-08-24 15:28:57 +00:00
refactor(assets): use ES6 # private fields instead of TS private
Convert all TypeScript `private` and `private readonly` declarations to ES6 `#` syntax for true runtime privacy. TypeScript's `private` keyword only provides compile-time checking but fields remain accessible in compiled JavaScript.
This commit is contained in:
@@ -22,14 +22,14 @@ export type Handler<T> = T extends void
|
||||
|
||||
/** Typed event bus — wraps DOM CustomEvents with type-safe emit/on/off. */
|
||||
export class TypedEventBus {
|
||||
private target = document
|
||||
#target = document
|
||||
|
||||
on<K extends keyof FixItEventMap>(event: K, handler: Handler<FixItEventMap[K]>): void {
|
||||
this.target.addEventListener(event as string, handler as EventListener)
|
||||
this.#target.addEventListener(event as string, handler as EventListener)
|
||||
}
|
||||
|
||||
off<K extends keyof FixItEventMap>(event: K, handler: Handler<FixItEventMap[K]>): void {
|
||||
this.target.removeEventListener(event as string, handler as EventListener)
|
||||
this.#target.removeEventListener(event as string, handler as EventListener)
|
||||
}
|
||||
|
||||
emit<K extends keyof FixItEventMap>(
|
||||
@@ -37,7 +37,7 @@ export class TypedEventBus {
|
||||
...args: FixItEventMap[K] extends void ? [] : [FixItEventMap[K]]
|
||||
): void {
|
||||
const detail = args[0]
|
||||
this.target.dispatchEvent(
|
||||
this.#target.dispatchEvent(
|
||||
detail !== undefined
|
||||
? new CustomEvent(event as string, { detail })
|
||||
: new CustomEvent(event as string),
|
||||
|
||||
@@ -16,10 +16,13 @@ const copyText = createCopyText()
|
||||
* - Re-initialize components after encrypted content is decrypted.
|
||||
*/
|
||||
export class ContentModule implements ContentService {
|
||||
constructor(
|
||||
private readonly core: CoreService,
|
||||
private readonly code: CodeService,
|
||||
) {}
|
||||
#core: CoreService
|
||||
#code: CodeService
|
||||
|
||||
constructor(core: CoreService, code: CodeService) {
|
||||
this.#core = core
|
||||
this.#code = code
|
||||
}
|
||||
|
||||
/** Fetch and inline SVG icons referenced by `data-svg-src` attributes. */
|
||||
initSVGIcon() {
|
||||
@@ -131,7 +134,7 @@ export class ContentModule implements ContentService {
|
||||
return
|
||||
const footnoteMap = new Map<HTMLElement, HTMLElement>()
|
||||
$footnoteRefs.forEach(($ref) => {
|
||||
if (this.core.config.tooltip) {
|
||||
if (this.#core.config.tooltip) {
|
||||
const $link = $ref.querySelector<HTMLAnchorElement>('a.footnote-ref')
|
||||
if ($link) {
|
||||
$link.addEventListener('click', (e) => {
|
||||
@@ -153,7 +156,7 @@ export class ContentModule implements ContentService {
|
||||
if ($ref.hasAttribute('title'))
|
||||
return
|
||||
$ref.setAttribute('title', $content.textContent!.trim())
|
||||
if (this.core.config.tooltip) {
|
||||
if (this.#core.config.tooltip) {
|
||||
CellTooltip.getOrCreateInstance($ref)
|
||||
}
|
||||
})
|
||||
@@ -161,7 +164,7 @@ export class ContentModule implements ContentService {
|
||||
|
||||
/** Initialize CellTooltip on action buttons, copy buttons, and footnotes. */
|
||||
initTooltip() {
|
||||
if (!this.core.config.tooltip)
|
||||
if (!this.#core.config.tooltip)
|
||||
return
|
||||
CellTooltip.initAll('li[data-task] > span[title]', { placement: 'right' })
|
||||
CellTooltip.initAll('.action-btn[title]', { placement: 'bottom' })
|
||||
@@ -177,9 +180,9 @@ export class ContentModule implements ContentService {
|
||||
*/
|
||||
initContent(target: Element | Document = document) {
|
||||
this.initDetails(target)
|
||||
this.code.initCodeWrapper()
|
||||
this.code.initCodeTabs()
|
||||
this.code.initDiagramCopyBtn()
|
||||
this.#code.initCodeWrapper()
|
||||
this.#code.initCodeTabs()
|
||||
this.#code.initDiagramCopyBtn()
|
||||
this.initTooltip()
|
||||
this.initLinkGuardDialog(target)
|
||||
}
|
||||
|
||||
+16
-16
@@ -16,8 +16,8 @@ export class CoreModule implements CoreService {
|
||||
themeMode: string
|
||||
isDark: boolean
|
||||
|
||||
private activeMaskOverlay: string | null = null
|
||||
private readonly maskOverlays = new Map<string, MaskOverlayHandler>()
|
||||
#activeMaskOverlay: string | null = null
|
||||
#maskOverlays = new Map<string, MaskOverlayHandler>()
|
||||
|
||||
constructor() {
|
||||
this.config = window.config
|
||||
@@ -30,46 +30,46 @@ export class CoreModule implements CoreService {
|
||||
|
||||
/** Register a named mask overlay with open/close/isActive handlers. */
|
||||
registerMaskOverlay(name: string, handlers: MaskOverlayHandler) {
|
||||
this.maskOverlays.set(name, handlers)
|
||||
this.#maskOverlays.set(name, handlers)
|
||||
}
|
||||
|
||||
/** Toggle the mask element's blur class based on active overlay state. */
|
||||
syncMaskState() {
|
||||
document.getElementById('mask')?.classList.toggle('is-blur', Boolean(this.activeMaskOverlay))
|
||||
document.getElementById('mask')?.classList.toggle('is-blur', Boolean(this.#activeMaskOverlay))
|
||||
}
|
||||
|
||||
/** Open a named mask overlay, closing any previously active one. */
|
||||
openMaskOverlay(name: string) {
|
||||
const overlay = this.maskOverlays.get(name)
|
||||
const overlay = this.#maskOverlays.get(name)
|
||||
if (!overlay)
|
||||
return
|
||||
if (this.activeMaskOverlay && this.activeMaskOverlay !== name) {
|
||||
this.closeMaskOverlay(this.activeMaskOverlay, true)
|
||||
if (this.#activeMaskOverlay && this.#activeMaskOverlay !== name) {
|
||||
this.closeMaskOverlay(this.#activeMaskOverlay, true)
|
||||
}
|
||||
overlay.onOpen?.()
|
||||
this.activeMaskOverlay = name
|
||||
this.#activeMaskOverlay = name
|
||||
this.syncMaskState()
|
||||
}
|
||||
|
||||
/** Close a named mask overlay and optionally skip mask state sync. */
|
||||
closeMaskOverlay(name: string, skipSync = false) {
|
||||
const overlay = this.maskOverlays.get(name)
|
||||
const overlay = this.#maskOverlays.get(name)
|
||||
if (!overlay)
|
||||
return
|
||||
overlay.onClose?.()
|
||||
if (this.activeMaskOverlay === name) {
|
||||
this.activeMaskOverlay = null
|
||||
if (this.#activeMaskOverlay === name) {
|
||||
this.#activeMaskOverlay = null
|
||||
}
|
||||
!skipSync && this.syncMaskState()
|
||||
}
|
||||
|
||||
/** Toggle a named mask overlay open/closed. */
|
||||
toggleMaskOverlay(name: string) {
|
||||
const overlay = this.maskOverlays.get(name)
|
||||
const overlay = this.#maskOverlays.get(name)
|
||||
if (!overlay)
|
||||
return
|
||||
const isActive = overlay.isActive?.() ?? this.activeMaskOverlay === name
|
||||
if (this.activeMaskOverlay === name && isActive) {
|
||||
const isActive = overlay.isActive?.() ?? this.#activeMaskOverlay === name
|
||||
if (this.#activeMaskOverlay === name && isActive) {
|
||||
this.closeMaskOverlay(name)
|
||||
return
|
||||
}
|
||||
@@ -78,10 +78,10 @@ export class CoreModule implements CoreService {
|
||||
|
||||
/** Close whichever mask overlay is currently active. */
|
||||
closeActiveMaskOverlay() {
|
||||
if (!this.activeMaskOverlay) {
|
||||
if (!this.#activeMaskOverlay) {
|
||||
this.syncMaskState()
|
||||
return
|
||||
}
|
||||
this.closeMaskOverlay(this.activeMaskOverlay)
|
||||
this.closeMaskOverlay(this.#activeMaskOverlay)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,18 @@ import type { CoreService, EncryptionService } from '../core/tokens'
|
||||
* - Initialize FixItDecryptor for full-page and shortcode-scoped decryption.
|
||||
*/
|
||||
export class EncryptionModule implements EncryptionService {
|
||||
constructor(private readonly core: CoreService) {}
|
||||
#core: CoreService
|
||||
|
||||
constructor(core: CoreService) {
|
||||
this.#core = core
|
||||
}
|
||||
|
||||
/** Initialize FixItDecryptor with encryption config. */
|
||||
setup() {
|
||||
if (!this.core.config.encryption || !window.FixItDecryptor)
|
||||
if (!this.#core.config.encryption || !window.FixItDecryptor)
|
||||
return
|
||||
const decryptor = new window.FixItDecryptor()
|
||||
|
||||
decryptor.init(this.core.config.encryption)
|
||||
decryptor.init(this.#core.config.encryption)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,13 @@ export class EventsModule implements EventsService {
|
||||
#resizeTimeout: number | null = null
|
||||
#newScrollTop = 0
|
||||
#oldScrollTop = 0
|
||||
#core: CoreService
|
||||
#code: CodeService
|
||||
|
||||
constructor(
|
||||
private readonly core: CoreService,
|
||||
private readonly code: CodeService,
|
||||
) {}
|
||||
constructor(core: CoreService, code: CodeService) {
|
||||
this.#core = core
|
||||
this.#code = code
|
||||
}
|
||||
|
||||
/** Bind scroll listener: auto-hide headers, reading progress, back-to-top, and TOC sync. */
|
||||
onScroll() {
|
||||
@@ -40,7 +42,7 @@ export class EventsModule implements EventsService {
|
||||
this.#newScrollTop = getScrollTop()
|
||||
const scroll = this.#newScrollTop - this.#oldScrollTop
|
||||
if (Math.abs(scroll) > ACCURACY) {
|
||||
this.core.closeActiveMaskOverlay()
|
||||
this.#core.closeActiveMaskOverlay()
|
||||
const isScrollingDown = scroll > 0
|
||||
$autoHeaders.forEach(($header) => {
|
||||
if (isScrollingDown) {
|
||||
@@ -97,7 +99,7 @@ export class EventsModule implements EventsService {
|
||||
|
||||
const _isMobile = isMobile()
|
||||
if (_isMobile !== resizeBefore) {
|
||||
this.core.closeActiveMaskOverlay()
|
||||
this.#core.closeActiveMaskOverlay()
|
||||
resizeBefore = _isMobile
|
||||
}
|
||||
}, 100)
|
||||
@@ -110,7 +112,7 @@ export class EventsModule implements EventsService {
|
||||
document.getElementById('mask')!.addEventListener('click', (e) => {
|
||||
if (!(e.target as HTMLElement).classList.contains('is-blur'))
|
||||
return
|
||||
this.core.closeActiveMaskOverlay()
|
||||
this.#core.closeActiveMaskOverlay()
|
||||
}, false)
|
||||
}
|
||||
|
||||
@@ -118,7 +120,7 @@ export class EventsModule implements EventsService {
|
||||
initPrint() {
|
||||
window.addEventListener('beforeprint', () => {
|
||||
const $content = document.getElementById('content')!
|
||||
const printConfig = this.core.config.print || {}
|
||||
const printConfig = this.#core.config.print || {}
|
||||
|
||||
if (printConfig.expandAdmonition) {
|
||||
$content.querySelectorAll('.admonition').forEach(($el: Element) => $el.classList.add('open'))
|
||||
@@ -156,7 +158,7 @@ export class EventsModule implements EventsService {
|
||||
}, false)
|
||||
|
||||
window.addEventListener('afterprint', () => {
|
||||
this.code.initCodeTabs()
|
||||
this.#code.initCodeTabs()
|
||||
}, false)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,11 @@ import type { CoreService, MenuService } from '../core/tokens'
|
||||
* - Sync menu state with mask overlay.
|
||||
*/
|
||||
export class MenuModule implements MenuService {
|
||||
constructor(private readonly core: CoreService) {}
|
||||
#core: CoreService
|
||||
|
||||
constructor(core: CoreService) {
|
||||
this.#core = core
|
||||
}
|
||||
|
||||
/** Set min-width on desktop sub-menus to match parent item width. */
|
||||
initDesktop() {
|
||||
@@ -24,7 +28,7 @@ export class MenuModule implements MenuService {
|
||||
const $menuMobile = document.getElementById('menu-mobile')
|
||||
if (!$menuToggleMobile || !$menuMobile)
|
||||
return
|
||||
this.core.registerMaskOverlay('menu-mobile', {
|
||||
this.#core.registerMaskOverlay('menu-mobile', {
|
||||
isActive: () => $menuMobile.classList.contains('active'),
|
||||
onOpen: () => {
|
||||
$menuToggleMobile.classList.add('active')
|
||||
@@ -38,7 +42,7 @@ export class MenuModule implements MenuService {
|
||||
},
|
||||
})
|
||||
$menuToggleMobile.addEventListener('click', () => {
|
||||
this.core.toggleMaskOverlay('menu-mobile')
|
||||
this.#core.toggleMaskOverlay('menu-mobile')
|
||||
}, false)
|
||||
// add nested menu toggler
|
||||
document.querySelectorAll<HTMLElement>('.menu-item>.nested-item').forEach(($nestedItem) => {
|
||||
|
||||
+17
-14
@@ -13,17 +13,20 @@ import { getScrollTop, isMobile, isValidDate, scrollIntoView } from '../utils'
|
||||
* - Initialize comment section UI and scroll-into-view.
|
||||
*/
|
||||
export class MiscModule implements MiscService {
|
||||
private siteTime: ReturnType<typeof setInterval> | undefined
|
||||
#siteTime: ReturnType<typeof setInterval> | undefined
|
||||
#core: CoreService
|
||||
|
||||
constructor(private readonly core: CoreService) {}
|
||||
constructor(core: CoreService) {
|
||||
this.#core = core
|
||||
}
|
||||
|
||||
/** Calculate and display the elapsed time since site launch. */
|
||||
getSiteTime() {
|
||||
const now = new Date()
|
||||
const run = new Date(this.core.config.siteTime!)
|
||||
const run = new Date(this.#core.config.siteTime!)
|
||||
const $runTimes = document.querySelector<HTMLElement>('.run-times')
|
||||
if (!isValidDate(run) || !$runTimes) {
|
||||
clearInterval(this.siteTime)
|
||||
clearInterval(this.#siteTime)
|
||||
$runTimes && $runTimes.parentNode!.removeChild($runTimes)
|
||||
return
|
||||
}
|
||||
@@ -38,20 +41,20 @@ export class MiscModule implements MiscService {
|
||||
|
||||
/** Start the site-time counter with visibility-change pausing. */
|
||||
initSiteTime() {
|
||||
if (this.core.config.siteTime) {
|
||||
this.siteTime = setInterval(() => this.getSiteTime(), 500)
|
||||
if (this.#core.config.siteTime) {
|
||||
this.#siteTime = setInterval(() => this.getSiteTime(), 500)
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.hidden) {
|
||||
return clearInterval(this.siteTime)
|
||||
return clearInterval(this.#siteTime)
|
||||
}
|
||||
this.siteTime = setInterval(() => this.getSiteTime(), 500)
|
||||
this.#siteTime = setInterval(() => this.getSiteTime(), 500)
|
||||
}, false)
|
||||
}
|
||||
}
|
||||
|
||||
/** Save and restore scroll position as an automatic bookmark. */
|
||||
initAutoMark() {
|
||||
if (!this.core.config.autoBookmark)
|
||||
if (!this.#core.config.autoBookmark)
|
||||
return
|
||||
window.addEventListener('beforeunload', () => {
|
||||
window.sessionStorage?.setItem(`fixit-bookmark/#${location.pathname}`, String(getScrollTop()))
|
||||
@@ -89,7 +92,7 @@ export class MiscModule implements MiscService {
|
||||
|
||||
/** Initialize the comment section UI. */
|
||||
initComment() {
|
||||
if (!this.core.config.comment?.enable)
|
||||
if (!this.#core.config.comment?.enable)
|
||||
return
|
||||
|
||||
if (document.querySelector('#comments')) {
|
||||
@@ -100,7 +103,7 @@ export class MiscModule implements MiscService {
|
||||
}, false)
|
||||
}
|
||||
|
||||
if (this.core.config.comment.expired)
|
||||
if (this.#core.config.comment.expired)
|
||||
document.querySelector('#comments')!.remove()
|
||||
}
|
||||
|
||||
@@ -109,8 +112,8 @@ export class MiscModule implements MiscService {
|
||||
*/
|
||||
initPostChat() {
|
||||
const initThemeCompatibility = () => {
|
||||
if (this.core.config.postChat) {
|
||||
document.body.classList.toggle('dark', this.core.isDark)
|
||||
if (this.#core.config.postChat) {
|
||||
document.body.classList.toggle('dark', this.#core.isDark)
|
||||
eventBus.on('fixit:switch-theme', ({ detail }) => {
|
||||
if (!detail.isChanged)
|
||||
return
|
||||
@@ -122,7 +125,7 @@ export class MiscModule implements MiscService {
|
||||
const initPostChatUser = () => {
|
||||
if (!window.postChatUser || !window.postChatConfig || window.postChatConfig.userMode === 'magic')
|
||||
return
|
||||
window.postChat_theme = this.core.isDark ? 'dark' : 'light'
|
||||
window.postChat_theme = this.#core.isDark ? 'dark' : 'light'
|
||||
eventBus.on('fixit:switch-theme', ({ detail }) => {
|
||||
if (!detail.isChanged)
|
||||
return
|
||||
|
||||
@@ -10,11 +10,15 @@ import { eventBus } from '../core/event-bus'
|
||||
* - Show update notification toast and handle refresh.
|
||||
*/
|
||||
export class PWAModule implements PWAService {
|
||||
constructor(private readonly core: CoreService) {}
|
||||
#core: CoreService
|
||||
|
||||
constructor(core: CoreService) {
|
||||
this.#core = core
|
||||
}
|
||||
|
||||
/** Register the service worker with update detection and notification binding. */
|
||||
async setup() {
|
||||
const pwa = this.core.config.PWA
|
||||
const pwa = this.#core.config.PWA
|
||||
if (!pwa?.enable || !('serviceWorker' in navigator))
|
||||
return
|
||||
|
||||
@@ -49,14 +53,14 @@ export class PWAModule implements PWAService {
|
||||
window.location.reload()
|
||||
})
|
||||
|
||||
this.bindUpdateNotification(registration)
|
||||
this.#bindUpdateNotification(registration)
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the template-rendered update notification toast.
|
||||
* Shows the toast on `fixit:sw-update` and wires the refresh button to skip waiting.
|
||||
*/
|
||||
private bindUpdateNotification(registration: ServiceWorkerRegistration) {
|
||||
#bindUpdateNotification(registration: ServiceWorkerRegistration) {
|
||||
const toast = document.querySelector<HTMLElement>('.sw-update-notification')
|
||||
if (!toast)
|
||||
return
|
||||
|
||||
@@ -19,8 +19,11 @@ export class SearchModule implements SearchService {
|
||||
#openDialog: (() => void) | undefined
|
||||
#closeDialog: (() => void) | undefined
|
||||
#initialized = false
|
||||
#core: CoreService
|
||||
|
||||
constructor(private readonly core: CoreService) {}
|
||||
constructor(core: CoreService) {
|
||||
this.#core = core
|
||||
}
|
||||
|
||||
/** Create the appropriate search engine based on type. */
|
||||
#createEngine(type: string, searchConfig: SearchConfig): SearchEngine {
|
||||
@@ -30,9 +33,9 @@ export class SearchModule implements SearchService {
|
||||
case 'fuse':
|
||||
return createFuseEngine(searchConfig)
|
||||
case 'cse':
|
||||
return createCSEEngine(this.core.config.cse)
|
||||
return createCSEEngine(this.#core.config.cse)
|
||||
case 'pagefind':
|
||||
return createPagefindEngine(searchConfig, this.core.config.pagefind!)
|
||||
return createPagefindEngine(searchConfig, this.#core.config.pagefind!)
|
||||
default:
|
||||
console.warn(`[FixIt] Unknown search type: "${type}". Supported types: algolia, fuse, cse, pagefind.`)
|
||||
return {
|
||||
@@ -45,7 +48,7 @@ export class SearchModule implements SearchService {
|
||||
|
||||
/** Initialize @algolia/autocomplete-js instance. */
|
||||
#initAutosearch() {
|
||||
const searchConfig = this.core.config.search
|
||||
const searchConfig = this.#core.config.search
|
||||
if (!searchConfig || !this.#engine)
|
||||
return
|
||||
|
||||
@@ -218,7 +221,7 @@ export class SearchModule implements SearchService {
|
||||
|
||||
document.querySelector('.search-trigger.desktop')?.addEventListener('click', open)
|
||||
document.querySelector('.search-trigger.mobile')?.addEventListener('click', () => {
|
||||
this.core.closeMaskOverlay('menu-mobile')
|
||||
this.#core.closeMaskOverlay('menu-mobile')
|
||||
open()
|
||||
})
|
||||
|
||||
@@ -247,7 +250,7 @@ export class SearchModule implements SearchService {
|
||||
|
||||
/** Initialize the search overlay, autocomplete, and engine-specific logic. */
|
||||
setup() {
|
||||
const searchConfig = this.core.config.search
|
||||
const searchConfig = this.#core.config.search
|
||||
if (!searchConfig || !searchConfig.type)
|
||||
return
|
||||
|
||||
|
||||
+18
-15
@@ -10,9 +10,12 @@ import { eventBus } from '../core/event-bus'
|
||||
* - Persist user preference to localStorage.
|
||||
*/
|
||||
export class ThemeModule implements ThemeService {
|
||||
private readonly mql = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
#mql = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
#core: CoreService
|
||||
|
||||
constructor(private readonly core: CoreService) {}
|
||||
constructor(core: CoreService) {
|
||||
this.#core = core
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a theme mode and emit the `fixit:switch-theme` event.
|
||||
@@ -20,19 +23,19 @@ export class ThemeModule implements ThemeService {
|
||||
* @param persist - Whether to save the choice to localStorage (default: `true`).
|
||||
*/
|
||||
setThemeMode(mode: string, persist = true) {
|
||||
const prevIsDark = this.core.isDark
|
||||
this.core.themeMode = mode
|
||||
const prevIsDark = this.#core.isDark
|
||||
this.#core.themeMode = mode
|
||||
document.documentElement.dataset.themeMode = mode
|
||||
this.core.isDark = mode === 'auto' ? this.mql.matches : mode === 'dark'
|
||||
this.#core.isDark = mode === 'auto' ? this.#mql.matches : mode === 'dark'
|
||||
|
||||
if (persist) {
|
||||
window.localStorage?.setItem('theme-mode', mode)
|
||||
}
|
||||
|
||||
eventBus.emit('fixit:switch-theme', {
|
||||
isDark: this.core.isDark,
|
||||
isDark: this.#core.isDark,
|
||||
mode,
|
||||
isChanged: prevIsDark !== this.core.isDark,
|
||||
isChanged: prevIsDark !== this.#core.isDark,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -49,7 +52,7 @@ export class ThemeModule implements ThemeService {
|
||||
return
|
||||
applyThemeColor(detail.isDark)
|
||||
})
|
||||
applyThemeColor(this.core.isDark)
|
||||
applyThemeColor(this.#core.isDark)
|
||||
}
|
||||
|
||||
/** Initialize the theme switch button cycle and system preference listener. */
|
||||
@@ -58,21 +61,21 @@ export class ThemeModule implements ThemeService {
|
||||
|
||||
document.querySelectorAll('.theme-switch').forEach(($themeSwitch: Element) => {
|
||||
$themeSwitch.addEventListener('click', () => {
|
||||
const currentIndex = modes.indexOf(this.core.themeMode as typeof modes[number])
|
||||
const currentIndex = modes.indexOf(this.#core.themeMode as typeof modes[number])
|
||||
const nextMode = modes[(currentIndex + 1) % modes.length]
|
||||
this.setThemeMode(nextMode)
|
||||
}, false)
|
||||
})
|
||||
|
||||
this.mql.addEventListener('change', (e: MediaQueryListEvent) => {
|
||||
if (this.core.themeMode !== 'auto')
|
||||
this.#mql.addEventListener('change', (e: MediaQueryListEvent) => {
|
||||
if (this.#core.themeMode !== 'auto')
|
||||
return
|
||||
const prevIsDark = this.core.isDark
|
||||
this.core.isDark = e.matches
|
||||
const prevIsDark = this.#core.isDark
|
||||
this.#core.isDark = e.matches
|
||||
eventBus.emit('fixit:switch-theme', {
|
||||
isDark: this.core.isDark,
|
||||
isDark: this.#core.isDark,
|
||||
mode: 'auto',
|
||||
isChanged: prevIsDark !== this.core.isDark,
|
||||
isChanged: prevIsDark !== this.#core.isDark,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ const TOC_CONTAINER_IDS = ['toc-content-auto', 'toc-content-static', 'toc-conten
|
||||
* - Clone TOC nodes to detach APlayer event listeners.
|
||||
*/
|
||||
export class TocModule implements TocService {
|
||||
private activeTocId: string | null = null
|
||||
#activeTocId: string | null = null
|
||||
|
||||
/** Get all TOC content containers (auto, static, and drawer). */
|
||||
#getTocContainers(): HTMLElement[] {
|
||||
@@ -141,8 +141,8 @@ export class TocModule implements TocService {
|
||||
$tocRoots.forEach(($tocRoot) => {
|
||||
this.#applyTocActiveState($tocRoot, activeId)
|
||||
})
|
||||
if (this.activeTocId !== activeId) {
|
||||
this.activeTocId = activeId
|
||||
if (this.#activeTocId !== activeId) {
|
||||
this.#activeTocId = activeId
|
||||
if (!isTocStatic()) {
|
||||
const $autoTocRoot = document.querySelector<HTMLElement>('#toc-content-auto > nav')
|
||||
const $autoTocContainer = document.getElementById('toc-content-auto')
|
||||
@@ -160,7 +160,7 @@ export class TocModule implements TocService {
|
||||
/** Sync TOC layout state: drawer button visibility, height, and active heading. */
|
||||
#syncTocLayout() {
|
||||
document.querySelector<HTMLElement>('#toc-drawer-button')?.classList.toggle('hidden', !isTocStatic())
|
||||
this.activeTocId = null
|
||||
this.#activeTocId = null
|
||||
this.syncTocHeight()
|
||||
this.syncTocActiveState()
|
||||
}
|
||||
@@ -229,7 +229,7 @@ export class TocModule implements TocService {
|
||||
this.syncTocHeight()
|
||||
this.syncTocActiveState()
|
||||
const $dialogTocRoot = document.querySelector<HTMLElement>('#toc-content-drawer > nav')!
|
||||
this.#scrollActiveTocLinkIntoView($dialogTocRoot, this.activeTocId!, $dialogTocRoot)
|
||||
this.#scrollActiveTocLinkIntoView($dialogTocRoot, this.#activeTocId!, $dialogTocRoot)
|
||||
;(document.activeElement as HTMLElement)?.blur()
|
||||
})
|
||||
dialog.addEventListener('close', () => {
|
||||
|
||||
Reference in New Issue
Block a user