Improved overall structure

This commit is contained in:
squidfunk 2020-02-20 14:44:41 +01:00
parent 9b0410962d
commit 3aa251fb03
26 changed files with 241 additions and 224 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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.02fd1bf7.min.js", "assets/javascripts/bundle.js": "assets/javascripts/bundle.4cda9c77.min.js",
"assets/javascripts/bundle.js.map": "assets/javascripts/bundle.02fd1bf7.min.js.map", "assets/javascripts/bundle.js.map": "assets/javascripts/bundle.4cda9c77.min.js.map",
"assets/javascripts/worker/search.js": "assets/javascripts/worker/search.926ffd9e.min.js", "assets/javascripts/worker/search.js": "assets/javascripts/worker/search.926ffd9e.min.js",
"assets/javascripts/worker/search.js.map": "assets/javascripts/worker/search.926ffd9e.min.js.map", "assets/javascripts/worker/search.js.map": "assets/javascripts/worker/search.926ffd9e.min.js.map",
"assets/stylesheets/app-palette.scss": "assets/stylesheets/app-palette.3f90c815.min.css", "assets/stylesheets/app-palette.scss": "assets/stylesheets/app-palette.3f90c815.min.css",

View File

@ -190,7 +190,7 @@
{% endblock %} {% endblock %}
</div> </div>
{% block scripts %} {% block scripts %}
<script src="{{ 'assets/javascripts/bundle.02fd1bf7.min.js' | url }}"></script> <script src="{{ 'assets/javascripts/bundle.4cda9c77.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

