From 699c0188252b3e36a6f77a9dae7ca9daf657904d Mon Sep 17 00:00:00 2001 From: James Brumond Date: Sat, 13 May 2023 20:31:00 -0700 Subject: [PATCH] integrate jsonschema2markdown; add support for inline markdown rendering from templates --- .gitignore | 2 - .../components/color-scheme-toggle-button.js | 175 +++++++ extras/components/outline-button.js | 129 +++++ extras/components/outline-inline.js | 105 ++++ extras/prism.css | 468 ++++++++++++++++++ package-lock.json | 124 +++-- package.json | 6 +- sample-config.yaml | 62 --- src/bin/docs2website.ts | 2 +- src/build.ts | 300 ++++++++++- src/conf.ts | 27 +- src/fs.ts | 70 +++ src/icons.ts | 23 + src/index.ts | 1 + src/template.ts | 58 ++- src/themes.ts | 64 +++ vendor/feather-icons/icons.json | 289 +++++++++++ vendor/feather-icons/license | 24 + vendor/feather-icons/readme.md | 6 + 19 files changed, 1793 insertions(+), 142 deletions(-) create mode 100644 extras/components/color-scheme-toggle-button.js create mode 100644 extras/components/outline-button.js create mode 100644 extras/components/outline-inline.js create mode 100644 extras/prism.css delete mode 100644 sample-config.yaml create mode 100644 src/fs.ts create mode 100644 src/icons.ts create mode 100644 src/themes.ts create mode 100644 vendor/feather-icons/icons.json create mode 100644 vendor/feather-icons/license create mode 100644 vendor/feather-icons/readme.md diff --git a/.gitignore b/.gitignore index 3618675..dd87e2d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ node_modules build -docs -www diff --git a/extras/components/color-scheme-toggle-button.js b/extras/components/color-scheme-toggle-button.js new file mode 100644 index 0000000..dbb6d65 --- /dev/null +++ b/extras/components/color-scheme-toggle-button.js @@ -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 = ` + +
+
+ {{{ icons.sun }}} + {{{ icons.moon }}} +
+
+ `; + + 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); + } + } + +})(); \ No newline at end of file diff --git a/extras/components/outline-button.js b/extras/components/outline-button.js new file mode 100644 index 0000000..07bc2df --- /dev/null +++ b/extras/components/outline-button.js @@ -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 = ` + +
+ {{{ icons.list }}} +
+ + `; + + 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 += `
    ${build_outline(root_selector)}
`; + 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(` +
  • + ${heading.innerText} +
  • + `); + } + + return outline.join(''); + } + +})(); \ No newline at end of file diff --git a/extras/components/outline-inline.js b/extras/components/outline-inline.js new file mode 100644 index 0000000..910fba6 --- /dev/null +++ b/extras/components/outline-inline.js @@ -0,0 +1,105 @@ +(() => { + + const template = ` + + + `; + + 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 = `
      ${outline}
    `; + } + } + ); + + 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(` +
  • + ${heading.innerText} +
  • + `); + } + + return outline.join(''); + } + +})(); \ No newline at end of file diff --git a/extras/prism.css b/extras/prism.css new file mode 100644 index 0000000..c68bb4a --- /dev/null +++ b/extras/prism.css @@ -0,0 +1,468 @@ +/* PrismJS 1.24.1 +https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+bash+http+jsx+tsx+typescript&plugins=line-highlight+line-numbers+treeview */ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + * + * --- + * Modified for use with customizable color variables and light/dark theming + */ + + code[class*="language-"], + pre[class*="language-"] { + color: var(--theme-code-normal); + background: none; + text-shadow: 0 1px var(--theme-code-shadow); + font-family: var(--font-monospace); + font-weight: 400; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; + } + + pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, + code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { + text-shadow: none; + background: var(--theme-code-selection); + } + + pre[class*="language-"]::selection, pre[class*="language-"] ::selection, + code[class*="language-"]::selection, code[class*="language-"] ::selection { + text-shadow: none; + background: var(--theme-code-selection); + } + + @media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } + } + + /* Code blocks */ + pre[class*="language-"] { + overflow: auto; + } + + :not(pre) > code[class*="language-"], + pre[class*="language-"] { + background: var(--theme-code-background); + } + + /* Inline code */ + :not(pre) > code[class*="language-"] { + padding: .1rem; + border-radius: .3rem; + white-space: normal; + } + + .token.comment, + .token.prolog, + .token.doctype, + .token.cdata { + color: var(--theme-code-comment); + } + + .token.punctuation { + color: var(--theme-code-punc); + } + + .token.namespace { + opacity: .7; + } + + .token.constant, + .token.symbol, + .token.deleted { + color: var(--theme-code-const-literal); + } + + .token.tag { + color: var(--theme-code-tag); + } + + .token.number { + color: var(--theme-code-number-literal); + } + + .token.boolean { + color: var(--theme-code-boolean-literal); + } + + .token.selector, + .token.attr-name { + color: var(--theme-code-attr-name); + } + + .token.builtin { + color: var(--theme-code-builtin); + } + + .token.string, + .token.char, + .token.inserted { + color: var(--theme-code-string); + } + + .token.operator, + .token.entity, + .token.url, + .language-css .token.string, + .style .token.string { + color: var(--theme-code-operator); + } + + .token.atrule, + .token.attr-value, + .token.keyword, + .token.request-line .token.method, + .token.request-line .token.http-version, + .token.response-status .token.http-version { + color: var(--theme-code-keyword); + } + + .token.function { + color: var(--theme-code-func-name); + } + + .token.class-name { + color: var(--theme-code-class-name); + } + + .token.regex, + .token.important { + color: var(--theme-code-regex-important); + } + + .token.property, + .token.variable { + color: var(--theme-code-variable); + } + + .token.important, + .token.bold { + font-weight: 700; + } + + .token.italic { + font-style: italic; + } + + .token.entity { + cursor: help; + } + + pre[data-line] { + position: relative; + padding: 1rem 0 1rem 3rem; + } + + .line-highlight { + position: absolute; + left: 0; + right: 0; + padding: inherit 0; + margin-top: 1rem; + + background: var(--theme-code-line-highlight); + + pointer-events: none; + + line-height: inherit; + white-space: pre; + } + + @media print { + .line-highlight { + /* + * This will prevent browsers from replacing the background color with white. + * It's necessary because the element is layered on top of the displayed code. + */ + -webkit-print-color-adjust: exact; + color-adjust: exact; + } + } + + .line-highlight:before, + .line-highlight[data-end]:after { + content: attr(data-start); + position: absolute; + top: .4rem; + left: .6rem; + min-width: 1rem; + padding: 0 .5rem; + background: var(--theme-code-line-highlight); + font: bold 65%/1.5 sans-serif; + text-align: center; + vertical-align: .3rem; + border-radius: 999px; + text-shadow: none; + box-shadow: 0 1px white; + } + + .line-highlight[data-end]:after { + content: attr(data-end); + top: auto; + bottom: .4rem; + } + + .line-numbers .line-highlight:before, + .line-numbers .line-highlight:after { + content: none; + } + + pre[id].linkable-line-numbers span.line-numbers-rows { + pointer-events: all; + } + + pre[id].linkable-line-numbers span.line-numbers-rows > span:before { + cursor: pointer; + } + + pre[id].linkable-line-numbers span.line-numbers-rows > span:hover:before { + /* TODO: Do something with this color */ + background-color: rgba(128, 128, 128, .2); + } + + pre[class*="language-"].line-numbers { + position: relative; + padding-left: 3.8rem; + counter-reset: linenumber; + } + + pre[class*="language-"].line-numbers > code { + position: relative; + white-space: inherit; + } + + .line-numbers .line-numbers-rows { + position: absolute; + pointer-events: none; + top: 0; + font-size: 100%; + left: -3.8rem; + width: 3rem; /* works for line-numbers below 1000 lines */ + letter-spacing: -1px; + border-right: 1px solid var(--theme-code-gutter-divider); + + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + } + + .line-numbers-rows > span { + display: block; + counter-increment: linenumber; + } + + .line-numbers-rows > span:before { + content: counter(linenumber); + color: var(--theme-code-line-number); + display: block; + padding-right: 0.8rem; + text-align: right; + } + + .token.treeview-part .entry-line { + position: relative; + text-indent: -99rem; + display: inline-block; + vertical-align: top; + width: 1.2rem; + } + + .token.treeview-part .entry-line:before, + .token.treeview-part .line-h:after { + content: ""; + position: absolute; + top: 0; + left: 50%; + width: 50%; + height: 100%; + } + + /* TODO: Do something with these colors */ + + .token.treeview-part .line-h:before, + .token.treeview-part .line-v:before { + border-left: 1px solid #ccc; + } + + .token.treeview-part .line-v-last:before { + height: 50%; + border-left: 1px solid #ccc; + border-bottom: 1px solid #ccc; + } + + .token.treeview-part .line-h:after { + height: 50%; + border-bottom: 1px solid #ccc; + } + + .token.treeview-part .entry-name { + position: relative; + display: inline-block; + vertical-align: top; + } + + .token.treeview-part .entry-name.dotfile { + opacity: 0.5; + } + + /* @GENERATED-FONT */ + @font-face { + font-family: "PrismTreeview"; + /** + * This font is generated from the .svg files in the `icons` folder. See the `treeviewIconFont` function in + * `gulpfile.js/index.js` for more information. + * + * Use the following escape sequences to refer to a specific icon: + * + * - \ea01 file + * - \ea02 folder + * - \ea03 image + * - \ea04 audio + * - \ea05 video + * - \ea06 text + * - \ea07 code + * - \ea08 archive + * - \ea09 pdf + * - \ea0a excel + * - \ea0b powerpoint + * - \ea0c word + */ + src: url("data:application/font-woff;base64,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") + format("woff"); + } + + .token.treeview-part .entry-name:before { + content: "\ea01"; + font-family: "PrismTreeview"; + font-size: inherit; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + width: 2.5ex; + display: inline-block; + } + + .token.treeview-part .entry-name.dir:before { + content: "\ea02"; + } + + .token.treeview-part .entry-name.ext-bmp:before, + .token.treeview-part .entry-name.ext-eps:before, + .token.treeview-part .entry-name.ext-gif:before, + .token.treeview-part .entry-name.ext-jpe:before, + .token.treeview-part .entry-name.ext-jpg:before, + .token.treeview-part .entry-name.ext-jpeg:before, + .token.treeview-part .entry-name.ext-png:before, + .token.treeview-part .entry-name.ext-svg:before, + .token.treeview-part .entry-name.ext-tiff:before { + content: "\ea03"; + } + + .token.treeview-part .entry-name.ext-cfg:before, + .token.treeview-part .entry-name.ext-conf:before, + .token.treeview-part .entry-name.ext-config:before, + .token.treeview-part .entry-name.ext-csv:before, + .token.treeview-part .entry-name.ext-ini:before, + .token.treeview-part .entry-name.ext-log:before, + .token.treeview-part .entry-name.ext-md:before, + .token.treeview-part .entry-name.ext-nfo:before, + .token.treeview-part .entry-name.ext-txt:before { + content: "\ea06"; + } + + .token.treeview-part .entry-name.ext-asp:before, + .token.treeview-part .entry-name.ext-aspx:before, + .token.treeview-part .entry-name.ext-c:before, + .token.treeview-part .entry-name.ext-cc:before, + .token.treeview-part .entry-name.ext-cpp:before, + .token.treeview-part .entry-name.ext-cs:before, + .token.treeview-part .entry-name.ext-css:before, + .token.treeview-part .entry-name.ext-h:before, + .token.treeview-part .entry-name.ext-hh:before, + .token.treeview-part .entry-name.ext-htm:before, + .token.treeview-part .entry-name.ext-html:before, + .token.treeview-part .entry-name.ext-jav:before, + .token.treeview-part .entry-name.ext-java:before, + .token.treeview-part .entry-name.ext-js:before, + .token.treeview-part .entry-name.ext-php:before, + .token.treeview-part .entry-name.ext-rb:before, + .token.treeview-part .entry-name.ext-xml:before { + content: "\ea07"; + } + + .token.treeview-part .entry-name.ext-7z:before, + .token.treeview-part .entry-name.ext-bz:before, + .token.treeview-part .entry-name.ext-bz2:before, + .token.treeview-part .entry-name.ext-gz:before, + .token.treeview-part .entry-name.ext-rar:before, + .token.treeview-part .entry-name.ext-tar:before, + .token.treeview-part .entry-name.ext-tgz:before, + .token.treeview-part .entry-name.ext-zip:before { + content: "\ea08"; + } + + .token.treeview-part .entry-name.ext-aac:before, + .token.treeview-part .entry-name.ext-au:before, + .token.treeview-part .entry-name.ext-cda:before, + .token.treeview-part .entry-name.ext-flac:before, + .token.treeview-part .entry-name.ext-mp3:before, + .token.treeview-part .entry-name.ext-oga:before, + .token.treeview-part .entry-name.ext-ogg:before, + .token.treeview-part .entry-name.ext-wav:before, + .token.treeview-part .entry-name.ext-wma:before { + content: "\ea04"; + } + + .token.treeview-part .entry-name.ext-avi:before, + .token.treeview-part .entry-name.ext-flv:before, + .token.treeview-part .entry-name.ext-mkv:before, + .token.treeview-part .entry-name.ext-mov:before, + .token.treeview-part .entry-name.ext-mp4:before, + .token.treeview-part .entry-name.ext-mpeg:before, + .token.treeview-part .entry-name.ext-mpg:before, + .token.treeview-part .entry-name.ext-ogv:before, + .token.treeview-part .entry-name.ext-webm:before { + content: "\ea05"; + } + + .token.treeview-part .entry-name.ext-pdf:before { + content: "\ea09"; + } + + .token.treeview-part .entry-name.ext-xls:before, + .token.treeview-part .entry-name.ext-xlsx:before { + content: "\ea0a"; + } + + .token.treeview-part .entry-name.ext-doc:before, + .token.treeview-part .entry-name.ext-docm:before, + .token.treeview-part .entry-name.ext-docx:before { + content: "\ea0c"; + } + + .token.treeview-part .entry-name.ext-pps:before, + .token.treeview-part .entry-name.ext-ppt:before, + .token.treeview-part .entry-name.ext-pptx:before { + content: "\ea0b"; + } + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5b7ab8c..313d592 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "name": "@doc-utils/docs2website", "version": "0.1.0", "dependencies": { + "@doc-utils/color-themes": "^0.1.1", + "@doc-utils/jsonschema2markdown": "^0.1.0", "@doc-utils/markdown2html": "^0.1.0", "glob": "^10.2.2", "luxon": "^3.3.0", @@ -26,10 +28,28 @@ "typescript": "^5.0.4" } }, + "node_modules/@doc-utils/color-themes": { + "version": "0.1.14", + "resolved": "https://gitea.jbrumond.me/api/packages/doc-utils/npm/%40doc-utils%2Fcolor-themes/-/0.1.14/color-themes-0.1.14.tgz", + "integrity": "sha512-j0U8v8Y+9zAm9D7pbCheTQYGEKt9FSpKSZQNGsogxWl95S9Z7QMjtmJns6QPgdOsSDss7sjMLFS5Gm50GCMzNA==" + }, + "node_modules/@doc-utils/jsonschema2markdown": { + "version": "0.1.1", + "resolved": "https://gitea.jbrumond.me/api/packages/doc-utils/npm/%40doc-utils%2Fjsonschema2markdown/-/0.1.1/jsonschema2markdown-0.1.1.tgz", + "integrity": "sha512-d3H5Jk7QXesu++3C/vaxnzXP6QkNHdOmMmbkSMywkInC5BC0RpUqtav5KheDZ7PzrnNO37YjdMODvdaMNUFYCw==", + "dependencies": { + "glob": "^10.2.2", + "json-schema": "^0.4.0", + "luxon": "^3.3.0", + "mustache": "^4.2.0", + "word-wrap": "^1.2.3", + "yaml": "^2.2.2" + } + }, "node_modules/@doc-utils/markdown2html": { - "version": "0.1.6", - "resolved": "https://gitea.jbrumond.me/api/packages/doc-utils/npm/%40doc-utils%2Fmarkdown2html/-/0.1.6/markdown2html-0.1.6.tgz", - "integrity": "sha512-lfb0vW0effsyKOXCXXF8d+xWjsKCF2rUuOq5F0GdOsw4PSJP3yCBW9oWZs4JdajwigGCBhWvXviyZeKInSdzgg==", + "version": "0.1.20", + "resolved": "https://gitea.jbrumond.me/api/packages/doc-utils/npm/%40doc-utils%2Fmarkdown2html/-/0.1.20/markdown2html-0.1.20.tgz", + "integrity": "sha512-kAkGITASEdT4jKPyeAAKZ/9QBmU1sOm0as3nHn9IolkZDydn02pmMIJCi4mrRWbj/3uj/WY5VtRa6T7sqYczNg==", "dependencies": { "bytefield-svg": "^1.6.1", "dompurify": "^2.3.6", @@ -111,9 +131,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.16.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.5.tgz", - "integrity": "sha512-seOA34WMo9KB+UA78qaJoCO20RJzZGVXQ5Sh6FWu0g/hfT44nKXnej3/tCQl7FL97idFpBhisLYCTB50S0EirA==", + "version": "18.16.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.9.tgz", + "integrity": "sha512-IeB32oIV4oGArLrd7znD2rkHQ6EDCM+2Sr76dJnrHwv9OHBTTM6nuDLK9bmikXzPa0ZlWMWtRGo/Uw4mrzQedA==", "dev": true }, "node_modules/@types/prismjs": { @@ -919,9 +939,9 @@ } }, "node_modules/glob": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.2.tgz", - "integrity": "sha512-Xsa0BcxIC6th9UwNjZkhrMtNo/MnyRL8jGCP+uEwhA5oFOCY1f2s1/oNKY47xQ0Bg5nkjsfAEIej1VeH62bDDQ==", + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.3.tgz", + "integrity": "sha512-Kb4rfmBVE3eQTAimgmeqc2LwSnN0wIOkkUL6HmxEFxNJ4fHghYHVbFba/HcGcRjE6s9KoMNK3rSOwkL4PioZjg==", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^2.0.3", @@ -1086,6 +1106,11 @@ } } }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, "node_modules/katex": { "version": "0.16.7", "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.7.tgz", @@ -1211,9 +1236,9 @@ } }, "node_modules/node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -1341,11 +1366,11 @@ } }, "node_modules/path-scurry": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.7.0.tgz", - "integrity": "sha512-UkZUeDjczjYRE495+9thsgcVgsaCPkaw80slmfVFgllxY+IO8ubTsOpFVjDPROBqJdHfVPUFRHPBV/WciOVfWg==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.8.0.tgz", + "integrity": "sha512-IjTrKseM404/UAWA8bBbL3Qp6O2wXkanuIE3seCxBH7ctRuvH1QRawy1N3nVDHGkdeZsjOsSe/8AQBL/VQCy2g==", "dependencies": { - "lru-cache": "^9.0.0", + "lru-cache": "^9.1.1", "minipass": "^5.0.0" }, "engines": { @@ -1500,9 +1525,9 @@ } }, "node_modules/signal-exit": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.1.tgz", - "integrity": "sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz", + "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==", "engines": { "node": ">=14" }, @@ -2425,10 +2450,28 @@ } }, "dependencies": { + "@doc-utils/color-themes": { + "version": "0.1.14", + "resolved": "https://gitea.jbrumond.me/api/packages/doc-utils/npm/%40doc-utils%2Fcolor-themes/-/0.1.14/color-themes-0.1.14.tgz", + "integrity": "sha512-j0U8v8Y+9zAm9D7pbCheTQYGEKt9FSpKSZQNGsogxWl95S9Z7QMjtmJns6QPgdOsSDss7sjMLFS5Gm50GCMzNA==" + }, + "@doc-utils/jsonschema2markdown": { + "version": "0.1.1", + "resolved": "https://gitea.jbrumond.me/api/packages/doc-utils/npm/%40doc-utils%2Fjsonschema2markdown/-/0.1.1/jsonschema2markdown-0.1.1.tgz", + "integrity": "sha512-d3H5Jk7QXesu++3C/vaxnzXP6QkNHdOmMmbkSMywkInC5BC0RpUqtav5KheDZ7PzrnNO37YjdMODvdaMNUFYCw==", + "requires": { + "glob": "^10.2.2", + "json-schema": "^0.4.0", + "luxon": "^3.3.0", + "mustache": "^4.2.0", + "word-wrap": "^1.2.3", + "yaml": "^2.2.2" + } + }, "@doc-utils/markdown2html": { - "version": "0.1.6", - "resolved": "https://gitea.jbrumond.me/api/packages/doc-utils/npm/%40doc-utils%2Fmarkdown2html/-/0.1.6/markdown2html-0.1.6.tgz", - "integrity": "sha512-lfb0vW0effsyKOXCXXF8d+xWjsKCF2rUuOq5F0GdOsw4PSJP3yCBW9oWZs4JdajwigGCBhWvXviyZeKInSdzgg==", + "version": "0.1.20", + "resolved": "https://gitea.jbrumond.me/api/packages/doc-utils/npm/%40doc-utils%2Fmarkdown2html/-/0.1.20/markdown2html-0.1.20.tgz", + "integrity": "sha512-kAkGITASEdT4jKPyeAAKZ/9QBmU1sOm0as3nHn9IolkZDydn02pmMIJCi4mrRWbj/3uj/WY5VtRa6T7sqYczNg==", "requires": { "bytefield-svg": "^1.6.1", "dompurify": "^2.3.6", @@ -2501,9 +2544,9 @@ "dev": true }, "@types/node": { - "version": "18.16.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.5.tgz", - "integrity": "sha512-seOA34WMo9KB+UA78qaJoCO20RJzZGVXQ5Sh6FWu0g/hfT44nKXnej3/tCQl7FL97idFpBhisLYCTB50S0EirA==", + "version": "18.16.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.9.tgz", + "integrity": "sha512-IeB32oIV4oGArLrd7znD2rkHQ6EDCM+2Sr76dJnrHwv9OHBTTM6nuDLK9bmikXzPa0ZlWMWtRGo/Uw4mrzQedA==", "dev": true }, "@types/prismjs": { @@ -3091,9 +3134,9 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "glob": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.2.tgz", - "integrity": "sha512-Xsa0BcxIC6th9UwNjZkhrMtNo/MnyRL8jGCP+uEwhA5oFOCY1f2s1/oNKY47xQ0Bg5nkjsfAEIej1VeH62bDDQ==", + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.3.tgz", + "integrity": "sha512-Kb4rfmBVE3eQTAimgmeqc2LwSnN0wIOkkUL6HmxEFxNJ4fHghYHVbFba/HcGcRjE6s9KoMNK3rSOwkL4PioZjg==", "requires": { "foreground-child": "^3.1.0", "jackspeak": "^2.0.3", @@ -3209,6 +3252,11 @@ "xml-name-validator": "^4.0.0" } }, + "json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, "katex": { "version": "0.16.7", "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.7.tgz", @@ -3291,9 +3339,9 @@ "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==" }, "node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", "requires": { "whatwg-url": "^5.0.0" }, @@ -3385,11 +3433,11 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "path-scurry": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.7.0.tgz", - "integrity": "sha512-UkZUeDjczjYRE495+9thsgcVgsaCPkaw80slmfVFgllxY+IO8ubTsOpFVjDPROBqJdHfVPUFRHPBV/WciOVfWg==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.8.0.tgz", + "integrity": "sha512-IjTrKseM404/UAWA8bBbL3Qp6O2wXkanuIE3seCxBH7ctRuvH1QRawy1N3nVDHGkdeZsjOsSe/8AQBL/VQCy2g==", "requires": { - "lru-cache": "^9.0.0", + "lru-cache": "^9.1.1", "minipass": "^5.0.0" } }, @@ -3504,9 +3552,9 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, "signal-exit": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.1.tgz", - "integrity": "sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz", + "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==" }, "source-map": { "version": "0.6.1", diff --git a/package.json b/package.json index f0c8598..381572f 100644 --- a/package.json +++ b/package.json @@ -6,12 +6,12 @@ }, "scripts": { "tsc": "tsc", - "clean": "rm -rf ./build" + "clean": "rm -rf ./build ./www" }, "bin": { "docs2website": "./bin/docs2website" }, - "exports": "./build/index.js", + "main": "./build/index.js", "devDependencies": { "@types/jsdom": "^20.0.0", "@types/luxon": "^3.1.0", @@ -21,6 +21,8 @@ "typescript": "^5.0.4" }, "dependencies": { + "@doc-utils/color-themes": "^0.1.1", + "@doc-utils/jsonschema2markdown": "^0.1.0", "@doc-utils/markdown2html": "^0.1.0", "glob": "^10.2.2", "luxon": "^3.3.0", diff --git a/sample-config.yaml b/sample-config.yaml deleted file mode 100644 index 9749508..0000000 --- a/sample-config.yaml +++ /dev/null @@ -1,62 +0,0 @@ - -# Input Configuration (where your documents are) -input: - # The root directory for where to find input file. All other - # input paths are relative to this, and must be under this - root: ./docs - - # Files identified in this section are copied over to the - # output directory unprocessed - raw: - - ./**/*.{png,jpg,jpeg,gif} - - # Files in this section will be processed as mustache templates, - # but will receive no other processing - text: - - ./**/*.{css,html,js,txt} - - # Files to be parsed as markdown (optionally with front matter) - # and rendered to HTML pages - markdown: - - ./**/*.md - - # Files to be parsed as JSON Schema definitions and rendered - # to HTML documentation. Additionally, the original JSON / Yaml - # file will also be copied to the output directory, unaltered - schema+json: - - ./**/*.schema.json - schema+yaml: - - ./**/*.schema.{yaml,yml} - - # Files to be parsed as OpenAPI V3 specifications and rendered - # to HTML documentation. Additionally, the original JSON / Yaml - # file will also be copied to the output directory, unaltered - openapi+json: - - ./**/*.openapi.json - openapi+yaml: - - ./**/*.openapi.{yaml,yml} - -# Template Configuration (used by mustache to actually render pages) -templates: - # Root directory where layout files are stored - layouts: ./layouts - - # Root directory where partial files are stored - partials: ./partials - - # (Optional) whitelist of environment variables to be made accessible - # under `env` when processing templates - env: - - EXAMPLE_ENVIRONMENT_VARIABLE - - FOO_BAR_BAZ - -# Output Configuration (where to put your website) -output: - # The root directory to output your website at. The path of an - # input file relative to $.input.root will match (aside from file - # extension) the path of the output file relative to $.output.root - root: ./www - -# Markdown-to-HTML Configuration -markdown: - # diff --git a/src/bin/docs2website.ts b/src/bin/docs2website.ts index 9e09a41..57a3e00 100644 --- a/src/bin/docs2website.ts +++ b/src/bin/docs2website.ts @@ -5,6 +5,6 @@ import { build_docs_project } from '../build'; main(); async function main() { - const conf = await read_config('./sample-config.yaml'); + const conf = await read_config(process.argv[2]); await build_docs_project(conf); } diff --git a/src/build.ts b/src/build.ts index 77c4f9b..b818043 100644 --- a/src/build.ts +++ b/src/build.ts @@ -1,13 +1,20 @@ +import { ColorTheme } from '@doc-utils/color-themes'; +import { build_markdown_from_json_schema } from '@doc-utils/jsonschema2markdown'; +import { render_markdown_to_html, render_markdown_to_html_inline_sync } from '@doc-utils/markdown2html'; + import { glob } from 'glob'; import { Config } from './conf'; import { promises as fs } from 'fs'; import { dirname, join as path_join } from 'path'; import { build_env_scope } from './env'; -import { process_frontmatter } from '@doc-utils/markdown2html'; -import { load_layout, Context, render_template, load_partials } from './template'; +import { load_layout, Context, render_template, load_partials, load_extras, FrontMatter } from './template'; import { DateTime } from 'luxon'; import assert = require('assert'); +import { load_themes, render_theme_css_properties } from './themes'; +import { icons } from './icons'; +import { mkdirp, read_json, read_text, read_yaml, write_text } from './fs'; +import { stringify as to_yaml } from 'yaml'; interface BuildState { conf: Config; @@ -15,6 +22,9 @@ interface BuildState { env: Record; partials?: Record; layouts: Record; + themes: Record; + theme_groups: ThemeGroups; + extras: Record; made_directories: Set; build_time: { iso: string; @@ -22,13 +32,42 @@ interface BuildState { }; } +export interface ThemeGroups { + all: ColorTheme[]; + light: ColorTheme[]; + dark: ColorTheme[]; + high_contrast: ColorTheme[]; + low_contrast: ColorTheme[]; + monochrome: ColorTheme[]; + greyscale: ColorTheme[]; + protanopia_safe: ColorTheme[]; + deuteranopia_safe: ColorTheme[]; + tritanopia_safe: ColorTheme[]; +} + export async function build_docs_project(conf: Config) { const now = DateTime.now(); + const themes = await load_themes(conf); const state: BuildState = { conf, seen_files: new Set(), env: build_env_scope(conf), layouts: Object.create(null), + themes: themes, + theme_groups: { + // fixme: this is horribly inefficient + all: Object.values(themes), + light: Object.values(themes).filter((theme) => theme.labels.includes('light')), + dark: Object.values(themes).filter((theme) => theme.labels.includes('dark')), + high_contrast: Object.values(themes).filter((theme) => theme.labels.includes('high_contrast')), + low_contrast: Object.values(themes).filter((theme) => theme.labels.includes('low_contrast')), + monochrome: Object.values(themes).filter((theme) => theme.labels.includes('monochrome')), + greyscale: Object.values(themes).filter((theme) => theme.labels.includes('greyscale')), + protanopia_safe: Object.values(themes).filter((theme) => theme.labels.includes('protanopia_safe')), + deuteranopia_safe: Object.values(themes).filter((theme) => theme.labels.includes('deuteranopia_safe')), + tritanopia_safe: Object.values(themes).filter((theme) => theme.labels.includes('tritanopia_safe')), + }, + extras: await load_extras(), made_directories: new Set(), build_time: { iso: now.toISO(), @@ -45,12 +84,20 @@ export async function build_docs_project(conf: Config) { } if (conf.input.markdown) { - // + await render_markdown_files(state); + } + + if (conf.input['schema+json'] || conf.input['schema+yaml']) { + await render_json_schema_files(state); } // todo... } + + +// ===== Raw File Copy ===== + async function copy_raw_files(state: BuildState) { const promises: Promise[] = [ ]; const files = await glob(state.conf.input.raw, { @@ -68,13 +115,17 @@ async function copy_raw_files(state: BuildState) { const out_file = map_input_file_to_output_file(state, in_file); promises.push( - fs.copyFile(in_file, await out_file, 0o600) + fs.copyFile(in_file, await out_file) ); } await Promise.all(promises); } + + +// ===== File Renderers ===== + async function render_text_file_templates(state: BuildState) { const promises: Promise[] = [ ]; const files = await glob(state.conf.input.text, { @@ -83,7 +134,7 @@ async function render_text_file_templates(state: BuildState) { }); if (! state.partials) { - state.partials = await load_partials(state.conf); + await build_partials(state); } for (const in_file of files) { @@ -103,7 +154,7 @@ async function render_text_file_templates(state: BuildState) { async function render_text_file_template(state: BuildState, in_file: string) { const out_file = map_input_file_to_output_file(state, in_file); - const { frontmatter, document } = process_frontmatter(await fs.readFile(in_file, 'utf8')); + const { frontmatter, text } = await read_text(in_file); let layout: string; const layout_file = frontmatter?.layout; @@ -116,16 +167,202 @@ async function render_text_file_template(state: BuildState, in_file: string) { layout = state.layouts[layout_file]; } - const context: Context = { - env: state.env, - page: frontmatter, - build_time: state.build_time, - }; - - const rendered = render_template(document, context, layout, structuredClone(state.partials)); - await fs.writeFile(await out_file, rendered, 'utf8'); + const tags = state.conf.templates?.tags; + const context = mustache_context(state, frontmatter); + const rendered = render_template(text, context, layout, structuredClone(state.partials), tags); + await write_text(await out_file, rendered); } +async function render_markdown_files(state: BuildState) { + const promises: Promise[] = [ ]; + const files = await glob(state.conf.input.markdown, { + absolute: true, + cwd: state.conf.input.root, + }); + + if (! state.partials) { + await build_partials(state); + } + + for (const in_file of files) { + if (state.seen_files.has(in_file)) { + continue; + } + + state.seen_files.add(in_file); + + promises.push( + render_markdown_file(state, in_file) + ); + } + + await Promise.all(promises); +} + +async function render_markdown_file(state: BuildState, in_file: string) { + const out_file = map_input_file_to_output_file(state, in_file, [ '.md', '.markdown' ], '.html'); + const { frontmatter, text } = await read_text(in_file); + + const html = render_markdown_to_html(text, state.conf.markdown); + + let layout: string; + const layout_file = frontmatter?.layout; + + if (layout_file) { + if (! state.layouts[layout_file]) { + state.layouts[layout_file] = await load_layout(state.conf, layout_file); + } + + layout = state.layouts[layout_file]; + } + + const tags = state.conf.templates?.tags; + const context = mustache_context(state, frontmatter); + const rendered = render_template(await html, context, layout, structuredClone(state.partials), tags); + await write_text(await out_file, rendered); +} + +async function render_json_schema_files(state: BuildState) { + const promises: Promise[] = [ ]; + const json_files = await glob(state.conf.input['schema+json'], { + absolute: true, + cwd: state.conf.input.root, + }); + const yaml_files = await glob(state.conf.input['schema+yaml'], { + absolute: true, + cwd: state.conf.input.root, + }); + + if (! state.partials) { + await build_partials(state); + } + + for (const in_file of json_files) { + if (state.seen_files.has(in_file)) { + continue; + } + + state.seen_files.add(in_file); + + promises.push( + handle_json_schema_json_file(state, in_file) + ); + } + + for (const in_file of yaml_files) { + if (state.seen_files.has(in_file)) { + continue; + } + + state.seen_files.add(in_file); + + promises.push( + handle_json_schema_yaml_file(state, in_file) + ); + } + + await Promise.all(promises); +} + +async function handle_json_schema_json_file(state: BuildState, in_file: string) { + const promises: Promise[] = [ ]; + + const out_file = map_input_file_to_output_file(state, in_file, [ '.json' ], '.html'); + const { frontmatter, parsed, json } = await read_json(in_file, true); + + promises.push( + render_json_schema(state, parsed, await out_file, frontmatter) + ); + + if (state.conf.output.include_inputs?.includes('schema+json')) { + const json_file = await map_input_file_to_output_file(state, in_file); + + promises.push( + write_text(json_file, json) + ); + + if (state.conf.output.include_yaml_and_json) { + const yaml_file = await map_input_file_to_output_file(state, in_file, [ '.json' ], '.yaml'); + + promises.push( + write_text(yaml_file, to_yaml(parsed)) + ); + } + } + + await Promise.all(promises); +} + +async function handle_json_schema_yaml_file(state: BuildState, in_file: string) { + const promises: Promise[] = [ ]; + + const out_file = map_input_file_to_output_file(state, in_file, [ '.yaml', '.yml' ], '.html'); + const { frontmatter, parsed, yaml } = await read_yaml(in_file, true); + + promises.push( + render_json_schema(state, parsed, await out_file, frontmatter) + ); + + if (state.conf.output.include_inputs?.includes('schema+yaml')) { + const yaml_file = await map_input_file_to_output_file(state, in_file); + + promises.push( + write_text(yaml_file, yaml) + ); + + if (state.conf.output.include_yaml_and_json) { + const json_file = await map_input_file_to_output_file(state, in_file, [ '.yaml', '.yml' ], '.json'); + + promises.push( + write_text(json_file, JSON.stringify(parsed, null, ' ')) + ); + } + } + + await Promise.all(promises); +} + +async function render_json_schema(state: BuildState, schema: unknown, out_file: string, frontmatter?: any) { + const promises: Promise[] = [ ]; + const markdown = build_markdown_from_json_schema(schema); + + if (state.conf.output.include_intermediate_markdown) { + promises.push( + map_input_file_to_output_file(state, out_file, [ '.html' ], '.md') + .then((md_file) => write_text(md_file, markdown)) + ); + } + + const html = render_markdown_to_html(markdown, state.conf.markdown); + + let layout: string; + const layout_file = frontmatter?.layout; + + if (layout_file) { + if (! state.layouts[layout_file]) { + state.layouts[layout_file] = await load_layout(state.conf, layout_file); + } + + layout = state.layouts[layout_file]; + } + + const tags = state.conf.templates?.tags; + const context = mustache_context(state, frontmatter); + const rendered = render_template(await html, context, layout, structuredClone(state.partials), tags); + + promises.push( + write_text(await out_file, rendered) + ); + + await Promise.all(promises); +} + + + + + +// ===== Helpers ===== + async function map_input_file_to_output_file(state: BuildState, in_file: string, remove_exts?: string[], add_ext?: string) { assert(in_file.startsWith(state.conf.input.root), 'input file expected to be inside input root'); let out_file = path_join(state.conf.output.root, in_file.slice(state.conf.input.root.length)); @@ -147,11 +384,38 @@ async function map_input_file_to_output_file(state: BuildState, in_file: string, if (! state.made_directories.has(dir)) { state.made_directories.add(dir); - await fs.mkdir(dir, { - mode: 0o700, - recursive: true, - }); + await mkdirp(dir); } return out_file; } + +async function build_partials(state: BuildState) { + state.partials = await load_partials(state.conf); + + for (const [name, theme] of Object.entries(state.themes)) { + state.partials[`.themes/${name}`] = render_theme_css_properties(theme); + } + + Object.assign(state.partials, state.extras); +} + +function mustache_context(state: BuildState, frontmatter?: FrontMatter) : Context { + return { + env: state.env, + page: frontmatter, + build_time: state.build_time, + icons: icons, + themes: Object.values(state.themes), + theme_groups: structuredClone(state.theme_groups), + markdown: { + render_inline() { + return (text, render) => { + const md = render(text) + const html = render_markdown_to_html_inline_sync(md, state.conf.markdown); + return html; + }; + }, + } + }; +} diff --git a/src/conf.ts b/src/conf.ts index 48aaabe..47058bc 100644 --- a/src/conf.ts +++ b/src/conf.ts @@ -2,12 +2,14 @@ import { promises as fs } from 'fs'; import { parse as parse_yaml } from 'yaml'; import { resolve as resolve_path, dirname } from 'path'; +import { MarkdownOptions } from '@doc-utils/markdown2html'; export async function read_config(file: string) { const path = resolve_path(process.cwd(), file); const yaml = await fs.readFile(path, 'utf8'); const config = parse_yaml(yaml); validate_config(config); + process_markdown_config(config); resolve_paths(path, config); return config; } @@ -27,14 +29,17 @@ export interface Config { templates?: { layouts?: string; partials?: string; + themes?: string; + tags?: [ string, string ]; env?: string[]; }; output: { root: string; + include_inputs?: string[]; + include_yaml_and_json?: boolean; + include_intermediate_markdown?: boolean; }; - markdown?: { - // - }; + markdown?: MarkdownOptions; schema?: { // }; @@ -61,3 +66,19 @@ function resolve_paths(file_path: string, config: Config) { config.templates.partials = resolve_path(base_path, config.templates.partials); } } + +function process_markdown_config(config: any) { + if (config?.markdown?.custom_elements) { + if (config.markdown.custom_elements.tag_names) { + const tags = new Set(config.markdown.custom_elements.tag_names); + config.markdown.custom_elements.tagNameCheck = (tag_name) => tags.has(tag_name); + delete config.markdown.custom_elements.tag_names; + } + + if (config.markdown.custom_elements.attribute_names) { + const attrs = new Set(config.markdown.custom_elements.attribute_names); + config.markdown.custom_elements.attributeNameCheck = (attr_name) => attrs.has(attr_name); + delete config.markdown.custom_elements.attribute_names; + } + } +} diff --git a/src/fs.ts b/src/fs.ts new file mode 100644 index 0000000..6f45fd3 --- /dev/null +++ b/src/fs.ts @@ -0,0 +1,70 @@ + +import { process_frontmatter } from '@doc-utils/markdown2html'; +import { promises as fs } from 'fs'; +import { resolve as resolve_path } from 'path'; +import { parse as parse_yaml, stringify as to_yaml } from 'yaml'; + +export async function load_from_dir(dir: string, file: string) { + if (! dir) { + return null; + } + + const rel_path = resolve_path('/', file); + const abs_path = resolve_path(dir, '.' + rel_path); + return await fs.readFile(abs_path, 'utf8'); +} + +export function mkdirp(dir: string, mode = 0o700) { + return fs.mkdir(dir, { mode, recursive: true }); +} + +export async function read_text(file: string, check_for_frontmatter = true) { + const text = await fs.readFile(file, 'utf8'); + + if (check_for_frontmatter) { + const { frontmatter, document } = process_frontmatter(text); + return { frontmatter, text: document }; + } + + return { text, frontmatter: null }; +} + +export async function read_json(file: string, check_for_frontmatter = true) { + const json = await fs.readFile(file, 'utf8'); + + if (check_for_frontmatter) { + const { frontmatter, document } = process_frontmatter(json); + const parsed = JSON.parse(document); + return { frontmatter, json: document, parsed }; + } + + const parsed = JSON.parse(json); + return { json, parsed, frontmatter: null }; +} + +export async function read_yaml(file: string, check_for_frontmatter = true) { + const yaml = await fs.readFile(file, 'utf8'); + + if (check_for_frontmatter) { + const { frontmatter, document } = process_frontmatter(yaml); + const parsed = parse_yaml(document); + return { frontmatter, yaml: document, parsed }; + } + + const parsed = parse_yaml(yaml); + return { yaml, parsed, frontmatter: null }; +} + +export function write_text(file: string, text: string) { + return fs.writeFile(file, text, 'utf8'); +} + +export function write_json(file: string, obj: any, pretty = true) { + const json = JSON.stringify(obj, null, pretty ? ' ' : null); + return fs.writeFile(file, json, 'utf8'); +} + +export function write_yaml(file: string, obj: any) { + const yaml = to_yaml(obj); + return fs.writeFile(file, yaml, 'utf8'); +} diff --git a/src/icons.ts b/src/icons.ts new file mode 100644 index 0000000..01a4775 --- /dev/null +++ b/src/icons.ts @@ -0,0 +1,23 @@ + +export const icons: Record = Object.create(null); + +const whitespace = /[\s\t\n]+/g; +const feather_icons: Record = require('../vendor/feather-icons/icons.json'); + +for (const [name, contents] of Object.entries(feather_icons)) { + icons[name] = ` + + `.replace(whitespace, ' ').trim(); +} + +Object.freeze(icons); diff --git a/src/index.ts b/src/index.ts index 74af906..27693c1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,3 @@ export { read_config, Config } from './conf'; +export { build_docs_project, ThemeGroups } from './build'; diff --git a/src/template.ts b/src/template.ts index 0a1250d..f20dce8 100644 --- a/src/template.ts +++ b/src/template.ts @@ -4,35 +4,57 @@ import { render as mustache_render } from 'mustache'; import { promises as fs } from 'fs'; import { resolve as resolve_path } from 'path'; import { glob } from 'glob'; +import { load_from_dir } from './fs'; +import { ColorTheme } from '@doc-utils/color-themes'; +import { ThemeGroups } from './build'; export interface Context { env?: Record; - page?: { - title?: string; - layout?: string; - [key: string]: string | number | boolean; - }; + page?: FrontMatter; + icons: Record; + themes: ColorTheme[]; + theme_groups: ThemeGroups; build_time: { iso: string; rfc2822: string; }; + markdown: { + render_inline(): MustacheRenderer; + } } -export function render_template(template: string, context: Context, layout?: string, partials?: Record) { +export interface FrontMatter { + title?: string; + layout?: string; + [key: string]: unknown; +} + +export function render_template(template: string, context: Context, layout?: string, partials: Record = { }, tags?: [ string, string ]) { partials['.content'] = template; - return mustache_render(layout || template, context, partials); + return mustache_render(layout || template, context, partials, tags); +} + +export async function load_extras() { + const extras: Record = Object.create(null); + const extras_dir = resolve_path(__dirname, '../extras'); + const extras_files = [ + 'components/color-scheme-toggle-button.js', + 'components/outline-button.js', + 'components/outline-inline.js', + 'prism.css', + ]; + + const promises = extras_files.map((file) => load_from_dir(extras_dir, file)); + + for (let i = 0; i < extras_files.length; i++) { + extras[`.extras/${extras_files[i]}`] = await promises[i]; + } + + return extras; } export async function load_layout(conf: Config, file: string) { - const path = conf.templates?.layouts; - - if (! path) { - return null; - } - - const rel_path = resolve_path('/', file); - const abs_path = resolve_path(path, '.' + rel_path); - return await fs.readFile(abs_path, 'utf8'); + return load_from_dir(conf.templates?.layouts, file); } export async function load_partials(conf: Config) { @@ -55,3 +77,7 @@ export async function load_partials(conf: Config) { return partials; } + +export interface MustacheRenderer { + (this: void, text: string, render: (text: string) => string): string; +} diff --git a/src/themes.ts b/src/themes.ts new file mode 100644 index 0000000..4b01089 --- /dev/null +++ b/src/themes.ts @@ -0,0 +1,64 @@ + +import { glob } from 'glob'; +import { Config } from './conf'; +import { load_from_dir } from './fs'; +import { ColorTheme, themes as builtin_themes, load_theme as load_builtin_theme, validate_theme } from '@doc-utils/color-themes'; + +export async function load_themes(conf: Config) { + const themes: Record = { }; + + for (const theme of builtin_themes) { + themes[theme] = load_builtin_theme(theme); + } + + if (conf.templates?.themes) { + const local_themes = await glob(conf.templates.themes + '/**/theme.json'); + + for (const theme_path of local_themes) { + const segments = theme_path.split('/'); + const theme = segments[segments.length - 2]; + themes[theme] = await load_local_theme(conf, theme); + } + } + + return themes; +} + +export async function load_theme(conf: Config, name: string) { + if (name.includes('/')) { + return null; + } + + const local_theme = await load_local_theme(conf, name); + + if (local_theme) { + return local_theme; + } + + return load_builtin_theme(name); +} + +export async function load_local_theme(conf: Config, name: string) { + let json: string; + + try { + json = await load_from_dir(conf.templates?.themes, `./${name}/theme.json`); + } + + catch (error) { + return null; + } + + const theme = JSON.parse(json); + validate_theme(theme); + return theme; +} + +export function render_theme_css_properties(theme: ColorTheme) { + return Object + .entries(theme.colors) + .map(([ name, color ]) => { + return `--theme-${name.replace(/_/g, '-')}: ${color};`; + }) + .join('\n') + '\n'; +} diff --git a/vendor/feather-icons/icons.json b/vendor/feather-icons/icons.json new file mode 100644 index 0000000..ba2001c --- /dev/null +++ b/vendor/feather-icons/icons.json @@ -0,0 +1,289 @@ +{ + "activity": "", + "airplay": "", + "alert-circle": "", + "alert-octagon": "", + "alert-triangle": "", + "align-center": "", + "align-justify": "", + "align-left": "", + "align-right": "", + "anchor": "", + "aperture": "", + "archive": "", + "arrow-down-circle": "", + "arrow-down-left": "", + "arrow-down-right": "", + "arrow-down": "", + "arrow-left-circle": "", + "arrow-left": "", + "arrow-right-circle": "", + "arrow-right": "", + "arrow-up-circle": "", + "arrow-up-left": "", + "arrow-up-right": "", + "arrow-up": "", + "at-sign": "", + "award": "", + "bar-chart-2": "", + "bar-chart": "", + "battery-charging": "", + "battery": "", + "bell-off": "", + "bell": "", + "bluetooth": "", + "bold": "", + "book-open": "", + "book": "", + "bookmark": "", + "box": "", + "briefcase": "", + "calendar": "", + "camera-off": "", + "camera": "", + "cast": "", + "check-circle": "", + "check-square": "", + "check": "", + "chevron-down": "", + "chevron-left": "", + "chevron-right": "", + "chevron-up": "", + "chevrons-down": "", + "chevrons-left": "", + "chevrons-right": "", + "chevrons-up": "", + "chrome": "", + "circle": "", + "clipboard": "", + "clock": "", + "cloud-drizzle": "", + "cloud-lightning": "", + "cloud-off": "", + "cloud-rain": "", + "cloud-snow": "", + "cloud": "", + "code": "", + "codepen": "", + "codesandbox": "", + "coffee": "", + "columns": "", + "command": "", + "compass": "", + "copy": "", + "corner-down-left": "", + "corner-down-right": "", + "corner-left-down": "", + "corner-left-up": "", + "corner-right-down": "", + "corner-right-up": "", + "corner-up-left": "", + "corner-up-right": "", + "cpu": "", + "credit-card": "", + "crop": "", + "crosshair": "", + "database": "", + "delete": "", + "disc": "", + "divide-circle": "", + "divide-square": "", + "divide": "", + "dollar-sign": "", + "download-cloud": "", + "download": "", + "dribbble": "", + "droplet": "", + "edit-2": "", + "edit-3": "", + "edit": "", + "external-link": "", + "eye-off": "", + "eye": "", + "facebook": "", + "fast-forward": "", + "feather": "", + "figma": "", + "file-minus": "", + "file-plus": "", + "file-text": "", + "file": "", + "film": "", + "filter": "", + "flag": "", + "folder-minus": "", + "folder-plus": "", + "folder": "", + "framer": "", + "frown": "", + "gift": "", + "git-branch": "", + "git-commit": "", + "git-merge": "", + "git-pull-request": "", + "github": "", + "gitlab": "", + "globe": "", + "grid": "", + "hard-drive": "", + "hash": "", + "headphones": "", + "heart": "", + "help-circle": "", + "hexagon": "", + "home": "", + "image": "", + "inbox": "", + "info": "", + "instagram": "", + "italic": "", + "key": "", + "layers": "", + "layout": "", + "life-buoy": "", + "link-2": "", + "link": "", + "linkedin": "", + "list": "", + "loader": "", + "lock": "", + "log-in": "", + "log-out": "", + "mail": "", + "map-pin": "", + "map": "", + "maximize-2": "", + "maximize": "", + "meh": "", + "menu": "", + "message-circle": "", + "message-square": "", + "mic-off": "", + "mic": "", + "minimize-2": "", + "minimize": "", + "minus-circle": "", + "minus-square": "", + "minus": "", + "monitor": "", + "moon": "", + "more-horizontal": "", + "more-vertical": "", + "mouse-pointer": "", + "move": "", + "music": "", + "navigation-2": "", + "navigation": "", + "octagon": "", + "package": "", + "paperclip": "", + "pause-circle": "", + "pause": "", + "pen-tool": "", + "percent": "", + "phone-call": "", + "phone-forwarded": "", + "phone-incoming": "", + "phone-missed": "", + "phone-off": "", + "phone-outgoing": "", + "phone": "", + "pie-chart": "", + "play-circle": "", + "play": "", + "plus-circle": "", + "plus-square": "", + "plus": "", + "pocket": "", + "power": "", + "printer": "", + "radio": "", + "refresh-ccw": "", + "refresh-cw": "", + "repeat": "", + "rewind": "", + "rotate-ccw": "", + "rotate-cw": "", + "rss": "", + "save": "", + "scissors": "", + "search": "", + "send": "", + "server": "", + "settings": "", + "share-2": "", + "share": "", + "shield-off": "", + "shield": "", + "shopping-bag": "", + "shopping-cart": "", + "shuffle": "", + "sidebar": "", + "skip-back": "", + "skip-forward": "", + "slack": "", + "slash": "", + "sliders": "", + "smartphone": "", + "smile": "", + "speaker": "", + "square": "", + "star": "", + "stop-circle": "", + "sun": "", + "sunrise": "", + "sunset": "", + "table": "", + "tablet": "", + "tag": "", + "target": "", + "terminal": "", + "thermometer": "", + "thumbs-down": "", + "thumbs-up": "", + "toggle-left": "", + "toggle-right": "", + "tool": "", + "trash-2": "", + "trash": "", + "trello": "", + "trending-down": "", + "trending-up": "", + "triangle": "", + "truck": "", + "tv": "", + "twitch": "", + "twitter": "", + "type": "", + "umbrella": "", + "underline": "", + "unlock": "", + "upload-cloud": "", + "upload": "", + "user-check": "", + "user-minus": "", + "user-plus": "", + "user-x": "", + "user": "", + "users": "", + "video-off": "", + "video": "", + "voicemail": "", + "volume-1": "", + "volume-2": "", + "volume-x": "", + "volume": "", + "watch": "", + "wifi-off": "", + "wifi": "", + "wind": "", + "x-circle": "", + "x-octagon": "", + "x-square": "", + "x": "", + "youtube": "", + "zap-off": "", + "zap": "", + "zoom-in": "", + "zoom-out": "" +} \ No newline at end of file diff --git a/vendor/feather-icons/license b/vendor/feather-icons/license new file mode 100644 index 0000000..4bb4ff7 --- /dev/null +++ b/vendor/feather-icons/license @@ -0,0 +1,24 @@ +https://github.com/feathericons/feather/blob/master/LICENSE +--- + +The MIT License (MIT) + +Copyright (c) 2013-2017 Cole Bemis + +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 NONINFRINGEMENT. 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. \ No newline at end of file diff --git a/vendor/feather-icons/readme.md b/vendor/feather-icons/readme.md new file mode 100644 index 0000000..84e0d9c --- /dev/null +++ b/vendor/feather-icons/readme.md @@ -0,0 +1,6 @@ + +https://github.com/feathericons/feather + +`icons.json` here is sourced from `dist/icons.json` from the bundle, version 4.29.0. + +This is intentionally not installed from `npm install feather-icons` because that package includes all of `core-js` as a dependency (which this project gets zero benefit from and is very large, impacting container image size).