Added support for version selector

This commit is contained in:
squidfunk 2021-02-22 21:01:04 +01:00
parent 99cc2e201b
commit 4bd9fba470
19 changed files with 271 additions and 25 deletions

View File

@ -18,7 +18,6 @@ documentation remain untouched.
[:octicons-file-code-24: Source][2] ·
[:octicons-package-24: Utility][1] ·
:octicons-beaker-24: Experimental ·
[:octicons-heart-fill-24:{: .tx-heart } Insiders only][2]{: .tx-insiders }
[mike][1] makes it easy to deploy multiple versions of your project
documentation. It integrates natively with Material for MkDocs and can be
@ -27,7 +26,7 @@ enabled via `mkdocs.yml`:
``` yaml
extra:
version:
method: mike
provider: mike
```
This will render a version selector in the header next to the title of your
@ -62,7 +61,7 @@ _Note that you don't need to run_ `mike install-extras` _as noted in the
[official documentation][6], as [mike][1] is now natively integrated with
Material for MkDocs._
[2]: ../insiders.md
[2]: https://github.com/squidfunk/mkdocs-material/blob/master/src/partials/header.html
[3]: ../assets/screenshots/versioning.png
[4]: https://squidfunk.github.io/mkdocs-material-example-versioning/
[5]: https://github.com/jimporter/mike#why-use-mike

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -39,7 +39,7 @@
{% endif %}
{% endblock %}
{% block styles %}
<link rel="stylesheet" href="{{ 'assets/stylesheets/main.793c28ab.min.css' | url }}">
<link rel="stylesheet" href="{{ 'assets/stylesheets/main.3149646c.min.css' | url }}">
{% if config.theme.palette %}
{% set palette = config.theme.palette %}
<link rel="stylesheet" href="{{ 'assets/stylesheets/palette.7fa14f5b.min.css' | url }}">
@ -192,6 +192,7 @@
"features": features,
"translations": {},
"search": "assets/javascripts/workers/search.217ffd95.min.js" | url,
"version": config.extra.version or None
} -%}
{%- set translations = app.translations -%}
{%- for key in [
@ -216,7 +217,7 @@
</script>
{% endblock %}
{% block scripts %}
<script src="{{ 'assets/javascripts/bundle.f3bc8daa.min.js' | url }}"></script>
<script src="{{ 'assets/javascripts/bundle.d349485d.min.js' | url }}"></script>
{% for path in config["extra_javascript"] %}
<script src="{{ path | url }}"></script>
{% endfor %}

View File

@ -53,5 +53,5 @@
{% endblock %}
{% block scripts %}
{{ super() }}
<script src="{{ 'overrides/assets/javascripts/bundle.c0db31d2.min.js' | url }}"></script>
<script src="{{ 'overrides/assets/javascripts/bundle.711bd5c6.min.js' | url }}"></script>
{% endblock %}

View File

@ -64,6 +64,13 @@ export type Translations = Record<Translation, string>
/* ------------------------------------------------------------------------- */
/**
* Versioning
*/
export interface Versioning {
provider: "mike" /* Version provider */
}
/**
* Configuration
*/
@ -72,6 +79,7 @@ export interface Config {
features: Flag[] /* Feature flags */
translations: Translations /* Translations */
search: string /* Search worker URL */
version?: Versioning /* Versioning */
}
/* ----------------------------------------------------------------------------

View File

@ -41,9 +41,9 @@ import {
* @returns Response observable
*/
export function request(
url: string, options: RequestInit = { credentials: "same-origin" }
url: URL | string, options: RequestInit = { credentials: "same-origin" }
): Observable<Response> {
return from(fetch(url, options))
return from(fetch(url.toString(), options))
.pipe(
filter(res => res.status === 200),
)
@ -60,7 +60,7 @@ export function request(
* @returns Data observable
*/
export function requestJSON<T>(
url: string, options?: RequestInit
url: URL | string, options?: RequestInit
): Observable<T> {
return request(url, options)
.pipe(
@ -78,7 +78,7 @@ export function requestJSON<T>(
* @returns Data observable
*/
export function requestXML(
url: string, options?: RequestInit
url: URL | string, options?: RequestInit
): Observable<Document> {
const dom = new DOMParser()
return request(url, options)

View File

@ -34,6 +34,7 @@ import {
tap
} from "rxjs/operators"
import { configuration } from "~/_"
import {
resetHeaderTitleState,
setHeaderTitleState
@ -41,9 +42,15 @@ import {
import {
Viewport,
getElement,
getElementOrThrow,
getElementSize,
requestJSON,
watchViewportAt
} from "~/browser"
import {
Version,
renderVersionSelector
} from "~/templates"
import { Component } from "../../_"
import { Header } from "../_"
@ -132,6 +139,15 @@ export function mountHeaderTitle(
resetHeaderTitleState(el)
})
/* Render version selector */
const config = configuration()
if (config.version?.provider === "mike")
requestJSON<Version[]>(new URL("../versions.json", config.base))
.subscribe(versions => {
const topic = getElementOrThrow(".md-header__topic", el)
topic.appendChild(renderVersionSelector(versions))
})
/* Obtain headline, if any */
const headline = getElement<HTMLHeadingElement>("article h1")
if (typeof headline === "undefined")

View File

@ -277,7 +277,7 @@ export function setupInstantLoading(
/* Components */
"[data-md-component=announce]",
"[data-md-component=header-title]",
"[data-md-component=header-topic]",
"[data-md-component=container]",
"[data-md-component=skip]"
]) {

View File

@ -24,3 +24,4 @@ export * from "./clipboard"
export * from "./search"
export * from "./source"
export * from "./table"
export * from "./version"

View File

@ -0,0 +1,80 @@
/*
* 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 { configuration } from "~/_"
import { h } from "~/utilities"
/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
/**
* Version
*/
export interface Version {
version: string /* Version identifier */
title: string /* Version title */
aliases: string[] /* Version aliases */
}
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Render a version selector
*
* @param versions - Versions
*
* @returns Element
*/
export function renderVersionSelector(versions: Version[]): HTMLElement {
const config = configuration()
/* Determine active version */
const [, current] = config.base.match(/([^/]+)\/?$/)!
const active =
versions.find(({ version, aliases }) => (
version === current || aliases.includes(current)
)) || versions[0]
/* Render version selector */
return (
<div class="md-version">
<span class="md-version__current">
{active.version}
</span>
<ul class="md-version__list">
{versions.map(version => (
<li class="md-version__item">
<a
class="md-version__link"
href={`${new URL(version.version, config.base)}`}
>
{version.title}
</a>
</li>
))}
</ul>
</div>
)
}

View File

@ -55,6 +55,7 @@
@import "main/layout/sidebar";
@import "main/layout/source";
@import "main/layout/tabs";
@import "main/layout/version";
@import "main/extensions/markdown/admonition";
@import "main/extensions/markdown/footnotes";

View File

@ -0,0 +1,139 @@
////
/// 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
// ----------------------------------------------------------------------------
// Icon definitions
:root {
--md-version-icon: svg-load("fontawesome/solid/caret-down.svg");
}
// ----------------------------------------------------------------------------
// Version selection
.md-version {
flex-shrink: 0;
height: px2rem(48px);
font-size: px2rem(16px);
// Current selection
&__current {
position: relative;
// Hack: in general, we would use `vertical-align` to align the version at
// the bottom with the title, but since the list uses absolute positioning,
// this won't work consistently. Furthermore, we would need to use inline
// positioning to align the links, which looks jagged.
top: px2rem(1px);
margin-right: px2rem(8px);
margin-left: px2rem(28px);
// Adjust for right-to-left languages
[dir="rtl"] & {
margin-right: px2rem(28px);
margin-left: px2rem(8px);
}
// Version selection icon
&::after {
display: inline-block;
width: px2rem(8px);
height: px2rem(12px);
margin-left: px2rem(8px);
background-color: currentColor;
mask-image: var(--md-version-icon);
mask-repeat: no-repeat;
content: "";
// Adjust for right-to-left languages
[dir="rtl"] & {
margin-right: px2rem(8px);
margin-left: initial;
}
}
}
// Version selection list
&__list {
position: absolute;
top: px2rem(3px);
z-index: 1;
max-height: px2rem(36px);
margin: px2rem(4px) px2rem(16px);
padding: 0;
overflow: auto;
color: var(--md-default-fg-color);
list-style-type: none;
background-color: var(--md-default-bg-color);
border-radius: px2rem(2px);
box-shadow:
0 px2rem(4px) px2rem(10px) hsla(0, 0%, 0%, 0.1),
0 0 px2rem(1px) hsla(0, 0%, 0%, 0.25);
opacity: 0;
transition:
max-height 0ms 500ms,
opacity 250ms 250ms;
scroll-snap-type: y mandatory;
// List on focus/hover
&:focus-within,
&:hover {
max-height: px2rem(200px);
opacity: 1;
transition:
max-height 250ms,
opacity 250ms;
}
}
// Version selection item
&__item {
line-height: px2rem(36px);
}
// Version selection link
&__link {
display: block;
width: 100%;
padding-right: px2rem(24px);
padding-left: px2rem(12px);
white-space: nowrap;
cursor: pointer;
transition:
color 250ms,
background-color 250ms;
scroll-snap-align: start;
// Adjust for right-to-left languages
[dir="rtl"] & {
padding-right: px2rem(12px);
padding-left: px2rem(24px);
}
// Link on focus/hover
&:focus,
&:hover {
background-color: var(--md-default-fg-color--lightest);
}
}
}

View File

@ -356,6 +356,7 @@
"features": features,
"translations": {},
"search": "assets/javascripts/workers/search.js" | url,
"version": config.extra.version or None
} -%}
<!-- Translations -->