@ -20,9 +20,9 @@
* IN THE SOFTWARE. * IN THE SOFTWARE.
*/ */
import { NEVER, Observable, OperatorFunction, pipe } from "rxjs" import { Observable, OperatorFunction, pipe } from "rxjs"
import { import {
catchError, filter,
map, map,
shareReplay, shareReplay,
switchMap switchMap
@ -31,7 +31,7 @@ import {
import { import {
Header, Header,
Viewport, Viewport,
getElementOrThrow, getElement,
paintHeaderTitle, paintHeaderTitle,
watchViewportAt watchViewportAt
} from "observables" } from "observables"
@ -67,15 +67,15 @@ export function mountHeaderTitle(
return pipe( return pipe(
switchMap(el => useComponent("main") switchMap(el => useComponent("main")
.pipe( .pipe(
map(main => getElementOrThrow("h1, h2, h3, h4, h5, h6", main)), map(main => getElement("h1, h2, h3, h4, h5, h6", main)!),
switchMap(headline => watchViewportAt(headline, { header$, viewport$ }) filter(hx => typeof hx !== "undefined"),
switchMap(hx => watchViewportAt(hx, { header$, viewport$ })
.pipe( .pipe(
map(({ offset: { y } }) => y >= headline.offsetHeight), map(({ offset: { y } }) => y >= hx.offsetHeight),
paintHeaderTitle(el) paintHeaderTitle(el)
) )
), ),
shareReplay(1), shareReplay(1)
catchError(() => NEVER)
) )
) )
) )

View File

@ -20,17 +20,11 @@
* IN THE SOFTWARE. * IN THE SOFTWARE.
*/ */
import { OperatorFunction, combineLatest, pipe } from "rxjs" import { Observable, OperatorFunction, combineLatest, pipe } from "rxjs"
import { map, shareReplay, switchMap } from "rxjs/operators" import { map, shareReplay, switchMap } from "rxjs/operators"
import { SearchResult } from "integrations/search" import { SearchResult } from "integrations/search"
import { SearchQuery, WorkerHandler } from "observables" import { SearchQuery } from "observables"
import { SearchMessage } from "workers"
import { useComponent } from "../../_"
import { mountSearchQuery } from "../query"
import { mountSearchReset } from "../reset"
import { mountSearchResult } from "../result"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Types * Types
@ -44,6 +38,19 @@ export interface Search {
result: SearchResult[] /* Search result list */ result: SearchResult[] /* Search result list */
} }
/* ----------------------------------------------------------------------------
* Helper types
* ------------------------------------------------------------------------- */
/**
* Mount options
*/
interface MountOptions {
query$: Observable<SearchQuery> /* Search query observable */
reset$: Observable<void> /* Search reset observable */
result$: Observable<SearchResult[]> /* Search result observable */
}
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Functions * Functions
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
@ -51,42 +58,18 @@ export interface Search {
/** /**
* Mount search from source observable * Mount search from source observable
* *
* @param handler - Worker handler
* @param options - Options * @param options - Options
* *
* @return Search observable * @return Search observable
*/ */
export function mountSearch( export function mountSearch(
handler: WorkerHandler<SearchMessage> { query$, reset$, result$ }: MountOptions
): OperatorFunction<HTMLElement, Search> { ): OperatorFunction<HTMLElement, Search> {
return pipe( return pipe(
switchMap(() => { switchMap(() => combineLatest([query$, result$, reset$])
.pipe(
/* Mount search query */ map(([query, result]) => ({ query, result })),
const query$ = useComponent<HTMLInputElement>("search-query") shareReplay(1)
.pipe( ))
mountSearchQuery(handler),
shareReplay(1)
)
/* Mount search reset */
const reset$ = useComponent("search-reset")
.pipe(
mountSearchReset()
)
/* Mount search result */
const result$ = useComponent("search-result")
.pipe(
mountSearchResult(handler, { query$ })
)
/* Combine into a single hot observable */
return combineLatest([query$, result$, reset$])
.pipe(
map(([query, result]) => ({ query, result })),
shareReplay(1)
)
})
) )
} }

View File

@ -41,6 +41,17 @@ import {
SearchQueryMessage SearchQueryMessage
} from "workers" } from "workers"
/* ----------------------------------------------------------------------------
* Helper types
* ------------------------------------------------------------------------- */
/**
* Mount options
*/
interface MountOptions {
transform?(value: string): string /* Transformation function */
}
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Functions * Functions
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
@ -49,16 +60,17 @@ import {
* Mount search query from source observable * Mount search query from source observable
* *
* @param handler - Worker handler * @param handler - Worker handler
* @param options - Options
* *
* @return Operator function * @return Operator function
*/ */
export function mountSearchQuery( export function mountSearchQuery(
{ tx$ }: WorkerHandler<SearchMessage> { tx$ }: WorkerHandler<SearchMessage>, options: MountOptions = {}
): OperatorFunction<HTMLInputElement, SearchQuery> { ): OperatorFunction<HTMLInputElement, SearchQuery> {
const toggle$ = useToggle("search") const toggle$ = useToggle("search")
return pipe( return pipe(
switchMap(el => { switchMap(el => {
const query$ = watchSearchQuery(el) const query$ = watchSearchQuery(el, options)
/* Subscribe worker to search query */ /* Subscribe worker to search query */
query$ query$

View File

@ -29,7 +29,6 @@ import "../stylesheets/app-palette.scss"
import { values } from "ramda" import { values } from "ramda"
import { import {
merge, merge,
of,
combineLatest, combineLatest,
animationFrameScheduler animationFrameScheduler
} from "rxjs" } from "rxjs"
@ -40,7 +39,9 @@ import {
filter, filter,
withLatestFrom, withLatestFrom,
observeOn, observeOn,
take take,
mapTo,
shareReplay
} from "rxjs/operators" } from "rxjs/operators"
import { import {
@ -52,9 +53,9 @@ import {
watchLocation, watchLocation,
watchLocationHash, watchLocationHash,
watchViewport, watchViewport,
watchKeyboard,
watchToggleMap, watchToggleMap,
useToggle useToggle,
getElement
} from "./observables" } from "./observables"
import { setupSearchWorker } from "./workers" import { setupSearchWorker } from "./workers"
@ -69,7 +70,10 @@ import {
mountTabs, mountTabs,
useComponent, useComponent,
watchComponentMap, watchComponentMap,
mountHeaderTitle mountHeaderTitle,
mountSearchQuery,
mountSearchReset,
mountSearchResult
} from "components" } from "components"
import { setupClipboard } from "./integrations/clipboard" import { setupClipboard } from "./integrations/clipboard"
import { setupKeyboard } from "./integrations/keyboard" import { setupKeyboard } from "./integrations/keyboard"
@ -80,7 +84,7 @@ import {
patchSource patchSource
} from "patches" } from "patches"
import { isConfig } from "utilities" import { isConfig } from "utilities"
import { renderDialog } from "templates/dialog" import { setupDialog } from "integrations/dialog"
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
@ -153,11 +157,34 @@ export function initialize(config: unknown) {
/* ----------------------------------------------------------------------- */ /* ----------------------------------------------------------------------- */
/* Mount search query */
const query$ = useComponent<HTMLInputElement>("search-query")
.pipe(
mountSearchQuery(worker),
shareReplay(1) // TODO: this must be put onto EVERY component!
)
/* Mount search reset */
const reset$ = useComponent("search-reset")
.pipe(
mountSearchReset()
)
/* Mount search result */
const result$ = useComponent("search-result")
.pipe(
mountSearchResult(worker, { query$ })
)
/* ----------------------------------------------------------------------- */
const search$ = useComponent("search") const search$ = useComponent("search")
.pipe( .pipe(
mountSearch(worker) mountSearch({ query$, reset$, result$ })
) )
/* ----------------------------------------------------------------------- */
const navigation$ = useComponent("navigation") const navigation$ = useComponent("navigation")
.pipe( .pipe(
mountNavigation({ header$, main$, viewport$, screen$ }) mountNavigation({ header$, main$, viewport$, screen$ })
@ -178,7 +205,6 @@ export function initialize(config: unknown) {
mountHero({ header$, viewport$ }) mountHero({ header$, viewport$ })
) )
// TODO: make this part of mountHeader!?
const title$ = useComponent("header-title") const title$ = useComponent("header-title")
.pipe( .pipe(
mountHeaderTitle({ header$, viewport$ }) mountHeaderTitle({ header$, viewport$ })
@ -196,57 +222,38 @@ export function initialize(config: unknown) {
if (navigator.userAgent.match(/(iPad|iPhone|iPod)/g)) if (navigator.userAgent.match(/(iPad|iPhone|iPod)/g))
patchScrollfix({ document$ }) patchScrollfix({ document$ })
// snackbar for copy to clipboard /* Setup clipboard and dialog */
const dialog = renderDialog("Copied to Clipboard") const dialog$ = setupDialog()
setupClipboard({ document$ }) const clipboard$ = setupClipboard({ document$, dialog$ })
.pipe(
switchMap(ev => {
ev.clearSelection()
return useComponent("container")
.pipe(
tap(el => el.appendChild(dialog)), // only set text on dialog... render once...
observeOn(animationFrameScheduler),
tap(() => dialog.dataset.mdState = "open"),
delay(2000),
tap(() => dialog.dataset.mdState = ""),
delay(400),
tap(() => dialog.remove())
)
})
)
.subscribe()
/* ----------------------------------------------------------------------- */ /* ----------------------------------------------------------------------- */
// Close drawer and search on hash change // Close drawer and search on hash change
// put into navigation...
hash$.subscribe(x => { hash$.subscribe(x => {
useToggle("drawer").subscribe(el => { useToggle("drawer").subscribe(el => {
setToggle(el, false) setToggle(el, false)
}) })
}) })
// TODO: // put into search...
hash$ hash$
.pipe( .pipe(
switchMap(hash => { switchMap(hash => useToggle("search")
.pipe(
return useToggle("search") filter(x => x.checked), // only active
.pipe( tap(toggle => setToggle(toggle, false)),
filter(x => x.checked), // only active delay(125), // ensure that it runs after the body scroll reset...
tap(toggle => setToggle(toggle, false)), mapTo(hash)
delay(125), // ensure that it runs after the body scroll reset... )
tap(() => { )
location.hash = " "
location.hash = hash
}) // encapsulate this...
)
})
) )
.subscribe() .subscribe(hash => {
getElement(hash)!.scrollIntoView()
})
// Scroll lock // Scroll lock // document -> document$ => { body } !?
// put into search...
const toggle$ = useToggle("search") const toggle$ = useToggle("search")
combineLatest([ combineLatest([
toggle$.pipe(switchMap(watchToggle)), toggle$.pipe(switchMap(watchToggle)),
@ -256,13 +263,13 @@ export function initialize(config: unknown) {
withLatestFrom(viewport$), withLatestFrom(viewport$),
switchMap(([[toggle, tablet], { offset: { y }}]) => { switchMap(([[toggle, tablet], { offset: { y }}]) => {
const active = toggle && !tablet const active = toggle && !tablet
return of(document.body) return document$
.pipe( .pipe(
delay(active ? 400 : 100), delay(active ? 400 : 100), // TOOD: directly combine this with the hash!
observeOn(animationFrameScheduler), observeOn(animationFrameScheduler),
tap(el => active tap(({ body }) => active
? setScrollLock(el, y) ? setScrollLock(body, y)
: resetScrollLock(el) : resetScrollLock(body)
) )
) )
}) })
@ -283,18 +290,21 @@ export function initialize(config: unknown) {
link.style.visibility = "visible" link.style.visibility = "visible"
}) })
// build a notification component! feed txt into it...
/* ----------------------------------------------------------------------- */ /* ----------------------------------------------------------------------- */
const state = { const state = {
search$, search$,
clipboard$,
location$,
hash$,
keyboard$,
dialog$,
main$, main$,
navigation$, navigation$,
toc$, toc$,
tabs$, tabs$,
hero$, hero$,
title$ title$ // TODO: header title
} }
const { ...rest } = state const { ...rest } = state

View File

@ -21,11 +21,12 @@
*/ */
import * as ClipboardJS from "clipboard" import * as ClipboardJS from "clipboard"
import { NEVER, Observable, fromEventPattern } from "rxjs" import { NEVER, Observable, Subject, fromEventPattern } from "rxjs"
import { shareReplay } from "rxjs/operators" import { mapTo, shareReplay, tap } from "rxjs/operators"
import { getElements } from "observables" import { getElements } from "observables"
import { renderClipboard } from "templates" import { renderClipboard } from "templates"
import { translate } from "utilities"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Helper types * Helper types
@ -36,6 +37,7 @@ import { renderClipboard } from "templates"
*/ */
interface SetupOptions { interface SetupOptions {
document$: Observable<Document> /* Document observable */ document$: Observable<Document> /* Document observable */
dialog$: Subject<string> /* Dialog subject */
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
@ -53,7 +55,7 @@ interface SetupOptions {
* @return Clipboard observable * @return Clipboard observable
*/ */
export function setupClipboard( export function setupClipboard(
{ document$ }: SetupOptions { document$, dialog$ }: SetupOptions
): Observable<ClipboardJS.Event> { ): Observable<ClipboardJS.Event> {
if (!ClipboardJS.isSupported()) if (!ClipboardJS.isSupported())
return NEVER return NEVER
@ -74,9 +76,15 @@ export function setupClipboard(
new ClipboardJS(".md-clipboard").on("success", next) new ClipboardJS(".md-clipboard").on("success", next)
}) })
// TODO: integrate rendering of dialog /* Display notification upon clipboard copy */
clipboard$
.pipe(
tap(ev => ev.clearSelection()),
mapTo(translate("clipboard.copied"))
)
.subscribe(dialog$)
/* */ /* Return clipboard as hot observable */
return clipboard$ return clipboard$
.pipe( .pipe(
shareReplay(1) shareReplay(1)

View File

@ -20,17 +20,26 @@
* IN THE SOFTWARE. * IN THE SOFTWARE.
*/ */
import { h } from "utilities" import { Subject, animationFrameScheduler } from "rxjs"
import {
delay,
map,
observeOn,
switchMap,
tap
} from "rxjs/operators"
import { useComponent } from "components"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Data * Types
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/** /**
* CSS classes * Setup options
*/ */
const css = { interface SetupOptions {
container: "md-dialog md-typeset" duration?: number /* Display duration (default: 2s) */
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
@ -38,18 +47,44 @@ const css = {
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/** /**
* Render a dismissable dialog * Setup dialog
* *
* @param text - Dialog text * @param options - Options
* *
* @return Element * @return Dialog observable
*/ */
export function renderDialog( export function setupDialog(
text: string { duration }: SetupOptions = {}
): HTMLElement { ): Subject<string> {
return ( const dialog$ = new Subject<string>()
<div class={css.container}>
{text} /* Create dialog */
</div> const dialog = document.createElement("div") // TODO: improve scoping
) dialog.classList.add("md-dialog", "md-typeset")
/* Display dialog */
dialog$
.pipe(
switchMap(text => useComponent("container")
.pipe(
map(container => container.appendChild(dialog)),
delay(1), // Strangley it doesnt work when we push things to the new animation frame...
tap(el => {
el.innerHTML = text
el.setAttribute("data-md-state", "open")
}),
delay(duration || 2000),
tap(el => el.removeAttribute("data-md-state")),
delay(400),
tap(el => {
el.innerHTML = ""
el.remove()
})
)
)
)
.subscribe()
/* Return dialog subject */
return dialog$
} }

View File

@ -198,6 +198,6 @@ export function setupKeyboard(): Observable<Keyboard> {
} }
}) })
/* Return keyboard observable */ /* Return keyboard */
return keyboard$ return keyboard$
} }

View File

@ -51,6 +51,6 @@ export function watchLocation(): Subject<string> {
) )
.subscribe(location$) .subscribe(location$)
/* Return subject */ /* Return location subject */
return location$ return location$
} }

View File

@ -21,7 +21,7 @@
*/ */
import { Observable, fromEvent } from "rxjs" import { Observable, fromEvent } from "rxjs"
import { filter, map, share } from "rxjs/operators" import { filter, map, share, startWith } from "rxjs/operators"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Functions * Functions
@ -36,22 +36,6 @@ export function getLocationHash(): string {
return location.hash return location.hash
} }
/**
* Set location hash
*
* This will force a reset of the location hash, inducing an anchor jump if
* the hash matches the `id` of an element. It is implemented outside of the
* whole RxJS architecture using `setTimeout` to keep it plain and simple.
*
* @param value - Location hash
*/
export function setLocationHash(value: string): void {
location.hash = ""
setTimeout(() => {
location.hash = value
}, 1)
}
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
/** /**
@ -63,6 +47,7 @@ export function watchLocationHash(): Observable<string> {
return fromEvent<HashChangeEvent>(window, "hashchange") return fromEvent<HashChangeEvent>(window, "hashchange")
.pipe( .pipe(
map(getLocationHash), map(getLocationHash),
startWith(getLocationHash()),
filter(hash => hash.length > 0), filter(hash => hash.length > 0),
share() share()
) )

View File

@ -20,8 +20,8 @@
* IN THE SOFTWARE. * IN THE SOFTWARE.
*/ */
import { Observable, combineLatest, fromEvent, merge } from "rxjs" import { Observable, fromEvent } from "rxjs"
import { map, shareReplay, startWith } from "rxjs/operators" import { map, startWith } from "rxjs/operators"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Types * Types

View File

@ -99,7 +99,7 @@ export function watchNavigationLayer(
))) )))
) )
/* Return previous and next layer */ /* Return previous and next layer as hot observable */
return layer$ return layer$
.pipe( .pipe(
map(next => ({ next })), map(next => ({ next })),

View File

@ -21,20 +21,17 @@
*/ */
import { identity } from "ramda" import { identity } from "ramda"
import { NEVER, Observable, fromEvent, merge, of } from "rxjs" import { Observable, fromEvent, merge } from "rxjs"
import { import {
catchError,
filter, filter,
map, map,
switchMap,
switchMapTo, switchMapTo,
tap
} from "rxjs/operators" } from "rxjs/operators"
import { import {
getElementOrThrow, getElement,
getElements, getElements,
getLocationHash,
setLocationHash,
watchMedia watchMedia
} from "observables" } from "observables"
@ -72,8 +69,8 @@ export function patchDetails(
/* Open all details before printing */ /* Open all details before printing */
merge( merge(
watchMedia("print").pipe(filter(identity)), // Webkit watchMedia("print").pipe(filter(identity)), /* Webkit */
fromEvent(window, "beforeprint") // IE, FF fromEvent(window, "beforeprint") /* IE, FF */
) )
.pipe( .pipe(
switchMapTo(els$) switchMapTo(els$)
@ -83,20 +80,16 @@ export function patchDetails(
el.setAttribute("open", "") el.setAttribute("open", "")
}) })
/* Open parent details before anchor jump */ /* Open parent details and fix anchor jump */
merge(hash$, of(getLocationHash())) hash$
.pipe( .pipe(
filter(hash => !!hash.length), map(hash => getElement(hash)!),
switchMap(hash => of(getElementOrThrow<HTMLElement>(hash)) filter(el => typeof el !== "undefined"),
.pipe( tap(el => {
map(el => [el.closest("details")!, hash] as const), const details = el.closest("details")
filter(([el]) => el && !el.open) if (details && !details.open)
) details.setAttribute("open", "")
),
catchError(() => NEVER)
)
.subscribe(([el, hash]) => {
el.setAttribute("open", "")
setLocationHash(hash)
}) })
)
.subscribe(el => el.scrollIntoView())
} }

View File

@ -58,7 +58,10 @@ export function patchScrollfix(
.pipe( .pipe(
map(() => getElements("[data-md-scrollfix]")), map(() => getElements("[data-md-scrollfix]")),
switchMap(els => merge(...els.map(el => ( switchMap(els => merge(...els.map(el => (
fromEvent(el, "touchstart").pipe(mapTo(el)) fromEvent(el, "touchstart")
.pipe(
mapTo(el)
)
)))) ))))
) )
.subscribe(el => { .subscribe(el => {

View File

@ -23,12 +23,7 @@
import { Repo, User } from "github-types" import { Repo, User } from "github-types"
import { Observable, of } from "rxjs" import { Observable, of } from "rxjs"
import { ajax } from "rxjs/ajax" import { ajax } from "rxjs/ajax"
import { import { filter, pluck, switchMap } from "rxjs/operators"
filter,
pluck,
shareReplay,
switchMap
} from "rxjs/operators"
import { round } from "utilities" import { round } from "utilities"
@ -75,7 +70,6 @@ export function fetchSourceFactsFromGitHub(
`${round(public_repos || 0)} Repositories` `${round(public_repos || 0)} Repositories`
]) ])
} }
}), })
shareReplay(1)
) )
} }

