mirror of
https://github.com/squidfunk/mkdocs-material.git
synced 2024-11-27 17:00:54 +01:00
Merge of Insiders features tied to 'Black Pearl' funding goal
This commit is contained in:
parent
8677190f3f
commit
ca3da9e3ca
File diff suppressed because one or more lines are too long
32
material/assets/javascripts/bundle.d892486b.min.js
vendored
Normal file
32
material/assets/javascripts/bundle.d892486b.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
material/assets/stylesheets/main.33e2939f.min.css
vendored
Normal file
2
material/assets/stylesheets/main.33e2939f.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
material/assets/stylesheets/main.33e2939f.min.css.map
Normal file
1
material/assets/stylesheets/main.33e2939f.min.css.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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
material/assets/stylesheets/palette.ef6f36e2.min.css.map
Normal file
1
material/assets/stylesheets/palette.ef6f36e2.min.css.map
Normal file
File diff suppressed because one or more lines are too long
@ -39,10 +39,10 @@
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block styles %}
|
||||
<link rel="stylesheet" href="{{ 'assets/stylesheets/main.c772ddf0.min.css' | url }}">
|
||||
<link rel="stylesheet" href="{{ 'assets/stylesheets/main.33e2939f.min.css' | url }}">
|
||||
{% if config.theme.palette %}
|
||||
{% set palette = config.theme.palette %}
|
||||
<link rel="stylesheet" href="{{ 'assets/stylesheets/palette.7fa14f5b.min.css' | url }}">
|
||||
<link rel="stylesheet" href="{{ 'assets/stylesheets/palette.ef6f36e2.min.css' | url }}">
|
||||
{% if palette.primary %}
|
||||
{% import "partials/palette.html" as map %}
|
||||
{% set primary = map.primary(
|
||||
@ -87,13 +87,14 @@
|
||||
{% set primary = palette.primary | replace(" ", "-") | lower %}
|
||||
{% set accent = palette.accent | replace(" ", "-") | lower %}
|
||||
<body dir="{{ direction }}" data-md-color-scheme="{{ scheme }}" data-md-color-primary="{{ primary }}" data-md-color-accent="{{ accent }}">
|
||||
{% if "preference" == scheme %}
|
||||
<script>matchMedia("(prefers-color-scheme: dark)").matches&&document.body.setAttribute("data-md-color-scheme","slate")</script>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<body dir="{{ direction }}">
|
||||
{% endif %}
|
||||
{% set features = config.theme.features or [] %}
|
||||
{% include "partials/javascripts/base.html" %}
|
||||
{% if not config.theme.palette is mapping %}
|
||||
{% include "partials/javascripts/palette.html" %}
|
||||
{% endif %}
|
||||
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
|
||||
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
|
||||
<label class="md-overlay" for="__drawer"></label>
|
||||
@ -178,6 +179,11 @@
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
{% if "navigation.top" in features %}
|
||||
<a href="#" class="md-top md-icon" data-md-component="top" data-md-state="hidden">
|
||||
{% include ".icons/material/arrow-up.svg" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</main>
|
||||
{% block footer %}
|
||||
{% include "partials/footer.html" %}
|
||||
@ -217,7 +223,7 @@
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script src="{{ 'assets/javascripts/bundle.65ce87ac.min.js' | url }}"></script>
|
||||
<script src="{{ 'assets/javascripts/bundle.d892486b.min.js' | url }}"></script>
|
||||
{% for path in config["extra_javascript"] %}
|
||||
<script src="{{ path | url }}"></script>
|
||||
{% endfor %}
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -35,5 +35,5 @@
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script src="{{ 'overrides/assets/javascripts/bundle.afdf7228.min.js' | url }}"></script>
|
||||
<script src="{{ 'overrides/assets/javascripts/bundle.3b3ca511.min.js' | url }}"></script>
|
||||
{% endblock %}
|
||||
|
@ -27,8 +27,20 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="md-header__options">
|
||||
{% if config.extra.alternate %}
|
||||
{% if not config.theme.palette is mapping %}
|
||||
<form class="md-header__option" data-md-component="palette">
|
||||
{% for option in config.theme.palette %}
|
||||
{% set primary = option.primary | replace(" ", "-") | lower %}
|
||||
{% set accent = option.accent | replace(" ", "-") | lower %}
|
||||
<input class="md-option" data-md-color-media="{{ option.media }}" data-md-color-scheme="{{ option.scheme }}" data-md-color-primary="{{ primary }}" data-md-color-accent="{{ accent }}" type="radio" name="__palette" id="__palette_{{ loop.index }}">
|
||||
<label class="md-header__button md-icon" title="{{ option.toggle.name }}" for="__palette_{{ loop.index0 or loop.length }}" hidden>
|
||||
{% include ".icons/" ~ option.toggle.icon ~ ".svg" %}
|
||||
</label>
|
||||
{% endfor %}
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if config.extra.alternate %}
|
||||
<div class="md-header__option">
|
||||
<div class="md-select">
|
||||
{% set icon = config.theme.icon.alternate or "material/translate" %}
|
||||
<span class="md-header__button md-icon">
|
||||
@ -46,8 +58,8 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if "search" in config["plugins"] %}
|
||||
<label class="md-header__button md-icon" for="__search">
|
||||
{% include ".icons/material/magnify.svg" %}
|
||||
|
4
material/partials/javascripts/base.html
Normal file
4
material/partials/javascripts/base.html
Normal file
@ -0,0 +1,4 @@
|
||||
{#-
|
||||
This file was automatically generated - do not edit
|
||||
-#}
|
||||
<script>function __prefix(e){return new URL("{{ base_url }}",location).pathname+"."+e}function __get(e,t=localStorage){return JSON.parse(t.getItem(__prefix(e)))}</script>
|
4
material/partials/javascripts/palette.html
Normal file
4
material/partials/javascripts/palette.html
Normal file
@ -0,0 +1,4 @@
|
||||
{#-
|
||||
This file was automatically generated - do not edit
|
||||
-#}
|
||||
<script>var palette=__get("__palette");if(null!==palette&&"object"==typeof palette.color)for(var key in palette.color)document.body.setAttribute("data-md-color-"+key,palette.color[key])</script>
|
17
mkdocs.yml
17
mkdocs.yml
@ -55,9 +55,20 @@ theme:
|
||||
- navigation.sections
|
||||
- navigation.tabs
|
||||
palette:
|
||||
scheme: default
|
||||
primary: indigo
|
||||
accent: indigo
|
||||
- media: "(prefers-color-scheme: light)"
|
||||
scheme: default
|
||||
primary: indigo
|
||||
accent: indigo
|
||||
toggle:
|
||||
icon: material/toggle-switch-off-outline
|
||||
name: Switch to dark mode
|
||||
- media: "(prefers-color-scheme: dark)"
|
||||
scheme: slate
|
||||
primary: red
|
||||
accent: red
|
||||
toggle:
|
||||
icon: material/toggle-switch
|
||||
name: Switch to light mode
|
||||
font:
|
||||
text: Roboto
|
||||
code: Roboto Mono
|
||||
|
@ -35,6 +35,7 @@ export type Flag =
|
||||
| "navigation.instant" /* Instant loading */
|
||||
| "navigation.sections" /* Sections navigation */
|
||||
| "navigation.tabs" /* Tabs navigation */
|
||||
| "navigation.top" /* Back-to-top button */
|
||||
| "toc.integrate" /* Integrated table of contents */
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
@ -28,3 +28,4 @@ export * from "./search"
|
||||
export * from "./sidebar"
|
||||
export * from "./source"
|
||||
export * from "./tabs"
|
||||
export * from "./top"
|
||||
|
48
src/assets/javascripts/actions/top/index.ts
Normal file
48
src/assets/javascripts/actions/top/index.ts
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2021 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.
|
||||
*/
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Set back-to-top state
|
||||
*
|
||||
* @param el - Back-to-top element
|
||||
* @param state - Back-to-top state
|
||||
*/
|
||||
export function setBackToTopState(
|
||||
el: HTMLElement, state: "hidden"
|
||||
): void {
|
||||
el.setAttribute("data-md-state", state)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset back-to-top state
|
||||
*
|
||||
* @param el - Back-to-top element
|
||||
*/
|
||||
export function resetBackToTopState(
|
||||
el: HTMLElement
|
||||
): void {
|
||||
el.removeAttribute("data-md-state")
|
||||
}
|
@ -43,7 +43,7 @@ import {
|
||||
export function request(
|
||||
url: URL | string, options: RequestInit = { credentials: "same-origin" }
|
||||
): Observable<Response> {
|
||||
return from(fetch(url.toString(), options))
|
||||
return from(fetch(`${url}`, options))
|
||||
.pipe(
|
||||
filter(res => res.status === 200),
|
||||
)
|
||||
|
@ -48,10 +48,12 @@ import {
|
||||
import {
|
||||
getComponentElement,
|
||||
getComponentElements,
|
||||
mountBackToTop,
|
||||
mountContent,
|
||||
mountDialog,
|
||||
mountHeader,
|
||||
mountHeaderTitle,
|
||||
mountPalette,
|
||||
mountSearch,
|
||||
mountSidebar,
|
||||
mountSource,
|
||||
@ -173,17 +175,17 @@ const control$ = merge(
|
||||
...getComponentElements("header")
|
||||
.map(el => mountHeader(el, { viewport$, header$, main$ })),
|
||||
|
||||
/* Color palette */
|
||||
...getComponentElements("palette")
|
||||
.map(el => mountPalette(el)),
|
||||
|
||||
/* Search */
|
||||
...getComponentElements("search")
|
||||
.map(el => mountSearch(el, { index$, keyboard$ })),
|
||||
|
||||
/* Repository information */
|
||||
...getComponentElements("source")
|
||||
.map(el => mountSource(el)),
|
||||
|
||||
/* Navigation tabs */
|
||||
...getComponentElements("tabs")
|
||||
.map(el => mountTabs(el, { viewport$, header$ })),
|
||||
.map(el => mountSource(el))
|
||||
)
|
||||
|
||||
/* Set up content component observables */
|
||||
@ -204,9 +206,17 @@ const content$ = defer(() => merge(
|
||||
: at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))
|
||||
),
|
||||
|
||||
/* Navigation tabs */
|
||||
...getComponentElements("tabs")
|
||||
.map(el => mountTabs(el, { viewport$, header$ })),
|
||||
|
||||
/* Table of contents */
|
||||
...getComponentElements("toc")
|
||||
.map(el => mountTableOfContents(el, { viewport$, header$ })),
|
||||
|
||||
/* Back-to-top button */
|
||||
...getComponentElements("top")
|
||||
.map(el => mountBackToTop(el, { viewport$, main$ }))
|
||||
))
|
||||
|
||||
/* Set up component observables */
|
||||
|
@ -38,6 +38,7 @@ export type ComponentType =
|
||||
| "header-title" /* Header title */
|
||||
| "header-topic" /* Header topic */
|
||||
| "main" /* Main area */
|
||||
| "palette" /* Color palette */
|
||||
| "search" /* Search */
|
||||
| "search-query" /* Search input */
|
||||
| "search-result" /* Search results */
|
||||
@ -46,6 +47,7 @@ export type ComponentType =
|
||||
| "source" /* Repository information */
|
||||
| "tabs" /* Navigation tabs */
|
||||
| "toc" /* Table of contents */
|
||||
| "top" /* Back-to-top button */
|
||||
|
||||
/**
|
||||
* A component
|
||||
@ -77,6 +79,7 @@ interface ComponentTypeMap {
|
||||
"header-title": HTMLElement /* Header title */
|
||||
"header-topic": HTMLElement /* Header topic */
|
||||
"main": HTMLElement /* Main area */
|
||||
"palette": HTMLElement /* Color palette */
|
||||
"search": HTMLElement /* Search */
|
||||
"search-query": HTMLInputElement /* Search input */
|
||||
"search-result": HTMLElement /* Search results */
|
||||
@ -85,6 +88,7 @@ interface ComponentTypeMap {
|
||||
"source": HTMLAnchorElement /* Repository information */
|
||||
"tabs": HTMLElement /* Navigation tabs */
|
||||
"toc": HTMLElement /* Table of contents */
|
||||
"top": HTMLAnchorElement /* Back-to-top button */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
|
@ -22,4 +22,5 @@
|
||||
|
||||
export * from "./_"
|
||||
export * from "./code"
|
||||
export * from "./details"
|
||||
export * from "./table"
|
||||
|
@ -120,7 +120,7 @@ function isHidden({ viewport$ }: WatchOptions): Observable<boolean> {
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
/* Compute threshold for autohiding */
|
||||
/* Compute threshold for hiding */
|
||||
const search$ = watchToggle("search")
|
||||
return combineLatest([viewport$, search$])
|
||||
.pipe(
|
||||
|
@ -25,8 +25,10 @@ export * from "./content"
|
||||
export * from "./dialog"
|
||||
export * from "./header"
|
||||
export * from "./main"
|
||||
export * from "./palette"
|
||||
export * from "./search"
|
||||
export * from "./sidebar"
|
||||
export * from "./source"
|
||||
export * from "./tabs"
|
||||
export * from "./toc"
|
||||
export * from "./top"
|
||||
|
147
src/assets/javascripts/components/palette/index.ts
Normal file
147
src/assets/javascripts/components/palette/index.ts
Normal file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2021 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,
|
||||
Subject,
|
||||
fromEvent,
|
||||
of
|
||||
} from "rxjs"
|
||||
import {
|
||||
finalize,
|
||||
map,
|
||||
mapTo,
|
||||
mergeMap,
|
||||
shareReplay,
|
||||
startWith,
|
||||
tap
|
||||
} from "rxjs/operators"
|
||||
|
||||
import { getElements } from "~/browser"
|
||||
|
||||
import { Component } from "../_"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Palette colors
|
||||
*/
|
||||
export interface PaletteColor {
|
||||
scheme?: string /* Color scheme */
|
||||
primary?: string /* Primary color */
|
||||
accent?: string /* Accent color */
|
||||
}
|
||||
|
||||
/**
|
||||
* Palette
|
||||
*/
|
||||
export interface Palette {
|
||||
index: number /* Palette index */
|
||||
color: PaletteColor /* Palette colors */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Watch color palette
|
||||
*
|
||||
* @param inputs - Color palette element
|
||||
*
|
||||
* @returns Color palette observable
|
||||
*/
|
||||
export function watchPalette(
|
||||
inputs: HTMLInputElement[]
|
||||
): Observable<Palette> {
|
||||
const data = localStorage.getItem(__prefix("__palette"))!
|
||||
const current = JSON.parse(data) || {
|
||||
index: inputs.findIndex(input => (
|
||||
matchMedia(input.getAttribute("data-md-color-media")!).matches
|
||||
))
|
||||
}
|
||||
|
||||
/* Emit changes in color palette */
|
||||
const palette$ = of(...inputs)
|
||||
.pipe(
|
||||
mergeMap(input => fromEvent(input, "change")
|
||||
.pipe(
|
||||
mapTo(input)
|
||||
)
|
||||
),
|
||||
startWith(inputs[Math.max(0, current.index)]),
|
||||
map(input => ({
|
||||
index: inputs.indexOf(input),
|
||||
color: {
|
||||
scheme: input.getAttribute("data-md-color-scheme"),
|
||||
primary: input.getAttribute("data-md-color-primary"),
|
||||
accent: input.getAttribute("data-md-color-accent")
|
||||
}
|
||||
} as Palette)),
|
||||
shareReplay(1)
|
||||
)
|
||||
|
||||
/* Persist preference in local storage */
|
||||
palette$.subscribe(palette => {
|
||||
localStorage.setItem(__prefix("__palette"), JSON.stringify(palette))
|
||||
})
|
||||
|
||||
/* Return palette */
|
||||
return palette$
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount color palette
|
||||
*
|
||||
* @param el - Color palette element
|
||||
*
|
||||
* @returns Color palette component observable
|
||||
*/
|
||||
export function mountPalette(
|
||||
el: HTMLElement
|
||||
): Observable<Component<Palette>> {
|
||||
const internal$ = new Subject<Palette>()
|
||||
|
||||
/* Set color palette */
|
||||
internal$.subscribe(palette => {
|
||||
for (const [key, value] of Object.entries(palette.color))
|
||||
if (typeof value === "string")
|
||||
document.body.setAttribute(`data-md-color-${key}`, value)
|
||||
|
||||
/* Toggle visibility */
|
||||
for (let index = 0; index < inputs.length; index++) {
|
||||
const label = inputs[index].nextElementSibling as HTMLElement
|
||||
label.hidden = palette.index !== index
|
||||
}
|
||||
})
|
||||
|
||||
/* Create and return component */
|
||||
const inputs = getElements<HTMLInputElement>("input", el)
|
||||
return watchPalette(inputs)
|
||||
.pipe(
|
||||
tap(internal$),
|
||||
finalize(() => internal$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
}
|
@ -32,7 +32,6 @@ import {
|
||||
|
||||
import { setSourceFacts, setSourceState } from "~/actions"
|
||||
import { renderSourceFacts } from "~/templates"
|
||||
import { digest } from "~/utilities"
|
||||
|
||||
import { Component } from "../../_"
|
||||
import { SourceFacts, fetchSourceFacts } from "../facts"
|
||||
@ -75,14 +74,14 @@ export function watchSource(
|
||||
el: HTMLAnchorElement
|
||||
): Observable<Source> {
|
||||
return fetch$ ||= defer(() => {
|
||||
const data = sessionStorage.getItem(digest("__repo"))
|
||||
const data = sessionStorage.getItem(__prefix("__source"))
|
||||
if (data) {
|
||||
return of<SourceFacts>(JSON.parse(data))
|
||||
} else {
|
||||
const value$ = fetchSourceFacts(el.href)
|
||||
value$.subscribe(value => {
|
||||
try {
|
||||
sessionStorage.setItem(digest("__repo"), JSON.stringify(value))
|
||||
sessionStorage.setItem(__prefix("__source"), JSON.stringify(value))
|
||||
} catch (err) {
|
||||
/* Uncritical, just swallow */
|
||||
}
|
||||
@ -94,7 +93,7 @@ export function watchSource(
|
||||
})
|
||||
.pipe(
|
||||
catchError(() => NEVER),
|
||||
filter(facts => facts.length > 0),
|
||||
filter(facts => Object.keys(facts).length > 0),
|
||||
map(facts => ({ facts })),
|
||||
shareReplay(1)
|
||||
)
|
||||
|
@ -29,10 +29,30 @@ import { fetchSourceFactsFromGitLab } from "../gitlab"
|
||||
* Types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Repository facts for repositories
|
||||
*/
|
||||
export interface RepositoryFacts {
|
||||
stars?: number /* Number of stars */
|
||||
forks?: number /* Number of forks */
|
||||
version?: string /* Latest version */
|
||||
}
|
||||
|
||||
/**
|
||||
* Repository facts for organizations
|
||||
*/
|
||||
export interface OrganizationFacts {
|
||||
repositories?: number /* Number of repositories */
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Repository facts
|
||||
*/
|
||||
export type SourceFacts = string[]
|
||||
export type SourceFacts =
|
||||
| RepositoryFacts
|
||||
| OrganizationFacts
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
|
@ -21,14 +21,24 @@
|
||||
*/
|
||||
|
||||
import { Repo, User } from "github-types"
|
||||
import { Observable } from "rxjs"
|
||||
import { Observable, zip } from "rxjs"
|
||||
import { defaultIfEmpty, map } from "rxjs/operators"
|
||||
|
||||
import { requestJSON } from "~/browser"
|
||||
import { round } from "~/utilities"
|
||||
|
||||
import { SourceFacts } from "../_"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Helper types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* GitHub release (partial)
|
||||
*/
|
||||
interface Release {
|
||||
tag_name: string /* Tag name */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
@ -44,29 +54,42 @@ import { SourceFacts } from "../_"
|
||||
export function fetchSourceFactsFromGitHub(
|
||||
user: string, repo?: string
|
||||
): Observable<SourceFacts> {
|
||||
const url = typeof repo !== "undefined"
|
||||
? `https://api.github.com/repos/${user}/${repo}`
|
||||
: `https://api.github.com/users/${user}`
|
||||
return requestJSON<Repo & User>(url)
|
||||
.pipe(
|
||||
map(data => {
|
||||
if (typeof repo !== "undefined") {
|
||||
const url = `https://api.github.com/repos/${user}/${repo}`
|
||||
return zip(
|
||||
|
||||
/* GitHub repository */
|
||||
if (typeof repo !== "undefined") {
|
||||
const { stargazers_count, forks_count }: Repo = data
|
||||
return [
|
||||
`${round(stargazers_count!)} Stars`,
|
||||
`${round(forks_count!)} Forks`
|
||||
]
|
||||
/* Fetch version */
|
||||
requestJSON<Release>(`${url}/releases/latest`)
|
||||
.pipe(
|
||||
map(release => ({
|
||||
version: release.tag_name
|
||||
})),
|
||||
defaultIfEmpty({})
|
||||
),
|
||||
|
||||
/* GitHub user/organization */
|
||||
} else {
|
||||
const { public_repos }: User = data
|
||||
return [
|
||||
`${round(public_repos!)} Repositories`
|
||||
]
|
||||
}
|
||||
}),
|
||||
defaultIfEmpty([])
|
||||
/* Fetch stars and forks */
|
||||
requestJSON<Repo>(url)
|
||||
.pipe(
|
||||
map(info => ({
|
||||
stars: info.stargazers_count,
|
||||
forks: info.forks_count
|
||||
})),
|
||||
defaultIfEmpty({})
|
||||
)
|
||||
)
|
||||
.pipe(
|
||||
map(([release, info]) => ({ ...release, ...info }))
|
||||
)
|
||||
|
||||
/* User or organization */
|
||||
} else {
|
||||
const url = `https://api.github.com/repos/${user}`
|
||||
return requestJSON<User>(url)
|
||||
.pipe(
|
||||
map(info => ({
|
||||
repositories: info.public_repos
|
||||
})),
|
||||
defaultIfEmpty({})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ import { Observable } from "rxjs"
|
||||
import { defaultIfEmpty, map } from "rxjs/operators"
|
||||
|
||||
import { requestJSON } from "~/browser"
|
||||
import { round } from "~/utilities"
|
||||
|
||||
import { SourceFacts } from "../_"
|
||||
|
||||
@ -47,10 +46,10 @@ export function fetchSourceFactsFromGitLab(
|
||||
const url = `https://${base}/api/v4/projects/${encodeURIComponent(project)}`
|
||||
return requestJSON<ProjectSchema>(url)
|
||||
.pipe(
|
||||
map(({ star_count, forks_count }) => ([
|
||||
`${round(star_count)} Stars`,
|
||||
`${round(forks_count)} Forks`
|
||||
])),
|
||||
defaultIfEmpty([])
|
||||
map(({ star_count, forks_count }) => ({
|
||||
stars: star_count,
|
||||
forks: forks_count
|
||||
})),
|
||||
defaultIfEmpty({})
|
||||
)
|
||||
}
|
||||
|
@ -26,11 +26,16 @@ import {
|
||||
finalize,
|
||||
map,
|
||||
observeOn,
|
||||
switchMap,
|
||||
tap
|
||||
} from "rxjs/operators"
|
||||
|
||||
import { resetTabsState, setTabsState } from "~/actions"
|
||||
import { Viewport, watchViewportAt } from "~/browser"
|
||||
import {
|
||||
Viewport,
|
||||
watchElementSize,
|
||||
watchViewportAt
|
||||
} from "~/browser"
|
||||
|
||||
import { Component } from "../_"
|
||||
import { Header } from "../header"
|
||||
@ -81,8 +86,9 @@ interface MountOptions {
|
||||
export function watchTabs(
|
||||
el: HTMLElement, { viewport$, header$ }: WatchOptions
|
||||
): Observable<Tabs> {
|
||||
return watchViewportAt(el, { header$, viewport$ })
|
||||
return watchElementSize(document.body)
|
||||
.pipe(
|
||||
switchMap(() => watchViewportAt(el, { header$, viewport$ })),
|
||||
map(({ offset: { y } }) => {
|
||||
return {
|
||||
hidden: y >= 10
|
||||
|
160
src/assets/javascripts/components/top/index.ts
Normal file
160
src/assets/javascripts/components/top/index.ts
Normal file
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2021 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,
|
||||
Subject,
|
||||
animationFrameScheduler,
|
||||
combineLatest
|
||||
} from "rxjs"
|
||||
import {
|
||||
bufferCount,
|
||||
distinctUntilChanged,
|
||||
distinctUntilKeyChanged,
|
||||
finalize,
|
||||
map,
|
||||
observeOn,
|
||||
tap
|
||||
} from "rxjs/operators"
|
||||
|
||||
import { resetBackToTopState, setBackToTopState } from "~/actions"
|
||||
import { Viewport } from "~/browser"
|
||||
|
||||
import { Component } from "../_"
|
||||
import { Main } from "../main"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Back-to-top button
|
||||
*/
|
||||
export interface BackToTop {
|
||||
hidden: boolean /* User scrolled up */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Helper types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Watch options
|
||||
*/
|
||||
interface WatchOptions {
|
||||
viewport$: Observable<Viewport> /* Viewport observable */
|
||||
main$: Observable<Main> /* Main area observable */
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount options
|
||||
*/
|
||||
interface MountOptions {
|
||||
viewport$: Observable<Viewport> /* Viewport observable */
|
||||
main$: Observable<Main> /* Main area observable */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Watch back-to-top
|
||||
*
|
||||
* @param _el - Back-to-top element
|
||||
* @param options - Options
|
||||
*
|
||||
* @returns Back-to-top observable
|
||||
*/
|
||||
export function watchBackToTop(
|
||||
_el: HTMLElement, { viewport$, main$ }: WatchOptions
|
||||
): Observable<BackToTop> {
|
||||
|
||||
/* Compute direction */
|
||||
const direction$ = viewport$
|
||||
.pipe(
|
||||
map(({ offset: { y } }) => y),
|
||||
bufferCount(2, 1),
|
||||
map(([a, b]) => a > b),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
/* Compute whether button should be hidden */
|
||||
const hidden$ = main$
|
||||
.pipe(
|
||||
distinctUntilKeyChanged("active")
|
||||
)
|
||||
|
||||
/* Compute threshold for hiding */
|
||||
return combineLatest([hidden$, direction$])
|
||||
.pipe(
|
||||
map(([{ active }, direction]) => ({
|
||||
hidden: !(active && direction)
|
||||
})),
|
||||
distinctUntilChanged((a, b) => (
|
||||
a.hidden === b.hidden
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Mount back-to-top
|
||||
*
|
||||
* @param el - Back-to-top element
|
||||
* @param options - Options
|
||||
*
|
||||
* @returns Back-to-top component observable
|
||||
*/
|
||||
export function mountBackToTop(
|
||||
el: HTMLElement, options: MountOptions
|
||||
): Observable<Component<BackToTop>> {
|
||||
const internal$ = new Subject<BackToTop>()
|
||||
internal$
|
||||
.pipe(
|
||||
observeOn(animationFrameScheduler)
|
||||
)
|
||||
.subscribe({
|
||||
|
||||
/* Update state */
|
||||
next({ hidden }) {
|
||||
if (hidden)
|
||||
setBackToTopState(el, "hidden")
|
||||
else
|
||||
resetBackToTopState(el)
|
||||
},
|
||||
|
||||
/* Reset on complete */
|
||||
complete() {
|
||||
resetBackToTopState(el)
|
||||
}
|
||||
})
|
||||
|
||||
/* Create and return component */
|
||||
return watchBackToTop(el, options)
|
||||
.pipe(
|
||||
tap(internal$),
|
||||
finalize(() => internal$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
}
|
@ -240,7 +240,7 @@ export function setupInstantLoading(
|
||||
sample(response$)
|
||||
)
|
||||
.subscribe(({ url }) => {
|
||||
history.pushState({}, "", url.toString())
|
||||
history.pushState({}, "", `${url}`)
|
||||
})
|
||||
|
||||
/* Parse and emit fetched document */
|
||||
@ -274,14 +274,14 @@ export function setupInstantLoading(
|
||||
|
||||
/* Meta tags */
|
||||
"title",
|
||||
"link[rel='canonical']",
|
||||
"meta[name='author']",
|
||||
"meta[name='description']",
|
||||
"link[rel=canonical]",
|
||||
"meta[name=author]",
|
||||
"meta[name=description]",
|
||||
|
||||
/* Components */
|
||||
"[data-md-component=announce]",
|
||||
"[data-md-component=header-topic]",
|
||||
"[data-md-component=container]",
|
||||
"[data-md-component=header-topic]",
|
||||
"[data-md-component=logo], .md-logo", // compat
|
||||
"[data-md-component=skip]"
|
||||
]) {
|
||||
|
@ -21,7 +21,7 @@
|
||||
*/
|
||||
|
||||
import { SourceFacts } from "~/components"
|
||||
import { h } from "~/utilities"
|
||||
import { h, round } from "~/utilities"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
@ -37,8 +37,10 @@ import { h } from "~/utilities"
|
||||
export function renderSourceFacts(facts: SourceFacts): HTMLElement {
|
||||
return (
|
||||
<ul class="md-source__facts">
|
||||
{facts.map(fact => (
|
||||
<li class="md-source__fact">{fact}</li>
|
||||
{Object.entries(facts).map(([key, value]) => (
|
||||
<li class={`md-source__fact md-source__fact--${key}`}>
|
||||
{typeof value === "number" ? round(value) : value}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
|
@ -20,8 +20,6 @@
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { configuration } from "~/_"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
@ -90,18 +88,3 @@ export function hash(value: string): number {
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a digest to a value to ensure uniqueness
|
||||
*
|
||||
* When a single account hosts multiple sites on the same GitHub Pages domain,
|
||||
* entries would collide, because session and local storage are not unique.
|
||||
*
|
||||
* @param value - Value
|
||||
*
|
||||
* @returns Value with digest
|
||||
*/
|
||||
export function digest(value: string): string {
|
||||
const config = configuration()
|
||||
return `${value}[${hash(config.base)}]`
|
||||
}
|
||||
|
@ -55,6 +55,7 @@
|
||||
@import "main/layout/sidebar";
|
||||
@import "main/layout/source";
|
||||
@import "main/layout/tabs";
|
||||
@import "main/layout/top";
|
||||
@import "main/layout/version";
|
||||
|
||||
@import "main/extensions/markdown/admonition";
|
||||
|
@ -121,12 +121,31 @@ body {
|
||||
// Rules: navigational elements
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Toggle - this class is applied to the checkbox elements, which are used to
|
||||
// Toggle - this class is applied to checkbox elements, which are used to
|
||||
// implement the CSS-only drawer and navigation, as well as the search
|
||||
.md-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Option - this class is applied to radio elements, which are used to
|
||||
// implement the color palette toggle
|
||||
.md-option {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
|
||||
// Option label for checked radio button
|
||||
&:checked + label:not([hidden]) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
// Option label on focus
|
||||
&.focus-visible + label {
|
||||
outline-style: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip link
|
||||
.md-skip {
|
||||
position: fixed;
|
||||
|
@ -39,9 +39,6 @@
|
||||
box-shadow:
|
||||
0 0 px2rem(4px) rgba(0, 0, 0, 0),
|
||||
0 px2rem(4px) px2rem(8px) rgba(0, 0, 0, 0);
|
||||
transition:
|
||||
color 250ms,
|
||||
background-color 250ms;
|
||||
|
||||
// [print]: Hide header
|
||||
@media print {
|
||||
@ -54,20 +51,21 @@
|
||||
0 0 px2rem(4px) rgba(0, 0, 0, 0.1),
|
||||
0 px2rem(4px) px2rem(8px) rgba(0, 0, 0, 0.2);
|
||||
transition:
|
||||
transform 250ms cubic-bezier(0.1, 0.7, 0.1, 1),
|
||||
color 250ms,
|
||||
background-color 250ms,
|
||||
box-shadow 250ms;
|
||||
transform 250ms cubic-bezier(0.1, 0.7, 0.1, 1),
|
||||
box-shadow 250ms;
|
||||
}
|
||||
|
||||
// Header in hidden state, i.e. moved out of sight
|
||||
&[data-md-state="hidden"] {
|
||||
transform: translateY(-100%);
|
||||
transition:
|
||||
transform 250ms cubic-bezier(0.8, 0, 0.6, 1),
|
||||
color 250ms,
|
||||
background-color 250ms,
|
||||
box-shadow 250ms;
|
||||
transform 250ms cubic-bezier(0.8, 0, 0.6, 1),
|
||||
box-shadow 250ms;
|
||||
}
|
||||
|
||||
// Link or button on focus
|
||||
.focus-visible {
|
||||
outline-color: currentColor;
|
||||
}
|
||||
|
||||
// Header wrapper
|
||||
@ -81,7 +79,6 @@
|
||||
&__button {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: inline-block;
|
||||
margin: px2rem(4px);
|
||||
padding: px2rem(8px);
|
||||
color: currentColor;
|
||||
@ -89,15 +86,20 @@
|
||||
cursor: pointer;
|
||||
transition: opacity 250ms;
|
||||
|
||||
// Button on focus/hover
|
||||
&:focus,
|
||||
// Button on hover
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
// Header button is visible
|
||||
&:not([hidden]) {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
// Hide outline for pointer devices
|
||||
&:not(.focus-visible) {
|
||||
outline: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
// Button with logo, pointing to `config.site_url`
|
||||
@ -223,8 +225,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Header options
|
||||
&__options {
|
||||
// Header option
|
||||
&__option {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
max-width: 100%;
|
||||
@ -233,11 +235,6 @@
|
||||
max-width 0ms 250ms,
|
||||
opacity 250ms 250ms;
|
||||
|
||||
// Hide inactive buttons
|
||||
> [data-md-state="hidden"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Hide toggle when search is active
|
||||
[data-md-toggle="search"]:checked ~ .md-header & {
|
||||
max-width: 0;
|
||||
|
@ -56,6 +56,16 @@
|
||||
// Rules
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Icon definitions
|
||||
:root {
|
||||
--md-source-forks-icon: svg-load("octicons/repo-forked-16.svg");
|
||||
--md-source-repositories-icon: svg-load("octicons/repo-16.svg");
|
||||
--md-source-stars-icon: svg-load("octicons/star-16.svg");
|
||||
--md-source-version-icon: svg-load("octicons/tag-16.svg");
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Repository information
|
||||
.md-source {
|
||||
display: block;
|
||||
@ -66,8 +76,7 @@
|
||||
backface-visibility: hidden;
|
||||
transition: opacity 250ms;
|
||||
|
||||
// Repository information on focus/hover
|
||||
&:focus,
|
||||
// Repository information on hover
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
@ -75,7 +84,7 @@
|
||||
// Repository icon
|
||||
&__icon {
|
||||
display: inline-block;
|
||||
width: px2rem(48px);
|
||||
width: px2rem(40px);
|
||||
height: px2rem(48px);
|
||||
vertical-align: middle;
|
||||
|
||||
@ -112,17 +121,15 @@
|
||||
max-width: calc(100% - #{px2rem(24px)});
|
||||
margin-left: px2rem(12px);
|
||||
overflow: hidden;
|
||||
font-weight: 700;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
// Repository facts
|
||||
&__facts {
|
||||
margin: 0;
|
||||
margin: px2rem(2px) 0 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
font-weight: 700;
|
||||
font-size: px2rem(11px);
|
||||
list-style-type: none;
|
||||
opacity: 0.75;
|
||||
@ -135,27 +142,61 @@
|
||||
|
||||
// Repository fact
|
||||
&__fact {
|
||||
float: left;
|
||||
|
||||
// Adjust for right-to-left languages
|
||||
[dir="rtl"] & {
|
||||
float: right;
|
||||
}
|
||||
display: inline-block;
|
||||
|
||||
// Show after the data was loaded
|
||||
[data-md-state="done"] & {
|
||||
animation: md-source__fact--done 400ms ease-out;
|
||||
}
|
||||
|
||||
// Middle dot before fact
|
||||
// Repository fact icon
|
||||
&::before {
|
||||
margin: 0 px2rem(2px);
|
||||
content: "\00B7";
|
||||
display: inline-block;
|
||||
width: px2rem(12px);
|
||||
height: px2rem(12px);
|
||||
margin-right: px2rem(2px);
|
||||
vertical-align: text-top;
|
||||
background-color: currentColor;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
content: "";
|
||||
}
|
||||
|
||||
// Remove middle dot on first fact
|
||||
&:first-child::before {
|
||||
display: none;
|
||||
// Adjust spacing for repository fact icon
|
||||
&:nth-child(1n+2)::before {
|
||||
margin-left: px2rem(8px);
|
||||
}
|
||||
|
||||
// Adjust for right-to-left languages
|
||||
[dir="rtl"] & {
|
||||
margin-right: initial;
|
||||
margin-left: px2rem(2px);
|
||||
|
||||
// Adjust spacing for repository fact icon
|
||||
&:nth-child(1n+2)::before {
|
||||
margin-right: px2rem(8px);
|
||||
margin-left: initial;
|
||||
}
|
||||
}
|
||||
|
||||
// Repository fact: version
|
||||
&--version::before {
|
||||
mask-image: var(--md-source-version-icon);
|
||||
}
|
||||
|
||||
// Repository fact: stars
|
||||
&--stars::before {
|
||||
mask-image: var(--md-source-stars-icon);
|
||||
}
|
||||
|
||||
// Repository fact: forks
|
||||
&--forks::before {
|
||||
mask-image: var(--md-source-forks-icon);
|
||||
}
|
||||
|
||||
// Repository fact: repositories
|
||||
&--repositories::before {
|
||||
mask-image: var(--md-source-repositories-icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,6 @@
|
||||
overflow: auto;
|
||||
color: var(--md-primary-bg-color);
|
||||
background-color: var(--md-primary-fg-color);
|
||||
transition: background-color 250ms;
|
||||
|
||||
// [print]: Hide tabs
|
||||
@media print {
|
||||
|
65
src/assets/stylesheets/main/layout/_top.scss
Normal file
65
src/assets/stylesheets/main/layout/_top.scss
Normal file
@ -0,0 +1,65 @@
|
||||
////
|
||||
/// Copyright (c) 2016-2021 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
|
||||
////
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Rules
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Back-to-top button
|
||||
.md-top {
|
||||
position: sticky;
|
||||
bottom: px2rem(8px);
|
||||
z-index: 1;
|
||||
float: right;
|
||||
margin: px2rem(-56px) px2rem(8px) px2rem(8px);
|
||||
padding: px2rem(8px);
|
||||
color: var(--md-primary-bg-color);
|
||||
background: var(--md-primary-fg-color);
|
||||
border-radius: 100%;
|
||||
outline: none;
|
||||
box-shadow:
|
||||
0 px2rem(4px) px2rem(10px) hsla(0, 0%, 0%, 0.1),
|
||||
0 px2rem(0.5px) px2rem(1px) hsla(0, 0%, 0%, 0.1);
|
||||
transform: translateY(0);
|
||||
transition:
|
||||
opacity 125ms,
|
||||
transform 125ms cubic-bezier(0.4, 0, 0.2, 1),
|
||||
background-color 125ms;
|
||||
|
||||
// Adjust for right-to-left languages
|
||||
[dir="rtl"] & {
|
||||
float: left;
|
||||
}
|
||||
|
||||
// Back-to-top button in hidden state
|
||||
&[data-md-state="hidden"] {
|
||||
transform: translateY(px2rem(-4px));
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
// Back-to-top button on focus/hover
|
||||
&:focus,
|
||||
&:hover {
|
||||
background: var(--md-accent-fg-color);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
@ -57,9 +57,16 @@
|
||||
--md-code-hl-constant-color: hsla(250, 62%, 70%, 1);
|
||||
--md-code-hl-keyword-color: hsla(219, 66%, 64%, 1);
|
||||
--md-code-hl-string-color: hsla(150, 58%, 44%, 1);
|
||||
--md-code-hl-name-color: var(--md-code-fg-color);
|
||||
--md-code-hl-operator-color: var(--md-default-fg-color--light);
|
||||
--md-code-hl-punctuation-color: var(--md-default-fg-color--light);
|
||||
--md-code-hl-comment-color: var(--md-default-fg-color--light);
|
||||
--md-code-hl-generic-color: var(--md-default-fg-color--light);
|
||||
--md-code-hl-variable-color: var(--md-default-fg-color--light);
|
||||
|
||||
// Typeset color shades
|
||||
--md-typeset-a-color: var(--md-primary-fg-color--light);
|
||||
--md-typeset-color: var(--md-default-fg-color);
|
||||
--md-typeset-a-color: var(--md-primary-fg-color);
|
||||
|
||||
// Typeset `mark` color shades
|
||||
--md-typeset-mark-color: hsla(#{hex2hsl($clr-blue-a200)}, 0.3);
|
||||
|
@ -168,21 +168,18 @@
|
||||
data-md-color-primary="{{ primary }}"
|
||||
data-md-color-accent="{{ accent }}"
|
||||
>
|
||||
|
||||
<!-- Experimental: set color scheme based on preference -->
|
||||
{% if "preference" == scheme %}
|
||||
<script>
|
||||
if (matchMedia("(prefers-color-scheme: dark)").matches)
|
||||
document.body.setAttribute("data-md-color-scheme", "slate")
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
<body dir="{{ direction }}">
|
||||
{% endif %}
|
||||
|
||||
<!-- Retrieve features from configuration -->
|
||||
{% set features = config.theme.features or [] %}
|
||||
{% include "partials/javascripts/base.html" %}
|
||||
|
||||
<!-- User preference: color palette -->
|
||||
{% if not config.theme.palette is mapping %}
|
||||
{% include "partials/javascripts/palette.html" %}
|
||||
{% endif %}
|
||||
|
||||
<!--
|
||||
State toggles - we need to set autocomplete="off" in order to reset the
|
||||
@ -338,6 +335,18 @@
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Back-to-top button -->
|
||||
{% if "navigation.top" in features %}
|
||||
<a
|
||||
href="#"
|
||||
class="md-top md-icon"
|
||||
data-md-component="top"
|
||||
data-md-state="hidden"
|
||||
>
|
||||
{% include ".icons/material/arrow-up.svg" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
|
@ -83,14 +83,12 @@ export function mountIconSearch(
|
||||
`${config.base}/overrides/assets/javascripts/iconsearch_index.json`
|
||||
)
|
||||
|
||||
/* Retrieve nested components */
|
||||
/* Retrieve query and result components */
|
||||
const query = getComponentElement("iconsearch-query", el)
|
||||
const result = getComponentElement("iconsearch-result", el)
|
||||
|
||||
/* Create and return component */
|
||||
const query$ = mountIconSearchQuery(query)
|
||||
return merge(
|
||||
query$,
|
||||
mountIconSearchResult(result, { index$, query$ })
|
||||
)
|
||||
const query$ = mountIconSearchQuery(query)
|
||||
const result$ = mountIconSearchResult(result, { index$, query$ })
|
||||
return merge(query$, result$)
|
||||
}
|
||||
|
@ -63,11 +63,37 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Header options -->
|
||||
<div class="md-header__options">
|
||||
<!-- Color palette -->
|
||||
{% if not config.theme.palette is mapping %}
|
||||
<form class="md-header__option" data-md-component="palette">
|
||||
{% for option in config.theme.palette %}
|
||||
{% set primary = option.primary | replace(" ", "-") | lower %}
|
||||
{% set accent = option.accent | replace(" ", "-") | lower %}
|
||||
<input
|
||||
class="md-option"
|
||||
data-md-color-media="{{ option.media }}"
|
||||
data-md-color-scheme="{{ option.scheme }}"
|
||||
data-md-color-primary="{{ primary }}"
|
||||
data-md-color-accent="{{ accent }}"
|
||||
type="radio"
|
||||
name="__palette"
|
||||
id="__palette_{{ loop.index }}"
|
||||
/>
|
||||
<label
|
||||
class="md-header__button md-icon"
|
||||
title="{{ option.toggle.name }}"
|
||||
for="__palette_{{ loop.index0 or loop.length }}"
|
||||
hidden
|
||||
>
|
||||
{% include ".icons/" ~ option.toggle.icon ~ ".svg" %}
|
||||
</label>
|
||||
{% endfor %}
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<!-- Switch to toggle language -->
|
||||
{% if config.extra.alternate %}
|
||||
<!-- Site language selector -->
|
||||
{% if config.extra.alternate %}
|
||||
<div class="md-header__option"></form>
|
||||
<div class="md-select">
|
||||
{% set icon = config.theme.icon.alternate or "material/translate" %}
|
||||
<span class="md-header__button md-icon">
|
||||
@ -85,8 +111,8 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Button to open search modal -->
|
||||
{% if "search" in config["plugins"] %}
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
<!-- Google Analytics integration -->
|
||||
{% set analytics = config.google_analytics %}
|
||||
<script type="application/javascript">
|
||||
<script>
|
||||
window.ga = window.ga || function() {
|
||||
(ga.q = ga.q || []).push(arguments)
|
||||
}
|
||||
|
@ -30,7 +30,7 @@
|
||||
{% if not page.is_homepage and disqus %}
|
||||
<h2 id="__comments">{{ lang.t("meta.comments") }}</h2>
|
||||
<div id="disqus_thread"></div>
|
||||
<script type="application/javascript">
|
||||
<script>
|
||||
var disqus_config = function () {
|
||||
this.page.url = "{{ page.canonical_url }}";
|
||||
this.page.identifier =
|
||||
|
39
src/partials/javascripts/base.html
Normal file
39
src/partials/javascripts/base.html
Normal file
@ -0,0 +1,39 @@
|
||||
<!--
|
||||
Copyright (c) 2016-2021 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.
|
||||
-->
|
||||
|
||||
<!--
|
||||
A collection of functions used from within some partials to allow the usage
|
||||
of state saved in local or session storage, e.g. to model preferences.
|
||||
-->
|
||||
<script>
|
||||
|
||||
/* Prepend the base path to the given key to ensure uniqueness */
|
||||
function __prefix(key) {
|
||||
var prefix = new URL("{{ base_url }}", location)
|
||||
return prefix.pathname + "." + key
|
||||
}
|
||||
|
||||
/* Fetch the given key from the given storage */
|
||||
function __get(key, storage = localStorage) {
|
||||
return JSON.parse(storage.getItem(__prefix(key)))
|
||||
}
|
||||
</script>
|
29
src/partials/javascripts/palette.html
Normal file
29
src/partials/javascripts/palette.html
Normal file
@ -0,0 +1,29 @@
|
||||
<!--
|
||||
Copyright (c) 2016-2021 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.
|
||||
-->
|
||||
|
||||
<!-- User preference: color palette -->
|
||||
<script>
|
||||
var palette = __get("__palette")
|
||||
if (palette !== null && typeof palette.color === "object")
|
||||
for (var key in palette.color)
|
||||
document.body.setAttribute("data-md-color-" + key, palette.color[key])
|
||||
</script>
|
Loading…
Reference in New Issue
Block a user