mirror of
https://github.com/squidfunk/mkdocs-material.git
synced 2026-07-22 22:03:35 -04:00
Added more inline comments and simplified component mounting
This commit is contained in:
@@ -31,8 +31,11 @@ import { getElementOrThrow, getLocation } from "~/browser"
|
||||
*/
|
||||
export type Flag =
|
||||
| "header.autohide" /* Hide header */
|
||||
| "navigation.tabs" /* Tabs navigation */
|
||||
| "navigation.expand" /* Automatic expansion */
|
||||
| "navigation.instant" /* Instant loading */
|
||||
| "navigation.sections" /* Sections navigation */
|
||||
| "navigation.tabs" /* Tabs navigation */
|
||||
| "toc.integrate" /* Integrated table of contents */
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
|
||||
@@ -67,19 +67,19 @@ export function watchPrint(): Observable<void> {
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Toggle an observable with another one
|
||||
* Toggle an observable with a media observable
|
||||
*
|
||||
* @template T - Data type
|
||||
*
|
||||
* @param toggle$ - Toggle observable
|
||||
* @param query$ - Media observable
|
||||
* @param factory - Observable factory
|
||||
*
|
||||
* @returns Toggled observable
|
||||
*/
|
||||
export function at<T>(
|
||||
toggle$: Observable<boolean>, factory: () => Observable<T>
|
||||
query$: Observable<boolean>, factory: () => Observable<T>
|
||||
): Observable<T> {
|
||||
return toggle$
|
||||
return query$
|
||||
.pipe(
|
||||
switchMap(active => active ? factory() : NEVER)
|
||||
)
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { getElementOrThrow, getElements } from "~/browser"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Types
|
||||
* ------------------------------------------------------------------------- */
|
||||
@@ -31,12 +33,14 @@ export type ComponentType =
|
||||
| "announce" /* Announcement bar */
|
||||
| "container" /* Container */
|
||||
| "content" /* Content */
|
||||
| "dialog" /* Dialog */
|
||||
| "header" /* Header */
|
||||
| "header-title" /* Header title */
|
||||
| "main" /* Main area */
|
||||
| "search" /* Search */
|
||||
| "search-query" /* Search input */
|
||||
| "search-result" /* Search results */
|
||||
| "sidebar" /* Sidebar */
|
||||
| "skip" /* Skip link */
|
||||
| "source" /* Repository information */
|
||||
| "tabs" /* Navigation tabs */
|
||||
@@ -55,3 +59,39 @@ export type Component<
|
||||
T & {
|
||||
ref: U /* Component reference */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Retrieve the element for a given component or throw a reference error
|
||||
*
|
||||
* @template T - Element type
|
||||
*
|
||||
* @param type - Component type
|
||||
* @param node - Node of reference
|
||||
*
|
||||
* @returns Element
|
||||
*/
|
||||
export function getComponentElement<T extends HTMLElement>(
|
||||
type: ComponentType, node: ParentNode = document
|
||||
): T {
|
||||
return getElementOrThrow(`[data-md-component=${type}]`, node)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all elements for a given component
|
||||
*
|
||||
* @template T - Element type
|
||||
*
|
||||
* @param type - Component type
|
||||
* @param node - Node of reference
|
||||
*
|
||||
* @returns Elements
|
||||
*/
|
||||
export function getComponentElements<T extends HTMLElement>(
|
||||
type: ComponentType, node: ParentNode = document
|
||||
): T[] {
|
||||
return getElements(`[data-md-component=${type}]`, node)
|
||||
}
|
||||
|
||||
@@ -61,6 +61,9 @@ interface MountOptions {
|
||||
/**
|
||||
* Mount content
|
||||
*
|
||||
* This function mounts all components that are found in the content of the
|
||||
* actual article, including code blocks, data tables and details.
|
||||
*
|
||||
* @param el - Content element
|
||||
* @param options - Options
|
||||
*
|
||||
|
||||
@@ -111,8 +111,8 @@ export function watchCodeBlock(
|
||||
/**
|
||||
* Mount code block
|
||||
*
|
||||
* This function ensures that overflowing code blocks are focusable by keyboard,
|
||||
* so they can be scrolled without a mouse to improve on accessibility.
|
||||
* This function ensures that an overflowing code block is focusable through
|
||||
* keyboard, so it can be scrolled without a mouse to improve on accessibility.
|
||||
*
|
||||
* @param el - Code block element
|
||||
* @param options - Options
|
||||
|
||||
@@ -88,8 +88,8 @@ export function watchDetails(
|
||||
/**
|
||||
* Mount details
|
||||
*
|
||||
* This function ensures that `details` tags are opened prior to printing, so
|
||||
* the whole content of the page is included and on anchor jumps.
|
||||
* This function ensures that `details` tags are opened on anchor jumps and
|
||||
* prior to printing, so the whole content of the page is visible.
|
||||
*
|
||||
* @param el - Details element
|
||||
* @param options - Options
|
||||
|
||||
@@ -52,6 +52,9 @@ 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.
|
||||
*
|
||||
* @param el - Data table element
|
||||
*
|
||||
* @returns Data table component observable
|
||||
|
||||
@@ -105,6 +105,9 @@ 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.
|
||||
*
|
||||
* @param el - Dialog element
|
||||
* @param options - Options
|
||||
*
|
||||
|
||||
@@ -34,12 +34,15 @@ import {
|
||||
combineLatestWith,
|
||||
distinctUntilChanged,
|
||||
distinctUntilKeyChanged,
|
||||
endWith,
|
||||
filter,
|
||||
finalize,
|
||||
map,
|
||||
observeOn,
|
||||
shareReplay,
|
||||
startWith,
|
||||
switchMap
|
||||
switchMap,
|
||||
tap
|
||||
} from "rxjs/operators"
|
||||
|
||||
import { feature } from "~/_"
|
||||
@@ -118,7 +121,7 @@ function isHidden({ viewport$ }: WatchOptions): Observable<boolean> {
|
||||
.pipe(
|
||||
filter(([{ offset }, [, y]]) => Math.abs(y - offset.y) > 100),
|
||||
map(([, [direction]]) => direction),
|
||||
distinctUntilChanged(),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
/* Compute threshold for autohiding */
|
||||
@@ -127,7 +130,7 @@ function isHidden({ viewport$ }: WatchOptions): Observable<boolean> {
|
||||
.pipe(
|
||||
map(([{ offset }, search]) => offset.y > 400 && !search),
|
||||
distinctUntilChanged(),
|
||||
switchMap(active => active ? hidden$ : NEVER),
|
||||
switchMap(active => active ? hidden$ : of(false)),
|
||||
startWith(false)
|
||||
)
|
||||
}
|
||||
@@ -173,10 +176,8 @@ export function watchHeader(
|
||||
/**
|
||||
* Mount header
|
||||
*
|
||||
* The header must be connected to the main area observable outside of the
|
||||
* operator function, as the header will persist in-between document switches
|
||||
* while the main area is replaced. However, the header observable must be
|
||||
* passed to this function, so we connect both via a long-living subject.
|
||||
* This function manages the different states of the header, i.e. whether it's
|
||||
* hidden or rendered with a shadow. This depends heavily on the main area.
|
||||
*
|
||||
* @param el - Header element
|
||||
* @param options - Options
|
||||
|
||||
@@ -20,7 +20,12 @@
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { NEVER, Observable, Subject, animationFrameScheduler } from "rxjs"
|
||||
import {
|
||||
NEVER,
|
||||
Observable,
|
||||
Subject,
|
||||
animationFrameScheduler
|
||||
} from "rxjs"
|
||||
import {
|
||||
distinctUntilKeyChanged,
|
||||
finalize,
|
||||
@@ -104,6 +109,9 @@ export function watchHeaderTitle(
|
||||
/**
|
||||
* Mount header title
|
||||
*
|
||||
* This function swaps the header title from the site title to the title of the
|
||||
* current page when the user scrolls past the first headline.
|
||||
*
|
||||
* @param el - Header title element
|
||||
* @param options - Options
|
||||
*
|
||||
|
||||
@@ -27,7 +27,6 @@ import { configuration } from "~/_"
|
||||
import {
|
||||
Keyboard,
|
||||
getActiveElement,
|
||||
getElementOrThrow,
|
||||
getElements,
|
||||
requestJSON,
|
||||
setElementFocus,
|
||||
@@ -41,7 +40,7 @@ import {
|
||||
setupSearchWorker
|
||||
} from "~/integrations"
|
||||
|
||||
import { Component } from "../../_"
|
||||
import { Component, getComponentElement } from "../../_"
|
||||
import { SearchQuery, mountSearchQuery } from "../query"
|
||||
import { SearchResult, mountSearchResult } from "../result"
|
||||
|
||||
@@ -89,6 +88,9 @@ function fetchSearchIndex(url: string) {
|
||||
/**
|
||||
* Mount search
|
||||
*
|
||||
* This function sets up the search functionality, including the underlying
|
||||
* web worker and all keyboard bindings.
|
||||
*
|
||||
* @param el - Search element
|
||||
* @param options - Options
|
||||
*
|
||||
@@ -103,8 +105,8 @@ export function mountSearch(
|
||||
))
|
||||
|
||||
/* Retrieve elements */
|
||||
const query = getElementOrThrow("[data-md-component=search-query]", el)
|
||||
const result = getElementOrThrow("[data-md-component=search-result]", el)
|
||||
const query = getComponentElement("search-query", el)
|
||||
const result = getComponentElement("search-result", el)
|
||||
|
||||
/* Re-emit query when search is ready */
|
||||
const { tx$, rx$ } = worker
|
||||
|
||||
@@ -64,6 +64,9 @@ 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.
|
||||
*
|
||||
* @param el - Repository information element
|
||||
*
|
||||
* @returns Repository information observable
|
||||
|
||||
@@ -95,6 +95,9 @@ export function watchTabs(
|
||||
/**
|
||||
* Mount navigation tabs
|
||||
*
|
||||
* This function hides the navigation tabs when scrolling past the threshold
|
||||
* and makes them reappear in a nice CSS animation when scrolling back up.
|
||||
*
|
||||
* @param el - Navigation tabs element
|
||||
* @param options - Options
|
||||
*
|
||||
|
||||
@@ -34,8 +34,6 @@ import { feature } from "./_"
|
||||
import {
|
||||
at,
|
||||
getElement,
|
||||
getElementOrThrow,
|
||||
getElements,
|
||||
setToggle,
|
||||
watchDocument,
|
||||
watchKeyboard,
|
||||
@@ -46,6 +44,8 @@ import {
|
||||
watchViewport
|
||||
} from "./browser"
|
||||
import {
|
||||
getComponentElement,
|
||||
getComponentElements,
|
||||
mountContent,
|
||||
mountDialog,
|
||||
mountHeader,
|
||||
@@ -129,16 +129,11 @@ keyboard$
|
||||
patchIndeterminate({ document$ })
|
||||
patchScrollfix({ document$ })
|
||||
|
||||
/* Set up header observable */
|
||||
const header$ = watchHeader(
|
||||
getElementOrThrow("[data-md-component=header]"),
|
||||
{ viewport$ }
|
||||
)
|
||||
|
||||
/* Set up main area observable */
|
||||
/* Set up header and main area observable */
|
||||
const header$ = watchHeader(getComponentElement("header"), { viewport$ })
|
||||
const main$ = document$
|
||||
.pipe(
|
||||
map(() => getElementOrThrow("[data-md-component=main]")),
|
||||
map(() => getComponentElement("main")),
|
||||
switchMap(el => watchMain(el, { viewport$, header$ })),
|
||||
shareReplay(1)
|
||||
)
|
||||
@@ -147,23 +142,23 @@ const main$ = document$
|
||||
const control$ = merge(
|
||||
|
||||
/* Dialog */
|
||||
...getElements("[data-md-component=dialog]")
|
||||
...getComponentElements("dialog")
|
||||
.map(child => mountDialog(child, { alert$ })),
|
||||
|
||||
/* Header */
|
||||
...getElements("[data-md-component=header]")
|
||||
...getComponentElements("header")
|
||||
.map(child => mountHeader(child, { viewport$, header$, main$ })),
|
||||
|
||||
/* Search */
|
||||
...getElements("[data-md-component=search]")
|
||||
...getComponentElements("search")
|
||||
.map(child => mountSearch(child, { keyboard$ })),
|
||||
|
||||
/* Repository information */
|
||||
...getElements("[data-md-component=source]")
|
||||
...getComponentElements("source")
|
||||
.map(child => mountSource(child as HTMLAnchorElement)),
|
||||
|
||||
/* Navigation tabs */
|
||||
...getElements("[data-md-component=tabs]")
|
||||
...getComponentElements("tabs")
|
||||
.map(child => mountTabs(child, { viewport$, header$ })),
|
||||
)
|
||||
|
||||
@@ -171,22 +166,22 @@ const control$ = merge(
|
||||
const content$ = defer(() => merge(
|
||||
|
||||
/* Content */
|
||||
...getElements("[data-md-component=content]")
|
||||
...getComponentElements("content")
|
||||
.map(child => mountContent(child, { target$, viewport$, print$ })),
|
||||
|
||||
/* Header title */
|
||||
...getElements("[data-md-component=header-title]")
|
||||
...getComponentElements("header-title")
|
||||
.map(child => mountHeaderTitle(child, { viewport$, header$ })),
|
||||
|
||||
/* Sidebar */
|
||||
...getElements("[data-md-component=sidebar]")
|
||||
...getComponentElements("sidebar")
|
||||
.map(child => child.getAttribute("data-md-type") === "navigation"
|
||||
? at(screen$, () => mountSidebar(child, { viewport$, header$, main$ }))
|
||||
: at(tablet$, () => mountSidebar(child, { viewport$, header$, main$ }))
|
||||
),
|
||||
|
||||
/* Table of contents */
|
||||
...getElements("[data-md-component=toc]")
|
||||
...getComponentElements("toc")
|
||||
.map(child => mountTableOfContents(child, { viewport$, header$ })),
|
||||
))
|
||||
|
||||
@@ -194,7 +189,8 @@ const content$ = defer(() => merge(
|
||||
const component$ = document$
|
||||
.pipe(
|
||||
switchMap(() => content$),
|
||||
mergeWith(control$)
|
||||
mergeWith(control$),
|
||||
shareReplay(1)
|
||||
)
|
||||
|
||||
/* Subscribe to all components */
|
||||
|
||||
@@ -51,7 +51,6 @@ import {
|
||||
ViewportOffset,
|
||||
createElement,
|
||||
getElement,
|
||||
getElementOrThrow,
|
||||
getElements,
|
||||
replaceElement,
|
||||
request,
|
||||
@@ -60,6 +59,7 @@ import {
|
||||
setLocationHash,
|
||||
setViewportOffset
|
||||
} from "~/browser"
|
||||
import { getComponentElement } from "~/components"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Types
|
||||
@@ -261,14 +261,12 @@ export function setupInstantLoading(
|
||||
setViewportOffset(offset || { y: 0 })
|
||||
})
|
||||
|
||||
/* Replace components */
|
||||
/* Replace meta tags and components */
|
||||
document$
|
||||
.pipe(
|
||||
skip(1)
|
||||
)
|
||||
.subscribe(replacement => {
|
||||
|
||||
/* Replace meta tags and components */
|
||||
for (const selector of [
|
||||
|
||||
/* Meta tags */
|
||||
@@ -298,7 +296,7 @@ export function setupInstantLoading(
|
||||
document$
|
||||
.pipe(
|
||||
skip(1),
|
||||
map(() => getElementOrThrow("[data-md-component=container]")),
|
||||
map(() => getComponentElement("container")),
|
||||
switchMap(el => of(...getElements("script", el))),
|
||||
concatMap(el => {
|
||||
const script = createElement("script")
|
||||
|
||||
@@ -42,6 +42,9 @@ interface PatchOptions {
|
||||
/**
|
||||
* Patch indeterminate checkboxes
|
||||
*
|
||||
* This function will replace the indeterminate "pseudo state" with the actual
|
||||
* indeterminate state, which is used to keep navigation always expanded.
|
||||
*
|
||||
* @param options - Options
|
||||
*/
|
||||
export function patchIndeterminate(
|
||||
|
||||
@@ -34,7 +34,7 @@ import { h } from "~/utilities"
|
||||
*
|
||||
* @returns Element
|
||||
*/
|
||||
export function renderClipboardButton(id: string) {
|
||||
export function renderClipboardButton(id: string): HTMLElement {
|
||||
return (
|
||||
<button
|
||||
class="md-clipboard md-icon"
|
||||
|
||||
@@ -47,7 +47,7 @@ const enum Flag {
|
||||
/**
|
||||
* Render a search document
|
||||
*
|
||||
* @param section - Search document
|
||||
* @param document - Search document
|
||||
* @param flag - Render flags
|
||||
*
|
||||
* @returns Element
|
||||
@@ -106,8 +106,8 @@ function renderSearchDocument(
|
||||
* @returns Element
|
||||
*/
|
||||
export function renderSearchResult(
|
||||
result: SearchResult, threshold: number = Infinity
|
||||
) {
|
||||
result: SearchResult, threshold = Infinity
|
||||
): HTMLElement {
|
||||
const docs = [...result]
|
||||
|
||||
/* Find and extract parent article */
|
||||
|
||||
@@ -34,7 +34,7 @@ import { h } from "~/utilities"
|
||||
*
|
||||
* @returns Element
|
||||
*/
|
||||
export function renderSourceFacts(facts: SourceFacts) {
|
||||
export function renderSourceFacts(facts: SourceFacts): HTMLElement {
|
||||
return (
|
||||
<ul class="md-source__facts">
|
||||
{facts.map(fact => (
|
||||
|
||||
@@ -33,7 +33,7 @@ import { h } from "utilities"
|
||||
*
|
||||
* @returns Element
|
||||
*/
|
||||
export function renderTable(table: HTMLElement) {
|
||||
export function renderTable(table: HTMLElement): HTMLElement {
|
||||
return (
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
|
||||
@@ -20,5 +20,5 @@
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
export * from "./jsx"
|
||||
export * from "./h"
|
||||
export * from "./string"
|
||||
|
||||
Reference in New Issue
Block a user