mirror of
https://github.com/squidfunk/mkdocs-material.git
synced 2026-07-23 14:23:39 -04:00
Finished search and moved components to setup functions
This commit is contained in:
@@ -50,7 +50,7 @@ export function setSearchResultMeta(
|
||||
export function resetSearchResultMeta(
|
||||
el: HTMLElement
|
||||
): void {
|
||||
el.textContent = translate("search.placeholder")
|
||||
el.textContent = translate("search.result.placeholder")
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
@@ -70,7 +70,7 @@ interface Options {
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Watch component map
|
||||
* Watch component mapping
|
||||
*
|
||||
* This function returns an observable that will maintain bindings to the given
|
||||
* components in-between document switches and update the document in-place.
|
||||
@@ -78,7 +78,7 @@ interface Options {
|
||||
* @param names - Component names
|
||||
* @param options - Options
|
||||
*
|
||||
* @return Component map observable
|
||||
* @return Component mapping observable
|
||||
*/
|
||||
export function watchComponentMap(
|
||||
names: Component[], { document$ }: Options
|
||||
|
||||
@@ -28,7 +28,8 @@ import {
|
||||
switchMapTo
|
||||
} from "rxjs/operators"
|
||||
|
||||
import { ViewportOffset, ViewportSize } from "../../../utilities"
|
||||
import { Agent, ViewportOffset } from "utilities"
|
||||
|
||||
import { Header } from "../_"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
@@ -39,8 +40,6 @@ import { Header } from "../_"
|
||||
* Options
|
||||
*/
|
||||
interface Options {
|
||||
size$: Observable<ViewportSize> /* Viewport size observable */
|
||||
offset$: Observable<ViewportOffset> /* Viewport offset observable */
|
||||
header$: Observable<Header> /* Header observable */
|
||||
}
|
||||
|
||||
@@ -55,16 +54,17 @@ interface Options {
|
||||
* top of the given element based on the current viewport offset.
|
||||
*
|
||||
* @param el - HTML element
|
||||
* @param agent - Agent
|
||||
* @param options - Options
|
||||
*
|
||||
* @return Viewport offset observable
|
||||
*/
|
||||
export function watchHeaderOffsetToTopOf(
|
||||
el: HTMLElement, { size$, offset$, header$ }: Options
|
||||
el: HTMLElement, { viewport }: Agent, { header$ }: Options
|
||||
): Observable<ViewportOffset> {
|
||||
|
||||
/* Compute necessary adjustment for offset */
|
||||
const adjust$ = size$
|
||||
const adjust$ = viewport.size$
|
||||
.pipe(
|
||||
switchMapTo(header$),
|
||||
map(({ height }) => el.offsetTop - height),
|
||||
@@ -72,7 +72,7 @@ export function watchHeaderOffsetToTopOf(
|
||||
)
|
||||
|
||||
/* Compute relative offset and return as hot observable */
|
||||
return combineLatest([offset$, adjust$])
|
||||
return combineLatest([viewport.offset$, adjust$])
|
||||
.pipe(
|
||||
map(([{ x, y }, adjust]) => ({ x, y: y - adjust })),
|
||||
shareReplay(1)
|
||||
@@ -86,16 +86,17 @@ export function watchHeaderOffsetToTopOf(
|
||||
* bottom of the given element based on the current viewport offset.
|
||||
*
|
||||
* @param el - HTML element
|
||||
* @param agent - Agent
|
||||
* @param options - Options
|
||||
*
|
||||
* @return Viewport offset observable
|
||||
*/
|
||||
export function watchHeaderOffsetToBottomOf(
|
||||
el: HTMLElement, { size$, offset$, header$ }: Options
|
||||
el: HTMLElement, { viewport }: Agent, { header$ }: Options
|
||||
): Observable<ViewportOffset> {
|
||||
|
||||
/* Compute necessary adjustment for offset */
|
||||
const adjust$ = size$
|
||||
const adjust$ = viewport.size$
|
||||
.pipe(
|
||||
switchMapTo(header$),
|
||||
map(({ height }) => el.offsetTop + el.offsetHeight - height),
|
||||
@@ -103,7 +104,7 @@ export function watchHeaderOffsetToBottomOf(
|
||||
)
|
||||
|
||||
/* Compute relative offset and return as hot observable */
|
||||
return combineLatest([offset$, adjust$])
|
||||
return combineLatest([viewport.offset$, adjust$])
|
||||
.pipe(
|
||||
map(([{ x, y }, adjust]) => ({ x, y: y - adjust })),
|
||||
shareReplay(1)
|
||||
|
||||
@@ -32,7 +32,8 @@ import {
|
||||
tap
|
||||
} from "rxjs/operators"
|
||||
|
||||
import { resetHeaderShadow, setHeaderShadow } from "../../../actions"
|
||||
import { resetHeaderShadow, setHeaderShadow } from "actions"
|
||||
|
||||
import { Main } from "../../main"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
|
||||
86
src/assets/javascripts/components/hero/index.ts
Normal file
86
src/assets/javascripts/components/hero/index.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { Observable, OperatorFunction, pipe } from "rxjs"
|
||||
import { map, shareReplay } from "rxjs/operators"
|
||||
|
||||
import { switchMapIf } from "extensions"
|
||||
import { Agent, paintHidden } from "utilities"
|
||||
|
||||
import { Header, watchHeaderOffsetToTopOf } from "../header"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Hero
|
||||
*/
|
||||
export interface Hero {
|
||||
hidden: boolean /* Whether the hero is hidden */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Helper types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Options
|
||||
*/
|
||||
interface Options {
|
||||
header$: Observable<Header> /* Header observable */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Setup hero from source observable
|
||||
*
|
||||
* @param agent - Agent
|
||||
* @param options - Options
|
||||
*
|
||||
* @return Operator function
|
||||
*/
|
||||
export function setupHero(
|
||||
agent: Agent, { header$ }: Options
|
||||
): OperatorFunction<HTMLElement, Hero> {
|
||||
const { media } = agent
|
||||
return pipe(
|
||||
switchMapIf(media.screen$, el => {
|
||||
|
||||
/* Watch and paint visibility */
|
||||
const hidden$ = watchHeaderOffsetToTopOf(el, agent, { header$ })
|
||||
.pipe(
|
||||
paintHidden(el, 20)
|
||||
)
|
||||
|
||||
/* Combine into a single hot observable */
|
||||
return hidden$
|
||||
.pipe(
|
||||
map(hidden => ({ hidden }))
|
||||
)
|
||||
}),
|
||||
shareReplay(1)
|
||||
)
|
||||
}
|
||||
@@ -21,9 +21,10 @@
|
||||
*/
|
||||
|
||||
export * from "./_"
|
||||
export * from "./anchor"
|
||||
export * from "./header"
|
||||
export * from "./hidden"
|
||||
export * from "./hero"
|
||||
export * from "./navigation"
|
||||
export * from "./main"
|
||||
export * from "./search"
|
||||
export * from "./sidebar"
|
||||
export * from "./tabs"
|
||||
export * from "./toc"
|
||||
|
||||
125
src/assets/javascripts/components/main/_/index.ts
Normal file
125
src/assets/javascripts/components/main/_/index.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { Observable, OperatorFunction, combineLatest, pipe } from "rxjs"
|
||||
import {
|
||||
distinctUntilChanged,
|
||||
map,
|
||||
pluck,
|
||||
shareReplay,
|
||||
switchMap
|
||||
} from "rxjs/operators"
|
||||
|
||||
import { Agent } from "utilities"
|
||||
|
||||
import { Header } from "../../header"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Main area
|
||||
*/
|
||||
export interface Main {
|
||||
offset: number /* Main area top offset */
|
||||
height: number /* Main area visible height */
|
||||
active: boolean /* Scrolled past top offset */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Helper types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Options
|
||||
*/
|
||||
interface Options {
|
||||
header$: Observable<Header> /* Header observable */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Setup main area from source observable
|
||||
*
|
||||
* This function returns an observable that computes the visual parameters of
|
||||
* the main area from the viewport height and vertical offset, as well as the
|
||||
* height of the header element. The height of the main area is corrected by
|
||||
* the height of the header (if fixed) and footer element.
|
||||
*
|
||||
* @param agent - Agent
|
||||
* @param options - Options
|
||||
*
|
||||
* @return Operator function
|
||||
*/
|
||||
export function setupMain(
|
||||
{ viewport }: Agent, { header$ }: Options
|
||||
): OperatorFunction<HTMLElement, Main> {
|
||||
return pipe(
|
||||
switchMap(el => {
|
||||
|
||||
/* Compute necessary adjustment for header */
|
||||
const adjust$ = header$
|
||||
.pipe(
|
||||
pluck("height")
|
||||
)
|
||||
|
||||
/* Compute the main area's visible height */
|
||||
const height$ = combineLatest([
|
||||
viewport.offset$,
|
||||
viewport.size$,
|
||||
adjust$
|
||||
])
|
||||
.pipe(
|
||||
map(([{ y }, { height }, adjust]) => {
|
||||
const top = el.offsetTop
|
||||
const bottom = el.offsetHeight + top
|
||||
return height
|
||||
- Math.max(0, top - y, adjust)
|
||||
- Math.max(0, height + y - bottom)
|
||||
}),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
/* Compute whether the viewport offset is past the main area's top */
|
||||
const active$ = combineLatest([viewport.offset$, adjust$])
|
||||
.pipe(
|
||||
map(([{ y }, adjust]) => y >= el.offsetTop - adjust),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
/* Combine into a single hot observable */
|
||||
return combineLatest([height$, adjust$, active$])
|
||||
.pipe(
|
||||
map(([height, adjust, active]) => ({
|
||||
offset: el.offsetTop - adjust,
|
||||
height,
|
||||
active
|
||||
}))
|
||||
)
|
||||
}),
|
||||
shareReplay(1)
|
||||
)
|
||||
}
|
||||
@@ -20,99 +20,5 @@
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { Observable, combineLatest } from "rxjs"
|
||||
import {
|
||||
distinctUntilChanged,
|
||||
map,
|
||||
pluck,
|
||||
shareReplay
|
||||
} from "rxjs/operators"
|
||||
|
||||
import { ViewportOffset, ViewportSize } from "utilities"
|
||||
|
||||
import { Header } from "../header"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Main area
|
||||
*/
|
||||
export interface Main {
|
||||
offset: number /* Main area top offset */
|
||||
height: number /* Main area visible height */
|
||||
active: boolean /* Scrolled past top offset */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Helper types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Options
|
||||
*/
|
||||
interface Options {
|
||||
size$: Observable<ViewportSize> /* Viewport size observable */
|
||||
offset$: Observable<ViewportOffset> /* Viewport offset observable */
|
||||
header$: Observable<Header> /* Header observable */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Watch main area
|
||||
*
|
||||
* This function returns an observable that computes the visual parameters of
|
||||
* the main area from the viewport height and vertical offset, as well as the
|
||||
* height of the header element. The height of the main area is corrected by
|
||||
* the height of the header (if fixed) and footer element.
|
||||
*
|
||||
* @param el - Main area element
|
||||
* @param options - Options
|
||||
*
|
||||
* @return Main area observable
|
||||
*/
|
||||
export function watchMain(
|
||||
el: HTMLElement, { size$, offset$, header$ }: Options
|
||||
): Observable<Main> {
|
||||
|
||||
/* Compute necessary adjustment for header */
|
||||
const adjust$ = header$
|
||||
.pipe(
|
||||
pluck("height")
|
||||
)
|
||||
|
||||
/* Compute the main area's visible height */
|
||||
const height$ = combineLatest([offset$, size$, adjust$])
|
||||
.pipe(
|
||||
map(([{ y }, { height }, adjust]) => {
|
||||
const top = el.offsetTop
|
||||
const bottom = el.offsetHeight + top
|
||||
return height
|
||||
- Math.max(0, top - y, adjust)
|
||||
- Math.max(0, height + y - bottom)
|
||||
}),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
/* Compute whether the viewport offset is past the main area's top */
|
||||
const active$ = combineLatest([offset$, adjust$])
|
||||
.pipe(
|
||||
map(([{ y }, adjust]) => y >= el.offsetTop - adjust),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
/* Combine into a single hot observable */
|
||||
return combineLatest([height$, adjust$, active$])
|
||||
.pipe(
|
||||
map(([height, adjust, active]) => ({
|
||||
offset: el.offsetTop - adjust,
|
||||
height,
|
||||
active
|
||||
})),
|
||||
shareReplay(1)
|
||||
)
|
||||
}
|
||||
export * from "./_"
|
||||
export * from "./sidebar"
|
||||
|
||||
@@ -43,7 +43,7 @@ import {
|
||||
setSidebarHeight,
|
||||
setSidebarLock
|
||||
} from "actions"
|
||||
import { ViewportOffset } from "utilities"
|
||||
import { Agent } from "utilities"
|
||||
|
||||
import { Main } from "../main"
|
||||
|
||||
@@ -67,7 +67,6 @@ export interface Sidebar {
|
||||
* Options
|
||||
*/
|
||||
interface Options {
|
||||
offset$: Observable<ViewportOffset> /* Viewport offset observable */
|
||||
main$: Observable<Main> /* Main area observable */
|
||||
}
|
||||
|
||||
@@ -84,12 +83,13 @@ interface Options {
|
||||
* the sidebar is locked and fills the remaining space.
|
||||
*
|
||||
* @param el - Sidebar element
|
||||
* @param agent - Agent
|
||||
* @param options - Options
|
||||
*
|
||||
* @return Sidebar observable
|
||||
*/
|
||||
export function watchSidebar(
|
||||
el: HTMLElement, { offset$, main$ }: Options
|
||||
el: HTMLElement, { viewport }: Agent, { main$ }: Options
|
||||
): Observable<Sidebar> {
|
||||
|
||||
/* Adjust for internal main area offset */
|
||||
@@ -99,7 +99,7 @@ export function watchSidebar(
|
||||
)
|
||||
|
||||
/* Compute the sidebar's available height */
|
||||
const height$ = combineLatest([offset$, main$])
|
||||
const height$ = combineLatest([viewport.offset$, main$])
|
||||
.pipe(
|
||||
map(([{ y }, { offset, height }]) => {
|
||||
return height - adjust + Math.min(adjust, Math.max(0, y - offset))
|
||||
@@ -107,7 +107,7 @@ export function watchSidebar(
|
||||
)
|
||||
|
||||
/* Compute whether the sidebar should be locked */
|
||||
const lock$ = combineLatest([offset$, main$])
|
||||
const lock$ = combineLatest([viewport.offset$, main$])
|
||||
.pipe(
|
||||
map(([{ y }, { offset }]) => y >= offset + adjust)
|
||||
)
|
||||
91
src/assets/javascripts/components/navigation/_/index.ts
Normal file
91
src/assets/javascripts/components/navigation/_/index.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { Observable, OperatorFunction, pipe } from "rxjs"
|
||||
import { map, shareReplay } from "rxjs/operators"
|
||||
|
||||
import { switchMapIf } from "extensions"
|
||||
import { Agent } from "utilities"
|
||||
|
||||
import {
|
||||
Main,
|
||||
Sidebar,
|
||||
paintSidebar,
|
||||
watchSidebar
|
||||
} from "../../main"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Navigation
|
||||
*/
|
||||
export interface Navigation {
|
||||
sidebar: Sidebar /* Sidebar */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Helper types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Options
|
||||
*/
|
||||
interface Options {
|
||||
main$: Observable<Main> /* Main observable */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Setup navigation from source observable
|
||||
*
|
||||
* @param agent - Agent
|
||||
* @param options - Options
|
||||
*
|
||||
* @return Operator function
|
||||
*/
|
||||
export function setupNavigation(
|
||||
agent: Agent, { main$ }: Options
|
||||
): OperatorFunction<HTMLElement, Navigation> {
|
||||
const { media } = agent
|
||||
return pipe(
|
||||
switchMapIf(media.screen$, el => {
|
||||
|
||||
/* Watch and paint sidebar */
|
||||
const sidebar$ = watchSidebar(el, agent, { main$ })
|
||||
.pipe(
|
||||
paintSidebar(el)
|
||||
)
|
||||
|
||||
/* Combine into a single hot observable */
|
||||
return sidebar$
|
||||
.pipe(
|
||||
map(sidebar => ({ sidebar }))
|
||||
)
|
||||
}),
|
||||
shareReplay(1)
|
||||
)
|
||||
}
|
||||
23
src/assets/javascripts/components/navigation/index.ts
Normal file
23
src/assets/javascripts/components/navigation/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
export * from "./_"
|
||||
85
src/assets/javascripts/components/search/result/_/index.ts
Normal file
85
src/assets/javascripts/components/search/result/_/index.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { identity } from "ramda"
|
||||
import { Observable, OperatorFunction, pipe } from "rxjs"
|
||||
import {
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
map,
|
||||
switchMap,
|
||||
} from "rxjs/operators"
|
||||
|
||||
import { SearchResult } from "modules"
|
||||
import { Agent, watchElementOffset } from "utilities"
|
||||
|
||||
import { paintSearchResultList } from "../list"
|
||||
import { paintSearchResultMeta } from "../meta"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Helper types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Options
|
||||
*/
|
||||
interface Options {
|
||||
result$: Observable<SearchResult[]> /* Search result observable */
|
||||
query$: Observable<string> /* Search query observable */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Setup search result from source observable
|
||||
*
|
||||
* @param el - Search result element
|
||||
* @param agent - Agent
|
||||
*
|
||||
* @return Operator function
|
||||
*/
|
||||
export function setupSearchResult(
|
||||
agent: Agent, { result$, query$ }: Options
|
||||
): OperatorFunction<HTMLElement, SearchResult[]> {
|
||||
return pipe(
|
||||
switchMap(el => {
|
||||
const parent = el.parentElement!
|
||||
|
||||
/* Compute whether more elements need to be rendered */
|
||||
const render$ = watchElementOffset(parent, agent)
|
||||
.pipe(
|
||||
map(({ y }) => y >= parent.scrollHeight - parent.offsetHeight - 16),
|
||||
distinctUntilChanged(),
|
||||
filter(identity)
|
||||
)
|
||||
|
||||
/* Paint search results */
|
||||
return result$
|
||||
.pipe(
|
||||
paintSearchResultMeta(el, { query$ }),
|
||||
paintSearchResultList(el, { render$ })
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -20,99 +20,6 @@
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { identity } from "ramda"
|
||||
import {
|
||||
MonoTypeOperatorFunction,
|
||||
Observable,
|
||||
animationFrameScheduler,
|
||||
pipe
|
||||
} from "rxjs"
|
||||
import {
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
finalize,
|
||||
map,
|
||||
mapTo,
|
||||
observeOn,
|
||||
scan,
|
||||
switchMap,
|
||||
tap
|
||||
} from "rxjs/operators"
|
||||
|
||||
import {
|
||||
addToSearchResultList,
|
||||
resetSearchResultList,
|
||||
setSearchResultMeta
|
||||
} from "actions"
|
||||
import { SearchResult } from "modules"
|
||||
import { renderSearchResult } from "templates"
|
||||
import { ViewportSize, watchElementOffset } from "utilities"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Helper types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Options
|
||||
*/
|
||||
interface Options {
|
||||
size$: Observable<ViewportSize> /* Viewport size observable */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Paint search result from source observable
|
||||
*
|
||||
* @param el - Search result element
|
||||
*
|
||||
* @return Operator function
|
||||
*/
|
||||
export function paintSearchResult(
|
||||
el: HTMLElement, { size$ }: Options
|
||||
): MonoTypeOperatorFunction<SearchResult[]> {
|
||||
const container = el.parentElement!
|
||||
|
||||
/* Compute whether the container is near the bottom offset */
|
||||
const render$ = watchElementOffset(container, { size$ })
|
||||
.pipe(
|
||||
map(({ y }) => y >= container.scrollHeight - container.offsetHeight - 16),
|
||||
distinctUntilChanged(),
|
||||
filter(identity)
|
||||
)
|
||||
|
||||
/* Paint search results lazily */
|
||||
const [meta, list] = Array.from(el.children) as HTMLElement[]
|
||||
return pipe(
|
||||
|
||||
/* Paint search result metadata */
|
||||
tap(result => {
|
||||
setSearchResultMeta(meta, result.length)
|
||||
}),
|
||||
|
||||
/* Paint search result list */
|
||||
switchMap(result => render$
|
||||
.pipe(
|
||||
|
||||
/* Defer repaint to next animation frame */
|
||||
observeOn(animationFrameScheduler),
|
||||
scan(index => {
|
||||
while (index < result.length) {
|
||||
addToSearchResultList(list, renderSearchResult(result[index++]))
|
||||
if (container.scrollHeight - container.offsetHeight > 16)
|
||||
break
|
||||
}
|
||||
return index
|
||||
}, 0),
|
||||
mapTo(result),
|
||||
|
||||
/* Reset on complete or error */
|
||||
finalize(() => {
|
||||
resetSearchResultList(list)
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
export * from "./_"
|
||||
export * from "./list"
|
||||
export * from "./meta"
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import {
|
||||
MonoTypeOperatorFunction,
|
||||
Observable,
|
||||
animationFrameScheduler,
|
||||
pipe
|
||||
} from "rxjs"
|
||||
import {
|
||||
finalize,
|
||||
mapTo,
|
||||
observeOn,
|
||||
scan,
|
||||
switchMap
|
||||
} from "rxjs/operators"
|
||||
|
||||
import {
|
||||
addToSearchResultList,
|
||||
resetSearchResultList
|
||||
} from "actions"
|
||||
import { SearchResult } from "modules"
|
||||
import { renderSearchResult } from "templates"
|
||||
import { getElement } from "utilities"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Helper types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Options
|
||||
*/
|
||||
interface Options {
|
||||
render$: Observable<boolean> /* Render trigger observable */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Paint search result list from source observable
|
||||
*
|
||||
* @param el - Search result element
|
||||
* @param options - Options
|
||||
*
|
||||
* @return Operator function
|
||||
*/
|
||||
export function paintSearchResultList(
|
||||
el: HTMLElement, { render$ }: Options
|
||||
): MonoTypeOperatorFunction<SearchResult[]> {
|
||||
const parent = el.parentElement!
|
||||
const list = getElement(".md-search-result__list", el)!
|
||||
return pipe(
|
||||
switchMap(result => render$
|
||||
.pipe(
|
||||
|
||||
/* Defer repaint to next animation frame */
|
||||
observeOn(animationFrameScheduler),
|
||||
scan(index => {
|
||||
while (index < result.length) {
|
||||
addToSearchResultList(list, renderSearchResult(result[index++]))
|
||||
if (parent.scrollHeight - parent.offsetHeight > 16)
|
||||
break
|
||||
}
|
||||
return index
|
||||
}, 0),
|
||||
|
||||
/* Re-map to search result */
|
||||
mapTo(result),
|
||||
|
||||
/* Reset on complete or error */
|
||||
finalize(() => {
|
||||
resetSearchResultList(list)
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { MonoTypeOperatorFunction, Observable, pipe } from "rxjs"
|
||||
import { map, withLatestFrom } from "rxjs/operators"
|
||||
|
||||
import {
|
||||
resetSearchResultMeta,
|
||||
setSearchResultMeta
|
||||
} from "actions"
|
||||
import { SearchResult } from "modules"
|
||||
import { getElement } from "utilities"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Helper types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Options
|
||||
*/
|
||||
interface Options {
|
||||
query$: Observable<string> /* Search query observable */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Paint search result metadata from source observable
|
||||
*
|
||||
* @param el - Search result metadata element
|
||||
* @param options - Options
|
||||
*
|
||||
* @return Operator function
|
||||
*/
|
||||
export function paintSearchResultMeta(
|
||||
el: HTMLElement, { query$ }: Options
|
||||
): MonoTypeOperatorFunction<SearchResult[]> {
|
||||
const meta = getElement(".md-search-result__meta", el)!
|
||||
return pipe(
|
||||
withLatestFrom(query$),
|
||||
map(([result, query]) => {
|
||||
if (query) {
|
||||
setSearchResultMeta(meta, result.length)
|
||||
} else {
|
||||
resetSearchResultMeta(meta)
|
||||
}
|
||||
return result
|
||||
})
|
||||
)
|
||||
}
|
||||
86
src/assets/javascripts/components/tabs/index.ts
Normal file
86
src/assets/javascripts/components/tabs/index.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { Observable, OperatorFunction, pipe } from "rxjs"
|
||||
import { map, shareReplay } from "rxjs/operators"
|
||||
|
||||
import { switchMapIf } from "extensions"
|
||||
import { Agent, paintHidden } from "utilities"
|
||||
|
||||
import { Header, watchHeaderOffsetToTopOf } from "../header"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Tabs
|
||||
*/
|
||||
export interface Tabs {
|
||||
hidden: boolean /* Whether the tabs are hidden */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Helper types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Options
|
||||
*/
|
||||
interface Options {
|
||||
header$: Observable<Header> /* Header observable */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Setup tabs from source observable
|
||||
*
|
||||
* @param agent - Agent
|
||||
* @param options - Options
|
||||
*
|
||||
* @return Operator function
|
||||
*/
|
||||
export function setupTabs(
|
||||
agent: Agent, { header$ }: Options
|
||||
): OperatorFunction<HTMLElement, Tabs> {
|
||||
const { media } = agent
|
||||
return pipe(
|
||||
switchMapIf(media.screen$, el => {
|
||||
|
||||
/* Watch and paint visibility */
|
||||
const hidden$ = watchHeaderOffsetToTopOf(el, agent, { header$ })
|
||||
.pipe(
|
||||
paintHidden(el, 8)
|
||||
)
|
||||
|
||||
/* Combine into a single hot observable */
|
||||
return hidden$
|
||||
.pipe(
|
||||
map(hidden => ({ hidden }))
|
||||
)
|
||||
}),
|
||||
shareReplay(1)
|
||||
)
|
||||
}
|
||||
106
src/assets/javascripts/components/toc/_/index.ts
Normal file
106
src/assets/javascripts/components/toc/_/index.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { Observable, OperatorFunction, combineLatest, pipe } from "rxjs"
|
||||
import { map, shareReplay } from "rxjs/operators"
|
||||
|
||||
import { switchMapIf } from "extensions"
|
||||
import { Agent, getElements } from "utilities"
|
||||
|
||||
import { Header } from "../../header"
|
||||
import {
|
||||
Main,
|
||||
Sidebar,
|
||||
paintSidebar,
|
||||
watchSidebar
|
||||
} from "../../main"
|
||||
import {
|
||||
AnchorList,
|
||||
paintAnchorList,
|
||||
watchAnchorList
|
||||
} from "../anchor"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Table of contents
|
||||
*/
|
||||
export interface TableOfContents {
|
||||
sidebar: Sidebar /* Sidebar */
|
||||
anchors: AnchorList /* Anchor list */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Helper types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Options
|
||||
*/
|
||||
interface Options {
|
||||
header$: Observable<Header> /* Header observable */
|
||||
main$: Observable<Main> /* Main observable */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Setup table of contents from source observable
|
||||
*
|
||||
* @param agent - Agent
|
||||
* @param options - Options
|
||||
*
|
||||
* @return Operator function
|
||||
*/
|
||||
export function setupTableOfContents(
|
||||
agent: Agent, { header$, main$ }: Options
|
||||
): OperatorFunction<HTMLElement, TableOfContents> {
|
||||
const { media } = agent
|
||||
return pipe(
|
||||
switchMapIf(media.tablet$, el => {
|
||||
|
||||
/* Watch and paint sidebar */
|
||||
const sidebar$ = watchSidebar(el, agent, { main$ })
|
||||
.pipe(
|
||||
paintSidebar(el)
|
||||
)
|
||||
|
||||
/* Watch and paint anchor list (scroll spy) */
|
||||
const els = getElements<HTMLAnchorElement>(".md-nav__link", el)
|
||||
const anchors$ = watchAnchorList(els, agent, { header$ })
|
||||
.pipe(
|
||||
paintAnchorList(els)
|
||||
)
|
||||
|
||||
/* Combine into a single hot observable */
|
||||
return combineLatest([sidebar$, anchors$])
|
||||
.pipe(
|
||||
map(([sidebar, anchors]) => ({ sidebar, anchors }))
|
||||
)
|
||||
}),
|
||||
shareReplay(1)
|
||||
)
|
||||
}
|
||||
@@ -45,13 +45,9 @@ import {
|
||||
setAnchorActive,
|
||||
setAnchorBlur
|
||||
} from "actions"
|
||||
import {
|
||||
ViewportOffset,
|
||||
ViewportSize,
|
||||
getElement
|
||||
} from "utilities"
|
||||
import { Agent, getElement } from "utilities"
|
||||
|
||||
import { Header } from "../header"
|
||||
import { Header } from "../../header"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Types
|
||||
@@ -73,8 +69,6 @@ export interface AnchorList {
|
||||
* Options
|
||||
*/
|
||||
interface Options {
|
||||
size$: Observable<ViewportSize> /* Viewport size observable */
|
||||
offset$: Observable<ViewportOffset> /* Viewport offset observable */
|
||||
header$: Observable<Header> /* Header observable */
|
||||
}
|
||||
|
||||
@@ -98,12 +92,13 @@ interface Options {
|
||||
* Note that the current anchor is the last item of the `prev` anchor list.
|
||||
*
|
||||
* @param els - Anchor elements
|
||||
* @param agent - Agent
|
||||
* @param options - Options
|
||||
*
|
||||
* @return Anchor list observable
|
||||
*/
|
||||
export function watchAnchorList(
|
||||
els: HTMLAnchorElement[], { size$, offset$, header$ }: Options
|
||||
els: HTMLAnchorElement[], { viewport }: Agent, { header$ }: Options
|
||||
): Observable<AnchorList> {
|
||||
const table = new Map<HTMLAnchorElement, HTMLElement>()
|
||||
for (const el of els) {
|
||||
@@ -120,7 +115,7 @@ export function watchAnchorList(
|
||||
)
|
||||
|
||||
/* Compute partition of previous and next anchors */
|
||||
const partition$ = size$
|
||||
const partition$ = viewport.size$
|
||||
.pipe(
|
||||
|
||||
/* Build index to map anchor paths to vertical offsets */
|
||||
@@ -143,7 +138,7 @@ export function watchAnchorList(
|
||||
}),
|
||||
|
||||
/* Re-compute partition when viewport offset changes */
|
||||
switchMap(index => combineLatest(offset$, adjust$)
|
||||
switchMap(index => combineLatest(viewport.offset$, adjust$)
|
||||
.pipe(
|
||||
scan(([prev, next], [{ y }, adjust]) => {
|
||||
|
||||
24
src/assets/javascripts/components/toc/index.ts
Normal file
24
src/assets/javascripts/components/toc/index.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
export * from "./_"
|
||||
export * from "./anchor"
|
||||
@@ -20,16 +20,26 @@
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
// TODO: remove this later on
|
||||
|
||||
// tslint:disable
|
||||
|
||||
import { identity } from "ramda"
|
||||
import {
|
||||
EMPTY,
|
||||
MonoTypeOperatorFunction,
|
||||
NEVER,
|
||||
Observable,
|
||||
Subject,
|
||||
defer,
|
||||
forkJoin,
|
||||
fromEvent,
|
||||
merge,
|
||||
of,
|
||||
pipe,
|
||||
} from "rxjs"
|
||||
import {
|
||||
combineAll,
|
||||
delay,
|
||||
distinctUntilKeyChanged,
|
||||
filter,
|
||||
@@ -38,42 +48,44 @@ import {
|
||||
shareReplay,
|
||||
switchMap,
|
||||
switchMapTo,
|
||||
take,
|
||||
tap,
|
||||
} from "rxjs/operators"
|
||||
|
||||
import { ajax } from "rxjs/ajax"
|
||||
import {} from "components"
|
||||
import { AjaxResponse, ajax } from "rxjs/ajax"
|
||||
import {
|
||||
Component,
|
||||
paintHeaderShadow,
|
||||
paintHidden,
|
||||
paintSidebar,
|
||||
setupHero,
|
||||
setupMain,
|
||||
setupNavigation,
|
||||
setupSearchResult,
|
||||
switchComponent,
|
||||
watchBottomOffset,
|
||||
watchComponentMap,
|
||||
watchHeader,
|
||||
watchMain,
|
||||
watchSearchReset,
|
||||
watchSidebar,
|
||||
watchTopOffset
|
||||
} from "./components"
|
||||
import {
|
||||
not,
|
||||
switchMapIf
|
||||
} from "./extensions"
|
||||
import { SearchIndex } from "./modules/search"
|
||||
import { SearchIndex, SearchResult } from "./modules/search"
|
||||
import {
|
||||
getElement,
|
||||
setupAgent,
|
||||
watchDocument,
|
||||
watchDocumentSwitch,
|
||||
watchLocation,
|
||||
watchLocationFragment,
|
||||
watchLocationHash,
|
||||
watchMedia,
|
||||
watchToggle,
|
||||
watchViewportOffset,
|
||||
watchViewportSize,
|
||||
watchWorker
|
||||
} from "./utilities"
|
||||
import { SearchMessage, SearchMessageType } from "./workers"
|
||||
import {
|
||||
SearchMessage,
|
||||
SearchMessageType,
|
||||
SearchSetupMessage,
|
||||
isSearchDumpMessage,
|
||||
isSearchResultMessage
|
||||
} from "./workers"
|
||||
|
||||
/**
|
||||
* Configuration
|
||||
@@ -86,9 +98,13 @@ export interface Config {
|
||||
}
|
||||
}
|
||||
|
||||
import { PackerMessage, PackerMessageType } from "./workers/packer"
|
||||
import {
|
||||
PackerMessage,
|
||||
PackerMessageType
|
||||
} from "./workers/packer"
|
||||
|
||||
import { renderSearchResult } from "./templates"
|
||||
import { setupTabs } from "components/tabs"
|
||||
import { setupTableOfContents } from "components/toc/_"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
@@ -109,7 +125,7 @@ export function isConfig(config: any): config is Config {
|
||||
// TBD
|
||||
|
||||
// TODO: put this somewhere else... (merge with config!) JSON schema!?
|
||||
const names = [
|
||||
const names: Component[] = [
|
||||
"header", /* Header */
|
||||
"title", /* Header title */
|
||||
"search", /* Search */
|
||||
@@ -122,7 +138,7 @@ const names = [
|
||||
"tabs", /* Tabs */
|
||||
"navigation", /* Navigation */
|
||||
"toc" /* Table of contents */
|
||||
] as const
|
||||
]
|
||||
|
||||
// modernizr for the poor
|
||||
document.documentElement.classList.remove("no-js")
|
||||
@@ -132,6 +148,19 @@ 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 {
|
||||
const newvalue = value
|
||||
.replace(/(?:^|\s+)[*+-:^~]+(?=\s+|$)/g, "")
|
||||
.trim()
|
||||
|
||||
return newvalue ? newvalue.replace(/\s+|$/g, "* ") : ""
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Material for MkDocs
|
||||
*
|
||||
@@ -141,72 +170,113 @@ export function initialize(config: unknown) {
|
||||
if (!isConfig(config))
|
||||
throw new SyntaxError(`Invalid configuration: ${JSON.stringify(config)}`)
|
||||
|
||||
const agent = setupAgent()
|
||||
|
||||
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, { message$: packerMessage$ })
|
||||
const packer$ = watchWorker(packer, { send$: packerMessage$ })
|
||||
|
||||
// send a message, then switchMapTo worker!
|
||||
|
||||
packer$.subscribe(message => {
|
||||
console.log("PACKER.MSG", message)
|
||||
console.log("PACKER.MSG", message.data.length)
|
||||
// is always packed!
|
||||
console.log(message.data.length)
|
||||
localStorage.setItem("index", message.data)
|
||||
if (message.type === PackerMessageType.BINARY && message.data[0] !== "{")
|
||||
localStorage.setItem("index", message.data)
|
||||
})
|
||||
|
||||
// storing = experimental feature
|
||||
|
||||
const searchMessage$ = new Subject<SearchMessage>()
|
||||
|
||||
const search$ = watchWorker(worker, { message$: searchMessage$ })
|
||||
search$.subscribe(message => {
|
||||
if (message.type === SearchMessageType.DUMP) {
|
||||
console.log(message.data.length)
|
||||
packerMessage$.next({
|
||||
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(
|
||||
filter(isSearchDumpMessage),
|
||||
map(message => ({
|
||||
type: PackerMessageType.STRING,
|
||||
data: message.data
|
||||
})
|
||||
} else if (message.type === SearchMessageType.RESULT) {
|
||||
console.log("RESULT", message)
|
||||
})),
|
||||
tap(message => packerMessage$.next(message)) // send message and wait!
|
||||
// switchMapTo(packer$)
|
||||
)
|
||||
.subscribe()
|
||||
|
||||
const list = document.querySelector(".md-search-result__list")!
|
||||
list.innerHTML = ""
|
||||
for (const el of message.data.map(renderSearchResult)) // TODO: perform entire lazy render!!!!
|
||||
list.appendChild(el as any) // only render visibile stuff...!
|
||||
|
||||
// paint on next animation frame!?
|
||||
|
||||
// build a rendering pipeline for search results + scroll bottom!
|
||||
|
||||
}
|
||||
// if (message.type === 0) {
|
||||
// console.log("Packing...")
|
||||
// packerMessage$.next(message.toString())
|
||||
// } else {
|
||||
// console.log((message as any).term, ":", (message as any).res)
|
||||
// }
|
||||
})
|
||||
|
||||
// filter singular "+" or "-",as it will result in a lunr.js error
|
||||
|
||||
ajax({
|
||||
const data$ = ajax({
|
||||
url: `${config.base}/search/search_index.json`,
|
||||
responseType: "json",
|
||||
withCredentials: true
|
||||
})
|
||||
.pipe(
|
||||
pluck("response"),
|
||||
map<SearchIndex, SearchMessage>(data => ({
|
||||
.pipe<SearchIndex>(
|
||||
pluck("response")
|
||||
// take(1)
|
||||
)
|
||||
|
||||
const fromLocal = localStorage.getItem("index")
|
||||
|
||||
;
|
||||
(fromLocal ? of({
|
||||
type: PackerMessageType.BINARY,
|
||||
data: localStorage.getItem("index")!
|
||||
}) : EMPTY)
|
||||
.subscribe(x => {
|
||||
// console.log("send message to packer")
|
||||
packerMessage$.next(x)
|
||||
})
|
||||
|
||||
const index$ = fromLocal ? packer$.pipe(pluck("data"), take(1)) : of(undefined) // of(localStorage.getItem("index"))
|
||||
|
||||
// index$.subscribe(xx => console.log("INDEX", xx))
|
||||
|
||||
forkJoin([data$, index$])
|
||||
.pipe<SearchSetupMessage>(
|
||||
map(([data, index]) => ({
|
||||
type: SearchMessageType.SETUP,
|
||||
data
|
||||
data: { ...data, index }
|
||||
}))
|
||||
)
|
||||
.subscribe(message => {
|
||||
searchMessage$.next(message) // TODO: this shall not complete
|
||||
})
|
||||
|
||||
// filter singular "+" or "-",as it will result in a lunr.js error
|
||||
|
||||
// data$
|
||||
// .pipe(
|
||||
// map<SearchIndex, SearchMessage>(data => ({
|
||||
// type: SearchMessageType.SETUP,
|
||||
// data
|
||||
// }))
|
||||
// )
|
||||
// .subscribe(message => {
|
||||
// searchMessage$.next(message) // TODO: this shall not complete
|
||||
// })
|
||||
|
||||
/* ----------------------------------------------------------------------- */
|
||||
|
||||
/* Create viewport observables */
|
||||
@@ -219,10 +289,16 @@ export function initialize(config: unknown) {
|
||||
|
||||
/* Create location observables */
|
||||
const location$ = watchLocation()
|
||||
const fragment$ = watchLocationFragment()
|
||||
const fragment$ = watchLocationHash()
|
||||
|
||||
/* Create document observables */
|
||||
const load$ = watchDocument()
|
||||
|
||||
// Complete set of AgentObservables...
|
||||
|
||||
// component map!
|
||||
//
|
||||
|
||||
// const switch$ = watchDocumentSwitch({ location$ })
|
||||
|
||||
/* ----------------------------------------------------------------------- */
|
||||
@@ -230,44 +306,95 @@ export function initialize(config: unknown) {
|
||||
/* Create component map observable */
|
||||
const components$ = watchComponentMap(names, { document$: load$ })
|
||||
|
||||
const component = (name: Component): Observable<HTMLElement> => {
|
||||
const component = <T extends HTMLElement>(name: Component): Observable<T> => {
|
||||
return components$
|
||||
.pipe(
|
||||
switchComponent(name)
|
||||
switchComponent<T>(name)
|
||||
)
|
||||
}
|
||||
|
||||
/* Create header observable */
|
||||
const header$ = component("header")
|
||||
const header$ = component("header") // TODO:!
|
||||
.pipe(
|
||||
switchMap(watchHeader)
|
||||
)
|
||||
|
||||
/* Create main area observable */
|
||||
// DONE
|
||||
const main$ = component("main")
|
||||
.pipe(
|
||||
switchMap(el => watchMain(el, { size$, offset$, header$ })),
|
||||
shareReplay(1)
|
||||
setupMain(agent, { header$ })
|
||||
)
|
||||
|
||||
// setupHeader(agent) ??
|
||||
|
||||
// setupSearch
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
component("query")
|
||||
|
||||
/* Create header shadow toggle */
|
||||
component("header")
|
||||
.pipe(
|
||||
switchMap(el => fromEvent(el, "keyup") // not super nice...
|
||||
switchMap(el => main$
|
||||
.pipe(
|
||||
map<Event, SearchMessage>(() => ({
|
||||
type: SearchMessageType.QUERY,
|
||||
data: (el as HTMLInputElement).value
|
||||
})), // TODO. ugly...
|
||||
distinctUntilKeyChanged("data")
|
||||
paintHeaderShadow(el)
|
||||
)
|
||||
)
|
||||
)
|
||||
.subscribe()
|
||||
|
||||
// watchSearchResult // emit, if at bottom...
|
||||
// receive results as a second observable!? filter stuff, paint
|
||||
|
||||
const result$ = search$
|
||||
.pipe(
|
||||
filter(isSearchResultMessage),
|
||||
pluck("data")
|
||||
)
|
||||
|
||||
const query$ = component<HTMLInputElement>("query")
|
||||
.pipe(
|
||||
switchMap(el => fromEvent(el, "keyup")
|
||||
.pipe(
|
||||
map(() => prepareQuery(el.value))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
// DONE
|
||||
component("result")
|
||||
.pipe(
|
||||
setupSearchResult(agent, { result$, query$ })
|
||||
)
|
||||
.subscribe()
|
||||
|
||||
query$
|
||||
.pipe(
|
||||
map(data => ({ // put this into some function...
|
||||
type: SearchMessageType.QUERY,
|
||||
data
|
||||
})), // TODO. ugly...
|
||||
distinctUntilKeyChanged("data")
|
||||
)
|
||||
|
||||
.subscribe(x => {
|
||||
searchMessage$.next(x)
|
||||
searchMessage$.next(x as any) // TODO
|
||||
})
|
||||
|
||||
// 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(
|
||||
@@ -310,62 +437,27 @@ export function initialize(config: unknown) {
|
||||
|
||||
/* ----------------------------------------------------------------------- */
|
||||
|
||||
/* Create header shadow toggle */
|
||||
component("header")
|
||||
.pipe(
|
||||
switchMap(el => main$
|
||||
.pipe(
|
||||
paintHeaderShadow(el)
|
||||
)
|
||||
)
|
||||
)
|
||||
.subscribe()
|
||||
|
||||
/* Create sidebar with navigation */
|
||||
component("navigation")
|
||||
.pipe(
|
||||
switchMapIf(screen$, el => watchSidebar(el, { offset$, main$ })
|
||||
.pipe(
|
||||
paintSidebar(el)
|
||||
)
|
||||
),
|
||||
shareReplay(1)
|
||||
setupNavigation(agent, { main$ })
|
||||
)
|
||||
.subscribe()
|
||||
|
||||
/* Create sidebar with table of contents */
|
||||
component("toc")
|
||||
.pipe(
|
||||
switchMapIf(tablet$, el => watchSidebar(el, { offset$, main$ })
|
||||
.pipe(
|
||||
paintSidebar(el)
|
||||
)
|
||||
),
|
||||
shareReplay(1)
|
||||
setupTableOfContents(agent, { header$, main$ })
|
||||
)
|
||||
.subscribe()
|
||||
|
||||
/* Create tabs visibility toggle */
|
||||
component("tabs")
|
||||
.pipe(
|
||||
switchMapIf(screen$, el => watchTopOffset(el, { size$, offset$, header$ })
|
||||
.pipe(
|
||||
paintHidden(el, 8)
|
||||
)
|
||||
),
|
||||
shareReplay(1)
|
||||
setupTabs(agent, { header$ })
|
||||
)
|
||||
.subscribe()
|
||||
|
||||
/* Create hero visibility toggle */
|
||||
component("hero")
|
||||
.pipe(
|
||||
switchMap(el => watchTopOffset(el, { size$, offset$, header$ })
|
||||
.pipe(
|
||||
paintHidden(el, 20)
|
||||
)
|
||||
),
|
||||
shareReplay(1)
|
||||
setupHero(agent, { header$ })
|
||||
)
|
||||
.subscribe()
|
||||
|
||||
@@ -429,6 +521,20 @@ export function initialize(config: unknown) {
|
||||
)
|
||||
.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)
|
||||
// })
|
||||
|
||||
return {
|
||||
// agent, // agent.viewport.offset$
|
||||
// component, // component.toc$
|
||||
}
|
||||
|
||||
/* Return observable factories */
|
||||
return {
|
||||
|
||||
|
||||
113
src/assets/javascripts/utilities/agent/_/index.ts
Normal file
113
src/assets/javascripts/utilities/agent/_/index.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { Observable, Subject } from "rxjs"
|
||||
|
||||
import {
|
||||
ViewportOffset,
|
||||
ViewportSize,
|
||||
watchDocument,
|
||||
watchLocation,
|
||||
watchLocationHash,
|
||||
watchMedia,
|
||||
watchViewportOffset,
|
||||
watchViewportSize
|
||||
} from "utilities"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Agent document
|
||||
*/
|
||||
export interface AgentDocument {
|
||||
load$: Observable<Document> /* Document observable */
|
||||
}
|
||||
|
||||
/**
|
||||
* Agent location
|
||||
*/
|
||||
export interface AgentLocation {
|
||||
href$: Subject<string> /* Location subject */
|
||||
hash$: Observable<string> /* Location hash observable */
|
||||
}
|
||||
|
||||
/**
|
||||
* Agent media
|
||||
*/
|
||||
export interface AgentMedia {
|
||||
screen$: Observable<boolean> /* Media observable for screen */
|
||||
tablet$: Observable<boolean> /* Media observable for tablet */
|
||||
}
|
||||
|
||||
/**
|
||||
* Agent viewport
|
||||
*/
|
||||
export interface AgentViewport {
|
||||
offset$: Observable<ViewportOffset> /* Viewport offset observable */
|
||||
size$: Observable<ViewportSize> /* Viewport size observable */
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Agent
|
||||
*/
|
||||
export interface Agent {
|
||||
document: AgentDocument /* Document observables */
|
||||
location: AgentLocation /* Location observables */
|
||||
media: AgentMedia /* Media observables */
|
||||
viewport: AgentViewport /* Viewport observables */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Create the agent
|
||||
*
|
||||
* This function returns a data structure that contains all observables that
|
||||
* are related to the browser and/or environment.
|
||||
*
|
||||
* @return Agent
|
||||
*/
|
||||
export function setupAgent(): Agent {
|
||||
return {
|
||||
document: {
|
||||
load$: watchDocument()
|
||||
},
|
||||
location: {
|
||||
href$: watchLocation(),
|
||||
hash$: watchLocationHash()
|
||||
},
|
||||
media: {
|
||||
screen$: watchMedia("(min-width: 1220px)"),
|
||||
tablet$: watchMedia("(min-width: 960px)")
|
||||
},
|
||||
viewport: {
|
||||
offset$: watchViewportOffset(),
|
||||
size$: watchViewportSize()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
import { Observable, fromEvent, merge } from "rxjs"
|
||||
import { map, shareReplay, startWith } from "rxjs/operators"
|
||||
|
||||
import { Agent } from "../../_"
|
||||
import { ViewportSize } from "../../viewport"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
@@ -71,16 +72,16 @@ export function getElementOffset(el: HTMLElement): ElementOffset {
|
||||
/**
|
||||
* Watch element offset
|
||||
*
|
||||
* @paramel - Element
|
||||
* @param options - Options
|
||||
* @param el - Element
|
||||
* @param agent - Agent
|
||||
*
|
||||
* @return Element offset observable
|
||||
*/
|
||||
export function watchElementOffset(
|
||||
el: HTMLElement, { size$ }: Options
|
||||
el: HTMLElement, { viewport }: Agent
|
||||
): Observable<ElementOffset> {
|
||||
const scroll$ = fromEvent(el, "scroll")
|
||||
return merge(scroll$, size$)
|
||||
return merge(scroll$, viewport.size$)
|
||||
.pipe(
|
||||
map(() => getElementOffset(el)),
|
||||
startWith(getElementOffset(el)),
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
export * from "./_"
|
||||
export * from "./document"
|
||||
export * from "./element"
|
||||
export * from "./location"
|
||||
|
||||
@@ -60,11 +60,11 @@ export function watchLocation(): Subject<string> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch location fragment
|
||||
* Watch location hash
|
||||
*
|
||||
* @return Location fragment observable
|
||||
* @return Location hash observable
|
||||
*/
|
||||
export function watchLocationFragment(): Observable<string> {
|
||||
export function watchLocationHash(): Observable<string> {
|
||||
return hashchange$
|
||||
.pipe(
|
||||
map(() => location.hash),
|
||||
|
||||
@@ -30,7 +30,8 @@ import {
|
||||
} from "rxjs/operators"
|
||||
|
||||
import { resetHidden, setHidden } from "actions"
|
||||
import { ViewportOffset } from "utilities"
|
||||
|
||||
import { ViewportOffset } from "../agent"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
@@ -21,5 +21,6 @@
|
||||
*/
|
||||
|
||||
export * from "./agent"
|
||||
export * from "./hidden"
|
||||
export * from "./string"
|
||||
export * from "./toggle"
|
||||
|
||||
Reference in New Issue
Block a user