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.map": "assets/javascripts/bundle.4a3df536.min.js.map",
"assets/javascripts/bundle.js": "assets/javascripts/bundle.7cbaf05d.min.js",
"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.map": "assets/javascripts/worker/packer.c14659e8.min.js.map",
"assets/javascripts/worker/search.js": "assets/javascripts/worker/search.0a5433f7.min.js",

View File

@ -190,7 +190,7 @@
{% endblock %}
</div>
{% 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">
{%- set translations = {} -%}
{%- for key in [

View File

@ -24,6 +24,31 @@
* 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
*

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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