integrate jsonschema2markdown; add support for inline markdown rendering from templates

This commit is contained in:
2023-05-13 20:31:00 -07:00
parent 4061b40eb4
commit 699c018825
19 changed files with 1793 additions and 142 deletions

View File

@@ -0,0 +1,175 @@
(() => {
const animation = 'linear .5s';
const color_scheme = 'color_scheme';
const color_scheme_attr = 'data-color-scheme';
const transition_attr = 'data-color-transition-enabled';
const prefers_dark_scheme = window.matchMedia('(prefers-color-scheme: dark)');
// Make sure we set the correct starting color scheme where loading
const override = localStorage.getItem(color_scheme);
if (override) {
document.body.setAttribute(color_scheme_attr, override);
}
setTimeout(() => {
if (document.body.scrollHeight > 30000) {
console.warn('document too large, disabling color theme transition animation');
return;
}
document.body.setAttribute(transition_attr, '');
}, 50);
const size = 1.25;
const styles = `
:host {
display: contents;
}
:host .wrapper {
width: ${size}rem;
height: ${size}rem;
padding: 0.5rem;
border-radius: 100%;
cursor: pointer;
overflow: hidden;
position: relative;
}
:host .wrapper .icons {
width: ${size * 2}rem;
display: flex;
flex-direction: row;
justify-content: space-between;
transition: transform ${animation};
pointer-events: none;
}
:host .wrapper .icons[data-mode='light'] {
/* */
}
:host .wrapper .icons[data-mode='dark'] {
transform: translateX(-${size}rem);
}
:host svg.icon {
--icon-size: ${size}rem;
transition: opacity ${animation};
}
:host svg.icon.sun {
color: var(--theme-text-body);
}
:host [data-mode='dark'] svg.icon.sun {
opacity: 0;
}
:host svg.icon.moon {
color: var(--theme-text-body);
}
:host [data-mode='light'] svg.icon.moon {
opacity: 0;
}
`;
const template = `
<style>${styles}</style>
<div class="wrapper" title="Toggle Light / Dark Mode" tabindex="0" role="button" aria-pressed="false">
<div class="icons">
{{{ icons.sun }}}
{{{ icons.moon }}}
</div>
</div>
`;
customElements.define('color-scheme-toggle-button',
class ColorSchemeToggleButton extends HTMLElement {
#wrapper = null;
#icons = null;
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = template;
this.#wrapper = this.shadowRoot.querySelector('.wrapper');
this.#icons = this.shadowRoot.querySelector('.icons');
this.update_icons();
}
connectedCallback() {
this.addEventListener('click', this.onSelect);
this.addEventListener('keydown', this.onKeydown);
this.addEventListener('keyup', this.onKeyup);
}
disconnectedCallback() {
this.removeEventListener('click', this.onSelect);
this.removeEventListener('keydown', this.onKeydown);
this.removeEventListener('keyup', this.onKeyup);
}
onKeydown = (/** @type KeyboardEvent */ event) => {
if (event.keyCode === 32) {
event.preventDefault();
}
if (event.keyCode === 13) {
event.preventDefault();
this.onSelect();
}
};
onKeyup = (/** @type KeyboardEvent */ event) => {
if (event.keyCode === 32) {
event.preventDefault();
this.onSelect();
}
};
onSelect = () => {
toggle();
this.update_icons();
};
update_icons() {
const current = get_current();
this.#icons.setAttribute('data-mode', current);
this.#wrapper.setAttribute('aria-pressed', current === 'dark');
}
}
);
function get_current() {
const override = localStorage.getItem(color_scheme);
if (override) {
return override;
}
return prefers_dark_scheme.matches ? 'dark' : 'light';
}
function toggle() {
if (document.body.hasAttribute(color_scheme_attr)) {
localStorage.removeItem(color_scheme);
document.body.removeAttribute(color_scheme_attr);
}
else {
const preference = prefers_dark_scheme.matches ? 'light' : 'dark';
localStorage.setItem(color_scheme, preference);
document.body.setAttribute(color_scheme_attr, preference);
}
}
})();

View File

