mirror of
https://github.com/squidfunk/mkdocs-material.git
synced 2024-11-28 09:20:52 +01:00
Restructured observables
This commit is contained in:
parent
46ecf3b055
commit
d1afa51726
24
material/assets/javascripts/bundle.0e463231.min.js
vendored
Normal file
24
material/assets/javascripts/bundle.0e463231.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
material/assets/javascripts/bundle.0e463231.min.js.map
Normal file
1
material/assets/javascripts/bundle.0e463231.min.js.map
Normal file
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
@ -1,6 +1,6 @@
|
||||
{
|
||||
"assets/javascripts/bundle.js": "assets/javascripts/bundle.f566fb0d.min.js",
|
||||
"assets/javascripts/bundle.js.map": "assets/javascripts/bundle.f566fb0d.min.js.map",
|
||||
"assets/javascripts/bundle.js": "assets/javascripts/bundle.0e463231.min.js",
|
||||
"assets/javascripts/bundle.js.map": "assets/javascripts/bundle.0e463231.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",
|
||||
|
@ -190,7 +190,7 @@
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% block scripts %}
|
||||
<script src="{{ 'assets/javascripts/bundle.f566fb0d.min.js' | url }}"></script>
|
||||
<script src="{{ 'assets/javascripts/bundle.0e463231.min.js' | url }}"></script>
|
||||
<script id="__lang" type="application/json">
|
||||
{%- set translations = {} -%}
|
||||
{%- for key in [
|
||||
|
@ -20,5 +20,5 @@
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
export * from "./_"
|
||||
export * from "./shadow"
|
||||
export * from "./title"
|
||||
|
@ -28,7 +28,6 @@ import "../stylesheets/app-palette.scss"
|
||||
|
||||
import { values } from "ramda"
|
||||
import {
|
||||
EMPTY,
|
||||
merge,
|
||||
of,
|
||||
combineLatest,
|
||||
@ -45,7 +44,6 @@ import {
|
||||
} from "rxjs/operators"
|
||||
|
||||
import {
|
||||
getElement,
|
||||
watchToggle,
|
||||
getElements,
|
||||
watchMedia,
|
||||
@ -60,7 +58,7 @@ import {
|
||||
mayReceiveKeyboardEvents
|
||||
} from "./observables"
|
||||
import { setupSearchWorker } from "./workers"
|
||||
import { renderSource } from "templates"
|
||||
|
||||
import { setToggle, setScrollLock, resetScrollLock } from "actions"
|
||||
import {
|
||||
mountHeader,
|
||||
@ -74,10 +72,10 @@ import {
|
||||
watchComponentMap,
|
||||
mountHeaderTitle
|
||||
} from "components"
|
||||
import { mountClipboard } from "./integrations/clipboard"
|
||||
import { setupClipboard } from "./integrations/clipboard"
|
||||
import { patchTables, patchDetails, patchScrollfix } from "patches"
|
||||
import { takeIf, not, isConfig } from "utilities"
|
||||
import { fetchSourceFacts } from "integrations/source"
|
||||
import { setupSourceFacts } from "integrations/source"
|
||||
import { renderDialog } from "templates/dialog"
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
@ -89,31 +87,6 @@ document.documentElement.classList.add("js")
|
||||
if (navigator.userAgent.match(/(iPad|iPhone|iPod)/g))
|
||||
document.documentElement.classList.add("ios")
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Yes, this is a super hacky implementation. Needs clean up.
|
||||
*/
|
||||
function repository() {
|
||||
const el = getElement<HTMLAnchorElement>(".md-source[href]") // TODO: dont use classes
|
||||
// console.log(el)
|
||||
if (!el)
|
||||
return EMPTY
|
||||
|
||||
const data = sessionStorage.getItem("repository")
|
||||
if (data) {
|
||||
const x = JSON.parse(data)
|
||||
return of(x)
|
||||
}
|
||||
|
||||
return fetchSourceFacts(el.href)
|
||||
.pipe(
|
||||
tap(data => sessionStorage.setItem("repository", JSON.stringify(data)))
|
||||
)
|
||||
}
|
||||
|
||||
// memoize
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
@ -214,7 +187,7 @@ export function initialize(config: unknown) {
|
||||
const dialog = renderDialog("Copied to Clipboard")
|
||||
|
||||
// snackbar for copy to clipboard
|
||||
mountClipboard({ document$ })
|
||||
setupClipboard({ document$ })
|
||||
.pipe(
|
||||
switchMap(ev => {
|
||||
ev.clearSelection()
|
||||
@ -235,7 +208,7 @@ export function initialize(config: unknown) {
|
||||
patchTables({ document$ })
|
||||
.subscribe()
|
||||
|
||||
patchDetails({ document$ })
|
||||
patchDetails({ document$, hash$ })
|
||||
.subscribe()
|
||||
|
||||
/* Force 1px scroll offset to trigger overflow scrolling */
|
||||
@ -244,6 +217,7 @@ export function initialize(config: unknown) {
|
||||
.subscribe()
|
||||
|
||||
|
||||
|
||||
// TODO: general keyboard handler...
|
||||
// put into main!?
|
||||
|
||||
@ -277,28 +251,6 @@ export function initialize(config: unknown) {
|
||||
)
|
||||
.subscribe()
|
||||
|
||||
// TODO: patch details!
|
||||
|
||||
/* Open details after anchor jump */
|
||||
merge(hash$, of(location.hash)) // getLocationHash
|
||||
.subscribe(hash => {
|
||||
const el = getElement(hash)
|
||||
console.log("jump to", hash)
|
||||
if (typeof el !== "undefined") {
|
||||
const parent = el.closest("details")
|
||||
if (parent && !parent.open) { // only if it is not open!
|
||||
parent.open = true
|
||||
|
||||
/* Hack: force reload for repositioning */ // TODO. what happens here!?
|
||||
location.hash = "" // reset
|
||||
requestAnimationFrame(() => {
|
||||
location.hash = hash // tslint:disable-line
|
||||
})
|
||||
// TODO: setLocationHash() + forceLocationHashChange
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Scroll lock
|
||||
const toggle$ = useToggle("search")
|
||||
combineLatest([
|
||||
@ -353,20 +305,9 @@ export function initialize(config: unknown) {
|
||||
|
||||
// build a notification component! feed txt into it...
|
||||
|
||||
/* ----------------------------------------------------------------------- */
|
||||
|
||||
// TODO: WIP repo rendering
|
||||
repository().subscribe(facts => {
|
||||
if (facts.length) {
|
||||
const sources = getElements(".md-source__repository")
|
||||
sources.forEach(repo => {
|
||||
repo.dataset.mdState = "done"
|
||||
repo.appendChild(
|
||||
renderSource(facts)
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
setupSourceFacts({ document$ })
|
||||
.subscribe()
|
||||
|
||||
/* ----------------------------------------------------------------------- */
|
||||
|
||||
|
@ -32,9 +32,9 @@ import { renderClipboard } from "templates"
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Mount options
|
||||
* Setup options
|
||||
*/
|
||||
interface MountOptions {
|
||||
interface SetupOptions {
|
||||
document$: Observable<Document> /* Document observable */
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ interface MountOptions {
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Mount clipboard
|
||||
* Setup clipboard
|
||||
*
|
||||
* This function implements the Clipboard.js integration and injects a button
|
||||
* into all code blocks when the document changes.
|
||||
@ -52,8 +52,8 @@ interface MountOptions {
|
||||
*
|
||||
* @return Clipboard observable
|
||||
*/
|
||||
export function mountClipboard(
|
||||
{ document$ }: MountOptions
|
||||
export function setupClipboard(
|
||||
{ document$ }: SetupOptions
|
||||
): Observable<ClipboardJS.Event> {
|
||||
if (ClipboardJS.isSupported()) {
|
||||
return document$
|
||||
@ -69,7 +69,7 @@ export function mountClipboard(
|
||||
}
|
||||
}),
|
||||
|
||||
/* Initialize and mount clipboard */
|
||||
/* Initialize and setup clipboard */
|
||||
switchMap(() => {
|
||||
return fromEventPattern<ClipboardJS.Event>(next => {
|
||||
const clipboard = new ClipboardJS(".md-clipboard")
|
||||
|
@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2020 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, of } from "rxjs"
|
||||
|
||||
import { fetchSourceFactsFromGitHub } from "../github"
|
||||
import { fetchSourceFactsFromGitLab } from "../gitlab"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Source facts
|
||||
*/
|
||||
export type SourceFacts = string[]
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Fetch source facts
|
||||
*
|
||||
* @param url - Source repository URL
|
||||
*
|
||||
* @return Source facts observable
|
||||
*/
|
||||
export function fetchSourceFacts(
|
||||
url: string
|
||||
): Observable<SourceFacts> {
|
||||
const [type] = url.match(/(git(?:hub|lab))/i) || []
|
||||
switch (type.toLowerCase()) {
|
||||
|
||||
/* GitHub repository */
|
||||
case "github":
|
||||
const [, user, repo] = url.match(/^.+github\.com\/([^\/]+)\/?([^\/]+)/i)
|
||||
return fetchSourceFactsFromGitHub(user, repo)
|
||||
|
||||
/* GitLab repository */
|
||||
case "gitlab":
|
||||
const [, base, project] = url.match(/^.+?([^\/]*gitlab[^\/]+)\/(.+)/i)
|
||||
return fetchSourceFactsFromGitLab(base, project)
|
||||
|
||||
/* Everything else */
|
||||
default:
|
||||
return of([])
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Round a number for display with source facts
|
||||
*
|
||||
* This is a reverse engineered implementation of GitHub's weird rounding
|
||||
* algorithm for stars, forks and all other numbers. Probably incorrect.
|
||||
*
|
||||
* @param value - Original value
|
||||
*
|
||||
* @return Rounded value
|
||||
*/
|
||||
export function roundSourceFactValue(value: number) {
|
||||
if (value > 999) {
|
||||
const digits = +((value - 950) % 1000 > 99)
|
||||
return `${((value + 1) / 1000).toFixed(digits)}k`
|
||||
} else {
|
||||
return value.toString()
|
||||
}
|
||||
}
|
@ -25,7 +25,9 @@ import { Observable, of } from "rxjs"
|
||||
import { ajax } from "rxjs/ajax"
|
||||
import { filter, pluck, shareReplay, switchMap } from "rxjs/operators"
|
||||
|
||||
import { SourceFacts, roundSourceFactValue } from "../_"
|
||||
import { round } from "utilities"
|
||||
|
||||
import { SourceFacts } from ".."
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
@ -57,15 +59,15 @@ export function fetchSourceFactsFromGitHub(
|
||||
if (typeof repo !== "undefined") {
|
||||
const { stargazers_count, forks_count }: Repo = data
|
||||
return of([
|
||||
`${roundSourceFactValue(stargazers_count || 0)} Stars`,
|
||||
`${roundSourceFactValue(forks_count || 0)} Forks`
|
||||
`${round(stargazers_count || 0)} Stars`,
|
||||
`${round(forks_count || 0)} Forks`
|
||||
])
|
||||
|
||||
/* GitHub user/organization */
|
||||
} else {
|
||||
const { public_repos }: User = data
|
||||
return of([
|
||||
`${roundSourceFactValue(public_repos || 0)} Repositories`
|
||||
`${round(public_repos || 0)} Repositories`
|
||||
])
|
||||
}
|
||||
}),
|
||||
|
@ -25,7 +25,9 @@ import { Observable } from "rxjs"
|
||||
import { ajax } from "rxjs/ajax"
|
||||
import { filter, map, pluck, shareReplay } from "rxjs/operators"
|
||||
|
||||
import { SourceFacts, roundSourceFactValue } from "../_"
|
||||
import { round } from "utilities"
|
||||
|
||||
import { SourceFacts } from ".."
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
@ -50,8 +52,8 @@ export function fetchSourceFactsFromGitLab(
|
||||
filter(({ status }) => status === 200),
|
||||
pluck("response"),
|
||||
map(({ star_count, forks_count }: ProjectSchema) => ([
|
||||
`${roundSourceFactValue(star_count || 0)} Stars`,
|
||||
`${roundSourceFactValue(forks_count || 0)} Forks`
|
||||
`${round(star_count)} Stars`,
|
||||
`${round(forks_count)} Forks`
|
||||
])),
|
||||
shareReplay(1)
|
||||
)
|
||||
|
@ -20,5 +20,128 @@
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
export * from "./_"
|
||||
export * from "./github"
|
||||
import { NEVER, Observable, of } from "rxjs"
|
||||
import { switchMap, tap } from "rxjs/operators"
|
||||
|
||||
import { getElement, getElements } from "observables"
|
||||
import { renderSource } from "templates"
|
||||
|
||||
import { cache } from "utilities"
|
||||
import { fetchSourceFactsFromGitHub } from "./github"
|
||||
import { fetchSourceFactsFromGitLab } from "./gitlab"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Source facts
|
||||
*/
|
||||
export type SourceFacts = string[]
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Helper types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Setup options
|
||||
*/
|
||||
interface SetupOptions {
|
||||
document$: Observable<Document> /* Document observable */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Helper functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Fetch source facts
|
||||
*
|
||||
* @param url - Source repository URL
|
||||
*
|
||||
* @return Source facts observable
|
||||
*/
|
||||
function fetchSourceFacts(
|
||||
url: string
|
||||
): Observable<SourceFacts> {
|
||||
const [type] = url.match(/(git(?:hub|lab))/i) || []
|
||||
switch (type.toLowerCase()) {
|
||||
|
||||
/* GitHub repository */
|
||||
case "github":
|
||||
const [, user, repo] = url.match(/^.+github\.com\/([^\/]+)\/?([^\/]+)/i)
|
||||
return fetchSourceFactsFromGitHub(user, repo)
|
||||
|
||||
/* GitLab repository */
|
||||
case "gitlab":
|
||||
const [, base, project] = url.match(/^.+?([^\/]*gitlab[^\/]+)\/(.+)/i)
|
||||
return fetchSourceFactsFromGitLab(base, project)
|
||||
|
||||
/* Everything else */
|
||||
default:
|
||||
return of([])
|
||||
}
|
||||
}
|
||||
|
||||
// provide a function to set a unique key?
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
// TODO: this doesnt belong here...
|
||||
|
||||
// cache type.. session or local storage...
|
||||
// subscribe to observable and cache automatically
|
||||
|
||||
// withCache or useCache
|
||||
|
||||
/**
|
||||
* Setup source facts
|
||||
*
|
||||
* @param options - Options
|
||||
*
|
||||
* @return Source facts observable
|
||||
*/
|
||||
export function setupSourceFacts(
|
||||
{ document$ }: SetupOptions
|
||||
): Observable<any> {
|
||||
return document$
|
||||
.pipe(
|
||||
switchMap(() => cache("repository", () => {
|
||||
tap(console.log)
|
||||
const el = getElement<HTMLAnchorElement>(".md-source[href]") // TODO: useElement( document$ ) ? doesnt emit if no result
|
||||
if (!el) {
|
||||
return NEVER
|
||||
} else {
|
||||
// TODO: hash should be calculated from el.href
|
||||
return fetchSourceFacts(el.href)
|
||||
}
|
||||
})),
|
||||
|
||||
tap(facts => {
|
||||
console.log(facts)
|
||||
if (facts.length) {
|
||||
const sources = getElements(".md-source__repository")
|
||||
sources.forEach(repo => {
|
||||
repo.dataset.mdState = "done"
|
||||
repo.appendChild(renderSource(facts))
|
||||
})
|
||||
}
|
||||
})
|
||||
// TODO: use URL? to dinstinguish
|
||||
)
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Only emit a value if it is non-empty
|
||||
// *
|
||||
// * @template T - Value type
|
||||
// *
|
||||
// * @return Operator function
|
||||
// */
|
||||
// export function exists<T>(): OperatorFunction<T, NonNullable<T>> {
|
||||
// return pipe(
|
||||
// switchMap(value => !!value ? NEVER : of(value as NonNullable<T>))
|
||||
// )
|
||||
// }
|
||||
|
@ -21,16 +21,18 @@
|
||||
*/
|
||||
|
||||
import { identity } from "ramda"
|
||||
import { Observable, fromEvent, merge } from "rxjs"
|
||||
import { Observable, animationFrameScheduler, fromEvent, merge, of } from "rxjs"
|
||||
import {
|
||||
filter,
|
||||
map,
|
||||
observeOn,
|
||||
shareReplay,
|
||||
switchMap,
|
||||
switchMapTo,
|
||||
tap
|
||||
} from "rxjs/operators"
|
||||
|
||||
import { getElements, watchMedia } from "observables"
|
||||
import { getElement, getElements, watchMedia } from "observables"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Helper types
|
||||
@ -41,6 +43,7 @@ import { getElements, watchMedia } from "observables"
|
||||
*/
|
||||
interface MountOptions {
|
||||
document$: Observable<Document> /* Document observable */
|
||||
hash$: Observable<string> /* Location hash observable */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
@ -58,19 +61,52 @@ interface MountOptions {
|
||||
* @return Details elements observable
|
||||
*/
|
||||
export function patchDetails(
|
||||
{ document$ }: MountOptions
|
||||
{ document$, hash$ }: MountOptions
|
||||
): Observable<HTMLDetailsElement[]> {
|
||||
return merge(
|
||||
const els$ = document$
|
||||
.pipe(
|
||||
map(() => getElements<HTMLDetailsElement>("details")),
|
||||
shareReplay(1)
|
||||
)
|
||||
|
||||
/* Open all details before printing */
|
||||
merge(
|
||||
watchMedia("print").pipe(filter(identity)), // Webkit
|
||||
fromEvent(window, "beforeprint") // IE, FF
|
||||
)
|
||||
.pipe(
|
||||
switchMapTo(document$),
|
||||
map(() => getElements<HTMLDetailsElement>("details")),
|
||||
tap(els => {
|
||||
for (const detail of els)
|
||||
detail.setAttribute("open", "")
|
||||
}),
|
||||
shareReplay(1)
|
||||
switchMapTo(els$)
|
||||
)
|
||||
.subscribe(els => {
|
||||
for (const el of els)
|
||||
el.setAttribute("open", "")
|
||||
})
|
||||
|
||||
/* Open details before anchor jump */
|
||||
merge(hash$, of(location.hash))
|
||||
.pipe(
|
||||
filter(hash => !!hash.length),
|
||||
switchMap(hash => of(getElement<HTMLElement>(hash) || document.body)
|
||||
.pipe(
|
||||
map(el => el.closest("details")!),
|
||||
filter(el => el && !el.open),
|
||||
|
||||
/* Open details and temporarily reset anchor */
|
||||
tap(el => {
|
||||
el.setAttribute("open", "")
|
||||
location.hash = ""
|
||||
}),
|
||||
|
||||
/* Defer anchor jump to next animation frame */
|
||||
observeOn(animationFrameScheduler),
|
||||
tap(() => {
|
||||
location.hash = hash
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
.subscribe()
|
||||
|
||||
/* Return details elements */
|
||||
return els$
|
||||
}
|
||||
|
@ -20,7 +20,7 @@
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { Observable } from "rxjs"
|
||||
import { Observable, of } from "rxjs"
|
||||
import { map } from "rxjs/operators"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
@ -42,3 +42,39 @@ export function not(
|
||||
map(active => !active)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache the last value emitted by an observable in session storage
|
||||
*
|
||||
* Note that the value must be serializable as `JSON`.
|
||||
*
|
||||
* @template T - Value type
|
||||
*
|
||||
* @param key - Cache key
|
||||
* @param factory - Observable factory
|
||||
*
|
||||
* @return Value observable
|
||||
*/
|
||||
export function cache<T>(
|
||||
key: string, factory: () => Observable<T>
|
||||
): Observable<T> {
|
||||
const data = sessionStorage.getItem(key)
|
||||
if (data) {
|
||||
return of(JSON.parse(data) as T)
|
||||
|
||||
/* Retrieve value from observable factory and write to storage */
|
||||
} else {
|
||||
const value$ = factory()
|
||||
value$
|
||||
.subscribe(value => {
|
||||
try {
|
||||
sessionStorage.setItem(key, JSON.stringify(value))
|
||||
} catch (err) {
|
||||
/* Just swallow */
|
||||
}
|
||||
})
|
||||
|
||||
/* Return value observable */
|
||||
return value$
|
||||
}
|
||||
}
|
||||
|
@ -77,3 +77,40 @@ export function truncate(value: string, n: number): string {
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Round a number for display with source facts
|
||||
*
|
||||
* This is a reverse engineered implementation of GitHub's weird rounding
|
||||
* algorithm for stars, forks and all other numbers. Probably incorrect.
|
||||
*
|
||||
* @param value - Original value
|
||||
*
|
||||
* @return Rounded value
|
||||
*/
|
||||
export function round(value: number): string {
|
||||
if (value > 999) {
|
||||
const digits = +((value - 950) % 1000 > 99)
|
||||
return `${((value + 1) / 1000).toFixed(digits)}k`
|
||||
} else {
|
||||
return value.toString()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple hash function
|
||||
*
|
||||
* @see https://bit.ly/2wsVjJ4 - Original source
|
||||
*
|
||||
* @param value - Value to be hashed
|
||||
*
|
||||
* @return Hash
|
||||
*/
|
||||
export function hash(value: string): number {
|
||||
let k = 0
|
||||
for (let i = 0, len = value.length; i < len; i++) {
|
||||
k = ((k << 5) - k) + value.charCodeAt(i)
|
||||
k |= 0 // Convert to 32bit integer
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user