mirror of
https://github.com/squidfunk/mkdocs-material.git
synced 2024-11-28 09:20:52 +01:00
Fixed unmapped components
This commit is contained in:
parent
37b3870133
commit
f39c9f9e68
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -104,7 +104,7 @@
|
||||
</svg>
|
||||
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
|
||||
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
|
||||
<label class="md-overlay" data-md-component="overlay" for="__drawer"></label>
|
||||
<label class="md-overlay" for="__drawer"></label>
|
||||
{% if page.toc | first is defined %}
|
||||
<a href="{{ (page.toc | first).url }}" tabindex="1" class="md-skip">
|
||||
{{ lang.t('skip.link.title') }}
|
||||
|
@ -101,7 +101,7 @@ export function watchComponentMap(
|
||||
switch (name) {
|
||||
|
||||
/* Top-level components: update */
|
||||
case "title":
|
||||
case "header-title":
|
||||
case "container":
|
||||
if (name in prev && typeof prev[name] !== "undefined") {
|
||||
prev[name]!.replaceWith(next[name]!)
|
||||
|
@ -20,66 +20,56 @@
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
// TODO: remove this later on
|
||||
|
||||
// TODO: remove this after we finished refactoring
|
||||
// tslint:disable
|
||||
|
||||
import { identity } from "ramda"
|
||||
import { identity, values } from "ramda"
|
||||
import {
|
||||
EMPTY,
|
||||
MonoTypeOperatorFunction,
|
||||
NEVER,
|
||||
Observable,
|
||||
Subject,
|
||||
defer,
|
||||
forkJoin,
|
||||
fromEvent,
|
||||
merge,
|
||||
of,
|
||||
pipe,
|
||||
of
|
||||
} from "rxjs"
|
||||
import { ajax } from "rxjs/ajax"
|
||||
import {
|
||||
combineAll,
|
||||
delay,
|
||||
distinctUntilKeyChanged,
|
||||
filter,
|
||||
map,
|
||||
pluck,
|
||||
shareReplay,
|
||||
switchMap,
|
||||
switchMapTo,
|
||||
take,
|
||||
tap,
|
||||
} from "rxjs/operators"
|
||||
|
||||
import {} from "components"
|
||||
import { AjaxResponse, ajax } from "rxjs/ajax"
|
||||
import {
|
||||
Component,
|
||||
paintHeaderShadow,
|
||||
setupHero,
|
||||
setupMain,
|
||||
setupNavigation,
|
||||
setupSearchResult,
|
||||
mountHero,
|
||||
mountMain,
|
||||
mountNavigation,
|
||||
mountSearchResult,
|
||||
mountTableOfContents,
|
||||
mountTabs,
|
||||
switchComponent,
|
||||
watchComponentMap,
|
||||
watchHeader,
|
||||
watchSearchReset,
|
||||
watchSearchQuery,
|
||||
watchSearchReset
|
||||
} from "./components"
|
||||
import { SearchIndex, SearchResult } from "./modules/search"
|
||||
import { SearchIndexOptions } from "./modules"
|
||||
import {
|
||||
getElement,
|
||||
setupAgent,
|
||||
watchDocument,
|
||||
watchLocation,
|
||||
watchLocationHash,
|
||||
watchMedia,
|
||||
watchToggle,
|
||||
watchViewportOffset,
|
||||
watchViewportSize,
|
||||
watchWorker
|
||||
watchWorker,
|
||||
setToggle
|
||||
} from "./utilities"
|
||||
import {
|
||||
PackerMessage,
|
||||
PackerMessageType,
|
||||
SearchMessage,
|
||||
SearchMessageType,
|
||||
SearchSetupMessage,
|
||||
@ -87,73 +77,71 @@ import {
|
||||
isSearchResultMessage
|
||||
} from "./workers"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Configuration
|
||||
*/
|
||||
export interface Config {
|
||||
base: string /* Base URL */
|
||||
worker: {
|
||||
search: string /* Web worker URL */
|
||||
packer: string /* Web worker URL */
|
||||
search: string /* Search worker URL */
|
||||
packer: string /* Packer worker URL */
|
||||
}
|
||||
}
|
||||
|
||||
import {
|
||||
PackerMessage,
|
||||
PackerMessageType
|
||||
} from "./workers/packer"
|
||||
|
||||
import { setupTabs } from "components/tabs"
|
||||
import { setupTableOfContents } from "components/toc/_"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* TODO: where do we put this stuff?
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
document.documentElement.classList.remove("no-js")
|
||||
document.documentElement.classList.add("js")
|
||||
|
||||
const names: Component[] = [
|
||||
"container", /* Container */
|
||||
"header", /* Header */
|
||||
"header-title", /* Header title */
|
||||
"hero", /* Hero */
|
||||
"main", /* Main area */
|
||||
"navigation", /* Navigation */
|
||||
"search", /* Search */
|
||||
"search-query", /* Search input */
|
||||
"search-reset", /* Search reset */
|
||||
"search-result", /* Search results */
|
||||
"tabs", /* Tabs */
|
||||
"toc" /* Table of contents */
|
||||
]
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Helper functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Ensure that the given value is a valid configuration
|
||||
*
|
||||
* We could use `jsonschema` or any other schema validation framework, but that
|
||||
* would just add more bloat to the bundle, so we'll keep it plain and simple.
|
||||
*
|
||||
* @param config - Configuration
|
||||
*
|
||||
* @return Test result
|
||||
*/
|
||||
export function isConfig(config: any): config is Config {
|
||||
function isConfig(config: any): config is Config {
|
||||
return typeof config === "object"
|
||||
&& typeof config.base === "string"
|
||||
&& typeof config.worker === "object"
|
||||
&& typeof config.worker.search === "string"
|
||||
&& typeof config.worker.packer === "string"
|
||||
}
|
||||
|
||||
// TBD
|
||||
|
||||
// TODO: put this somewhere else... (merge with config!) JSON schema!?
|
||||
const names: Component[] = [
|
||||
"header", /* Header */
|
||||
"title", /* Header title */
|
||||
"search", /* Search */
|
||||
"query", /* Search input */
|
||||
"reset", /* Search reset */
|
||||
"result", /* Search results */
|
||||
"container", /* Container */
|
||||
"main", /* Main area */
|
||||
"hero", /* Hero */
|
||||
"tabs", /* Tabs */
|
||||
"navigation", /* Navigation */
|
||||
"toc" /* Table of contents */
|
||||
]
|
||||
|
||||
// modernizr for the poor
|
||||
document.documentElement.classList.remove("no-js")
|
||||
document.documentElement.classList.add("js")
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
*
|
||||
* Rogue control characters must be filtered before handing the query to the
|
||||
* search index, as lunr will throw otherwise.
|
||||
*/
|
||||
function prepareQuery(value: string): string {
|
||||
function prepare(value: string): string {
|
||||
const newvalue = value
|
||||
.replace(/(?:^|\s+)[*+-:^~]+(?=\s+|$)/g, "")
|
||||
.trim()
|
||||
@ -161,29 +149,17 @@ function prepareQuery(value: string): string {
|
||||
return newvalue ? newvalue.replace(/\s+|$/g, "* ") : ""
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Material for MkDocs
|
||||
*
|
||||
* @param config - Configuration
|
||||
*/
|
||||
export function initialize(config: unknown) {
|
||||
if (!isConfig(config))
|
||||
throw new SyntaxError(`Invalid configuration: ${JSON.stringify(config)}`)
|
||||
|
||||
const agent = setupAgent()
|
||||
|
||||
function setupWorkers(config: Config) {
|
||||
const worker = new Worker(config.worker.search)
|
||||
const packer = new Worker(config.worker.packer)
|
||||
|
||||
// const query = message.data.trim().replace(/\s+|$/g, "* ") // TODO: do this outside of the worker
|
||||
|
||||
const packerMessage$ = new Subject<PackerMessage>()
|
||||
const packer$ = watchWorker(packer, { send$: packerMessage$ })
|
||||
|
||||
// send a message, then switchMapTo worker!
|
||||
|
||||
packer$.subscribe(message => {
|
||||
console.log("PACKER.MSG", message.data.length)
|
||||
// console.log("PACKER.MSG", message.data.length)
|
||||
// is always packed!
|
||||
if (message.type === PackerMessageType.BINARY && message.data[0] !== "{")
|
||||
localStorage.setItem("index", message.data)
|
||||
@ -195,25 +171,6 @@ export function initialize(config: unknown) {
|
||||
|
||||
const search$ = watchWorker(worker, { send$: searchMessage$ })
|
||||
|
||||
// paintSearchResult <-- must paint META AND LIST!
|
||||
// list must be painted based on scroll offset...
|
||||
|
||||
/* Render search results */
|
||||
// search$
|
||||
// .pipe(
|
||||
// filter(isSearchResultMessage),
|
||||
// pluck("data")
|
||||
// )
|
||||
// .subscribe(result => {
|
||||
// const list = getElement(".md-search-result__list")!
|
||||
// list.innerHTML = ""
|
||||
// for (const el of result.map(renderSearchResult)) // TODO: perform entire lazy render!!!!
|
||||
// list.appendChild(el)
|
||||
// })
|
||||
|
||||
// scroll!
|
||||
// watchSearchResult
|
||||
|
||||
/* Link search to packer */
|
||||
search$
|
||||
.pipe(
|
||||
@ -232,7 +189,7 @@ export function initialize(config: unknown) {
|
||||
responseType: "json",
|
||||
withCredentials: true
|
||||
})
|
||||
.pipe<SearchIndex>(
|
||||
.pipe<SearchIndexOptions>(
|
||||
pluck("response")
|
||||
// take(1)
|
||||
)
|
||||
@ -264,48 +221,34 @@ export function initialize(config: unknown) {
|
||||
searchMessage$.next(message) // TODO: this shall not complete
|
||||
})
|
||||
|
||||
// filter singular "+" or "-",as it will result in a lunr.js error
|
||||
return [search$, searchMessage$] as const
|
||||
}
|
||||
|
||||
// data$
|
||||
// .pipe(
|
||||
// map<SearchIndex, SearchMessage>(data => ({
|
||||
// type: SearchMessageType.SETUP,
|
||||
// data
|
||||
// }))
|
||||
// )
|
||||
// .subscribe(message => {
|
||||
// searchMessage$.next(message) // TODO: this shall not complete
|
||||
// })
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/* ----------------------------------------------------------------------- */
|
||||
/**
|
||||
* Initialize Material for MkDocs
|
||||
*
|
||||
* @param config - Configuration
|
||||
*/
|
||||
export function initialize(config: unknown) {
|
||||
if (!isConfig(config))
|
||||
throw new SyntaxError(`Invalid configuration: ${JSON.stringify(config)}`)
|
||||
|
||||
/* Create viewport observables */
|
||||
const offset$ = watchViewportOffset()
|
||||
const size$ = watchViewportSize()
|
||||
// pass config here!?
|
||||
const agent = setupAgent() // TODO: add a config parameter here to configure media queries
|
||||
|
||||
/* Create media observables */
|
||||
const screen$ = watchMedia("(min-width: 1220px)")
|
||||
const tablet$ = watchMedia("(min-width: 960px)")
|
||||
|
||||
/* Create location observables */
|
||||
const location$ = watchLocation()
|
||||
const fragment$ = watchLocationHash()
|
||||
|
||||
/* Create document observables */
|
||||
const load$ = watchDocument()
|
||||
|
||||
// Complete set of AgentObservables...
|
||||
|
||||
// component map!
|
||||
//
|
||||
|
||||
// const switch$ = watchDocumentSwitch({ location$ })
|
||||
const [
|
||||
searchWorkerRecv$,
|
||||
searchMessage$
|
||||
] = setupWorkers(config)
|
||||
|
||||
/* ----------------------------------------------------------------------- */
|
||||
|
||||
/* Create component map observable */
|
||||
const components$ = watchComponentMap(names, { document$: load$ })
|
||||
|
||||
const components$ = watchComponentMap(names, { document$: agent.document.load$ })
|
||||
const component = <T extends HTMLElement>(name: Component): Observable<T> => {
|
||||
return components$
|
||||
.pipe(
|
||||
@ -319,233 +262,136 @@ export function initialize(config: unknown) {
|
||||
switchMap(watchHeader)
|
||||
)
|
||||
|
||||
// DONE
|
||||
const main$ = component("main")
|
||||
.pipe(
|
||||
setupMain(agent, { header$ })
|
||||
)
|
||||
|
||||
// setupHeader(agent) ??
|
||||
|
||||
// setupSearch
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
/* Create header shadow toggle */
|
||||
component("header")
|
||||
.pipe(
|
||||
switchMap(el => main$
|
||||
.pipe(
|
||||
paintHeaderShadow(el)
|
||||
paintHeaderShadow(el) // technically, this could be done in paintMain
|
||||
)
|
||||
)
|
||||
)
|
||||
.subscribe()
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// watchSearchResult // emit, if at bottom...
|
||||
// receive results as a second observable!? filter stuff, paint
|
||||
|
||||
const result$ = search$
|
||||
const result$ = searchWorkerRecv$ // move worker initialization into mountSearch ?
|
||||
.pipe(
|
||||
tap(m => console.log("message from worker", m)),
|
||||
filter(isSearchResultMessage),
|
||||
pluck("data")
|
||||
)
|
||||
|
||||
const query$ = component<HTMLInputElement>("query")
|
||||
// handleSearchResult <-- operator
|
||||
|
||||
const query$ = component<HTMLInputElement>("search-query")
|
||||
.pipe(
|
||||
switchMap(el => fromEvent(el, "keyup")
|
||||
.pipe(
|
||||
map(() => prepareQuery(el.value))
|
||||
)
|
||||
)
|
||||
switchMap(el => watchSearchQuery(el, { prepare }))
|
||||
)
|
||||
|
||||
// DONE
|
||||
component("result")
|
||||
.pipe(
|
||||
setupSearchResult(agent, { result$, query$ })
|
||||
query$
|
||||
.pipe<SearchMessage>(
|
||||
map(query => ({ // put this into some function...
|
||||
type: SearchMessageType.QUERY,
|
||||
data: query.value
|
||||
})), // TODO. ugly...
|
||||
// distinctUntilKeyChanged("data")
|
||||
)
|
||||
.subscribe()
|
||||
.subscribe(searchMessage$)
|
||||
|
||||
// create the message subject internally... and link it to the worker...?
|
||||
// watchSearchWorker(worker, agent, { query$ }) // message internally...
|
||||
|
||||
query$
|
||||
.pipe(
|
||||
map(data => ({ // put this into some function...
|
||||
type: SearchMessageType.QUERY,
|
||||
data
|
||||
})), // TODO. ugly...
|
||||
distinctUntilKeyChanged("data")
|
||||
)
|
||||
|
||||
.subscribe(x => {
|
||||
searchMessage$.next(x as any) // TODO
|
||||
tap(query => {
|
||||
if (query.focus)
|
||||
setToggle(search, true)
|
||||
})
|
||||
|
||||
// Focus on search input
|
||||
component("query")
|
||||
.pipe(
|
||||
switchMap(el => fromEvent(el, "focus")
|
||||
.pipe(
|
||||
tap(() => {
|
||||
if (!search.checked)
|
||||
search.click() // move this inside the search query stuff? not important...
|
||||
})
|
||||
)
|
||||
) // not super nice...
|
||||
)
|
||||
.subscribe()
|
||||
|
||||
// // WIP: instant loading
|
||||
// load$
|
||||
// .pipe(
|
||||
// switchMap(({ body }) => fromEvent(body, "click")),
|
||||
// switchMap(ev => {
|
||||
// if (ev.target instanceof HTMLElement) {
|
||||
// const el = ev.target.closest("a") || undefined
|
||||
// if (el) {
|
||||
// if (!/^(https?:|#)/.test(el.getAttribute("href")!)) {
|
||||
// ev.preventDefault()
|
||||
// }
|
||||
// const href = el.href
|
||||
// history.pushState({}, "", href) // TODO: reference necessary!?
|
||||
// return of(href)
|
||||
// }
|
||||
// }
|
||||
// return EMPTY
|
||||
// })
|
||||
|
||||
// // try to reduce the jiggle upon instant page load. ideally, the location
|
||||
// // should directly be resolved and the respective document loaded, but
|
||||
// // we must scroll to the top at first and wait at least 250ms.
|
||||
// //
|
||||
// // Furthermore, this doesn't include the back/next buttons of the browser
|
||||
// // which must be delayed
|
||||
// // tap(url => {
|
||||
// // if (!/#/.test(url))
|
||||
// // scrollTo({ top: 0 })
|
||||
// // }) // only when loading something we havent loaded!
|
||||
// // delay(250)
|
||||
// )
|
||||
// .subscribe(location$)
|
||||
|
||||
// location$.subscribe(x => {
|
||||
// console.log("L", x)
|
||||
// })
|
||||
// switch$.subscribe(x => {
|
||||
// console.log("S", x)
|
||||
// })
|
||||
|
||||
/* ----------------------------------------------------------------------- */
|
||||
|
||||
component("navigation")
|
||||
const main$ = component("main")
|
||||
.pipe(
|
||||
setupNavigation(agent, { main$ })
|
||||
mountMain(agent, { header$ })
|
||||
)
|
||||
.subscribe()
|
||||
|
||||
component("toc")
|
||||
const navigation$ = component("navigation")
|
||||
.pipe(
|
||||
setupTableOfContents(agent, { header$, main$ })
|
||||
mountNavigation(agent, { main$ })
|
||||
)
|
||||
.subscribe()
|
||||
|
||||
component("tabs")
|
||||
const toc$ = component("toc")
|
||||
.pipe(
|
||||
setupTabs(agent, { header$ })
|
||||
mountTableOfContents(agent, { header$, main$ })
|
||||
)
|
||||
.subscribe()
|
||||
|
||||
component("hero")
|
||||
// TODO: naming?
|
||||
const resultComponent$ = component("search-result")
|
||||
.pipe(
|
||||
setupHero(agent, { header$ })
|
||||
mountSearchResult(agent, { result$, query$: query$.pipe(pluck("value")) })
|
||||
) // temporary fix
|
||||
|
||||
const tabs$ = component("tabs")
|
||||
.pipe(
|
||||
mountTabs(agent, { header$ })
|
||||
)
|
||||
.subscribe()
|
||||
|
||||
// /* Create header title toggle */
|
||||
// component("main")
|
||||
// .pipe(
|
||||
// delay(1000), // initial delay
|
||||
// switchMap(el => typeof getElement("h1", el) !== "undefined"
|
||||
// ? watchBottomOffset(getElement("h1", el)!, { size$, offset$, header$ })
|
||||
// .pipe(
|
||||
// map(({ y }) => y >= 0),
|
||||
// withLatestFrom(component("title")),
|
||||
// tap(([active, title]) => {
|
||||
// title.dataset.mdState = active ? "active" : ""
|
||||
// })
|
||||
// )
|
||||
// : NEVER
|
||||
// )
|
||||
// )
|
||||
// .subscribe()
|
||||
const hero$ = component("hero")
|
||||
.pipe(
|
||||
mountHero(agent, { header$ })
|
||||
)
|
||||
|
||||
// TODO: replace title as inner text
|
||||
|
||||
/* ----------------------------------------------------------------------- */
|
||||
|
||||
const drawer = getElement<HTMLInputElement>("[data-md-toggle=drawer]")!
|
||||
const search = getElement<HTMLInputElement>("[data-md-toggle=search]")!
|
||||
|
||||
// watchToggle
|
||||
|
||||
// --> watchSearchQuery?
|
||||
|
||||
// watchSearch
|
||||
// watchSearchReset
|
||||
|
||||
// toggles stay the same...
|
||||
|
||||
const a$ = watchToggle(search)
|
||||
.pipe(
|
||||
filter(identity),
|
||||
delay(400)
|
||||
)
|
||||
|
||||
// watchSearchReset()
|
||||
|
||||
const b$ = component("reset")
|
||||
const reset$ = component("search-reset")
|
||||
.pipe(
|
||||
switchMap(watchSearchReset)
|
||||
)
|
||||
|
||||
function focusQuery(): MonoTypeOperatorFunction<HTMLElement> {
|
||||
return pipe(
|
||||
tap(el => el.focus())
|
||||
)
|
||||
}
|
||||
|
||||
merge(a$, b$)
|
||||
merge(a$, reset$)
|
||||
.pipe(
|
||||
switchMapTo(component("query")),
|
||||
focusQuery()
|
||||
switchMapTo(component<HTMLInputElement>("search-query")),
|
||||
tap(el => el.focus())
|
||||
)
|
||||
.subscribe()
|
||||
|
||||
/* Wrap all data tables for better overflow scrolling */
|
||||
// const tables = getElements<HTMLTableElement>("table:not([class])")
|
||||
// tables.forEach(table => {
|
||||
// console.log("x", table)
|
||||
// table.parentNode!.insertBefore(renderTable(table), table)
|
||||
// table.replaceWith(renderTable(table) as any)
|
||||
// // table.parentElement!.replaceChild(, table)
|
||||
// })
|
||||
/* ----------------------------------------------------------------------- */
|
||||
|
||||
const state = {
|
||||
search: {
|
||||
query$,
|
||||
result$: resultComponent$,
|
||||
reset$,
|
||||
},
|
||||
main$,
|
||||
navigation$,
|
||||
toc$,
|
||||
tabs$,
|
||||
hero$
|
||||
}
|
||||
|
||||
const { search: temp, ...rest } = state
|
||||
merge(...values(rest), ...values(temp))
|
||||
.subscribe() // potential memleak <-- use takeUntil
|
||||
|
||||
return {
|
||||
// agent, // agent.viewport.offset$
|
||||
// component, // component.toc$
|
||||
}
|
||||
|
||||
/* Return observable factories */
|
||||
return {
|
||||
|
||||
/* User interface */
|
||||
watchDocument: () => load$,
|
||||
// watchDocumentSwitch: () => switch$,
|
||||
watchLocation: () => location$,
|
||||
watchLocationFragment: () => fragment$,
|
||||
watchMediaScreen: () => screen$,
|
||||
watchMediaTablet: () => tablet$,
|
||||
watchViewportOffset: () => offset$,
|
||||
watchViewportSize: () => size$
|
||||
agent,
|
||||
state
|
||||
}
|
||||
}
|
||||
|
@ -238,11 +238,7 @@
|
||||
/>
|
||||
|
||||
<!-- Overlay for expanded drawer -->
|
||||
<label
|
||||
class="md-overlay"
|
||||
data-md-component="overlay"
|
||||
for="__drawer"
|
||||
></label>
|
||||
<label class="md-overlay" for="__drawer"></label>
|
||||
|
||||
<!-- Link to skip to content -->
|
||||
{% if page.toc | first is defined %}
|
||||
|
Loading…
Reference in New Issue
Block a user