mirror of
https://github.com/squidfunk/mkdocs-material.git
synced 2026-07-31 17:58:41 -04:00
Formatting + moved search index fetching to top level
This commit is contained in:
@@ -123,7 +123,7 @@ export function feature(flag: Flag): boolean {
|
||||
* Retrieve the translation for the given key
|
||||
*
|
||||
* @param key - Key to be translated
|
||||
* @param value - Value to be replaced
|
||||
* @param value - Positional value, if any
|
||||
*
|
||||
* @returns Translation
|
||||
*/
|
||||
|
||||
@@ -30,9 +30,8 @@ import { mapTo } from "rxjs/operators"
|
||||
/**
|
||||
* Watch document
|
||||
*
|
||||
* Documents must be implemented as subjects, so all downstream observables are
|
||||
* automatically updated when a new document is emitted. This enabled features
|
||||
* like instant loading.
|
||||
* Documents are implemented as subjects, so all downstream observables are
|
||||
* automatically updated when a new document is emitted.
|
||||
*
|
||||
* @returns Document subject
|
||||
*/
|
||||
|
||||
@@ -21,9 +21,16 @@
|
||||
*/
|
||||
|
||||
import { Observable, fromEvent, merge } from "rxjs"
|
||||
import { distinctUntilChanged, map, startWith } from "rxjs/operators"
|
||||
import {
|
||||
distinctUntilChanged,
|
||||
map,
|
||||
startWith
|
||||
} from "rxjs/operators"
|
||||
|
||||
import { getElementContentSize, getElementSize } from "../size"
|
||||
import {
|
||||
getElementContentSize,
|
||||
getElementSize
|
||||
} from "../size"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Types
|
||||
|
||||
@@ -29,10 +29,10 @@ import { Subject } from "rxjs"
|
||||
/**
|
||||
* Retrieve location
|
||||
*
|
||||
* This function will return a `URL` object (and not `Location`) in order to
|
||||
* normalize typings across the application. Furthermore, locations need to be
|
||||
* tracked without setting them and `Location` is a singleton which represents
|
||||
* the current location.
|
||||
* This function returns a `URL` object (and not `Location`) to normalize the
|
||||
* typings across the application. Furthermore, locations need to be tracked
|
||||
* without setting them and `Location` is a singleton which represents the
|
||||
* current location.
|
||||
*
|
||||
* @returns URL
|
||||
*/
|
||||
|
||||
@@ -31,10 +31,11 @@ import {
|
||||
switchMap
|
||||
} from "rxjs/operators"
|
||||
|
||||
import { feature } from "./_"
|
||||
import { configuration, feature } from "./_"
|
||||
import {
|
||||
at,
|
||||
getElement,
|
||||
requestJSON,
|
||||
setToggle,
|
||||
watchDocument,
|
||||
watchKeyboard,
|
||||
@@ -60,6 +61,7 @@ import {
|
||||
watchMain
|
||||
} from "./components"
|
||||
import {
|
||||
SearchIndex,
|
||||
setupClipboardJS,
|
||||
setupInstantLoading
|
||||
} from "./integrations"
|
||||
@@ -89,6 +91,12 @@ const tablet$ = watchMedia("(min-width: 960px)")
|
||||
const screen$ = watchMedia("(min-width: 1220px)")
|
||||
const print$ = watchPrint()
|
||||
|
||||
/* Retrieve search index */
|
||||
const config = configuration()
|
||||
const index$ = __search?.index || requestJSON<SearchIndex>(
|
||||
`${config.base}/search/search_index.json`
|
||||
)
|
||||
|
||||
/* Set up Clipboard.js integration */
|
||||
const alert$ = new Subject<string>()
|
||||
setupClipboardJS({ alert$ })
|
||||
@@ -160,11 +168,11 @@ const control$ = merge(
|
||||
|
||||
/* Search */
|
||||
...getComponentElements("search")
|
||||
.map(el => mountSearch(el, { keyboard$ })),
|
||||
.map(el => mountSearch(el, { index$, keyboard$ })),
|
||||
|
||||
/* Repository information */
|
||||
...getComponentElements("source")
|
||||
.map(el => mountSource(el as HTMLAnchorElement)),
|
||||
.map(el => mountSource(el)),
|
||||
|
||||
/* Navigation tabs */
|
||||
...getComponentElements("tabs")
|
||||
|
||||
@@ -61,6 +61,32 @@ export type Component<
|
||||
ref: U /* Component reference */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Helper types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Component type map
|
||||
*/
|
||||
interface ComponentTypeMap {
|
||||
"announce": HTMLElement /* Announcement bar */
|
||||
"container": HTMLElement /* Container */
|
||||
"content": HTMLElement /* Content */
|
||||
"dialog": HTMLElement /* Dialog */
|
||||
"header": HTMLElement /* Header */
|
||||
"header-title": HTMLElement /* Header title */
|
||||
"header-topic": HTMLElement /* Header topic */
|
||||
"main": HTMLElement /* Main area */
|
||||
"search": HTMLElement /* Search */
|
||||
"search-query": HTMLInputElement /* Search input */
|
||||
"search-result": HTMLElement /* Search results */
|
||||
"sidebar": HTMLElement /* Sidebar */
|
||||
"skip": HTMLAnchorElement /* Skip link */
|
||||
"source": HTMLAnchorElement /* Repository information */
|
||||
"tabs": HTMLElement /* Navigation tabs */
|
||||
"toc": HTMLElement /* Table of contents */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
@@ -68,31 +94,31 @@ export type Component<
|
||||
/**
|
||||
* Retrieve the element for a given component or throw a reference error
|
||||
*
|
||||
* @template T - Element type
|
||||
* @template T - Component type
|
||||
*
|
||||
* @param type - Component type
|
||||
* @param node - Node of reference
|
||||
*
|
||||
* @returns Element
|
||||
*/
|
||||
export function getComponentElement<T extends HTMLElement>(
|
||||
type: ComponentType, node: ParentNode = document
|
||||
): T {
|
||||
export function getComponentElement<T extends ComponentType>(
|
||||
type: T, node: ParentNode = document
|
||||
): ComponentTypeMap[T] {
|
||||
return getElementOrThrow(`[data-md-component=${type}]`, node)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all elements for a given component
|
||||
*
|
||||
* @template T - Element type
|
||||
* @template T - Component type
|
||||
*
|
||||
* @param type - Component type
|
||||
* @param node - Node of reference
|
||||
*
|
||||
* @returns Elements
|
||||
*/
|
||||
export function getComponentElements<T extends HTMLElement>(
|
||||
type: ComponentType, node: ParentNode = document
|
||||
): T[] {
|
||||
export function getComponentElements<T extends ComponentType>(
|
||||
type: T, node: ParentNode = document
|
||||
): ComponentTypeMap[T][] {
|
||||
return getElements(`[data-md-component=${type}]`, node)
|
||||
}
|
||||
|
||||
@@ -95,8 +95,8 @@ let index = 0
|
||||
/**
|
||||
* Watch code block
|
||||
*
|
||||
* This function will monitor size changes of the viewport, as well as switches
|
||||
* of content tabs with embedded code blocks, as both may trigger overflow.
|
||||
* This function monitors size changes of the viewport, as well as switches of
|
||||
* content tabs with embedded code blocks, as both may trigger overflow.
|
||||
*
|
||||
* @param el - Code block element
|
||||
* @param options - Options
|
||||
@@ -163,7 +163,7 @@ export function mountCodeBlock(
|
||||
resetFocusable(el)
|
||||
})
|
||||
|
||||
/* Inject button for Clipboard.js integration */
|
||||
/* Render button for Clipboard.js integration */
|
||||
if (ClipboardJS.isSupported()) {
|
||||
const parent = el.closest("pre")!
|
||||
parent.id = `__code_${index++}`
|
||||
|
||||
@@ -52,8 +52,8 @@ const sentinel = createElement("table")
|
||||
/**
|
||||
* Mount data table
|
||||
*
|
||||
* This function wraps a data table in another scrollable container, so they
|
||||
* can be scrolled on smaller screen sizes and won't break the layout.
|
||||
* This function wraps a data table in another scrollable container, so it can
|
||||
* be smoothly scrolled on smaller screen sizes and won't break the layout.
|
||||
*
|
||||
* @param el - Data table element
|
||||
*
|
||||
|
||||
@@ -105,8 +105,8 @@ export function watchDialog(
|
||||
/**
|
||||
* Mount dialog
|
||||
*
|
||||
* This function makes the dialog in the right corner appear when a new alert
|
||||
* is emitted through the subject that is passed as part of the options.
|
||||
* This function reveals the dialog in the right cornerwhen a new alert is
|
||||
* emitted through the subject that is passed as part of the options.
|
||||
*
|
||||
* @param el - Dialog element
|
||||
* @param options - Options
|
||||
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
Keyboard,
|
||||
getActiveElement,
|
||||
getElements,
|
||||
requestJSON,
|
||||
setElementFocus,
|
||||
setElementSelection,
|
||||
setToggle
|
||||
@@ -63,24 +62,10 @@ export type Search =
|
||||
* Mount options
|
||||
*/
|
||||
interface MountOptions {
|
||||
index$: ObservableInput<SearchIndex> /* Search index observable */
|
||||
keyboard$: Observable<Keyboard> /* Keyboard observable */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Helper functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Fetch search index
|
||||
*
|
||||
* @param url - Search index URL
|
||||
*
|
||||
* @returns Promise or observable
|
||||
*/
|
||||
function fetchSearchIndex(url: string): ObservableInput<SearchIndex> {
|
||||
return __search?.index || requestJSON<SearchIndex>(url)
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
@@ -97,16 +82,14 @@ function fetchSearchIndex(url: string): ObservableInput<SearchIndex> {
|
||||
* @returns Search component observable
|
||||
*/
|
||||
export function mountSearch(
|
||||
el: HTMLElement, { keyboard$ }: MountOptions
|
||||
el: HTMLElement, { index$, keyboard$ }: MountOptions
|
||||
): Observable<Component<Search>> {
|
||||
if (location.protocol === "file:")
|
||||
return NEVER
|
||||
|
||||
/* Set up search worker */
|
||||
const config = configuration()
|
||||
const worker = setupSearchWorker(config.search, fetchSearchIndex(
|
||||
`${config.base}/search/search_index.json`
|
||||
))
|
||||
const worker = setupSearchWorker(config.search, index$)
|
||||
|
||||
/* Retrieve nested components */
|
||||
const query = getComponentElement("search-query", el)
|
||||
@@ -193,7 +176,7 @@ export function mountSearch(
|
||||
})
|
||||
|
||||
/* Create and return component */
|
||||
const query$ = mountSearchQuery(query as HTMLInputElement, worker)
|
||||
const query$ = mountSearchQuery(query, worker)
|
||||
return merge(
|
||||
query$,
|
||||
mountSearchResult(result, worker, { query$ })
|
||||
|
||||
@@ -33,7 +33,6 @@ import {
|
||||
distinctUntilKeyChanged,
|
||||
finalize,
|
||||
map,
|
||||
startWith,
|
||||
takeLast,
|
||||
takeUntil,
|
||||
tap
|
||||
@@ -96,7 +95,6 @@ export function watchSearchQuery(
|
||||
)
|
||||
.pipe(
|
||||
map(() => fn(el.value)),
|
||||
startWith(fn(el.value)),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
|
||||
@@ -89,8 +89,8 @@ interface MountOptions {
|
||||
/**
|
||||
* Mount search result list
|
||||
*
|
||||
* This function will perform a lazy rendering of the search results, depending
|
||||
* on the vertical offset of the search result container.
|
||||
* This function performs a lazy rendering of the search results, depending on
|
||||
* the vertical offset of the search result container.
|
||||
*
|
||||
* @param el - Search result list element
|
||||
* @param worker - Search worker
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
|
||||
import { setSourceFacts, setSourceState } from "~/actions"
|
||||
import { renderSourceFacts } from "~/templates"
|
||||
import { hash } from "~/utilities"
|
||||
import { digest } from "~/utilities"
|
||||
|
||||
import { Component } from "../../_"
|
||||
import { SourceFacts, fetchSourceFacts } from "../facts"
|
||||
@@ -53,7 +53,7 @@ export interface Source {
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Repository facts observable
|
||||
* Repository information observable
|
||||
*/
|
||||
let fetch$: Observable<Source>
|
||||
|
||||
@@ -64,8 +64,8 @@ let fetch$: Observable<Source>
|
||||
/**
|
||||
* Watch repository information
|
||||
*
|
||||
* This function will try to read the repository facts from session storage,
|
||||
* and if unsuccessful, fetch them from the underlying provider.
|
||||
* This function tries to read the repository facts from session storage, and
|
||||
* if unsuccessful, fetches them from the underlying provider.
|
||||
*
|
||||
* @param el - Repository information element
|
||||
*
|
||||
@@ -74,18 +74,15 @@ let fetch$: Observable<Source>
|
||||
export function watchSource(
|
||||
el: HTMLAnchorElement
|
||||
): Observable<Source> {
|
||||
const digest = hash(el.href).toString()
|
||||
|
||||
/* Fetch repository facts once */
|
||||
return fetch$ ||= defer(() => {
|
||||
const data = sessionStorage.getItem(digest)
|
||||
const data = sessionStorage.getItem(digest("__repo"))
|
||||
if (data) {
|
||||
return of(JSON.parse(data))
|
||||
return of<SourceFacts>(JSON.parse(data))
|
||||
} else {
|
||||
const value$ = fetchSourceFacts(el.href)
|
||||
value$.subscribe(value => {
|
||||
try {
|
||||
sessionStorage.setItem(digest, JSON.stringify(value))
|
||||
sessionStorage.setItem(digest("__repo"), JSON.stringify(value))
|
||||
} catch (err) {
|
||||
/* Uncritical, just swallow */
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ function setupSearchIndex(
|
||||
/**
|
||||
* Set up search worker
|
||||
*
|
||||
* This function will create a web worker to set up and query the search index
|
||||
* This function creates a web worker to set up and query the search index,
|
||||
* which is done using Lunr.js. The index must be passed as an observable to
|
||||
* enable hacks like _localsearch_ via search index embedding as JSON.
|
||||
*
|
||||
|
||||
@@ -62,8 +62,8 @@ let index: Search
|
||||
/**
|
||||
* Fetch (= import) multi-language support through `lunr-languages`
|
||||
*
|
||||
* This function will automatically import the stemmers necessary to process
|
||||
* the languages which were given through the search index configuration.
|
||||
* This function automatically imports the stemmers necessary to process the
|
||||
* languages, which are defined through the search index configuration.
|
||||
*
|
||||
* If the worker runs inside of an `iframe` (when using `iframe-worker` as
|
||||
* a shim), the base URL for the stemmers to be loaded must be determined by
|
||||
|
||||
@@ -42,7 +42,7 @@ interface PatchOptions {
|
||||
/**
|
||||
* Patch indeterminate checkboxes
|
||||
*
|
||||
* This function will replace the indeterminate "pseudo state" with the actual
|
||||
* This function replaces the indeterminate "pseudo state" with the actual
|
||||
* indeterminate state, which is used to keep navigation always expanded.
|
||||
*
|
||||
* @param options - Options
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { configuration } from "~/_"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
@@ -88,3 +90,18 @@ export function hash(value: string): number {
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a digest to a value to ensure uniqueness
|
||||
*
|
||||
* When a single account hosts multiple sites on the same GitHub Pages domain,
|
||||
* entries would collide, because session and local storage are not unique.
|
||||
*
|
||||
* @param value - Value
|
||||
*
|
||||
* @returns Value with digest
|
||||
*/
|
||||
export function digest(value: string): string {
|
||||
const config = configuration()
|
||||
return `${value}[${hash(config.base)}]`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user