feat(documentation): svelte app as document generator (#566)

* check in full site application

* rm unused assets components

* change base css

* i18n

* new doc workflow

* fix crosslink to doc in github

* nav, console link, assets, console brand

* edit configjs server

* rm go struct references

* cleanup input output bindings

* working dir

* export path

* always trigger

* rel paths

* cname

* rev workdir

* fix fallbacklanguage, home on large screens

* remove mit from site
This commit is contained in:
Max Peintner
2020-08-11 09:53:09 +02:00
committed by GitHub
parent 5efcd7b2f9
commit 2c517d6278
70 changed files with 6483 additions and 0 deletions

View File

@@ -0,0 +1,121 @@
<script>
let tabs = [
{lang: 'yaml', content:"main: halllo"},
{lang: 'js', content:"console.log();"},
{lang: 'html', content:"<p>hello</p>"},
]
</script>
<style>
.pc-tab > input,
.pc-tab section > div {
display: none;
}
#tab1:checked ~ section .tab1,
#tab2:checked ~ section .tab2,
#tab3:checked ~ section .tab3 {
display: block;
}
#tab1:checked ~ nav .tab1,
#tab2:checked ~ nav .tab2,
#tab3:checked ~ nav .tab3 {
color: red;
}
/* Visual Styles */
*, *:after, *:before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.pc-tab {
width: 100%;
max-width: 700px;
margin: 0 auto;
}
.pc-tab ul {
list-style: none;
margin: 0;
padding: 0;
}
.pc-tab ul li label {
float: left;
padding: 10px 25px;
border: 1px solid #8795a1;
border-bottom: 0;
background: #212224;
}
.pc-tab ul li:first-child label {
border-top-left-radius: 8px;
}
.pc-tab ul li:last-child label {
border-top-right-radius: 8px;
}
.pc-tab ul li label:hover {
background: #ffffff20;
}
.pc-tab ul li label:active {
background: #ffffff30;
}
.pc-tab ul li:not(:last-child) label {
border-right-width: 0;
}
.pc-tab section {
clear: both;
}
.pc-tab section div {
padding: 20px;
width: 100%;
border: 1px solid #8795a1;
background: #1e1e1e;
line-height: 1.5em;
letter-spacing: 0.3px;
}
#tab1:checked ~ nav .tab1 label,
#tab2:checked ~ nav .tab2 label,
#tab3:checked ~ nav .tab3 label {
background: #1e1e1e;
position: relative;
color: white;
}
#tab1:checked ~ nav .tab1 label:after,
#tab2:checked ~ nav .tab2 label:after,
#tab3:checked ~ nav .tab3 label:after {
content: '';
display: block;
position: absolute;
height: 2px;
width: 100%;
background: #1e1e1e;
left: 0;
bottom: -1px;
}
</style>
<div class="pc-tab">
<input checked id="tab1" type="radio" name="pct" />
<input id="tab2" type="radio" name="pct" />
<input id="tab3" type="radio" name="pct" />
<nav>
<ul>
{#each tabs as { lang }, i}
<li class="tab{i+1}">
<label for="tab{i+1}">{lang}</label>
</li>
{/each}
</ul>
</nav>
<section>
{#each tabs as { lang, content }, i}
<div class="tab{i+1}">
{content}
</div>
{/each}
</section>
</div>

View File

@@ -0,0 +1,428 @@
<script>
import { onMount } from "svelte";
import GuideContents from "./GuideContents.svelte"; // TODO rename
import Icon from "./Icon.svelte";
import manifest from '../../static/manifest.json';
export let owner = "caos";
export let project = "zitadel/site";
export let path = "/docs";
export let dir = "";
export let edit_title = "edit this section";
export let sections;
let active_section;
let container;
let aside;
let show_contents = false;
onMount(() => {
// don't update `active_section` for headings above level 4, see _sections.js
const anchors = container.querySelectorAll("[id]:not([data-scrollignore])");
let positions;
const onresize = () => {
const { top } = container.getBoundingClientRect();
positions = [].map.call(anchors, anchor => {
return anchor.getBoundingClientRect().top - top;
});
};
let last_id = window.location.hash.slice(1);
const onscroll = () => {
const top = -window.scrollY;
let i = anchors.length;
while (i--) {
if (positions[i] + top < 40) {
const anchor = anchors[i];
const { id } = anchor;
if (id !== last_id) {
active_section = id;
last_id = id;
}
return;
}
}
};
window.addEventListener("scroll", onscroll, true);
window.addEventListener("resize", onresize, true);
// wait for fonts to load...
const timeouts = [setTimeout(onresize, 1000), setTimeout(onscroll, 5000)];
onresize();
onscroll();
return () => {
window.removeEventListener("scroll", onscroll, true);
window.removeEventListener("resize", onresize, true);
timeouts.forEach(timeout => clearTimeout(timeout));
};
});
</script>
<style>
aside {
position: fixed;
background-color: var(--side-nav-back);
left: 0.8rem;
bottom: 0.8rem;
width: 2em;
height: 2em;
border-radius: .5rem;
overflow: hidden;
border: 1px solid #8795a1;
box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.1);
transition: width 0.2s, height 0.2s;
}
aside button {
position: absolute;
bottom: 0;
left: 0;
width: 3.4rem;
height: 3.4rem;
}
aside.open {
width: calc(100vw - 3rem);
height: calc(100vh - var(--nav-h) - 3rem);
}
aside.open::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: calc(100% - 2rem);
height: 2em;
pointer-events: none;
z-index: 2;
}
aside::after {
content: "";
position: absolute;
left: 0;
bottom: 1.9em;
width: calc(100% - 2rem);
height: 2em;
/* background: linear-gradient(
to bottom,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.7) 50%,
rgba(255, 255, 255, 1) 100%
); */
pointer-events: none;
}
.sidebar {
position: absolute;
font-family: var(--font);
overflow-y: auto;
width: 100%;
height: 100%;
padding: 4em 1.6rem 2em 3.2rem;
bottom: 2em;
}
.content {
width: 100%;
margin: 0;
padding: var(--top-offset) var(--side-nav);
tab-size: 2;
-moz-tab-size: 2;
}
@media (min-width: 832px) {
/* can't use vars in @media :( */
aside {
display: block;
width: var(--sidebar-w);
height: 100vh;
top: 0;
left: 0;
overflow: hidden;
box-shadow: none;
border: none;
overflow: hidden;
background-color: var(--side-nav-back);
color: white;
}
aside.open::before {
display: none;
}
aside::after {
content: "";
bottom: 0;
height: var(--top-offset);
/* background: linear-gradient(
to bottom,
rgba(103, 103, 120, 0) 0%,
rgba(103, 103, 120, 0.7) 50%,
rgba(103, 103, 120, 1) 100%
); */
}
aside button {
display: none;
}
.sidebar {
padding: var(--top-offset) 0 6.4rem 3.2rem;
font-family: var(--font);
overflow-y: auto;
height: 100%;
bottom: auto;
width: 100%;
}
.content {
padding-left: calc(var(--sidebar-w) + var(--side-nav));
}
.content :global(.side-by-side) {
display: grid;
grid-template-columns: calc(50% - 0.5em) calc(50% - 0.5em);
grid-gap: 1em;
}
.content :global(.side-by-side) :global(.code) {
padding: 1em 0;
}
}
.content h2 {
margin-top: 8rem;
padding: 2rem 1.6rem 4rem 0.2rem;
border-top: var(--border-w) solid #6767785b; /* based on --second */
color: var(--text);
line-height: 1;
font-size: var(--h3);
letter-spacing: 0.05em;
text-transform: uppercase;
}
.content section:first-of-type > h2 {
margin-top: -8rem;
border-top: none;
}
.content :global(h4) {
margin: 2em 0 1em 0;
}
.content :global(.offset-anchor) {
position: relative;
display: block;
top: calc(-1 * (var(--nav-h) + var(--top-offset) - 1rem));
width: 0;
height: 0;
}
.content :global(.anchor) {
position: absolute;
display: block;
/* TODO replace link icon */
/* background: url(../icons/link.svg) 0 50% no-repeat; */
background-size: 1em 1em;
width: 1.4em;
height: 1em;
left: -1.3em;
opacity: 0;
color: white;
transition: opacity 0.2s;
border: none !important; /* TODO get rid of linkify */
}
.content :global(.anchor) :global(i) {
color: #8795a1;
font-size: 24px;
}
@media (min-width: 768px) {
.content :global(h2):hover :global(.anchor),
.content :global(h3):hover :global(.anchor),
.content :global(h4):hover :global(.anchor),
.content :global(h5):hover :global(.anchor),
.content :global(h6):hover :global(.anchor) {
opacity: 1;
}
.content :global(h5) :global(.anchor),
.content :global(h6) :global(.anchor) {
top: 0.2em;
}
}
.content :global(h3),
.content :global(h3 > code) {
margin: 6.4rem 0 0 0;
padding: 2rem 1.6rem 5.6rem 0.2rem;
color: var(--text);
border-top: var(--border-w) solid #6767781f; /* based on --second */
background: transparent;
line-height: 1;
}
.content :global(h3):first-of-type {
border: none;
margin: 0;
}
/* avoid doubled border-top */
.content :global(h3 > code) {
border-radius: 0 0 0 0;
border: none;
font-size: inherit;
font-weight: 700;
}
.content :global(h4),
.content :global(h4 > code) {
font-family: inherit;
font-weight: 500;
font-size: 2.4rem;
color: var(--text);
margin: 6.4rem 0 1.6rem 0;
padding-left: 0;
background: transparent;
line-height: 1;
padding: 0;
top: 0;
}
.content :global(h4 > em) {
opacity: 0.7;
}
.content :global(h5) {
font-size: 2.4rem;
margin: 2em 0 0.5em 0;
}
.content :global(code) {
padding: 0.3rem 0.8rem 0.3rem;
margin: 0 0.2rem;
top: -0.1rem;
background: var(--back-api);
}
.content :global(pre) :global(code) {
padding: 0;
margin: 0;
top: 0;
background: transparent;
}
.content :global(pre) {
margin: 0 0 2em 0;
width: 100%;
max-width: 100%;
}
.content :global(.icon) {
width: 2rem;
height: 2rem;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
fill: none;
}
.content :global(table) {
margin: 0 0 2em 0;
}
section > :global(.code-block) > :global(pre) {
display: inline-block;
/* background: var(--back-api); */
color: white;
padding: 0.3rem 0.8rem;
margin: 0;
max-width: 100%;
}
section > :global(.code-block) > :global(pre.language-markup) {
padding: 0.3rem 0.8rem 0.2rem;
background: var(--back-api);
}
section > :global(p) {
max-width: var(--linemax);
}
section :global(p) {
margin: 1em 0;
}
small {
font-size: var(--h5);
float: right;
pointer-events: all;
color: var(--prime);
cursor: pointer;
}
/* no linkify on these */
small a {
all: unset;
}
small a:before {
all: unset;
}
section :global(blockquote) {
color: #e91e63;
border: 2px solid var(--flash);
}
section :global(blockquote) :global(code) {
background: hsl(204, 100%, 95%) !important;
color: #e91e63;
}
</style>
<div bind:this={container} class="content listify">
{#each sections as section}
<section data-id={section.slug}>
<h2>
<span class="offset-anchor" id={section.slug} />
<!-- svelte-ignore a11y-missing-content -->
<a href="{dir}#{section.slug}" class="anchor" aria-hidden />
{@html section.metadata.title}
<small>
<a
href="https://github.com/{owner}/{project}/edit/master{path}/{dir}/{section.file}"
title={edit_title}>
<Icon name="las la-external-link-alt" size="24px" />
</a>
</small>
</h2>
{@html section.html}
</section>
{/each}
</div>
<aside bind:this={aside} class="sidebar-container" class:open={show_contents}>
<div class="sidebar" on:click={() => (show_contents = false)}>
<!-- scroll container -->
<GuideContents {dir} {sections} {active_section} {show_contents} />
</div>
<button on:click={() => (show_contents = !show_contents)}>
<Icon name={show_contents ? 'las la-window-close' : 'las la-bars'} />
</button>
</aside>

View File

@@ -0,0 +1,142 @@
<script>
import { afterUpdate } from 'svelte';
import Icon from './Icon.svelte';
import CodeTable from './CodeTable.svelte';
export let dir = '';
export let sections = [];
export let active_section = null;
export let show_contents;
export let prevent_sidebar_scroll = false;
let ul;
afterUpdate(() => {
// bit of a hack — prevent sidebar scrolling if
// TOC is open on mobile, or scroll came from within sidebar
if (prevent_sidebar_scroll || show_contents && window.innerWidth < 832) return;
const active = ul.querySelector('.active');
if (active) {
const { top, bottom } = active.getBoundingClientRect();
const min = 200;
const max = window.innerHeight - 200;
if (top > max) {
ul.parentNode.scrollBy({
top: top - max,
left: 0,
behavior: 'smooth'
});
} else if (bottom < min) {
ul.parentNode.scrollBy({
top: bottom - min,
left: 0,
behavior: 'smooth'
});
}
}
});
</script>
<style>
.reference-toc li {
display: block;
line-height: 1.2;
margin: 0 0 4rem 0;
}
a {
position: relative;
transition: color 0.2s;
border-bottom: none;
padding: 0;
color: var(--dark-text);
}
.section {
display: block;
padding: 0 0 .8rem 0;
font-size: var(--h6);
text-transform: uppercase;
letter-spacing: 0.1em;
font-weight: 600;
}
.subsection {
display: block;
font-size: 1.6rem;
font-family: var(--font);
padding: 0 0 0.6em 0;
}
.section:hover,
.subsection:hover,
.active {
color: var(--flash);
font-weight: 500;
}
.subsection[data-level="4"] {
padding-left: 1.2rem;
}
.icon-container {
position: absolute;
top: -.2rem;
right: 2.4rem;
}
@media (min-width: 832px) {
a {
color: var(--sidebar-text);
}
a:hover,
.section:hover,
.subsection:hover,
.active {
color: #5282c1;
}
}
</style>
<ul
bind:this={ul}
class="reference-toc"
on:mouseenter="{() => prevent_sidebar_scroll = true}"
on:mouseleave="{() => prevent_sidebar_scroll = false}"
>
{#each sections as section}
<li>
<a class="section" class:active="{section.slug === active_section}" href="{dir}#{section.slug}">
{@html section.metadata.title}
{#if section.slug === active_section}
<div class="icon-container">
<Icon name="las la-arrow-right" />
</div>
{/if}
</a>
{#each section.subsections as subsection}
<!-- see <script> below: on:click='scrollTo(event, subsection.slug)' -->
<a
class="subsection"
class:active="{subsection.slug === active_section}"
href="{dir}#{subsection.slug}"
data-level="{subsection.level}"
>
{@html subsection.title}
{#if subsection.slug === active_section}
<div class="icon-container">
<Icon name="las la-arrow-right" />
</div>
{/if}
</a>
{/each}
</li>
{/each}
</ul>

View File

@@ -0,0 +1,20 @@
<script>
export let name;
export let size = '20px';
</script>
<style>
:global(i) {
font-size: 20px;
height: 20px;
position: relative;
overflow: hidden;
vertical-align: middle;
-o-object-fit: contain;
object-fit: contain;
-webkit-transform-origin: center center;
transform-origin: center center;
}
</style>
<i class={name} style="font-size: {size}; height: {size};"><use xlink:href="#{name}" /></i>

View File

@@ -0,0 +1,65 @@
<script context="module">
import { setCookie } from '../modules/cookie.js';
import { docLanguages } from '../modules/language-store.js'
import {LANGUAGES} from '../../config.js';
</script>
<script>
import { locale } from 'svelte-i18n';
import { startClient } from '../i18n.js';
let group= $locale;
$:setLocale(group);
function setLocale(language) {
if (typeof window !== 'undefined') {
locale.set(language);
}
}
</script>
<style>
:root {
--deep-blue: #1e3470;
--speed3: cubic-bezier(0.26, 0.48, 0.08, 0.9);
--height: 30px;
}
.language-switcher {
display: flex;
align-items: center;
position: relative;
}
.language-switcher input {
appearance: none;
display: none;
}
.language-switcher .select {
height: var(--height);
width: var(--height);
border-radius: 50vw;
font-size: calc(var(--height) / 2.5);
color: #fff;
mix-blend-mode: difference;
display: flex;
align-items: center;
cursor: pointer;
justify-content: center;
}
.language-switcher .current {
background-color: white;
color: black;
}
</style>
<div class="language-switcher">
{#each LANGUAGES as lang}
<label class="select {lang == group ? 'current' : 'notcurrent'}">
<input type=radio bind:group value={lang}>
{lang.toUpperCase()}
</label>
{/each}
</div>

View File

@@ -0,0 +1,168 @@
<script>
import LanguageSwitcher from './LanguageSwitcher.svelte'
import NavItem from './NavItem.svelte'
import { onMount, setContext } from 'svelte';
import { writable } from 'svelte/store';
import Icon from './Icon.svelte';
export let segment;
export let logo;
export let title;
import { _ } from 'svelte-i18n';
const current = writable(null);
setContext('nav', current);
let visible = true;
let hash_changed = false;
function handle_hashchange() {
hash_changed = true;
}
let last_scroll = 0;
function handle_scroll() {
const scroll = window.pageYOffset;
if (!hash_changed) {
visible = (scroll < 50 || scroll < last_scroll);
}
last_scroll = scroll;
hash_changed = false;
}
$: $current = segment;
</script>
<style>
header {
box-sizing: border-box;
position: fixed;
display: flex;
align-items: center;
justify-content: space-between;
width: 100vw;
height: var(--nav-h);
padding: 0 3rem;
margin: 0 auto;
box-shadow: 0 -0.4rem 0.9rem 0.2rem rgba(0,0,0,.5);
z-index: 100;
user-select: none;
transform: translate(0,calc(-100% - 1rem));
transition: transform 0.2s;
backdrop-filter: saturate(100%) blur(10px);
}
header.visible {
transform: none;
}
nav {
box-sizing: border-box;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: var(--nav-h);
padding: 0 3rem 0 3rem;
display: flex;
align-items: center;
background-color: transparent;
transform: none;
transition: none;
box-shadow: none;
}
.fill-space {
flex: 1;
}
.home {
width: 200px;
line-height: 22px;
font-size: 22px;
display: none;
}
.home:hover {
color: inherit;
text-decoration:none;
border: none;
}
.home img {
display: block;
}
a {
color: inherit;
border-bottom: none;
transition: none;
}
@media (min-width: 400px) {
.home {
display: inline-block;
}
}
a img {
max-height: 40px;
}
.switcher-wrapper {
padding: 0 1rem;
}
button {
display: flex;
align-items: center;
border-radius: 8px;
border: 1px solid hsla(0,0%,100%,.12);
box-shadow: 0 0 0 0 rgba(0,0,0,.2), 0 0 0 0 rgba(0,0,0,.14), 0 0 0 0 rgba(0,0,0,.12);
padding: 0 15px;
height: 36px;
color: var(--prime);
transition: background-color .2 ease;
margin: 0 1rem;
min-width: 120px;
}
button:hover {
background-color: #5282c110;
}
button:active {
background-color: #5282c120;
}
button span {
font-size: 14px;
line-height: 14px;
}
</style>
<svelte:window on:hashchange={handle_hashchange} on:scroll={handle_scroll} />
<header class:visible="{visible}">
<nav>
<a
rel="prefetch"
href="."
class="home"
title="{title}"
>
{#if logo}
<img src={logo} alt={title} />
{:else if title}
{title}
{/if}
</a>
<span class="fill-space"></span>
<a href='https://console.zitadel.ch'><button>
<span>{$_('toconsole')}</span>
</button>
</a>
<NavItem external="https://github.com/caos" title="GitHub Repo">
<Icon name="lab la-github" size="24px"></Icon>
</NavItem>
<div class="switcher-wrapper">
<LanguageSwitcher></LanguageSwitcher>
</div>
</nav>
</header>

View File

@@ -0,0 +1,30 @@
<style>
a {
display: flex;
align-items: center;
color: var(--text);
text-decoration: none;
border: none;
padding: 0;
}
a:hover {
text-decoration: none;
border: none;
padding: 0;
}
</style>
<script>
import { getContext } from 'svelte';
export let segment = null;
export let external = null;
export let title = '';
const current = getContext('nav');
</script>
{#if external}
<a href={external}><slot></slot></a>
{:else}
<a rel="prefetch" alt="{title}" href={segment}><slot></slot></a>
{/if}

View File

@@ -0,0 +1,15 @@
<style>
section {
position: relative;
margin: 10rem auto;
padding: 0 var(--side-nav);
max-width: 120rem;
}
section :global(h3) {
color: var(--text);
}
</style>
<section>
<slot></slot>
</section>

View File

@@ -0,0 +1,82 @@
<style>
.split {
display: grid;
grid-row-gap: 1em;
grid-template-areas:
"one"
"two"
"three"
"what"
"how";
}
.split :global(p) {
font-size: var(--h5);
}
.how {
/* needed to prevent the <pre> from
breaking the grid layout */
min-width: 0;
}
.how :global(.cta) :global(a) {
display: inline-block;
text-align: right;
background-color: var(--prime);
padding: 0.5em 1.8em 0.5em 1em;
border-radius: 16px;
color: white;
position: relative;
}
.how :global(.cta) :global(a)::after {
right: 0.5em;
top: 0.75em;
}
.what {
margin: 2em 0 0 0;
}
.how :global(.cta) {
margin: 0;
}
@media (min-width: 900px) {
.split {
grid-column-gap: 1em;
grid-row-gap: 1em;
grid-template-columns: repeat(2, 1fr);
grid-template-areas:
"one two"
"three how"
"what what";
}
}
@media (min-width: 1200px) {
.split {
grid-column-gap: 1em;
grid-row-gap: 5em;
grid-template-columns: repeat(6, 1fr);
grid-template-areas:
"one one two two three three"
"what what what how how how";
}
.what {
margin: 0;
}
}
</style>
<div class="split">
<div class="what" style="grid-area: what;">
<slot name="what"></slot>
</div>
<div class="how" style="grid-area: how;">
<slot name="how"></slot>
</div>
</div>