diff --git a/src/assets/javascripts/component/sidebar/index.ts b/src/assets/javascripts/component/sidebar/index.ts index 28a08ba2f..e46b5411a 100644 --- a/src/assets/javascripts/component/sidebar/index.ts +++ b/src/assets/javascripts/component/sidebar/index.ts @@ -39,7 +39,7 @@ import { Container } from "../container" /** * Sidebar state */ -interface Sidebar { +export interface Sidebar { height: number /* Sidebar height */ lock: boolean /* Sidebar lock */ } @@ -137,6 +137,6 @@ export function fromSidebar( setSidebarLock(sidebar, lock) }) - /* Return sidebar observable */ + /* Return observable */ return sidebar$ } diff --git a/src/assets/javascripts/index.ts b/src/assets/javascripts/index.ts index 7b5defdef..82579f40b 100644 --- a/src/assets/javascripts/index.ts +++ b/src/assets/javascripts/index.ts @@ -20,247 +20,49 @@ * IN THE SOFTWARE. */ -import { equals, reduce } from "rambda" -import { BehaviorSubject, combineLatest, fromEvent, merge, of } from "rxjs" import { - distinctUntilChanged, - filter, - map, - mapTo, - pluck, - startWith, - switchMapTo -} from "rxjs/operators" - + fromContainer, + fromSidebar +} from "./component" import { fromMediaQuery, - watchViewportOffset, - watchViewportSize -} from "./viewport" + fromViewportOffset, + fromViewportSize, + getElement, +} from "./ui" // ---------------------------------------------------------------------------- -const screen$ = fromMediaQuery("(min-width: 1220px)") -const offset$ = watchViewportOffset() -const size$ = watchViewportSize() +const offset$ = fromViewportOffset() +const size$ = fromViewportSize() -const hash$ = fromEvent(window, "hashchange").pipe( - startWith(document.location.hash), - map(() => document.location.hash), - filter(hash => hash.length > 0) -) +const screenAndAbove$ = fromMediaQuery("(min-width: 1220px)") +const tabletAndAbove$ = fromMediaQuery("(min-width: 960px)") // ---------------------------------------------------------------------------- -const component$ = of({ - container: document.querySelector("[data-md-component=container]") as HTMLElement, - navigation: document.querySelector("[data-md-component=navigation]") as HTMLElement, - header: document.querySelector("[data-md-component=header]") as HTMLElement, - title: document.querySelector("[data-md-component=title]") as HTMLElement, - toc: document.querySelector("[data-md-component=toc]") as HTMLElement, // TODO: !? - headline: document.querySelector(".md-typeset h1") as HTMLElement -}) - -// ---------------------------------------------------------------------------- - -// helper function -function toArray(collection: HTMLCollection): HTMLElement[] { - return Array.from(collection) as HTMLElement[] -} - +// modernizr for the poor document.documentElement.classList.remove("no-js") document.documentElement.classList.add("js") -// we need TWO observables. first, if it's a resize, we need to set up stuff anew -// bind size and offset to animationFrame !!! -// TODO: combine latest with element!? navigation -> el - // ---------------------------------------------------------------------------- // sidebar lock + height // ---------------------------------------------------------------------------- -// Sidebar { offset, height, locked } +const container = getElement("[data-md-component=container]")! +const header = getElement("[data-md-component=header]")! -const sidebarOffset$ = size$.pipe( - switchMapTo(component$), - map(({ header, navigation }) => { - return -1 * (header.offsetParent === null ? header.offsetHeight : 0) + - reduce((offset, child) => { - return Math.max(offset, child.offsetTop) - }, 0, toArray(navigation.parentElement!.children)) - }), - distinctUntilChanged(equals) -) +const container$ = fromContainer(container, header, { size$, offset$ }) -const sidebarHeight$ = combineLatest(offset$, size$, component$, sidebarOffset$).pipe( - map(([{ y }, { height }, { header, navigation }, offset]) => { - const parent = navigation.parentElement as HTMLElement - return height - ( - header.offsetParent === null ? header.offsetHeight : 0 - ) - Math.max(0, offset - y) - - Math.max(0, y + height - parent.offsetTop - parent.offsetHeight) - }) -) +// --- -const sidebarActive$ = combineLatest(offset$, sidebarOffset$).pipe( - map(([{ y }, threshold]) => y >= threshold), - distinctUntilChanged(equals) -) +const nav = getElement("[data-md-component=navigation")! +const nav$ = fromSidebar(nav, { container$, toggle$: screenAndAbove$ }) + +const toc = getElement("[data-md-component=toc")! +const toc$ = fromSidebar(toc, { container$, toggle$: tabletAndAbove$ }) // ---------------------------------------------------------------------------- -combineLatest(component$, sidebarActive$) - .subscribe(([{ navigation }, active]) => { - navigation.dataset.mdState = active ? "lock" : "" - }) - -combineLatest(component$, sidebarHeight$) - .subscribe(([{ navigation }, height]) => { - navigation.style.height = `${height}px` - }) - -// ---------------------------------------------------------------------------- - -// re-use calculation for toc, if present! -combineLatest(component$, sidebarActive$).pipe( - filter(([{ toc }]) => Boolean(toc)) -) - .subscribe(([{ toc }, active]) => { - toc.dataset.mdState = active ? "lock" : "" - }) - -combineLatest(component$, sidebarHeight$).pipe( - filter(([{ toc }]) => Boolean(toc)) -) - .subscribe(([{ toc }, height]) => { - toc.style.height = `${height}px` - }) - -// ---------------------------------------------------------------------------- -// header shadow -// ---------------------------------------------------------------------------- - -// Shadow { offset, locked } - -const shadowOffset$ = size$.pipe( - switchMapTo(component$), - map(({ container }) => { - let parent = container.parentElement as HTMLElement - let height = 0 - do { - parent = parent.previousElementSibling as HTMLElement // toElement -> throw if not! - height += parent.offsetHeight - } while (parent.previousElementSibling) - return height - }) -) - -const shadowActive$ = combineLatest(offset$, shadowOffset$).pipe( - map(([{ y }, threshold]) => y >= threshold), - distinctUntilChanged(equals) -) - -// ---------------------------------------------------------------------------- - -combineLatest(component$, shadowActive$) - .subscribe(([{ header }, active]) => { - header.dataset.mdState = active ? "shadow" : "" - }) - -// ---------------------------------------------------------------------------- -// header title swap -// ---------------------------------------------------------------------------- - -const headlineOffset$ = size$.pipe( - switchMapTo(component$), - map(({ headline }) => headline.offsetTop), - distinctUntilChanged(equals) -) - -const headlineWidth$ = size$.pipe( - switchMapTo(component$), - map(({ title }) => title.offsetWidth - 20), - distinctUntilChanged(equals) -) - -const headlineActive$ = combineLatest(offset$, headlineOffset$).pipe( - map(([{ y }, threshold]) => y >= threshold), - distinctUntilChanged(equals) -) - -// ---------------------------------------------------------------------------- - -combineLatest(component$, headlineActive$) - .subscribe(([{ title }, active]) => { - title.dataset.mdState = active ? "active" : "" - }) - -combineLatest(component$, headlineWidth$) - .subscribe(([{ title }, width]) => { - for (const child of toArray(title.children)) - child.style.width = `${width}px` - }) - -// ---------------------------------------------------------------------------- -// open details on hashchange -// ---------------------------------------------------------------------------- - -hash$.pipe( - map(hash => document.querySelector(hash) as HTMLElement), // TODO: write a getElement function... - filter(Boolean) -) - .subscribe(el => { - let parent = el.parentNode - while (parent && !(parent instanceof HTMLDetailsElement)) - parent = parent.parentNode - - /* If there's a details tag, open it */ - if (parent && !parent.open) { - parent.open = true - - /* Force reload, so the viewport repositions */ - const hash = location.hash - location.hash = " " - location.hash = hash // tslint:disable-line - } - }) - -// size$.pipe( -// switchMapTo(component$), -// map(({ title }) => { -// title.children -// } - -// just combine all header observables into one! - -// headerLock$.subscribe(console.log) -// headerHeight$.subscribe(console.log) -// TODO just tap both? toc and nav? - -// // first, calculate top offset -// size$.subscribe(size => { -// const children = Array.from(navigation.parentElement!.children) as HTMLElement[] // TODO - -// const top = children.reduce((offset, child) => Math.max(offset, child.offsetTop), 0) - -// const offset = top - (adjust ? header.offsetHeight : 0) - -// }) - -// const top = Array.from(navigation.parentElement!.children) -// .reduce() - -// this.parent_.children, (offset, child) => { -// return Math.max(offset, child.offsetTop) -// }, 0) - -/* Set lock offset for element with largest top offset */ -// this.offset_ = top - (this.pad_ ? this.header_.offsetHeight : 0) - -// offset$.subscribe(offset => { -// console.log(offset) -// }) - -export function app(_x: any) { - +export function app(config: any) { } diff --git a/tsconfig.json b/tsconfig.json index b4dac7f3c..24589456d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "baseUrl": ".", "declaration": false, "declarationMap": false, + "downlevelIteration": true, "lib": [ "dom", "es2017",