Added support for variable sized header

This commit is contained in:
squidfunk 2020-02-19 14:19:11 +01:00
parent a6bc272778
commit b0ebcc8d5b
14 changed files with 102 additions and 39 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{ {
"assets/javascripts/bundle.js": "assets/javascripts/bundle.4a3df536.min.js", "assets/javascripts/bundle.js": "assets/javascripts/bundle.7cbaf05d.min.js",
"assets/javascripts/bundle.js.map": "assets/javascripts/bundle.4a3df536.min.js.map", "assets/javascripts/bundle.js.map": "assets/javascripts/bundle.7cbaf05d.min.js.map",
"assets/javascripts/worker/packer.js": "assets/javascripts/worker/packer.c14659e8.min.js", "assets/javascripts/worker/packer.js": "assets/javascripts/worker/packer.c14659e8.min.js",
"assets/javascripts/worker/packer.js.map": "assets/javascripts/worker/packer.c14659e8.min.js.map", "assets/javascripts/worker/packer.js.map": "assets/javascripts/worker/packer.c14659e8.min.js.map",
"assets/javascripts/worker/search.js": "assets/javascripts/worker/search.0a5433f7.min.js", "assets/javascripts/worker/search.js": "assets/javascripts/worker/search.0a5433f7.min.js",

View File

@ -190,7 +190,7 @@
{% endblock %} {% endblock %}
</div> </div>
{% block scripts %} {% block scripts %}
<script src="{{ 'assets/javascripts/bundle.4a3df536.min.js' | url }}"></script> <script src="{{ 'assets/javascripts/bundle.7cbaf05d.min.js' | url }}"></script>
<script id="__lang" type="application/json"> <script id="__lang" type="application/json">
{%- set translations = {} -%} {%- set translations = {} -%}
{%- for key in [ {%- for key in [

View File

@ -24,6 +24,31 @@
* Functions * Functions
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/**
* Set sidebar offset
*
* @param el - Sidebar element
* @param value - Sidebar offset
*/
export function setSidebarOffset(
el: HTMLElement, value: number
): void {
el.style.top = `${value}px`
}
/**
* Reset sidebar offset
*
* @param el - Sidebar element
*/
export function resetSidebarOffset(
el: HTMLElement
): void {
el.style.top = ""
}
/* ------------------------------------------------------------------------- */
/** /**
* Set sidebar height * Set sidebar height
* *

View File

@ -24,6 +24,7 @@ import { Observable, OperatorFunction, pipe } from "rxjs"
import { map, shareReplay, switchMap } from "rxjs/operators" import { map, shareReplay, switchMap } from "rxjs/operators"
import { import {
Header,
Main, Main,
NavigationLayer, NavigationLayer,
Sidebar, Sidebar,
@ -70,6 +71,7 @@ export type Navigation =
* Mount options * Mount options
*/ */
interface MountOptions { interface MountOptions {
header$: Observable<Header> /* Header observable */
main$: Observable<Main> /* Main area observable */ main$: Observable<Main> /* Main area observable */
viewport$: Observable<Viewport> /* Viewport observable */ viewport$: Observable<Viewport> /* Viewport observable */
screen$: Observable<boolean> /* Screen media observable */ screen$: Observable<boolean> /* Screen media observable */
@ -87,7 +89,7 @@ interface MountOptions {
* @return Operator function * @return Operator function
*/ */
export function mountNavigation( export function mountNavigation(
{ main$, viewport$, screen$ }: MountOptions { header$, main$, viewport$, screen$ }: MountOptions
): OperatorFunction<HTMLElement, Navigation> { ): OperatorFunction<HTMLElement, Navigation> {
return pipe( return pipe(
switchMap(el => screen$ switchMap(el => screen$
@ -98,7 +100,7 @@ export function mountNavigation(
if (screen) { if (screen) {
return watchSidebar(el, { main$, viewport$ }) return watchSidebar(el, { main$, viewport$ })
.pipe( .pipe(
paintSidebar(el), paintSidebar(el, { header$ }),
map(sidebar => ({ sidebar })) map(sidebar => ({ sidebar }))
) )

View File

@ -108,7 +108,7 @@ export function mountTableOfContents(
/* Watch and paint sidebar */ /* Watch and paint sidebar */
const sidebar$ = watchSidebar(el, { main$, viewport$ }) const sidebar$ = watchSidebar(el, { main$, viewport$ })
.pipe( .pipe(
paintSidebar(el) paintSidebar(el, { header$ })
) )
/* Watch and paint anchor list (scroll spy) */ /* Watch and paint anchor list (scroll spy) */

View File

@ -161,7 +161,7 @@ export function initialize(config: unknown) {
const navigation$ = useComponent("navigation") const navigation$ = useComponent("navigation")
.pipe( .pipe(
mountNavigation({ main$, viewport$, screen$ }) mountNavigation({ header$, main$, viewport$, screen$ })
) )
const toc$ = useComponent("toc") const toc$ = useComponent("toc")

View File

@ -88,9 +88,9 @@ export function watchViewport(): Observable<Viewport> {
export function watchViewportAt( export function watchViewportAt(
el: HTMLElement, { header$, viewport$ }: WatchRelativeOptions el: HTMLElement, { header$, viewport$ }: WatchRelativeOptions
): Observable<Viewport> { ): Observable<Viewport> {
return combineLatest([viewport$, header$]) return combineLatest([header$, viewport$])
.pipe( .pipe(
map(([{ offset, size }, { height }]) => ({ map(([{ height }, { offset, size }]) => ({
offset: { offset: {
x: offset.x - el.offsetLeft, x: offset.x - el.offsetLeft,
y: offset.y - el.offsetTop + height y: offset.y - el.offsetTop + height

View File

@ -149,9 +149,9 @@ export function watchAnchorList(
}), }),
/* Re-compute partition when viewport offset changes */ /* Re-compute partition when viewport offset changes */
switchMap(index => combineLatest(viewport$, adjust$) switchMap(index => combineLatest(adjust$, viewport$)
.pipe( .pipe(
scan(([prev, next], [{ offset: { y } }, adjust]) => { scan(([prev, next], [adjust, { offset: { y } }]) => {
/* Look forward */ /* Look forward */
while (next.length) { while (next.length) {

View File

@ -83,29 +83,30 @@ export function watchMain(
) )
/* Compute the main area's visible height */ /* Compute the main area's visible height */
const height$ = combineLatest([viewport$, adjust$]) const height$ = combineLatest([adjust$, viewport$])
.pipe( .pipe(
map(([{ offset: { y }, size: { height } }, adjust]) => { map(([adjust, { offset: { y }, size: { height } }]) => {
const top = el.offsetTop const top = el.offsetTop
const bottom = el.offsetHeight + top const bottom = el.offsetHeight + top
return height return height
- Math.max(0, top - y, adjust) - Math.max(0, top - y, adjust)
- Math.max(0, height + y - bottom) - Math.max(0, height + y - bottom)
}), }),
map(height => Math.max(0, height)),
distinctUntilChanged() distinctUntilChanged()
) )
/* Compute whether the viewport offset is past the main area's top */ /* Compute whether the viewport offset is past the main area's top */
const active$ = combineLatest([viewport$, adjust$]) const active$ = combineLatest([adjust$, viewport$])
.pipe( .pipe(
map(([{ offset: { y } }, adjust]) => y >= el.offsetTop - adjust), map(([adjust, { offset: { y } }]) => y >= el.offsetTop - adjust),
distinctUntilChanged() distinctUntilChanged()
) )
/* Combine into a single hot observable */ /* Combine into a single hot observable */
return combineLatest([height$, adjust$, active$]) return combineLatest([adjust$, height$, active$])
.pipe( .pipe(
map(([height, adjust, active]) => ({ map(([adjust, height, active]) => ({
offset: el.offsetTop - adjust, offset: el.offsetTop - adjust,
height, height,
active active

View File

@ -20,7 +20,6 @@
* IN THE SOFTWARE. * IN THE SOFTWARE.
*/ */
import { equals } from "ramda"
import { import {
MonoTypeOperatorFunction, MonoTypeOperatorFunction,
Observable, Observable,
@ -30,21 +29,26 @@ import {
} from "rxjs" } from "rxjs"
import { import {
distinctUntilChanged, distinctUntilChanged,
distinctUntilKeyChanged,
finalize, finalize,
map, map,
observeOn, observeOn,
shareReplay, shareReplay,
tap tap,
withLatestFrom
} from "rxjs/operators" } from "rxjs/operators"
import { import {
resetSidebarHeight, resetSidebarHeight,
resetSidebarLock, resetSidebarLock,
resetSidebarOffset,
setSidebarHeight, setSidebarHeight,
setSidebarLock setSidebarLock,
setSidebarOffset
} from "actions" } from "actions"
import { Viewport } from "../../agent" import { Viewport } from "../../agent"
import { Header } from "../../header"
import { Main } from "../_" import { Main } from "../_"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
@ -71,6 +75,13 @@ interface WatchOptions {
viewport$: Observable<Viewport> /* Viewport observable */ viewport$: Observable<Viewport> /* Viewport observable */
} }
/**
* Paint options
*/
interface PaintOptions {
header$: Observable<Header> /* Header observable */
}
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Functions * Functions
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
@ -93,30 +104,42 @@ export function watchSidebar(
): Observable<Sidebar> { ): Observable<Sidebar> {
/* Adjust for internal main area offset */ /* Adjust for internal main area offset */
const adjust = parseFloat( const adjust$ = viewport$
.pipe(
distinctUntilKeyChanged("size"),
map(() => parseFloat(
getComputedStyle(el.parentElement!) getComputedStyle(el.parentElement!)
.getPropertyValue("padding-top") .getPropertyValue("padding-top")
)),
distinctUntilChanged()
) )
/* Compute the sidebar's available height */ /* Compute the sidebar's available height */
const height$ = combineLatest([viewport$, main$]) const height$ = viewport$
.pipe( .pipe(
map(([{ offset: { y } }, { offset, height }]) => { withLatestFrom(adjust$, main$),
return height - adjust + Math.min(adjust, Math.max(0, y - offset)) map(([{ offset: { y } }, adjust, { offset, height }]) => (
}) height
+ Math.min(adjust, Math.max(0, y - offset))
- adjust
)),
distinctUntilChanged()
) )
/* Compute whether the sidebar should be locked */ /* Compute whether the sidebar should be locked */
const lock$ = combineLatest([viewport$, main$]) const lock$ = viewport$
.pipe( .pipe(
map(([{ offset: { y } }, { offset }]) => y >= offset + adjust) withLatestFrom(adjust$, main$),
map(([{ offset: { y } }, adjust, { offset }]) => (
y >= offset + adjust
)),
distinctUntilChanged()
) )
/* Combine into single hot observable */ /* Combine into single hot observable */
return combineLatest([height$, lock$]) return combineLatest([height$, lock$])
.pipe( .pipe(
map(([height, lock]) => ({ height, lock })), map(([height, lock]) => ({ height, lock })),
distinctUntilChanged<Sidebar>(equals),
shareReplay(1) shareReplay(1)
) )
} }
@ -127,23 +150,35 @@ export function watchSidebar(
* Paint sidebar * Paint sidebar
* *
* @param el - Sidebar element * @param el - Sidebar element
* @param options - Options
* *
* @return Operator function * @return Operator function
*/ */
export function paintSidebar( export function paintSidebar(
el: HTMLElement el: HTMLElement, { header$ }: PaintOptions
): MonoTypeOperatorFunction<Sidebar> { ): MonoTypeOperatorFunction<Sidebar> {
return pipe( return pipe(
/* Defer repaint to next animation frame */ /* Defer repaint to next animation frame */
observeOn(animationFrameScheduler), observeOn(animationFrameScheduler),
tap(({ height, lock }) => { withLatestFrom(header$),
tap(([{ height, lock }, { height: offset }]) => {
setSidebarHeight(el, height) setSidebarHeight(el, height)
setSidebarLock(el, lock) setSidebarLock(el, lock)
/* Set offset in locked state depending on header height */
if (lock)
setSidebarOffset(el, offset)
else
resetSidebarOffset(el)
}), }),
/* Re-map to sidebar */
map(([sidebar]) => sidebar),
/* Reset on complete or error */ /* Reset on complete or error */
finalize(() => { finalize(() => {
resetSidebarOffset(el)
resetSidebarHeight(el) resetSidebarHeight(el)
resetSidebarLock(el) resetSidebarLock(el)
}) })

View File

@ -90,9 +90,9 @@ export function watchNavigationLayer(
} }
/* Determine topmost layer */ /* Determine topmost layer */
const layer$ = merge( const layer$ = merge(...[...table.keys()].map(input => (
...[...table.keys()].map(input => fromEvent(input, "change")) fromEvent(input, "change")
) )))
.pipe( .pipe(
map(() => getElementOrThrow(".md-nav__list", table.get( map(() => getElementOrThrow(".md-nav__list", table.get(
findLast(({ checked }) => checked, [...table.keys()])! findLast(({ checked }) => checked, [...table.keys()])!