mirror of
https://github.com/squidfunk/mkdocs-material.git
synced 2026-07-29 09:12:35 -04:00
Improved search result rendering and icon search
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
|
||||
import { translation } from "~/_"
|
||||
import { round } from "~/utilities"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
@@ -49,7 +50,7 @@ export function setSearchResultMeta(
|
||||
|
||||
/* Multiple result */
|
||||
default:
|
||||
el.textContent = translation("search.result.other", value)
|
||||
el.textContent = translation("search.result.other", round(value))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
*/
|
||||
|
||||
import { Observable, fromEvent, merge } from "rxjs"
|
||||
import { map, startWith } from "rxjs/operators"
|
||||
import { distinctUntilChanged, map, startWith } from "rxjs/operators"
|
||||
|
||||
import { getElementContentSize, getElementSize } from "../size"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Types
|
||||
@@ -74,3 +76,30 @@ export function watchElementOffset(
|
||||
startWith(getElementOffset(el))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch element threshold
|
||||
*
|
||||
* This function returns an observable which emits whether the bottom scroll
|
||||
* offset of an elements is within a certain threshold.
|
||||
*
|
||||
* @param el - Element
|
||||
* @param threshold - Threshold
|
||||
*
|
||||
* @returns Element threshold observable
|
||||
*/
|
||||
export function watchElementThreshold(
|
||||
el: HTMLElement, threshold = 16
|
||||
): Observable<boolean> {
|
||||
return watchElementOffset(el)
|
||||
.pipe(
|
||||
map(({ y }) => {
|
||||
const visible = getElementSize(el)
|
||||
const content = getElementContentSize(el)
|
||||
return y >= (
|
||||
content.height - visible.height - threshold
|
||||
)
|
||||
}),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ export function getElementContentSize(el: HTMLElement): ElementSize {
|
||||
/**
|
||||
* Watch element size
|
||||
*
|
||||
* This function returns an observable that will subscribe to a single internal
|
||||
* This function returns an observable that subscribes to a single internal
|
||||
* instance of `ResizeObserver` upon subscription, and emit resize events until
|
||||
* termination. Note that this function should not be called with the same
|
||||
* element twice, as the first unsubscription will terminate observation.
|
||||
|
||||
@@ -73,7 +73,7 @@ interface WatchOptions<T extends WorkerMessage> {
|
||||
/**
|
||||
* Watch a web worker
|
||||
*
|
||||
* This function returns an observable that will send all values emitted by the
|
||||
* This function returns an observable that sends all values emitted by the
|
||||
* message observable to the web worker. Web worker communication is expected
|
||||
* to be bidirectional (request-response) and synchronous. Messages that are
|
||||
* emitted during a pending request are throttled, the last one is emitted.
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { Observable, merge } from "rxjs"
|
||||
import { Observable, ObservableInput, merge } from "rxjs"
|
||||
import { filter, sample, take } from "rxjs/operators"
|
||||
|
||||
import { configuration } from "~/_"
|
||||
@@ -75,9 +75,9 @@ interface MountOptions {
|
||||
*
|
||||
* @param url - Search index URL
|
||||
*
|
||||
* @returns Promise resolving with search index
|
||||
* @returns Promise or observable
|
||||
*/
|
||||
function fetchSearchIndex(url: string) {
|
||||
function fetchSearchIndex(url: string): ObservableInput<SearchIndex> {
|
||||
return __search?.index || requestJSON<SearchIndex>(url)
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ export function mountSearch(
|
||||
`${config.base}/search/search_index.json`
|
||||
))
|
||||
|
||||
/* Retrieve elements */
|
||||
/* Retrieve nested components */
|
||||
const query = getComponentElement("search-query", el)
|
||||
const result = getComponentElement("search-result", el)
|
||||
|
||||
|
||||
@@ -20,14 +20,25 @@
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { Observable, Subject } from "rxjs"
|
||||
import {
|
||||
Observable,
|
||||
Subject,
|
||||
animationFrameScheduler,
|
||||
merge,
|
||||
of
|
||||
} from "rxjs"
|
||||
import {
|
||||
bufferCount,
|
||||
distinctUntilKeyChanged,
|
||||
filter,
|
||||
finalize,
|
||||
map,
|
||||
observeOn,
|
||||
startWith,
|
||||
switchMap,
|
||||
tap,
|
||||
withLatestFrom
|
||||
withLatestFrom,
|
||||
zipWith
|
||||
} from "rxjs/operators"
|
||||
|
||||
import {
|
||||
@@ -36,7 +47,10 @@ import {
|
||||
resetSearchResultMeta,
|
||||
setSearchResultMeta
|
||||
} from "~/actions"
|
||||
import { getElementOrThrow } from "~/browser"
|
||||
import {
|
||||
getElementOrThrow,
|
||||
watchElementThreshold
|
||||
} from "~/browser"
|
||||
import {
|
||||
SearchResult as SearchResultData,
|
||||
SearchWorker,
|
||||
@@ -76,6 +90,9 @@ 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.
|
||||
*
|
||||
* @param el - Search result list element
|
||||
* @param worker - Search worker
|
||||
* @param options - Options
|
||||
@@ -86,11 +103,16 @@ export function mountSearchResult(
|
||||
el: HTMLElement, { rx$ }: SearchWorker, { query$ }: MountOptions
|
||||
): Observable<Component<SearchResult>> {
|
||||
const internal$ = new Subject<SearchResult>()
|
||||
const boundary$ = watchElementThreshold(el.parentElement!)
|
||||
.pipe(
|
||||
filter(Boolean)
|
||||
)
|
||||
|
||||
/* Update search result metadata */
|
||||
const meta = getElementOrThrow(":scope > :first-child", el)
|
||||
internal$
|
||||
.pipe(
|
||||
observeOn(animationFrameScheduler),
|
||||
withLatestFrom(query$)
|
||||
)
|
||||
.subscribe(([{ data }, { value }]) => {
|
||||
@@ -103,16 +125,22 @@ export function mountSearchResult(
|
||||
/* Update search result list */
|
||||
const list = getElementOrThrow(":scope > :last-child", el)
|
||||
internal$
|
||||
.subscribe(({ data }) => {
|
||||
resetSearchResultList(list)
|
||||
|
||||
/* Compute thresholds and search results */
|
||||
const thresholds = [...data.map(([best]) => best.score), 0]
|
||||
for (let index = 0; index < data.length; index++)
|
||||
addToSearchResultList(list, renderSearchResult(
|
||||
data[index++], thresholds[index]
|
||||
))
|
||||
})
|
||||
.pipe(
|
||||
observeOn(animationFrameScheduler),
|
||||
tap(() => resetSearchResultList(list)),
|
||||
switchMap(({ data }) => merge(
|
||||
of(...data.slice(0, 10)),
|
||||
of(...data.slice(10))
|
||||
.pipe(
|
||||
bufferCount(4),
|
||||
zipWith(boundary$),
|
||||
switchMap(([chunk]) => of(...chunk))
|
||||
)
|
||||
))
|
||||
)
|
||||
.subscribe(result => {
|
||||
addToSearchResultList(list, renderSearchResult(result))
|
||||
})
|
||||
|
||||
/* Filter search result list */
|
||||
const result$ = rx$
|
||||
|
||||
@@ -54,7 +54,7 @@ const enum Flag {
|
||||
*/
|
||||
function renderSearchDocument(
|
||||
document: SearchDocument & SearchMetadata, flag: Flag
|
||||
) {
|
||||
): HTMLElement {
|
||||
const parent = flag & Flag.PARENT
|
||||
const teaser = flag & Flag.TEASER
|
||||
|
||||
@@ -101,13 +101,13 @@ function renderSearchDocument(
|
||||
* Render a search result
|
||||
*
|
||||
* @param result - Search result
|
||||
* @param threshold - Score threshold
|
||||
*
|
||||
* @returns Element
|
||||
*/
|
||||
export function renderSearchResult(
|
||||
result: SearchResult, threshold = Infinity
|
||||
result: SearchResult
|
||||
): HTMLElement {
|
||||
const threshold = result[0].score
|
||||
const docs = [...result]
|
||||
|
||||
/* Find and extract parent article */
|
||||
|
||||
Reference in New Issue
Block a user