View File

@ -23,12 +23,7 @@
import { ProjectSchema } from "gitlab" import { ProjectSchema } from "gitlab"
import { Observable } from "rxjs" import { Observable } from "rxjs"
import { ajax } from "rxjs/ajax" import { ajax } from "rxjs/ajax"
import { import { filter, map, pluck } from "rxjs/operators"
filter,
map,
pluck,
shareReplay
} from "rxjs/operators"
import { round } from "utilities" import { round } from "utilities"
@ -59,7 +54,6 @@ export function fetchSourceFactsFromGitLab(
map(({ star_count, forks_count }: ProjectSchema) => ([ map(({ star_count, forks_count }: ProjectSchema) => ([
`${round(star_count)} Stars`, `${round(star_count)} Stars`,
`${round(forks_count)} Forks` `${round(forks_count)} Forks`
])), ]))
shareReplay(1)
) )
} }

View File

@ -52,15 +52,15 @@ interface MountOptions {
export function patchTables( export function patchTables(
{ document$ }: MountOptions { document$ }: MountOptions
): void { ): void {
const placeholder = document.createElement("table") const sentinel = document.createElement("table")
document$ document$
.pipe( .pipe(
map(() => getElements<HTMLTableElement>("table:not([class])")) map(() => getElements<HTMLTableElement>("table:not([class])"))
) )
.subscribe(els => { .subscribe(els => {
for (const el of els) { for (const el of els) {
el.replaceWith(placeholder) el.replaceWith(sentinel)
placeholder.replaceWith(renderTable(el)) sentinel.replaceWith(renderTable(el))
} }
}) })
} }

View File

@ -21,7 +21,6 @@
*/ */
export * from "./clipboard" export * from "./clipboard"
export * from "./dialog"
export * from "./search" export * from "./search"
export * from "./source" export * from "./source"
export * from "./table" export * from "./table"

View File

@ -20,7 +20,7 @@
* IN THE SOFTWARE. * IN THE SOFTWARE.
*/ */
import { SourceFacts } from "integrations/source" import { SourceFacts } from "patches/source"
import { h } from "utilities" import { h } from "utilities"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
@ -49,7 +49,9 @@ const css = {
export function renderSource( export function renderSource(
facts: SourceFacts facts: SourceFacts
): HTMLElement { ): HTMLElement {
const children = facts.map(fact => <li class={css.fact}>{fact}</li>) const children = facts.map(fact => (
<li class={css.fact}>{fact}</li>
))
return ( return (
<ul class={css.facts}> <ul class={css.facts}>
{children} {children}

View File

@ -61,7 +61,7 @@ export function cache<T>(
} }
}) })
/* Return value observable */ /* Return value */
return value$ return value$
} }
}) })

View File

@ -81,14 +81,13 @@ export function truncate(value: string, n: number): string {
/** /**
* Round a number for display with source facts * Round a number for display with source facts
* *
* This is a reverse engineered implementation of GitHub's weird rounding * This is a reverse engineered version of GitHub's weird rounding algorithm
* algorithm for stars, forks and all other numbers. While all numbers below * for stars, forks and all other numbers. While all numbers below `1,000` are
* `1,000` are returned as-is, bigger numbers are converted to fixed numbers * returned as-is, bigger numbers are converted to fixed numbers:
* in the following way:
* *
* - `1,049` => `1k` * - `1,049` => `1k`
* - `1,050` => `1,1k` * - `1,050` => `1.1k`
* - `1,949` => `1,9k` * - `1,949` => `1.9k`
* - `1,950` => `2k` * - `1,950` => `2k`
* *
* @param value - Original value * @param value - Original value