refactor: restructure TypeScript modules and search config

- Create PublicAPI class to encapsulate module initialization
- Move search config types to modules/search/types.ts
- Rename ui.ts to global.ts for clarity
- Simplify pagefind engine (remove normalizeSortOrder, toObject)
- Move pagefind config to top-level in FixItConfig
- Add TSDoc comments and @param tags
- Update PagefindConfig.sortOrder type to 'asc' | 'desc'
- Improve pagefind detection warning message
This commit is contained in:
Cell
2026-06-18 16:09:16 +08:00
parent 01eadc4356
commit 6f83bc6da5
21 changed files with 211 additions and 210 deletions
+3 -2
View File
@@ -10,7 +10,7 @@ This document defines the detailed coding standards for the FixIt theme project.
- **SCSS variables**: hyphen-separated, semantic (`$global-font-family`, `$code-background-color`)
- **CSS custom properties**: prefixed with `fi-` / `--fi-`
### Guidelines
### SCSS Guidelines
- Use 2-space indentation
- Use CSS variables for theme switching support
@@ -48,6 +48,7 @@ export class ExampleModule implements ExampleService {
- Import the shared `eventBus` singleton from `core/event-bus` for cross-module communication
- Constructor injection for dependencies — no global state access
- `window.fixit` exposes a typed public API (`FixItPublicAPI`) for user custom scripts
- Comment style: follow [TSDoc](https://tsdoc.org/) conventions
### Utilities
@@ -63,7 +64,7 @@ export class ExampleModule implements ExampleService {
- **Translation**: Use `T` function for i18n (`{{ T "header.switchTheme" }}`)
- **Whitespace**: Use `{{- -}}` trim markers to control whitespace output
### Guidelines
### Hugo Template Guidelines
- Use `partialCached` for expensive partials that don't change per page
- Use `.Site.Store` for shared computed values (e.g. fingerprint)
+1
View File
@@ -117,6 +117,7 @@ Hugo Pipes processes all assets. The key orchestration is in `_partials/base/ass
- Constructor injection for dependencies — no global state access
- Import the shared `eventBus` singleton from `core/event-bus` — do not create new instances
- Pure functions only in `utils/` — no side effects, no DOM state
- Comment style: follow [TSDoc](https://tsdoc.org/) conventions
### Hugo Templates
+1
View File
@@ -1,5 +1,6 @@
/**
* Console banner — prints a styled FixIt version message in the browser console.
* @param version - The FixIt version string to display.
*/
export function printBanner(version: string) {
const color = '#FF735A'
+5
View File
@@ -9,6 +9,11 @@ export interface FixItEventMap {
'fixit:code-tab-sync': { lang: string, source: HTMLElement }
}
/** Document event map augmented with FixIt custom events. */
export type FixItDocumentEventMap = {
[K in keyof FixItEventMap]: CustomEvent<FixItEventMap[K]>
}
type Handler<T> = T extends void
? (() => void) | ((event: CustomEvent<void>) => void)
: (event: CustomEvent<T>) => void
+54
View File
@@ -0,0 +1,54 @@
import type { FixItPublicAPI } from './tokens'
import { CodeModule } from '../modules/code'
import { ContentModule } from '../modules/content'
import { CoreModule } from '../modules/core'
import { EncryptionModule } from '../modules/encryption'
import { EventsModule } from '../modules/events'
import { MenuModule } from '../modules/menu'
import { MiscModule } from '../modules/misc'
import { SearchModule } from '../modules/search'
import { ThemeModule } from '../modules/theme'
import { TocModule } from '../modules/toc'
import { eventBus } from './event-bus'
/**
* Public API facade — typed interface exposed on `window.fixit`.
*
* Initializes and exposes all service modules in dependency order.
*/
export class PublicAPI implements FixItPublicAPI {
readonly core
readonly theme
readonly code
readonly toc
readonly menu
readonly search
readonly enc
readonly misc
readonly content
readonly events
readonly eventBus = eventBus
constructor() {
// Initialize modules in dependency order
this.core = new CoreModule()
this.theme = new ThemeModule(this.core)
this.code = new CodeModule()
this.toc = new TocModule()
this.menu = new MenuModule(this.core)
this.search = new SearchModule(this.core)
this.enc = new EncryptionModule(this.core)
this.misc = new MiscModule(this.core)
this.content = new ContentModule(this.core, this.code)
this.events = new EventsModule(this.core, this.toc, this.code)
}
get config() { return this.core.config }
get version() { return this.core.version }
get themeMode() { return this.core.themeMode }
get isDark() { return this.core.isDark }
setThemeMode(mode: string, persist?: boolean) {
this.theme.setThemeMode(mode, persist)
}
}
+24 -3
View File
@@ -1,14 +1,13 @@
/** Service interfaces for all FixIt modules. */
import type { FixItConfig, MaskOverlayHandler } from '../types'
import type { TypedEventBus } from './event-bus'
// ─── CoreService ───
export interface CoreService {
readonly config: FixItConfig
readonly version: string
isDark: boolean
themeMode: string
disableScrollEvent: boolean
newScrollTop: number
oldScrollTop: number
registerMaskOverlay: (name: string, handlers: MaskOverlayHandler) => void
openMaskOverlay: (name: string) => void
closeMaskOverlay: (name: string, skipSync?: boolean) => void
@@ -79,3 +78,25 @@ export interface EventsService {
onClickMask: () => void
initPrint: () => void
}
// ─── FixItPublicAPI ───
export interface FixItPublicAPI {
readonly config: FixItConfig
readonly version: string
readonly themeMode: string
readonly isDark: boolean
// Modules
readonly core: CoreService
readonly theme: ThemeService
readonly code: CodeService
readonly toc: TocService
readonly menu: MenuService
readonly search: SearchService
readonly enc: EncryptionService
readonly misc: MiscService
readonly content: ContentService
readonly events: EventsService
readonly eventBus: TypedEventBus
// Methods
setThemeMode: (mode: string, persist?: boolean) => void
}
+25 -59
View File
@@ -1,77 +1,43 @@
import { printBanner } from './core/banner'
import { eventBus } from './core/event-bus'
import { CodeModule } from './modules/code'
import { ContentModule } from './modules/content'
import { CoreModule } from './modules/core'
import { EncryptionModule } from './modules/encryption'
import { EventsModule } from './modules/events'
import { MenuModule } from './modules/menu'
import { MiscModule } from './modules/misc'
import { SearchModule } from './modules/search'
import { ThemeModule } from './modules/theme'
import { TocModule } from './modules/toc'
import { PublicAPI } from './core/public-api'
/**
* FixIt theme entry point — initializes all modules and the window.fixit facade.
*
* Responsibilities:
* - Instantiate all service modules with direct constructor calls.
* - Build the `window.fixit` facade.
* - Run the init sequence on `DOMContentLoaded` (content, theme, menu, search, etc.).
* - Create PublicAPI which initializes all service modules.
* - Run the init sequence on `DOMContentLoaded`.
*/
function bootstrap(): void {
const core = new CoreModule()
const theme = new ThemeModule(core)
const code = new CodeModule()
const toc = new TocModule()
const menu = new MenuModule(core)
const search = new SearchModule(core)
const enc = new EncryptionModule(core)
const misc = new MiscModule(core)
const content = new ContentModule(core, code)
const events = new EventsModule(core, toc, search, code)
// Build window.fixit facade
window.fixit = {
get config() { return core.config },
get version() { return core.version },
get themeMode() { return core.themeMode },
get isDark() { return core.isDark },
get newScrollTop() { return core.newScrollTop },
get oldScrollTop() { return core.oldScrollTop },
setThemeMode: (mode, persist) => theme.setThemeMode(mode, persist),
registerMaskOverlay: (name, handlers) => core.registerMaskOverlay(name, handlers),
toggleMaskOverlay: name => core.toggleMaskOverlay(name),
closeMaskOverlay: (name, skipSync) => core.closeMaskOverlay(name, skipSync),
initContent: target => content.initContent(target),
eventBus,
}
// Build window.fixit facade with all modules
window.fixit = new PublicAPI()
function init() {
try {
toc.setup()
content.setup()
enc.initFixItDecryptor()
theme.initThemeColor()
content.initSVGIcon()
menu.initMenu()
theme.initSwitchTheme()
search.initSearch()
misc.initSiteTime()
misc.initServiceWorker()
misc.initAutoMark()
misc.initReward()
misc.initPostChatUser()
misc.initComment()
events.onScroll()
events.onResize()
events.onClickMask()
events.initPrint()
window.fixit.toc.setup()
window.fixit.content.setup()
window.fixit.enc.initFixItDecryptor()
window.fixit.theme.initThemeColor()
window.fixit.content.initSVGIcon()
window.fixit.menu.initMenu()
window.fixit.theme.initSwitchTheme()
window.fixit.search.initSearch()
window.fixit.misc.initSiteTime()
window.fixit.misc.initServiceWorker()
window.fixit.misc.initAutoMark()
window.fixit.misc.initReward()
window.fixit.misc.initPostChatUser()
window.fixit.misc.initComment()
window.fixit.content.initContent()
window.fixit.events.onScroll()
window.fixit.events.onResize()
window.fixit.events.onClickMask()
window.fixit.events.initPrint()
}
catch (err) {
console.error(err)
}
printBanner(core.version)
printBanner(window.fixit.version)
}
document.addEventListener('DOMContentLoaded', init, false)
+1 -9
View File
@@ -1,6 +1,6 @@
import type { CoreService } from '../core/tokens'
import type { FixItConfig, MaskOverlayHandler } from '../types'
import { getScrollTop, getThemeMode, isDarkMode } from '../utils'
import { getThemeMode, isDarkMode } from '../utils'
/**
* Core module — shared state initialization and mask overlay management.
@@ -15,9 +15,6 @@ export class CoreModule implements CoreService {
readonly version: string
themeMode: string
isDark: boolean
newScrollTop: number
oldScrollTop: number
disableScrollEvent: boolean
private activeMaskOverlay: string | null = null
private readonly maskOverlays = new Map<string, MaskOverlayHandler>()
@@ -27,9 +24,6 @@ export class CoreModule implements CoreService {
this.version = this.config.version
this.themeMode = getThemeMode()
this.isDark = isDarkMode()
this.newScrollTop = getScrollTop()
this.oldScrollTop = this.newScrollTop
this.disableScrollEvent = false
window.objectFitImages?.()
}
@@ -46,7 +40,6 @@ export class CoreModule implements CoreService {
/** Open a named mask overlay, closing any previously active one. */
openMaskOverlay(name: string) {
this.disableScrollEvent = true
const overlay = this.maskOverlays.get(name)
if (!overlay)
return
@@ -60,7 +53,6 @@ export class CoreModule implements CoreService {
/** Close a named mask overlay and optionally skip mask state sync. */
closeMaskOverlay(name: string, skipSync = false) {
this.disableScrollEvent = false
const overlay = this.maskOverlays.get(name)
if (!overlay)
return
+10 -14
View File
@@ -1,4 +1,4 @@
import type { CodeService, CoreService, EventsService, SearchService, TocService } from '../core/tokens'
import type { CodeService, CoreService, EventsService, TocService } from '../core/tokens'
import { eventBus } from '../core/event-bus'
import { animateCSS, getScrollTop, isMobile, scrollIntoView } from '../utils'
@@ -13,17 +13,18 @@ import { animateCSS, getScrollTop, isMobile, scrollIntoView } from '../utils'
*/
export class EventsModule implements EventsService {
#resizeTimeout: number | null = null
#newScrollTop = 0
#oldScrollTop = 0
constructor(
private readonly core: CoreService,
private readonly toc: TocService,
private readonly search: SearchService,
private readonly code: CodeService,
) {}
/** Bind scroll listener: auto-hide headers, reading progress, back-to-top, and TOC sync. */
onScroll() {
const ACCURACY = 20
const ACCURACY = 50
const $autoHeaders: HTMLElement[] = []
const $backToTop = document.querySelector<HTMLElement>('.back-to-top')
const $readingProgressBar = document.querySelector<HTMLElement>('.reading-progress-bar')
@@ -36,13 +37,9 @@ export class EventsModule implements EventsService {
$backToTop?.addEventListener('click', () => {
scrollIntoView('body')
})
window.addEventListener('scroll', (event) => {
if (this.core.disableScrollEvent) {
event.preventDefault()
return
}
this.core.newScrollTop = getScrollTop()
const scroll = this.core.newScrollTop - this.core.oldScrollTop
window.addEventListener('scroll', () => {
this.#newScrollTop = getScrollTop()
const scroll = this.#newScrollTop - this.#oldScrollTop
if (Math.abs(scroll) > ACCURACY) {
this.core.closeActiveMaskOverlay()
const isScrollingDown = scroll > 0
@@ -57,14 +54,14 @@ export class EventsModule implements EventsService {
}
})
}
else if (this.core.newScrollTop <= 0) {
else if (this.#newScrollTop <= 0) {
$autoHeaders.forEach(($header) => {
$header.classList.remove('header__fadeOutUp')
animateCSS($header, ['header__fadeInDown'], true)
})
}
const contentHeight = document.body.scrollHeight - window.innerHeight
const scrollPercent = Math.max(Math.min(100 * Math.max(this.core.newScrollTop, 0) / contentHeight, 100), 0)
const scrollPercent = Math.max(Math.min(100 * Math.max(this.#newScrollTop, 0) / contentHeight, 100), 0)
if ($readingProgressBar) {
$readingProgressBar.style.setProperty('--fi-progress', `${scrollPercent.toFixed(2)}%`)
}
@@ -88,7 +85,7 @@ export class EventsModule implements EventsService {
eventBus.emit('fixit:scroll')
this.toc.syncTocHeight()
this.toc.syncTocActiveState()
this.core.oldScrollTop = this.core.newScrollTop
this.#oldScrollTop = this.#newScrollTop
}, false)
}
@@ -101,7 +98,6 @@ export class EventsModule implements EventsService {
this.#resizeTimeout = null
eventBus.emit('fixit:resize')
this.toc.initToc()
this.search.initSearch()
this.toc.syncTocHeight()
this.toc.syncTocActiveState()
+3 -2
View File
@@ -1,10 +1,11 @@
import type { SearchConfig } from '../../../types/config'
import type { SearchEngine, SearchResult } from '../types'
import type { SearchConfig, SearchEngine, SearchResult } from '../types'
/**
* Create an Algolia search engine instance.
*
* Lazily initializes the Algolia v5 lite client on first search.
* @param searchConfig - The search configuration containing Algolia credentials.
* @returns A SearchEngine instance for Algolia.
*/
export function createAlgoliaEngine(searchConfig: SearchConfig): SearchEngine {
if (!searchConfig.algoliaAppID || !searchConfig.algoliaSearchKey || !searchConfig.algoliaIndex) {
+3 -2
View File
@@ -1,10 +1,11 @@
import type { CSEConfig } from '../../../types/config'
import type { SearchEngine, SearchResult } from '../types'
import type { CSEConfig, SearchEngine, SearchResult } from '../types'
/**
* Create a Google Custom Search Engine adapter.
*
* Returns a single result that links to the CSE results page.
* @param cseConfig - The CSE configuration, or `undefined` if not configured.
* @returns A SearchEngine instance for Google CSE.
*/
export function createCSEEngine(cseConfig: CSEConfig | undefined): SearchEngine {
return {
+3 -2
View File
@@ -1,11 +1,12 @@
import type { SearchConfig } from '../../../types/config'
import type { SearchEngine, SearchResult } from '../types'
import type { SearchConfig, SearchEngine, SearchResult } from '../types'
import { applyHighlightToText } from '../../../utils'
/**
* Create a Fuse.js search engine instance.
*
* Lazily fetches and indexes the search JSON on first search.
* @param searchConfig - The search configuration containing Fuse.js options.
* @returns A SearchEngine instance for Fuse.js.
*/
export function createFuseEngine(searchConfig: SearchConfig): SearchEngine {
return {
+10 -34
View File
@@ -1,30 +1,4 @@
import type { SearchEngine, SearchResult } from '../types'
/** Matches absolute URLs (e.g. "https://..." or "//...") */
const ABSOLUTE_URL_RE = /^(?:[a-z]+:)?\/\//i
/**
* Normalize a Pagefind bundle path to a full URL.
* Relative paths are resolved against the given baseURL or document.baseURI.
*/
function normalizeBundlePath(path: string, baseURL?: string): string {
let bundlePath = typeof path === 'string' && path.length > 0 ? path : 'pagefind/'
if (!bundlePath.endsWith('/')) {
bundlePath = `${bundlePath}/`
}
if (ABSOLUTE_URL_RE.test(bundlePath)) {
return bundlePath
}
return new URL(bundlePath, baseURL || document.baseURI).toString()
}
/** Safely cast a value to a plain object; returns `{}` for non-objects. */
const toObject = (value: unknown): Record<string, unknown> => (value && typeof value === 'object' ? value as Record<string, unknown> : {})
/** Normalize sort order to 'asc' or 'desc', defaulting to 'desc'. */
function normalizeSortOrder(value: unknown): 'asc' | 'desc' {
return String(value).toLowerCase() === 'asc' ? 'asc' : 'desc'
}
import type { PagefindConfig, SearchConfig, SearchEngine, SearchResult } from '../types'
/** Replace `<mark>` tags in Pagefind excerpts with the configured highlight tag. */
function replaceExcerptHighlightTag(excerpt: string, highlightTag: string): string {
@@ -40,15 +14,17 @@ function replaceExcerptHighlightTag(excerpt: string, highlightTag: string): stri
* Create a Pagefind search engine with lazy-loading and built-in filters.
*
* Wraps the Pagefind library to conform to the `SearchEngine` interface.
* @param searchConfig - The search configuration.
* @param pagefindConfig - The Pagefind engine configuration.
* @returns A SearchEngine instance for Pagefind.
*/
export function createPagefindEngine(searchConfig: Record<string, any>): SearchEngine {
const pagefindConfig = toObject(searchConfig.pagefind)
const bundlePath = normalizeBundlePath(pagefindConfig.bundlePath as string, pagefindConfig.baseURL as string)
export function createPagefindEngine(searchConfig: SearchConfig, pagefindConfig: PagefindConfig): SearchEngine {
const bundlePath = pagefindConfig.bundlePath || 'pagefind/'
const rawDebounceTimeout = Number(pagefindConfig.debounceTimeoutMs ?? 300)
const debounceTimeout = Number.isFinite(rawDebounceTimeout) ? Math.max(0, rawDebounceTimeout) : 300
const builtInFiltersEnabled = pagefindConfig.useBuiltInFilters !== false
const sortBy = typeof pagefindConfig.sortBy === 'string' ? (pagefindConfig.sortBy as string).trim() : ''
const sortOrder = normalizeSortOrder(pagefindConfig.sortOrder)
const sortBy = pagefindConfig.sortBy
const sortOrder = pagefindConfig.sortOrder ?? 'desc'
const highlightTag = searchConfig.highlightTag ?? 'em'
const excerptLength = Number(searchConfig.snippetLength ?? 30)
@@ -64,7 +40,7 @@ export function createPagefindEngine(searchConfig: Record<string, any>): SearchE
const ensurePagefind = async () => {
if (!state.loading) {
state.loading = import(/* @vite-ignore */ `${bundlePath}pagefind.js`)
state.loading = import(`${bundlePath}pagefind.js`)
.then(async (mod: any) => {
if (!state.initialized) {
const options: Record<string, any> = {}
@@ -96,7 +72,7 @@ export function createPagefindEngine(searchConfig: Record<string, any>): SearchE
return state.availableFilters
}
try {
state.availableFilters = toObject(await pagefind.filters())
state.availableFilters = (await pagefind.filters()) as Record<string, any> ?? {}
}
catch (error) {
console.warn('[FixIt] failed to read Pagefind filters:', error)
+5 -6
View File
@@ -1,6 +1,5 @@
import type { CoreService, SearchService } from '../../core/tokens'
import type { SearchEngine, SearchResult } from './types'
import type { SearchConfig, SearchEngine, SearchResult } from './types'
import { createAlgoliaEngine } from './engines/algolia'
import { createCSEEngine } from './engines/cse'
import { createFuseEngine } from './engines/fuse'
@@ -26,12 +25,12 @@ export class SearchModule implements SearchService {
/** Initialize the search overlay, autocomplete, and engine-specific logic. */
initSearch() {
const searchConfig = this.core.config.search
if (!searchConfig)
if (!searchConfig || !searchConfig.type)
return
// Initialize engine once
if (!this.#engine) {
this.#engine = this.#createEngine(searchConfig.type!, searchConfig)
this.#engine = this.#createEngine(searchConfig.type, searchConfig)
}
// Initialize dialog and autocomplete once
@@ -44,7 +43,7 @@ export class SearchModule implements SearchService {
}
/** Create the appropriate search engine based on type. */
#createEngine(type: string, searchConfig: Record<string, any>): SearchEngine {
#createEngine(type: string, searchConfig: SearchConfig): SearchEngine {
switch (type) {
case 'algolia':
return createAlgoliaEngine(searchConfig)
@@ -53,7 +52,7 @@ export class SearchModule implements SearchService {
case 'cse':
return createCSEEngine(this.core.config.cse)
case 'pagefind':
return createPagefindEngine(searchConfig)
return createPagefindEngine(searchConfig, this.core.config.pagefind!)
default:
console.warn(`[FixIt] Unknown search type: "${type}". Supported types: algolia, fuse, cse, pagefind.`)
return {
+42
View File
@@ -15,3 +15,45 @@ export interface SearchEngine {
preload?: () => Promise<void>
destroy?: () => void
}
/** Search configuration */
export interface SearchConfig {
type?: string
placeholder?: string
maxResultLength?: number
snippetLength?: number
highlightTag?: string
isCaseSensitive?: boolean
minMatchCharLength?: number
findAllMatches?: boolean
location?: number
threshold?: number
distance?: number
ignoreLocation?: boolean
useExtendedSearch?: boolean
ignoreFieldNorm?: boolean
fuseIndexURL?: string
algoliaAppID?: string
algoliaSearchKey?: string
algoliaIndex?: string
noResultsFound?: string
clearText?: string
}
/** Pagefind engine configuration */
export interface PagefindConfig {
bundlePath?: string
debounceTimeoutMs?: number
useBuiltInFilters?: boolean
sortBy?: 'date'
sortOrder?: 'asc' | 'desc'
}
/** Google Custom Search Engine configuration */
export interface CSEConfig {
engine?: string
cx?: string
resultsPage?: string
searchIn?: string
gotoResultsPage?: string
}
+2 -41
View File
@@ -1,3 +1,4 @@
import type { CSEConfig, PagefindConfig, SearchConfig } from '../modules/search/types'
import type { MermaidConfig } from './third-party'
/** Mask overlay handler */
@@ -13,6 +14,7 @@ export interface FixItConfig {
twemoji?: boolean
search?: SearchConfig
cse?: CSEConfig
pagefind?: PagefindConfig
echarts?: EchartsConfig
mapbox?: MapboxConfig
typeit?: TypeItConfig
@@ -31,47 +33,6 @@ export interface FixItConfig {
print?: PrintConfig
}
export interface SearchConfig {
type?: string
placeholder?: string
maxResultLength?: number
snippetLength?: number
highlightTag?: string
isCaseSensitive?: boolean
minMatchCharLength?: number
findAllMatches?: boolean
location?: number
threshold?: number
distance?: number
ignoreLocation?: boolean
useExtendedSearch?: boolean
ignoreFieldNorm?: boolean
fuseIndexURL?: string
algoliaAppID?: string
algoliaSearchKey?: string
algoliaIndex?: string
noResultsFound?: string
clearText?: string
pagefind?: PagefindConfig
}
export interface PagefindConfig {
bundlePath?: string
baseURL?: string
debounceTimeoutMs?: number
useBuiltInFilters?: boolean
sortBy?: string
sortOrder?: string
}
export interface CSEConfig {
engine?: string
cx?: string
resultsPage?: string
searchIn?: string
gotoResultsPage?: string
}
export interface EchartsConfig {
lightTheme?: object
darkTheme?: object
@@ -1,5 +1,6 @@
import type { FixItEventMap, TypedEventBus } from '../core/event-bus'
import type { FixItConfig, MaskOverlayHandler } from './config'
import type { FixItDocumentEventMap } from '../core/event-bus'
import type { FixItPublicAPI } from '../core/tokens'
import type { FixItConfig } from './config'
import type { MermaidRuntimeModule, PanzoomInstance } from './third-party'
export interface TabContainerChangedDetail {
@@ -10,26 +11,6 @@ export type TabContainerChangedEvent = CustomEvent<TabContainerChangedDetail> &
panel: Element | null
}
type FixItDocumentEventMap = {
[K in keyof FixItEventMap]: CustomEvent<FixItEventMap[K]>
}
/** Public API exposed on window.fixit. */
export interface FixItPublicAPI {
readonly config: FixItConfig
readonly version: string
readonly themeMode: string
readonly isDark: boolean
readonly newScrollTop: number
readonly oldScrollTop: number
setThemeMode: (mode: string, persist?: boolean) => void
registerMaskOverlay: (name: string, handlers: MaskOverlayHandler) => void
toggleMaskOverlay: (name: string) => void
closeMaskOverlay: (name: string, skipSync?: boolean) => void
initContent: (target?: Element | Document) => void
eventBus: TypedEventBus
}
declare global {
interface DocumentEventMap extends FixItDocumentEventMap {
'tab-container-changed': TabContainerChangedEvent
+1 -1
View File
@@ -1,3 +1,3 @@
export type * from './config'
export type * from './global'
export type * from './third-party'
export type * from './ui'
+1 -1
View File
@@ -507,7 +507,7 @@ dark = "#151b23"
# Search config
[params.search]
enable = true
# type of search engine ["algolia", "fuse", "pagefind", "cse"]
# type of search engine ["fuse", "algolia", "pagefind", "cse"]
type = "fuse"
# max index length of the chunked content
contentLength = 4000
+12 -10
View File
@@ -45,18 +45,20 @@
{{- dict "Source" "js/lib/fuse.ts" "Build" true "Fingerprint" $fingerprint "Defer" true | dict "Page" . "Data" | partial "store/script.html" -}}
{{- $config = dict "isCaseSensitive" $search.fuse.isCaseSensitive "minMatchCharLength" $search.fuse.minMatchCharLength "findAllMatches" $search.fuse.findAllMatches "location" $search.fuse.location "threshold" $search.fuse.threshold "distance" $search.fuse.distance "ignoreLocation" $search.fuse.ignoreLocation "useExtendedSearch" $search.fuse.useExtendedSearch "ignoreFieldNorm" $search.fuse.ignoreFieldNorm | dict "search" | merge $config -}}
{{- else if eq $search.type "pagefind" -}}
{{- $config = dict "type" "pagefind" | dict "search" | merge $config -}}
{{- $pagefind := $search.pagefind | default dict -}}
{{- $bundlePath := relURL ($pagefind.bundlePath | default "pagefind/") -}}
{{- if not (hasSuffix $bundlePath "/") -}}
{{- $bundlePath = printf "%v/" $bundlePath -}}
{{- end -}}
{{- $config = dict
"type" "pagefind"
"pagefind" (dict
"bundlePath" ($pagefind.bundlePath | default "pagefind/")
"baseURL" .Site.BaseURL
"debounceTimeoutMs" ($pagefind.debounceTimeoutMs | default 300)
"useBuiltInFilters" ($pagefind.useBuiltInFilters | default true)
"sortBy" ($pagefind.sortBy | default "")
"sortOrder" ($pagefind.sortOrder | default "desc")
)
| dict "search" | merge $config -}}
"bundlePath" $bundlePath
"debounceTimeoutMs" ($pagefind.debounceTimeoutMs | default 300)
"useBuiltInFilters" ($pagefind.useBuiltInFilters | default true)
"sortBy" ($pagefind.sortBy | default "")
"sortOrder" ($pagefind.sortOrder | default "desc")
| dict "pagefind" | merge $config
-}}
{{- else if eq $search.type "cse" -}}
{{- $config = dict "type" "cse" | dict "search" | merge $config -}}
{{- $cse := .Site.Params.cse -}}
@@ -1,3 +1,3 @@
{{- if and (eq .Site hugo.Sites.Default) .Site.Params.search.enable (eq .Site.Params.search.type "pagefind") -}}
{{- warnf "FixIt Pagefind search enabled\nRun `npx pagefind --site <publicDir>` after site build to create the search index.\n\n" -}}
{{- if and (eq .Site hugo.Sites.Default) .Site.Params.search.enable (eq .Site.Params.search.type "pagefind") (not (fileExists "public/pagefind")) -}}
{{- warnf "[FixIt] Pagefind search index not found.\nRun `npx pagefind --site public` in the site root directory after building.\n" -}}
{{- end -}}