@@ -0,0 +1,129 @@
(() => {
const size = 1.25;
const outline_attr = 'data-show-outline';
const styles = `
:host {
display: contents;
}
:host .wrapper {
width: ${size}rem;
height: ${size}rem;
padding: 0.5rem;
border-radius: 100%;
cursor: pointer;
overflow: hidden;
position: relative;
}
:host svg.icon {
--icon-size: ${size}rem;
color: var(--theme-text-body);
position: absolute;
top: 0.5rem;
left: 0.5rem;
pointer-events: none;
transition: color linear .5s;
}
`;
const template = `
<style>${styles}</style>
<div class="wrapper" title="Toggle Outline" tabindex="0" role="button" aria-pressed="false">
{{{ icons.list }}}
</div>
<nav id="outline-panel" aria-hidden="true">
<!-- todo: i18n -->
<h2>Outline</h2>
</nav>
`;
customElements.define('outline-button',
class OutlineButton extends HTMLElement {
#wrapper = null;
#outline = null;
#outline_built = false;
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = template;
this.#wrapper = this.shadowRoot.querySelector('.wrapper');
this.#outline = this.shadowRoot.querySelector('#outline-panel');
this.#outline.parentNode.removeChild(this.#outline);
document.body.appendChild(this.#outline);
}
connectedCallback() {
this.addEventListener('click', this.onSelect);
this.addEventListener('keydown', this.onKeydown);
this.addEventListener('keyup', this.onKeyup);
}
disconnectedCallback() {
this.removeEventListener('click', this.onSelect);
this.removeEventListener('keydown', this.onKeydown);
this.removeEventListener('keyup', this.onKeyup);
}
onKeydown = (/** @type KeyboardEvent */ event) => {
if (event.keyCode === 32) {
event.preventDefault();
}
if (event.keyCode === 13) {
event.preventDefault();
this.onSelect();
}
};
onKeyup = (/** @type KeyboardEvent */ event) => {
if (event.keyCode === 32) {
event.preventDefault();
this.onSelect();
}
};
onSelect = () => {
if (! this.#outline_built) {
const root_selector = this.getAttribute('content-root');
this.#outline.innerHTML += `<ol>${build_outline(root_selector)}</ol>`;
this.#outline_built = true;
}
if (document.body.hasAttribute(outline_attr)) {
document.body.removeAttribute(outline_attr);
this.#outline.setAttribute('aria-hidden', 'true');
this.#wrapper.setAttribute('aria-pressed', 'false');
}
else {
document.body.setAttribute(outline_attr, '');
this.#outline.removeAttribute('aria-hidden');
this.#wrapper.setAttribute('aria-pressed', 'true');
}
};
}
);
function build_outline(root_selector) {
const root = document.querySelector(root_selector);
const headings = root.querySelectorAll('h1, h2, h3, h4, h5, h6');
const outline = [ ];
for (const heading of headings) {
outline.push(`
<li data-depth="${heading.tagName.toLowerCase()}">
<a href="#${heading.id}">${heading.innerText}</a>
</li>
`);
}
return outline.join('');
}
})();

View File

@@ -0,0 +1,105 @@
(() => {
const template = `
<style>
ol {
list-style: none;
padding: 0;
}
li[data-depth='h1'] {
font-size: 1.2rem;
font-weight: 700;
margin-inline-start: 1rem;
}
li[data-depth='h2'] {
font-size: 1.1rem;
font-weight: 600;
margin-inline-start: 2rem;
}
li[data-depth='h3'] {
margin-inline-start: 3rem;
}
li[data-depth='h4'] {
font-size: 0.9rem;
margin-inline-start: 4rem;
}
li[data-depth='h5'] {
font-size: 0.9rem;
margin-inline-start: 5rem;
}
li[data-depth='h6'] {
font-size: 0.9rem;
margin-inline-start: 6rem;
}
a {
color: var(--theme-text-link);
}
a:active,
a:hover,
a:focus {
color: var(--theme-text-link-active);
}
a:visited {
color: var(--theme-text-link-visited);
}
</style>
<nav id="outline-inline" aria-hidden="true">
<em>Outline will render when the page has loaded...</em>
</nav>
`;
customElements.define('outline-inline',
class OutlineButton extends HTMLElement {
#outline = null;
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = template;
this.#outline = this.shadowRoot.querySelector('#outline-inline');
}
connectedCallback() {
if (document.readyState === 'complete') {
this.#render();
}
window.addEventListener('DOMContentLoaded', () => {
this.#render();
});
}
#render() {
const root_selector = this.getAttribute('content-root');
const outline = build_outline(root_selector);
this.#outline.innerHTML = `<ol>${outline}</ol>`;
}
}
);
function build_outline(root_selector) {
const root = document.querySelector(root_selector);
const headings = root.querySelectorAll('h1, h2, h3, h4, h5, h6');
const outline = [ ];
for (const heading of headings) {
outline.push(`
<li data-depth="${heading.tagName.toLowerCase()}">
<a href="#${heading.id}">${heading.innerText}</a>
</li>
`);
}
return outline.join('');
}
})();