Compare commits
27 Commits
v0.1.1
...
e99170f015
Author | SHA1 | Date | |
---|---|---|---|
e99170f015
|
|||
f48d0a6194
|
|||
92a9fe5685
|
|||
d0166b1db9
|
|||
821456c226
|
|||
5ff9cee8de
|
|||
df4545a7f1
|
|||
34c4144c3d
|
|||
fbe142f12f
|
|||
bcd60473b6
|
|||
46f7424bc9
|
|||
dc3b8ea5d8
|
|||
5d36def32c
|
|||
e3bf8dc139
|
|||
7297f9cd7a
|
|||
2dd300f4dc
|
|||
17a406e546
|
|||
5149be7c8c
|
|||
908b89b96b
|
|||
08b428d2f5
|
|||
099f2a68de
|
|||
e5f7af48cb
|
|||
5689c64c4e
|
|||
bf390c324f
|
|||
ab40bf4a09
|
|||
a02d6c429c
|
|||
03fbc802cb
|
36
.gitea/workflows/build-and-pubilsh.yaml
Normal file
36
.gitea/workflows/build-and-pubilsh.yaml
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
name: Build and publish
|
||||
|
||||
on:
|
||||
# - workflow_dispatch
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build-and-publish:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NPM_PUBLISH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Login to package registry
|
||||
run: |
|
||||
npm config set @doc-utils:registry https://gitea.jbrumond.me/api/packages/doc-utils/npm/
|
||||
npm config set -- '//gitea.jbrumond.me/api/packages/doc-utils/npm/:_authToken' "$NPM_PUBLISH_TOKEN"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Compile TypeScript
|
||||
run: npm run tsc
|
||||
|
||||
- name: Publish package
|
||||
run: npm publish
|
40
.gitea/workflows/build-and-test.yaml
Normal file
40
.gitea/workflows/build-and-test.yaml
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
name: Build and test
|
||||
|
||||
on:
|
||||
# push:
|
||||
# branches:
|
||||
# - master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18.x, 20.x]
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Login to package registry
|
||||
run: |
|
||||
npm config set @doc-utils:registry https://gitea.jbrumond.me/api/packages/doc-utils/npm/
|
||||
npm config set -- '//gitea.jbrumond.me/api/packages/doc-utils/npm/:_authToken' "$NPM_PUBLISH_TOKEN"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Compile TypeScript
|
||||
run: npm run tsc
|
||||
|
||||
# todo: tests
|
||||
- name: Run tests
|
||||
run: exit 0
|
3
.npmignore
Normal file
3
.npmignore
Normal file
@@ -0,0 +1,3 @@
|
||||
.editorconfig
|
||||
readme.md
|
||||
tsconfig.json
|
@@ -116,9 +116,16 @@
|
||||
const outline = [ ];
|
||||
|
||||
for (const heading of headings) {
|
||||
const content = heading.cloneNode(true);
|
||||
const anchor = content.querySelector('a.heading-anchor');
|
||||
|
||||
if (anchor) {
|
||||
anchor.parentNode.removeChild(anchor);
|
||||
}
|
||||
|
||||
outline.push(`
|
||||
<li data-depth="${heading.tagName.toLowerCase()}">
|
||||
<a href="#${heading.id}">${heading.innerText}</a>
|
||||
<a href="#${heading.id}">${content.innerHTML}</a>
|
||||
</li>
|
||||
`);
|
||||
}
|
||||
|
@@ -92,9 +92,16 @@
|
||||
const outline = [ ];
|
||||
|
||||
for (const heading of headings) {
|
||||
const content = heading.cloneNode(true);
|
||||
const anchor = content.querySelector('a.heading-anchor');
|
||||
|
||||
if (anchor) {
|
||||
anchor.parentNode.removeChild(anchor);
|
||||
}
|
||||
|
||||
outline.push(`
|
||||
<li data-depth="${heading.tagName.toLowerCase()}">
|
||||
<a href="#${heading.id}">${heading.innerText}</a>
|
||||
<a href="#${heading.id}">${content.innerHTML}</a>
|
||||
</li>
|
||||
`);
|
||||
}
|
||||
|
162
extras/figures.css
Normal file
162
extras/figures.css
Normal file
@@ -0,0 +1,162 @@
|
||||
|
||||
figure[data-lang] {
|
||||
margin-block: 2rem;
|
||||
}
|
||||
|
||||
figure a.view-svg {
|
||||
font-size: 0.85rem;
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
|
||||
figure[data-lang] svg {
|
||||
display: block;
|
||||
margin-inline: auto;
|
||||
margin-block: 2rem;
|
||||
|
||||
/* The auto-scaling font-size from typography/*.css does bad things to a lot of SVGs.
|
||||
* The SVGs themselves are inherently scalable, so there is no need for it here anyway. */
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
figure[data-size='medium']:has(svg) {
|
||||
margin-block: 4rem;
|
||||
}
|
||||
|
||||
figure[data-size='large'] {
|
||||
margin-block: 6rem;
|
||||
}
|
||||
|
||||
figure[data-size='small'] svg {
|
||||
max-width: 40rem;
|
||||
max-height: min(20rem, 50vw);
|
||||
}
|
||||
|
||||
figure[data-size='medium'] svg {
|
||||
max-width: 60rem;
|
||||
max-height: min(40rem, 50vw);
|
||||
}
|
||||
|
||||
figure[data-size='large'] svg {
|
||||
max-height: min(60rem, 80vw);
|
||||
}
|
||||
|
||||
/* figure[data-lang].big {
|
||||
background: var(--theme-bg-main);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
border: 0.25rem var(--theme-line) solid;
|
||||
margin: 3rem;
|
||||
}
|
||||
|
||||
figure[data-lang].big svg {
|
||||
max-width: none !important;
|
||||
max-height: none !important;
|
||||
} */
|
||||
|
||||
figure:is([data-lang='pikchr'], [data-lang='nomnoml'], [data-lang='bytefield']) svg text {
|
||||
fill: var(--theme-text-body);
|
||||
}
|
||||
|
||||
figure:is([data-lang='pikchr'], [data-lang='nomnoml'], [data-lang='bytefield']) svg text:not([font-family~='Courier']) {
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
|
||||
[data-lang='bash:samp'] samp {
|
||||
display: block;
|
||||
margin-block-start: 0.5rem;
|
||||
padding-block-start: 0.5rem;
|
||||
border-block-start: 0.1rem solid var(--theme-line);
|
||||
}
|
||||
|
||||
/* === KaTeX === */
|
||||
|
||||
.katex-display {
|
||||
color: var(--theme-text-body);
|
||||
}
|
||||
|
||||
.katex-display .katex {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
/* using "body" here to add specificity, to override styles from katex.min.css */
|
||||
body .katex-display {
|
||||
margin: 2rem;
|
||||
}
|
||||
|
||||
body figure.align-left .katex-display > .katex {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
body figure.align-right .katex-display > .katex {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
:not(.katex-display) > .katex {
|
||||
font-size: inherit;
|
||||
margin-inline: 0.5rem;
|
||||
}
|
||||
|
||||
.katex span[style~='color:transparent;'] {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* === Pikchr */
|
||||
|
||||
/* boxes */
|
||||
figure[data-lang='pikchr'] svg path[style*='fill:none;'] {
|
||||
fill: var(--theme-bg-light) !important;
|
||||
transition: fill linear .5s;
|
||||
}
|
||||
|
||||
/* lines and boxes */
|
||||
figure[data-lang='pikchr'] svg path[style*='stroke:rgb(0,0,0);'] {
|
||||
stroke: var(--theme-text-body) !important;
|
||||
}
|
||||
|
||||
/* circles */
|
||||
figure[data-lang='pikchr'] svg circle[style*='stroke:rgb(0,0,0);'] {
|
||||
stroke: var(--theme-text-body) !important;
|
||||
}
|
||||
|
||||
/* arrow heads */
|
||||
figure[data-lang='pikchr'] svg polygon[style='fill:rgb(0,0,0)'] {
|
||||
fill: var(--theme-text-body) !important;
|
||||
transition: fill linear .5s;
|
||||
}
|
||||
|
||||
/* === Bytefield === */
|
||||
|
||||
figure[data-lang='clojure:bytefield'] svg line[stroke-width='1'] {
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
figure[data-lang='clojure:bytefield'] svg :is(text, tspan)[font-size='11'] {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
figure[data-lang='clojure:bytefield'] svg :is(text, tspan)[font-size='18'] {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
figure[data-lang='clojure:bytefield'] svg :is(text, tspan)[font-family~='Courier'] {
|
||||
font-family: var(--font-monospace);
|
||||
}
|
||||
|
||||
figure[data-lang='clojure:bytefield'] svg :is(text, tspan)[font-family~='Times'] {
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
|
||||
figure[data-lang='clojure:bytefield'] svg line[stroke-dasharray='1,1'] {
|
||||
stroke-dasharray: 4px, 3px;
|
||||
}
|
||||
|
||||
figure[data-lang='clojure:bytefield'] svg line[stroke-dasharray='1,3'] {
|
||||
stroke-dasharray: 2px, 3px;
|
||||
}
|
||||
|
||||
|
0
extras/forms-inputs/compact.css
Normal file
0
extras/forms-inputs/compact.css
Normal file
174
extras/forms-inputs/general.css
Normal file
174
extras/forms-inputs/general.css
Normal file
@@ -0,0 +1,174 @@
|
||||
|
||||
label {
|
||||
display: block;
|
||||
color: var(--theme-text-body);
|
||||
font-family: var(--font-body);
|
||||
font-size: 1rem;
|
||||
margin-block-start: 1rem;
|
||||
}
|
||||
|
||||
label:not(:first-of-type) {
|
||||
margin-block-start: 2rem;
|
||||
}
|
||||
|
||||
label.radio {
|
||||
display: flex;
|
||||
margin-block: 1rem;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
display: block;
|
||||
margin-block-start: 0.35rem;
|
||||
margin-block-end: 1rem;
|
||||
border: 0.125rem var(--theme-border-input) solid;
|
||||
border-radius: 0.25rem;
|
||||
color: var(--theme-text-body);
|
||||
background-color: var(--theme-bg-input);
|
||||
padding: 0.5rem;
|
||||
font-family: var(--font-body);
|
||||
font-size: 1rem;
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
input[disabled],
|
||||
textarea[disabled] {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
input[type='text']:invalid,
|
||||
input[type='password']:invalid,
|
||||
textarea:invalid {
|
||||
border-color: var(--theme-border-input-invalid);
|
||||
}
|
||||
|
||||
input[type='text'][readonly] {
|
||||
color: var(--theme-text-light);
|
||||
border-color: var(--theme-line);
|
||||
}
|
||||
|
||||
input[type='checkbox'] {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input[type='checkbox'] + label {
|
||||
margin-inline-start: 0.5rem;
|
||||
margin-inline-end: 2rem;
|
||||
}
|
||||
|
||||
input[type='radio'] {
|
||||
flex: 0 0 2rem;
|
||||
margin: 0.2rem;
|
||||
}
|
||||
|
||||
textarea {
|
||||
line-height: 1.7rem;
|
||||
font-family: var(--font-monospace);
|
||||
}
|
||||
|
||||
::-webkit-input-placeholder {
|
||||
color: var(--theme-text-light);
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.5rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1rem;
|
||||
margin-block-start: 1rem;
|
||||
margin-inline-end: 1rem;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
appearance: none;
|
||||
color: var(--theme-text-button-primary);
|
||||
background: var(--theme-bg-button-primary);
|
||||
text-decoration: none;
|
||||
font-size: 1rem;
|
||||
font-family: var(--font-body);
|
||||
font-weight: 700;
|
||||
line-height: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
button.icon-only {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
button svg.icon {
|
||||
--icon-size: 2rem;
|
||||
}
|
||||
|
||||
button:not(.icon-only) svg.icon:first-child {
|
||||
margin-inline-end: 0.5rem;
|
||||
}
|
||||
|
||||
button:not(.icon-only) svg.icon:last-child {
|
||||
margin-inline-start: 0.5rem;
|
||||
}
|
||||
|
||||
button[data-style='secondary'] {
|
||||
color: var(--theme-text-button-secondary);
|
||||
background: var(--theme-bg-button-secondary);
|
||||
}
|
||||
|
||||
button:not([disabled]):hover {
|
||||
background: var(--theme-bg-button-primary-hover);
|
||||
}
|
||||
|
||||
button[data-style='secondary']:not([disabled]):hover {
|
||||
background: var(--theme-bg-button-secondary-hover);
|
||||
}
|
||||
|
||||
button[disabled] {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
select {
|
||||
display: block;
|
||||
margin-block-start: 0.35rem;
|
||||
margin-block-end: 1rem;
|
||||
border: 0.125rem var(--theme-border-input) solid;
|
||||
border-radius: 0.25rem;
|
||||
color: var(--theme-text-body);
|
||||
background-color: var(--theme-bg-input);
|
||||
padding: 0.5rem;
|
||||
font-family: var(--font-body);
|
||||
font-size: 1rem;
|
||||
width: 20rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select[disabled] {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
select option {
|
||||
color: var(--theme-text-body);
|
||||
background-color: var(--theme-bg-input);
|
||||
padding: 0.5rem;
|
||||
font-family: var(--font-body);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
:is(table, .table) :is(input, select) {
|
||||
margin-block: 0;
|
||||
}
|
||||
|
||||
p.error-box {
|
||||
margin: 0;
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
color: var(--theme-text-error-box);
|
||||
background: var(--theme-bg-error-box);
|
||||
border: 0.1rem solid var(--theme-border-error-box);
|
||||
}
|
||||
|
0
extras/forms-inputs/spacious.css
Normal file
0
extras/forms-inputs/spacious.css
Normal file
58
extras/svg-links.js
Normal file
58
extras/svg-links.js
Normal file
@@ -0,0 +1,58 @@
|
||||
(() => {
|
||||
|
||||
if (document.readyState === 'complete') {
|
||||
add_svg_links();
|
||||
}
|
||||
|
||||
else {
|
||||
window.addEventListener('DOMContentLoaded', add_svg_links);
|
||||
}
|
||||
|
||||
function add_svg_links() {
|
||||
const svg_elems = document.querySelectorAll('figure > svg');
|
||||
|
||||
for (const svg_elem of svg_elems) {
|
||||
const figure_elem = svg_elem.parentElement;
|
||||
const anchor_elem = document.createElement('a');
|
||||
const lang = figure_elem.getAttribute('data-lang') || '';
|
||||
|
||||
let svg_html;
|
||||
|
||||
if ('svg_link_css' in window) {
|
||||
const svg_clone = svg_elem.cloneNode(true);
|
||||
const style_elem = document.createElement('style');
|
||||
svg_clone.insertBefore(style_elem, svg_clone.firstChild);
|
||||
|
||||
const css
|
||||
= (window.svg_link_css['*'] || '')
|
||||
+ (lang ? window.svg_link_css[lang] || '' : '');
|
||||
|
||||
style_elem.setAttribute('type', 'text/css');
|
||||
style_elem.appendChild(document.createTextNode(css));
|
||||
|
||||
svg_html = svg_clone.outerHTML;
|
||||
}
|
||||
|
||||
else {
|
||||
svg_html = svg_elem.outerHTML;
|
||||
}
|
||||
|
||||
const box = document.createElement('div');
|
||||
const svg_xml = '<?xml version="1.0" standalone="no" ?>\r\n'
|
||||
+ svg_html.replace(/(&(?!(amp|gt|lt|quot|apos))[^;]+;)/g, ($0, $1) => {
|
||||
box.innerHTML = $0;
|
||||
return box.textContent;
|
||||
});
|
||||
|
||||
const svg_blob = new Blob([ svg_xml ], { type: 'image/svg+xml; charset=utf-8' });
|
||||
const svg_object_url = URL.createObjectURL(svg_blob);
|
||||
|
||||
anchor_elem.href = svg_object_url;
|
||||
anchor_elem.className = 'view-svg';
|
||||
anchor_elem.textContent = 'View / Download Graphic';
|
||||
|
||||
figure_elem.insertBefore(anchor_elem, svg_elem);
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
112
extras/theme-animation.css
Normal file
112
extras/theme-animation.css
Normal file
@@ -0,0 +1,112 @@
|
||||
|
||||
/* todo: is there a better way to handle defining these animations? despite efforts,
|
||||
this implementation still has some degree of transition stacking / delays in nested
|
||||
svg contexts in chrome */
|
||||
|
||||
/* BG Color Only */
|
||||
body[data-color-transition-enabled],
|
||||
body[data-color-transition-enabled] :is(
|
||||
[data-bg-transition]:not([data-color-transition]):not([data-border-transition]),
|
||||
button
|
||||
) {
|
||||
transition: background linear .5s;
|
||||
}
|
||||
|
||||
/* Border Color Only */
|
||||
body[data-color-transition-enabled] :is(
|
||||
[data-border-transition]:not([data-bg-transition]):not([data-color-transition])
|
||||
) {
|
||||
transition: border-color linear .5s;
|
||||
}
|
||||
|
||||
/* Color Only */
|
||||
body[data-color-transition-enabled] :is(
|
||||
[data-color-transition]:not([data-bg-transition]):not([data-border-transition]),
|
||||
p,
|
||||
a:not(h1 a),
|
||||
span:not(p span),
|
||||
time:not(p time),
|
||||
li:not([role='doc-footnote']), dt, dd,
|
||||
h1, h2, h3, h4, h5, h6,
|
||||
td, th
|
||||
) {
|
||||
transition: color linear .5s;
|
||||
}
|
||||
|
||||
/* BG and Color */
|
||||
body[data-color-transition-enabled] :is(
|
||||
[data-bg-transition][data-color-transition]:not([data-border-transition]),
|
||||
pre,
|
||||
code:not(pre code),
|
||||
samp:not(pre samp),
|
||||
mark,
|
||||
[role='doc-footnote']
|
||||
) {
|
||||
transition:
|
||||
color linear .5s,
|
||||
background linear .5s;
|
||||
}
|
||||
|
||||
/* BG and Border */
|
||||
body[data-color-transition-enabled] :is(
|
||||
[data-bg-transition][data-border-transition]:not([data-color-transition]),
|
||||
body > footer,
|
||||
p.error-box,
|
||||
.popup form,
|
||||
#outline
|
||||
) {
|
||||
transition:
|
||||
background linear .5s,
|
||||
border-color linear .5s;
|
||||
}
|
||||
|
||||
/** Color and Border */
|
||||
body[data-color-transition-enabled] :is(
|
||||
[data-color-transition][data-border-transition]:not([data-bg-transition]),
|
||||
tbody tr,
|
||||
.tbody .tr
|
||||
) {
|
||||
transition:
|
||||
color linear .5s,
|
||||
border-color linear .5s;
|
||||
}
|
||||
|
||||
/* BG, Color, and Border */
|
||||
body[data-color-transition-enabled] :is(
|
||||
[data-bg-transition][data-border-transition][data-color-transition],
|
||||
input, textarea, select,
|
||||
blockquote,
|
||||
:is(section, aside):is([role='note'], .info, .highlight, .warning, .problem)
|
||||
) {
|
||||
transition:
|
||||
color linear .5s,
|
||||
background linear .5s,
|
||||
border-color linear .5s;
|
||||
}
|
||||
|
||||
/* Fill and Stroke */
|
||||
body[data-color-transition-enabled] svg :is(
|
||||
[fill^='var('][stroke^='var(']:not([fill^='var('][stroke^='var('] *),
|
||||
[style*='fill:'][style*='stroke:']:not([style*='fill:'][style*='stroke:'] *)
|
||||
) {
|
||||
transition:
|
||||
fill linear .5s,
|
||||
stroke linear .5s;
|
||||
}
|
||||
|
||||
/* Fill Only */
|
||||
body[data-color-transition-enabled] svg :is(
|
||||
[fill^='var(']:not([stroke^='var(']):not([fill^='var('] *),
|
||||
[style*='fill:']:not([style*='stroke:']):not([style*='fill:'] *)
|
||||
) {
|
||||
transition: fill linear .5s;
|
||||
}
|
||||
|
||||
/* Stroke Only */
|
||||
body[data-color-transition-enabled] :is(
|
||||
svg [stroke^='var(']:not([fill^='var(']):not([stroke^='var('] *),
|
||||
svg [style*='stroke:']:not([style*='fill:']):not([style*='stroke:'] *)
|
||||
svg.icon
|
||||
) {
|
||||
transition: stroke linear .5s;
|
||||
}
|
101
extras/typography/compact.css
Normal file
101
extras/typography/compact.css
Normal file
@@ -0,0 +1,101 @@
|
||||
|
||||
:root {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (max-width: 400px) {
|
||||
:root {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1600px) or (min-height: 1600px) {
|
||||
:root {
|
||||
font-size: calc(12px + 80vw / 400);
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
:root {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* ===== Headings ===== */
|
||||
|
||||
h2 {
|
||||
margin-block: 2.5rem 1rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ===== Paragraphs ===== */
|
||||
|
||||
p {
|
||||
line-height: 1.5;
|
||||
margin-block: 1rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ===== Lists ===== */
|
||||
|
||||
li {
|
||||
line-height: 1.5;
|
||||
margin-block: 0.25rem;
|
||||
}
|
||||
|
||||
dl {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
dd + dt {
|
||||
margin-block-start: 1rem;
|
||||
}
|
||||
|
||||
:is(dt, dd) > p {
|
||||
line-height: 1.5;
|
||||
margin-block-start: 0;
|
||||
margin-block-end: 0.15rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ===== Figure Captions ===== */
|
||||
|
||||
figcaption {
|
||||
margin-block-start: 1.5rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ===== Pre-formatted Blocks ===== */
|
||||
|
||||
pre {
|
||||
padding: 0.5rem;
|
||||
margin-block: 1.5rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ===== Note Blocks ===== */
|
||||
|
||||
:is(aside, section):is([role='note'], .info, .highlight, .warning, .problem) > :first-child {
|
||||
margin-block-start: 0.25rem;
|
||||
}
|
||||
|
||||
:is(aside, section):is([role='note'], .info, .highlight, .warning, .problem) > :last-child {
|
||||
margin-block-end: 0.25rem;
|
||||
}
|
397
extras/typography/general.css
Normal file
397
extras/typography/general.css
Normal file
@@ -0,0 +1,397 @@
|
||||
|
||||
/* ===== Headings ===== */
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: var(--font-heading);
|
||||
line-height: 1;
|
||||
color: var(--theme-text-heading);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
font-weight: 300;
|
||||
margin-block-end: 1rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.17rem;
|
||||
}
|
||||
|
||||
h3, h4, h5, h6 {
|
||||
margin-block: 3rem 1rem;
|
||||
}
|
||||
|
||||
h5, h6 {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
:not(li) > :is(h1, h2, h3, h4, h5, h6),
|
||||
li > :is(h1, h2, h3, h4, h5, h6):not(:first-child) {
|
||||
padding-block-start: 1rem;
|
||||
}
|
||||
|
||||
:is(h1, h2, h3, h4, h5, h6) + :is(h1, h2, h3),
|
||||
:is(h1, h2, h3):first-child {
|
||||
margin-block-start: 1rem;
|
||||
}
|
||||
|
||||
:is(h1, h2, h3, h4, h5, h6) + :is(h4, h5, h6),
|
||||
:is(h4, h5, h6):first-child {
|
||||
margin-block-start: 0;
|
||||
}
|
||||
|
||||
a.heading-anchor {
|
||||
color: var(--theme-text-light);
|
||||
opacity: 0.3;
|
||||
margin-inline-start: 0.5rem;
|
||||
}
|
||||
|
||||
:is(h1, h2, h3, h4, h5, h6):hover > a.heading-anchor {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
:is(h1, h2, h3, h4, h5, h6) svg.icon:not(.heading-anchor > svg) {
|
||||
--icon-size: 1em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ===== Paragraphs ===== */
|
||||
|
||||
p {
|
||||
margin-inline: 0;
|
||||
font-size: 1rem;
|
||||
font-family: var(--font-body);
|
||||
color: var(--theme-text-body);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ===== Inline Styles ===== */
|
||||
|
||||
del {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* ===== Links ===== */
|
||||
|
||||
a {
|
||||
font-family: inherit;
|
||||
color: var(--theme-text-link);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
column-gap: 0.2rem;
|
||||
}
|
||||
|
||||
a:active,
|
||||
a:hover,
|
||||
a:focus {
|
||||
color: var(--theme-text-link-active);
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: var(--theme-text-link-visited);
|
||||
}
|
||||
|
||||
a.icon-link {
|
||||
display: inline-flex;
|
||||
column-gap: 0.3rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
a.icon-link svg.icon {
|
||||
--icon-size: 1rem;
|
||||
}
|
||||
|
||||
del a {
|
||||
text-decoration: line-through underline;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ===== Lists ===== */
|
||||
|
||||
ul, ol {
|
||||
padding-inline-start: 2.5rem;
|
||||
}
|
||||
|
||||
li, dt, dd {
|
||||
font-family: var(--font-body);
|
||||
color: var(--theme-text-body);
|
||||
}
|
||||
|
||||
li {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
ul[role='doc-endnotes'] {
|
||||
list-style: none;
|
||||
padding-inline-start: 1rem;
|
||||
}
|
||||
|
||||
[role='doc-footnote']:target {
|
||||
background: var(--theme-bg-heavy);
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.cite-label {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ===== Mark / Highlight ===== */
|
||||
|
||||
mark {
|
||||
color: var(--theme-text-highlight);
|
||||
background-color: var(--theme-bg-text-highlight);
|
||||
padding: 0.2rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ===== User Selection ===== */
|
||||
|
||||
::selection {
|
||||
fill: var(--theme-text-selection) !important;
|
||||
color: var(--theme-text-selection) !important;
|
||||
background-color: var(--theme-bg-text-selection) !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ===== Tables ===== */
|
||||
|
||||
table, .table, td, .td {
|
||||
color: var(--theme-text-body);
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
|
||||
table, .table {
|
||||
display: table;
|
||||
margin-block: 2rem;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table, th, td,
|
||||
.table, .th, .td {
|
||||
font-size: 1rem;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
thead, .thead {
|
||||
display: table-header-group;
|
||||
}
|
||||
|
||||
tbody, .tbody {
|
||||
display: table-row-group;
|
||||
}
|
||||
|
||||
tbody tr,
|
||||
.tbody .tr {
|
||||
border-top: 1px var(--theme-line) solid;
|
||||
}
|
||||
|
||||
tr, .tr {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
th, .th {
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
color: var(--theme-text-heading);
|
||||
}
|
||||
|
||||
th, .th,
|
||||
td, .td {
|
||||
padding-block: 0.5rem;
|
||||
padding-inline: 1rem;
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
td, .td {
|
||||
display: table-cell;
|
||||
font-weight: 300;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
:is(td, .td):not(:last-of-type) {
|
||||
border-inline-end: 0.1rem solid var(--theme-line);
|
||||
}
|
||||
|
||||
:is(table, .table) :is(input, select) {
|
||||
margin-block: 0;
|
||||
}
|
||||
|
||||
table dl {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ===== Pre-formatted Blocks ===== */
|
||||
|
||||
pre {
|
||||
color: var(--theme-code-normal);
|
||||
font-family: var(--font-monospace);
|
||||
margin-inline-start: 1rem;
|
||||
margin-inline-end: 5rem;
|
||||
border: 0.1rem solid var(--theme-line);
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1rem;
|
||||
overflow: auto;
|
||||
background: var(--theme-bg-light);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 60rem) {
|
||||
pre {
|
||||
margin-inline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* ===== Code / Sample Output ===== */
|
||||
|
||||
code, samp {
|
||||
font-size: inherit;
|
||||
color: var(--theme-code-normal);
|
||||
font-family: var(--font-monospace);
|
||||
}
|
||||
|
||||
:not(pre) > :is(code, samp) {
|
||||
color: inherit;
|
||||
background: var(--theme-bg-light);
|
||||
margin-inline: 0.15rem;
|
||||
padding-block: 0.1rem;
|
||||
padding-inline: 0.25rem;
|
||||
border: 0.1rem solid var(--theme-line);
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ===== Note Blocks ===== */
|
||||
|
||||
:is(aside, section):is([role='note'], .info, .highlight, .warning, .problem) {
|
||||
font-size: 1rem;
|
||||
color: var(--theme-text-light);
|
||||
padding-block: 0.6rem;
|
||||
padding-inline-start: 1rem;
|
||||
margin: 1rem;
|
||||
border-inline-start: 0.8rem solid var(--theme-line);
|
||||
background: var(--theme-bg-light);
|
||||
}
|
||||
|
||||
:is(aside, section).info {
|
||||
border-inline-start-color: var(--theme-accent-info);
|
||||
}
|
||||
|
||||
:is(aside, section).highlight {
|
||||
border-inline-start-color: var(--theme-accent-highlight);
|
||||
}
|
||||
|
||||
:is(aside, section).warning {
|
||||
border-inline-start-color: var(--theme-accent-warning);
|
||||
}
|
||||
|
||||
:is(aside, section).problem {
|
||||
border-inline-start-color: var(--theme-accent-problem);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ===== Figure Captions ===== */
|
||||
|
||||
figcaption {
|
||||
font-family: var(--font-body);
|
||||
text-align: center;
|
||||
font-size: 0.85rem;
|
||||
color: var(--theme-text-light);
|
||||
max-width: 60vw;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ===== Block Quotes ===== */
|
||||
|
||||
blockquote {
|
||||
margin-block: 2rem;
|
||||
margin-inline: 1rem;
|
||||
padding-block: 0.5rem;
|
||||
padding-inline: 1.5rem;
|
||||
border-inline-start: 0.25rem var(--theme-line) solid;
|
||||
}
|
||||
|
||||
blockquote p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ===== Small Spans ===== */
|
||||
|
||||
small {
|
||||
font-size: 0.8rem;
|
||||
font-family: var(--font-body);
|
||||
color: var(--theme-text-body);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ===== Horizontal Rules ===== */
|
||||
|
||||
hr {
|
||||
border-block-start-width: 0.1rem;
|
||||
border-style: solid;
|
||||
border-color: var(--theme-line);
|
||||
width: 90%;
|
||||
margin-block: 4rem 6rem;
|
||||
}
|
||||
|
101
extras/typography/spacious.css
Normal file
101
extras/typography/spacious.css
Normal file
@@ -0,0 +1,101 @@
|
||||
|
||||
:root {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
@media (max-width: 400px) {
|
||||
:root {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1600px) or (min-height: 1600px) {
|
||||
:root {
|
||||
font-size: calc(12px + 100vw / 400);
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
:root {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* ===== Headings ===== */
|
||||
|
||||
h2 {
|
||||
margin-block: 4rem 1rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ===== Paragraphs ===== */
|
||||
|
||||
p {
|
||||
line-height: 1.75;
|
||||
margin-block: 1.25rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ===== Note Blocks ===== */
|
||||
|
||||
:is(aside, section):is([role='note'], .info, .highlight, .warning, .problem) > :first-child {
|
||||
margin-block-start: 0.5rem;
|
||||
}
|
||||
|
||||
:is(aside, section):is([role='note'], .info, .highlight, .warning, .problem) > :last-child {
|
||||
margin-block-end: 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ===== Lists ===== */
|
||||
|
||||
li {
|
||||
line-height: 1.75;
|
||||
margin-block: 0.5rem;
|
||||
}
|
||||
|
||||
dl {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
dd + dt {
|
||||
margin-block-start: 1.25rem;
|
||||
}
|
||||
|
||||
:is(dt, dd) > p {
|
||||
margin-block-start: 0;
|
||||
margin-block-end: 0.25rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ===== Figure Captions ===== */
|
||||
|
||||
figcaption {
|
||||
margin-block-start: 2rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ===== Pre-formatted Blocks ===== */
|
||||
|
||||
pre {
|
||||
margin-block: 2rem;
|
||||
padding-block: 0.5rem;
|
||||
padding-inline: 1rem;
|
||||
}
|
512
package-lock.json
generated
512
package-lock.json
generated
@@ -1,25 +1,29 @@
|
||||
{
|
||||
"name": "@doc-utils/docs2website",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.8",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@doc-utils/docs2website",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.8",
|
||||
"dependencies": {
|
||||
"@doc-utils/color-themes": "^0.1.14",
|
||||
"@doc-utils/color-themes": "^0.2.0",
|
||||
"@doc-utils/jsonschema2markdown": "^0.1.1",
|
||||
"@doc-utils/markdown2html": "^0.1.21",
|
||||
"@doc-utils/markdown2html": "^0.3.5",
|
||||
"glob": "^10.2.3",
|
||||
"ical": "^0.8.0",
|
||||
"ical-generator": "^4.1.0",
|
||||
"luxon": "^3.3.0",
|
||||
"mustache": "^4.2.0",
|
||||
"xmlbuilder2": "^3.1.1",
|
||||
"yaml": "^2.2.2"
|
||||
},
|
||||
"bin": {
|
||||
"docs2website": "bin/docs2website"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ical": "^0.8.0",
|
||||
"@types/jsdom": "^20.0.1",
|
||||
"@types/luxon": "^3.3.0",
|
||||
"@types/mustache": "^4.2.2",
|
||||
@@ -29,9 +33,9 @@
|
||||
}
|
||||
},
|
||||
"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=="
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://gitea.jbrumond.me/api/packages/doc-utils/npm/%40doc-utils%2Fcolor-themes/-/0.2.0/color-themes-0.2.0.tgz",
|
||||
"integrity": "sha512-UtjY25B8m4qdMvrmTPK3I1JXckbc1cvCOmIygHFBexpSWBQmb+sdoXfdCsoSpgPncurz1kwlEXffgPELCGtP8g=="
|
||||
},
|
||||
"node_modules/@doc-utils/jsonschema2markdown": {
|
||||
"version": "0.1.1",
|
||||
@@ -47,21 +51,24 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@doc-utils/markdown2html": {
|
||||
"version": "0.1.21",
|
||||
"resolved": "https://gitea.jbrumond.me/api/packages/doc-utils/npm/%40doc-utils%2Fmarkdown2html/-/0.1.21/markdown2html-0.1.21.tgz",
|
||||
"integrity": "sha512-yoyWrOOm4NNvxvzGn0oFwGgJb8xwmdQcAzgvN2s+xIC2sVL1WBirOlM+iJ1pOhXfAiKEPqzgEim+SE74siWp9Q==",
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://gitea.jbrumond.me/api/packages/doc-utils/npm/%40doc-utils%2Fmarkdown2html/-/0.3.5/markdown2html-0.3.5.tgz",
|
||||
"integrity": "sha512-Z5zeHIRU7gyMu8taiNcQ2+FYD08184FGNzKBT2y4mtYTjSRBlFQH+trye6e14Mzc1wx9juoZ7KpysS1M6qe4sw==",
|
||||
"dependencies": {
|
||||
"bytefield-svg": "^1.6.1",
|
||||
"dompurify": "^2.3.6",
|
||||
"jsdom": "^20.0.1",
|
||||
"katex": "^0.16.7",
|
||||
"marked": "^4.1.1",
|
||||
"marked": "^5.0.2",
|
||||
"nomnoml": "^1.5.2",
|
||||
"pikchr": "^0.0.5",
|
||||
"prismjs": "^1.29.0",
|
||||
"qrcode": "^1.5.1",
|
||||
"vega": "^5.22.1",
|
||||
"yaml": "^2.2.2"
|
||||
},
|
||||
"bin": {
|
||||
"markdown2html": "bin/markdown2html"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
@@ -80,6 +87,50 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@oozcitak/dom": {
|
||||
"version": "1.15.10",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-1.15.10.tgz",
|
||||
"integrity": "sha512-0JT29/LaxVgRcGKvHmSrUTEvZ8BXvZhGl2LASRUgHqDTC1M5g1pLmVv56IYNyt3bG2CUjDkc67wnyZC14pbQrQ==",
|
||||
"dependencies": {
|
||||
"@oozcitak/infra": "1.0.8",
|
||||
"@oozcitak/url": "1.0.4",
|
||||
"@oozcitak/util": "8.3.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@oozcitak/infra": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-1.0.8.tgz",
|
||||
"integrity": "sha512-JRAUc9VR6IGHOL7OGF+yrvs0LO8SlqGnPAMqyzOuFZPSZSXI7Xf2O9+awQPSMXgIWGtgUf/dA6Hs6X6ySEaWTg==",
|
||||
"dependencies": {
|
||||
"@oozcitak/util": "8.3.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@oozcitak/url": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-1.0.4.tgz",
|
||||
"integrity": "sha512-kDcD8y+y3FCSOvnBI6HJgl00viO/nGbQoCINmQ0h98OhnGITrWR3bOGfwYCthgcrV8AnTJz8MzslTQbC3SOAmw==",
|
||||
"dependencies": {
|
||||
"@oozcitak/infra": "1.0.8",
|
||||
"@oozcitak/util": "8.3.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@oozcitak/util": {
|
||||
"version": "8.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-8.3.8.tgz",
|
||||
"integrity": "sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ==",
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
@@ -107,6 +158,43 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.4.tgz",
|
||||
"integrity": "sha512-MHmwBtCb7OCv1DSivz2UNJXPGU/1btAWRKlqJ2saEhVJkpkvqHMMaOpKg0v4sAbDWSQekHGvPVMM8nQ+Jen03Q=="
|
||||
},
|
||||
"node_modules/@types/ical": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/ical/-/ical-0.8.0.tgz",
|
||||
"integrity": "sha512-46KAYxAwRuCh+jRgl9k5cTaXJJkB16gWpvMVPuog9UBBb2zXTf9M0MsfWYBQ21JohFSuMZfPTvk6tohqGr1tCg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"rrule": "2.6.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ical/node_modules/luxon": {
|
||||
"version": "1.28.1",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz",
|
||||
"integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ical/node_modules/rrule": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/rrule/-/rrule-2.6.4.tgz",
|
||||
"integrity": "sha512-sLdnh4lmjUqq8liFiOUXD5kWp/FcnbDLPwq5YAc/RrN6120XOPb86Ae5zxF7ttBVq8O3LxjjORMEit1baluahA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tslib": "^1.10.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"luxon": "^1.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ical/node_modules/tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/jsdom": {
|
||||
"version": "20.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz",
|
||||
@@ -122,7 +210,7 @@
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.0.tgz",
|
||||
"integrity": "sha512-uKRI5QORDnrGFYgcdAVnHvEIvEZ8noTpP/Bg+HeUzZghwinDlIS87DEenV5r1YoOF9G4x600YsUXLWZ19rmTmg==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/@types/mustache": {
|
||||
"version": "4.2.2",
|
||||
@@ -131,10 +219,10 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.16.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.9.tgz",
|
||||
"integrity": "sha512-IeB32oIV4oGArLrd7znD2rkHQ6EDCM+2Sr76dJnrHwv9OHBTTM6nuDLK9bmikXzPa0ZlWMWtRGo/Uw4mrzQedA==",
|
||||
"dev": true
|
||||
"version": "18.16.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.13.tgz",
|
||||
"integrity": "sha512-uZRomboV1vBL61EBXneL4j9/hEn+1Yqa4LQdpGrKmXFyJmVfWc9JV9+yb2AlnOnuaDnb2PDO3hC6/LKmzJxP1A==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/@types/prismjs": {
|
||||
"version": "1.26.0",
|
||||
@@ -214,6 +302,14 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"dependencies": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/array-back": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz",
|
||||
@@ -939,14 +1035,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "10.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.2.3.tgz",
|
||||
"integrity": "sha512-Kb4rfmBVE3eQTAimgmeqc2LwSnN0wIOkkUL6HmxEFxNJ4fHghYHVbFba/HcGcRjE6s9KoMNK3rSOwkL4PioZjg==",
|
||||
"version": "10.2.5",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.2.5.tgz",
|
||||
"integrity": "sha512-Gj+dFYPZ5hc5dazjXzB0iHg2jKWJZYMjITXYPBRQ/xc2Buw7H0BINknRTwURJ6IC6MEFpYbLvtgVb3qD+DwyuA==",
|
||||
"dependencies": {
|
||||
"foreground-child": "^3.1.0",
|
||||
"jackspeak": "^2.0.3",
|
||||
"minimatch": "^9.0.0",
|
||||
"minipass": "^5.0.0",
|
||||
"minipass": "^5.0.0 || ^6.0.2",
|
||||
"path-scurry": "^1.7.0"
|
||||
},
|
||||
"bin": {
|
||||
@@ -1008,6 +1104,82 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/ical": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/ical/-/ical-0.8.0.tgz",
|
||||
"integrity": "sha512-/viUSb/RGLLnlgm0lWRlPBtVeQguQRErSPYl3ugnUaKUnzQswKqOG3M8/P1v1AB5NJwlHTuvTq1cs4mpeG2rCg==",
|
||||
"dependencies": {
|
||||
"rrule": "2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ical-generator": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ical-generator/-/ical-generator-4.1.0.tgz",
|
||||
"integrity": "sha512-5GrFDJ8SAOj8cB9P1uEZIfKrNxSZ1R2eOQfZePL+CtdWh4RwNXWe8b0goajz+Hu37vcipG3RVldoa2j57Y20IA==",
|
||||
"dependencies": {
|
||||
"uuid-random": "^1.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.8.0 || >=16.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@touch4it/ical-timezones": ">=1.6.0",
|
||||
"@types/luxon": ">= 1.26.0",
|
||||
"@types/mocha": ">= 8.2.1",
|
||||
"@types/node": ">= 15.0.0",
|
||||
"dayjs": ">= 1.10.0",
|
||||
"luxon": ">= 1.26.0",
|
||||
"moment": ">= 2.29.0",
|
||||
"moment-timezone": ">= 0.5.33",
|
||||
"rrule": ">= 2.6.8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@touch4it/ical-timezones": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/luxon": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/mocha": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"dayjs": {
|
||||
"optional": true
|
||||
},
|
||||
"luxon": {
|
||||
"optional": true
|
||||
},
|
||||
"moment": {
|
||||
"optional": true
|
||||
},
|
||||
"moment-timezone": {
|
||||
"optional": true
|
||||
},
|
||||
"rrule": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ical/node_modules/luxon": {
|
||||
"version": "1.28.1",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz",
|
||||
"integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/ical/node_modules/rrule": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/rrule/-/rrule-2.4.1.tgz",
|
||||
"integrity": "sha512-+NcvhETefswZq13T8nkuEnnQ6YgUeZaqMqVbp+ZiFDPCbp3AVgQIwUvNVDdMNrP05bKZG9ddDULFp0qZZYDrxg==",
|
||||
"optionalDependencies": {
|
||||
"luxon": "^1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
@@ -1062,6 +1234,18 @@
|
||||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"dependencies": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/jsdom": {
|
||||
"version": "20.0.3",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz",
|
||||
@@ -1171,14 +1355,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
|
||||
"integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==",
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-5.0.2.tgz",
|
||||
"integrity": "sha512-TXksm9GwqXCRNbFUZmMtqNLvy3K2cQHuWmyBDLOrY1e6i9UvZpOTJXoz7fBjYkJkaUFzV9hBFxMuZSyQt8R6KQ==",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
@@ -1215,11 +1399,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
|
||||
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-6.0.2.tgz",
|
||||
"integrity": "sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
@@ -1274,11 +1458,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nomnoml": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/nomnoml/-/nomnoml-1.5.3.tgz",
|
||||
"integrity": "sha512-2LBKi3ygeYAC9l/wowgtBRxvQGuTryIczULVk2rFbWGUvs5aX68/sH+WgL95CschnSnxucTIv9L+3xScS71obQ==",
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/nomnoml/-/nomnoml-1.6.0.tgz",
|
||||
"integrity": "sha512-+t3ZBx/yBBnvzQkVU0/pyXZNGHIyRdnPBg/8H7YvWBbvRroRG21amC2kZ1UVhGes2kU2cU6KjO4RSDgglajeQw==",
|
||||
"dependencies": {
|
||||
"graphre": "^0.1.3"
|
||||
"graphre": "^0.1.3",
|
||||
"nomnoml": "^1.5.3"
|
||||
},
|
||||
"bin": {
|
||||
"nomnoml": "dist/nomnoml-cli.js"
|
||||
@@ -1366,12 +1551,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/path-scurry": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.8.0.tgz",
|
||||
"integrity": "sha512-IjTrKseM404/UAWA8bBbL3Qp6O2wXkanuIE3seCxBH7ctRuvH1QRawy1N3nVDHGkdeZsjOsSe/8AQBL/VQCy2g==",
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.9.2.tgz",
|
||||
"integrity": "sha512-qSDLy2aGFPm8i4rsbHd4MNyTcrzHFsLQykrtbuGRknZZCBBVXSv2tSCDN2Cg6Rt/GFRw8GoW9y9Ecw5rIPG1sg==",
|
||||
"dependencies": {
|
||||
"lru-cache": "^9.1.1",
|
||||
"minipass": "^5.0.0"
|
||||
"minipass": "^5.0.0 || ^6.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
@@ -1479,6 +1664,16 @@
|
||||
"resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.1.tgz",
|
||||
"integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g=="
|
||||
},
|
||||
"node_modules/rrule": {
|
||||
"version": "2.7.2",
|
||||
"resolved": "https://registry.npmjs.org/rrule/-/rrule-2.7.2.tgz",
|
||||
"integrity": "sha512-NkBsEEB6FIZOZ3T8frvEBOB243dm46SPufpDckY/Ap/YH24V1zLeMmDY8OA10lk452NdrF621+ynDThE7FQU2A==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rw": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
|
||||
@@ -1544,6 +1739,11 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||
@@ -1721,6 +1921,13 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz",
|
||||
"integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
|
||||
@@ -1770,6 +1977,11 @@
|
||||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid-random": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid-random/-/uuid-random-1.3.2.tgz",
|
||||
"integrity": "sha512-UOzej0Le/UgkbWEO8flm+0y+G+ljUon1QWTEZOq1rnMAsxo2+SckbiZdKzAHHlVh6gJqI1TjC/xwgR50MuCrBQ=="
|
||||
},
|
||||
"node_modules/vega": {
|
||||
"version": "5.25.0",
|
||||
"resolved": "https://registry.npmjs.org/vega/-/vega-5.25.0.tgz",
|
||||
@@ -2360,6 +2572,20 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlbuilder2": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.1.1.tgz",
|
||||
"integrity": "sha512-WCSfbfZnQDdLQLiMdGUQpMxxckeQ4oZNMNhLVkcekTu7xhD4tuUDyAPoY8CwXvBYE6LwBHd6QW2WZXlOWr1vCw==",
|
||||
"dependencies": {
|
||||
"@oozcitak/dom": "1.15.10",
|
||||
"@oozcitak/infra": "1.0.8",
|
||||
"@oozcitak/util": "8.3.8",
|
||||
"js-yaml": "3.14.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlchars": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
|
||||
@@ -2451,9 +2677,9 @@
|
||||
},
|
||||
"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=="
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://gitea.jbrumond.me/api/packages/doc-utils/npm/%40doc-utils%2Fcolor-themes/-/0.2.0/color-themes-0.2.0.tgz",
|
||||
"integrity": "sha512-UtjY25B8m4qdMvrmTPK3I1JXckbc1cvCOmIygHFBexpSWBQmb+sdoXfdCsoSpgPncurz1kwlEXffgPELCGtP8g=="
|
||||
},
|
||||
"@doc-utils/jsonschema2markdown": {
|
||||
"version": "0.1.1",
|
||||
@@ -2469,15 +2695,15 @@
|
||||
}
|
||||
},
|
||||
"@doc-utils/markdown2html": {
|
||||
"version": "0.1.21",
|
||||
"resolved": "https://gitea.jbrumond.me/api/packages/doc-utils/npm/%40doc-utils%2Fmarkdown2html/-/0.1.21/markdown2html-0.1.21.tgz",
|
||||
"integrity": "sha512-yoyWrOOm4NNvxvzGn0oFwGgJb8xwmdQcAzgvN2s+xIC2sVL1WBirOlM+iJ1pOhXfAiKEPqzgEim+SE74siWp9Q==",
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://gitea.jbrumond.me/api/packages/doc-utils/npm/%40doc-utils%2Fmarkdown2html/-/0.3.5/markdown2html-0.3.5.tgz",
|
||||
"integrity": "sha512-Z5zeHIRU7gyMu8taiNcQ2+FYD08184FGNzKBT2y4mtYTjSRBlFQH+trye6e14Mzc1wx9juoZ7KpysS1M6qe4sw==",
|
||||
"requires": {
|
||||
"bytefield-svg": "^1.6.1",
|
||||
"dompurify": "^2.3.6",
|
||||
"jsdom": "^20.0.1",
|
||||
"katex": "^0.16.7",
|
||||
"marked": "^4.1.1",
|
||||
"marked": "^5.0.2",
|
||||
"nomnoml": "^1.5.2",
|
||||
"pikchr": "^0.0.5",
|
||||
"prismjs": "^1.29.0",
|
||||
@@ -2499,6 +2725,38 @@
|
||||
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
|
||||
}
|
||||
},
|
||||
"@oozcitak/dom": {
|
||||
"version": "1.15.10",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-1.15.10.tgz",
|
||||
"integrity": "sha512-0JT29/LaxVgRcGKvHmSrUTEvZ8BXvZhGl2LASRUgHqDTC1M5g1pLmVv56IYNyt3bG2CUjDkc67wnyZC14pbQrQ==",
|
||||
"requires": {
|
||||
"@oozcitak/infra": "1.0.8",
|
||||
"@oozcitak/url": "1.0.4",
|
||||
"@oozcitak/util": "8.3.8"
|
||||
}
|
||||
},
|
||||
"@oozcitak/infra": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-1.0.8.tgz",
|
||||
"integrity": "sha512-JRAUc9VR6IGHOL7OGF+yrvs0LO8SlqGnPAMqyzOuFZPSZSXI7Xf2O9+awQPSMXgIWGtgUf/dA6Hs6X6ySEaWTg==",
|
||||
"requires": {
|
||||
"@oozcitak/util": "8.3.8"
|
||||
}
|
||||
},
|
||||
"@oozcitak/url": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-1.0.4.tgz",
|
||||
"integrity": "sha512-kDcD8y+y3FCSOvnBI6HJgl00viO/nGbQoCINmQ0h98OhnGITrWR3bOGfwYCthgcrV8AnTJz8MzslTQbC3SOAmw==",
|
||||
"requires": {
|
||||
"@oozcitak/infra": "1.0.8",
|
||||
"@oozcitak/util": "8.3.8"
|
||||
}
|
||||
},
|
||||
"@oozcitak/util": {
|
||||
"version": "8.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-8.3.8.tgz",
|
||||
"integrity": "sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ=="
|
||||
},
|
||||
"@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
@@ -2520,6 +2778,40 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.4.tgz",
|
||||
"integrity": "sha512-MHmwBtCb7OCv1DSivz2UNJXPGU/1btAWRKlqJ2saEhVJkpkvqHMMaOpKg0v4sAbDWSQekHGvPVMM8nQ+Jen03Q=="
|
||||
},
|
||||
"@types/ical": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/ical/-/ical-0.8.0.tgz",
|
||||
"integrity": "sha512-46KAYxAwRuCh+jRgl9k5cTaXJJkB16gWpvMVPuog9UBBb2zXTf9M0MsfWYBQ21JohFSuMZfPTvk6tohqGr1tCg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"rrule": "2.6.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"luxon": {
|
||||
"version": "1.28.1",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz",
|
||||
"integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"rrule": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/rrule/-/rrule-2.6.4.tgz",
|
||||
"integrity": "sha512-sLdnh4lmjUqq8liFiOUXD5kWp/FcnbDLPwq5YAc/RrN6120XOPb86Ae5zxF7ttBVq8O3LxjjORMEit1baluahA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"luxon": "^1.21.3",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/jsdom": {
|
||||
"version": "20.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz",
|
||||
@@ -2535,7 +2827,7 @@
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.0.tgz",
|
||||
"integrity": "sha512-uKRI5QORDnrGFYgcdAVnHvEIvEZ8noTpP/Bg+HeUzZghwinDlIS87DEenV5r1YoOF9G4x600YsUXLWZ19rmTmg==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"@types/mustache": {
|
||||
"version": "4.2.2",
|
||||
@@ -2544,10 +2836,10 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "18.16.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.9.tgz",
|
||||
"integrity": "sha512-IeB32oIV4oGArLrd7znD2rkHQ6EDCM+2Sr76dJnrHwv9OHBTTM6nuDLK9bmikXzPa0ZlWMWtRGo/Uw4mrzQedA==",
|
||||
"dev": true
|
||||
"version": "18.16.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.13.tgz",
|
||||
"integrity": "sha512-uZRomboV1vBL61EBXneL4j9/hEn+1Yqa4LQdpGrKmXFyJmVfWc9JV9+yb2AlnOnuaDnb2PDO3hC6/LKmzJxP1A==",
|
||||
"devOptional": true
|
||||
},
|
||||
"@types/prismjs": {
|
||||
"version": "1.26.0",
|
||||
@@ -2606,6 +2898,14 @@
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"requires": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"array-back": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz",
|
||||
@@ -3134,14 +3434,14 @@
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
|
||||
},
|
||||
"glob": {
|
||||
"version": "10.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.2.3.tgz",
|
||||
"integrity": "sha512-Kb4rfmBVE3eQTAimgmeqc2LwSnN0wIOkkUL6HmxEFxNJ4fHghYHVbFba/HcGcRjE6s9KoMNK3rSOwkL4PioZjg==",
|
||||
"version": "10.2.5",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.2.5.tgz",
|
||||
"integrity": "sha512-Gj+dFYPZ5hc5dazjXzB0iHg2jKWJZYMjITXYPBRQ/xc2Buw7H0BINknRTwURJ6IC6MEFpYbLvtgVb3qD+DwyuA==",
|
||||
"requires": {
|
||||
"foreground-child": "^3.1.0",
|
||||
"jackspeak": "^2.0.3",
|
||||
"minimatch": "^9.0.0",
|
||||
"minipass": "^5.0.0",
|
||||
"minipass": "^5.0.0 || ^6.0.2",
|
||||
"path-scurry": "^1.7.0"
|
||||
}
|
||||
},
|
||||
@@ -3182,6 +3482,38 @@
|
||||
"debug": "4"
|
||||
}
|
||||
},
|
||||
"ical": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/ical/-/ical-0.8.0.tgz",
|
||||
"integrity": "sha512-/viUSb/RGLLnlgm0lWRlPBtVeQguQRErSPYl3ugnUaKUnzQswKqOG3M8/P1v1AB5NJwlHTuvTq1cs4mpeG2rCg==",
|
||||
"requires": {
|
||||
"rrule": "2.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"luxon": {
|
||||
"version": "1.28.1",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz",
|
||||
"integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==",
|
||||
"optional": true
|
||||
},
|
||||
"rrule": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/rrule/-/rrule-2.4.1.tgz",
|
||||
"integrity": "sha512-+NcvhETefswZq13T8nkuEnnQ6YgUeZaqMqVbp+ZiFDPCbp3AVgQIwUvNVDdMNrP05bKZG9ddDULFp0qZZYDrxg==",
|
||||
"requires": {
|
||||
"luxon": "^1.3.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ical-generator": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ical-generator/-/ical-generator-4.1.0.tgz",
|
||||
"integrity": "sha512-5GrFDJ8SAOj8cB9P1uEZIfKrNxSZ1R2eOQfZePL+CtdWh4RwNXWe8b0goajz+Hu37vcipG3RVldoa2j57Y20IA==",
|
||||
"requires": {
|
||||
"uuid-random": "^1.3.2"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
@@ -3219,6 +3551,15 @@
|
||||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"jsdom": {
|
||||
"version": "20.0.3",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz",
|
||||
@@ -3298,9 +3639,9 @@
|
||||
"integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg=="
|
||||
},
|
||||
"marked": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
|
||||
"integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A=="
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-5.0.2.tgz",
|
||||
"integrity": "sha512-TXksm9GwqXCRNbFUZmMtqNLvy3K2cQHuWmyBDLOrY1e6i9UvZpOTJXoz7fBjYkJkaUFzV9hBFxMuZSyQt8R6KQ=="
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.52.0",
|
||||
@@ -3324,9 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"minipass": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
|
||||
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-6.0.2.tgz",
|
||||
"integrity": "sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
@@ -3368,11 +3709,12 @@
|
||||
}
|
||||
},
|
||||
"nomnoml": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/nomnoml/-/nomnoml-1.5.3.tgz",
|
||||
"integrity": "sha512-2LBKi3ygeYAC9l/wowgtBRxvQGuTryIczULVk2rFbWGUvs5aX68/sH+WgL95CschnSnxucTIv9L+3xScS71obQ==",
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/nomnoml/-/nomnoml-1.6.0.tgz",
|
||||
"integrity": "sha512-+t3ZBx/yBBnvzQkVU0/pyXZNGHIyRdnPBg/8H7YvWBbvRroRG21amC2kZ1UVhGes2kU2cU6KjO4RSDgglajeQw==",
|
||||
"requires": {
|
||||
"graphre": "^0.1.3"
|
||||
"graphre": "^0.1.3",
|
||||
"nomnoml": "^1.5.3"
|
||||
}
|
||||
},
|
||||
"nwsapi": {
|
||||
@@ -3433,12 +3775,12 @@
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
|
||||
},
|
||||
"path-scurry": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.8.0.tgz",
|
||||
"integrity": "sha512-IjTrKseM404/UAWA8bBbL3Qp6O2wXkanuIE3seCxBH7ctRuvH1QRawy1N3nVDHGkdeZsjOsSe/8AQBL/VQCy2g==",
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.9.2.tgz",
|
||||
"integrity": "sha512-qSDLy2aGFPm8i4rsbHd4MNyTcrzHFsLQykrtbuGRknZZCBBVXSv2tSCDN2Cg6Rt/GFRw8GoW9y9Ecw5rIPG1sg==",
|
||||
"requires": {
|
||||
"lru-cache": "^9.1.1",
|
||||
"minipass": "^5.0.0"
|
||||
"minipass": "^5.0.0 || ^6.0.2"
|
||||
}
|
||||
},
|
||||
"pikchr": {
|
||||
@@ -3515,6 +3857,16 @@
|
||||
"resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.1.tgz",
|
||||
"integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g=="
|
||||
},
|
||||
"rrule": {
|
||||
"version": "2.7.2",
|
||||
"resolved": "https://registry.npmjs.org/rrule/-/rrule-2.7.2.tgz",
|
||||
"integrity": "sha512-NkBsEEB6FIZOZ3T8frvEBOB243dm46SPufpDckY/Ap/YH24V1zLeMmDY8OA10lk452NdrF621+ynDThE7FQU2A==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"rw": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
|
||||
@@ -3562,6 +3914,11 @@
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"optional": true
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
|
||||
},
|
||||
"string-width": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||
@@ -3695,6 +4052,13 @@
|
||||
"punycode": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz",
|
||||
"integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"type-check": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
|
||||
@@ -3728,6 +4092,11 @@
|
||||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"uuid-random": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid-random/-/uuid-random-1.3.2.tgz",
|
||||
"integrity": "sha512-UOzej0Le/UgkbWEO8flm+0y+G+ljUon1QWTEZOq1rnMAsxo2+SckbiZdKzAHHlVh6gJqI1TjC/xwgR50MuCrBQ=="
|
||||
},
|
||||
"vega": {
|
||||
"version": "5.25.0",
|
||||
"resolved": "https://registry.npmjs.org/vega/-/vega-5.25.0.tgz",
|
||||
@@ -4239,6 +4608,17 @@
|
||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
||||
"integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw=="
|
||||
},
|
||||
"xmlbuilder2": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.1.1.tgz",
|
||||
"integrity": "sha512-WCSfbfZnQDdLQLiMdGUQpMxxckeQ4oZNMNhLVkcekTu7xhD4tuUDyAPoY8CwXvBYE6LwBHd6QW2WZXlOWr1vCw==",
|
||||
"requires": {
|
||||
"@oozcitak/dom": "1.15.10",
|
||||
"@oozcitak/infra": "1.0.8",
|
||||
"@oozcitak/util": "8.3.8",
|
||||
"js-yaml": "3.14.1"
|
||||
}
|
||||
},
|
||||
"xmlchars": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
|
||||
|
12
package.json
12
package.json
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "@doc-utils/docs2website",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.8",
|
||||
"publishConfig": {
|
||||
"registry": "https://gitea.home.jbrumond.me/api/packages/doc-utils/npm/"
|
||||
"registry": "https://gitea.jbrumond.me/api/packages/doc-utils/npm/"
|
||||
},
|
||||
"scripts": {
|
||||
"tsc": "tsc",
|
||||
@@ -13,6 +13,7 @@
|
||||
},
|
||||
"main": "./build/index.js",
|
||||
"devDependencies": {
|
||||
"@types/ical": "^0.8.0",
|
||||
"@types/jsdom": "^20.0.1",
|
||||
"@types/luxon": "^3.3.0",
|
||||
"@types/mustache": "^4.2.2",
|
||||
@@ -21,12 +22,15 @@
|
||||
"typescript": "^5.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@doc-utils/color-themes": "^0.1.14",
|
||||
"@doc-utils/color-themes": "^0.2.0",
|
||||
"@doc-utils/jsonschema2markdown": "^0.1.1",
|
||||
"@doc-utils/markdown2html": "^0.1.21",
|
||||
"@doc-utils/markdown2html": "^0.3.6",
|
||||
"glob": "^10.2.3",
|
||||
"ical": "^0.8.0",
|
||||
"ical-generator": "^4.1.0",
|
||||
"luxon": "^3.3.0",
|
||||
"mustache": "^4.2.0",
|
||||
"xmlbuilder2": "^3.1.1",
|
||||
"yaml": "^2.2.2"
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
|
||||
import { read_config } from '../conf';
|
||||
import { build_docs_project } from '../build';
|
||||
import { build_docs_project } from '../build-files';
|
||||
|
||||
main();
|
||||
|
||||
|
357
src/build-files/helpers.ts
Normal file
357
src/build-files/helpers.ts
Normal file
@@ -0,0 +1,357 @@
|
||||
|
||||
import { dirname, join as path_join } from 'path';
|
||||
import { icons } from '../icons';
|
||||
import { BuildState } from './state';
|
||||
import { mkdirp, write_text } from '../fs';
|
||||
import { CalendarConfig, RSSConfig } from '../conf';
|
||||
import { render_theme_css_properties } from '../themes';
|
||||
import { load_partials, FrontMatter, Context, load_layout, render_template, EventFrontmatter } from '../template';
|
||||
import { render_markdown_to_html, render_markdown_to_html_inline_sync } from '@doc-utils/markdown2html';
|
||||
import { RSSEntry } from './rss';
|
||||
import { EventEntry } from './icalendar';
|
||||
import { as_context_time, from_iso } from '../time';
|
||||
import { FileMetadata } from '../metadata';
|
||||
|
||||
export interface OutFileURL {
|
||||
base_url: string;
|
||||
rel_path: string;
|
||||
dir_url: string;
|
||||
abs_url: string;
|
||||
}
|
||||
|
||||
export function map_output_file_to_url(state: BuildState, out_file: string, index_file?: string) : OutFileURL {
|
||||
let rel_path = out_file.slice(state.conf.input.root.length);
|
||||
|
||||
if (index_file && rel_path.endsWith('/' + index_file)) {
|
||||
rel_path = rel_path.slice(0, -(index_file.length + 1));
|
||||
}
|
||||
|
||||
if (! rel_path.startsWith('/')) {
|
||||
rel_path = '/' + rel_path;
|
||||
}
|
||||
|
||||
const base_url = state.conf.base_url?.endsWith('/')
|
||||
? state.conf.base_url.slice(0, -1)
|
||||
: (state.conf.base_url ?? '');
|
||||
|
||||
return {
|
||||
base_url,
|
||||
rel_path,
|
||||
dir_url: base_url + dirname(rel_path),
|
||||
abs_url: base_url + rel_path
|
||||
};
|
||||
}
|
||||
|
||||
export async function map_input_file_to_output_file(state: BuildState, in_file: string, remove_exts?: string[], add_ext?: string) {
|
||||
if (! in_file.startsWith(state.conf.input.root)) {
|
||||
throw new Error('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));
|
||||
|
||||
if (remove_exts) {
|
||||
for (const ext of remove_exts) {
|
||||
if (out_file.endsWith(ext)) {
|
||||
out_file = out_file.slice(0, -ext.length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (add_ext) {
|
||||
out_file += add_ext;
|
||||
}
|
||||
|
||||
const dir = dirname(out_file);
|
||||
|
||||
if (! state.made_directories.has(dir)) {
|
||||
state.made_directories.add(dir);
|
||||
await mkdirp(dir);
|
||||
}
|
||||
|
||||
return out_file;
|
||||
}
|
||||
|
||||
export 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);
|
||||
}
|
||||
|
||||
export function mustache_context(state: BuildState, page_url: string, metadata: FileMetadata, frontmatter?: FrontMatter) : Context {
|
||||
let event: Context['event'];
|
||||
|
||||
if (frontmatter?.event) {
|
||||
event = Array.isArray(frontmatter.event)
|
||||
? frontmatter.event.map(to_context_event)
|
||||
: to_context_event(frontmatter.event);
|
||||
|
||||
function to_context_event(event_fm: EventFrontmatter) {
|
||||
const start = from_iso(event_fm.start, event_fm.time_zone);
|
||||
const end = from_iso(event_fm.end, event_fm.time_zone);
|
||||
|
||||
return {
|
||||
start: as_context_time(start),
|
||||
end: as_context_time(end),
|
||||
time_zone: event_fm.time_zone,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const has_been_updated = metadata.first_seen_time !== metadata.last_updated_time;
|
||||
|
||||
return {
|
||||
env: state.env,
|
||||
page: frontmatter,
|
||||
base_url: state.conf.base_url ?? '/',
|
||||
page_url: page_url,
|
||||
page_published: as_context_time(from_iso(metadata.first_seen_time), 'dt-published'),
|
||||
page_updated: has_been_updated ? as_context_time(from_iso(metadata.last_updated_time), 'dt-updated') : null,
|
||||
site_title: state.conf.title,
|
||||
author: get_author(state, frontmatter),
|
||||
event: event,
|
||||
event_series: Array.isArray(event) && {
|
||||
start: event[0].start,
|
||||
end: event[event.length - 1].end,
|
||||
},
|
||||
build_time: state.build_time,
|
||||
icons: icons,
|
||||
rss_feeds: state.conf.rss || [ ],
|
||||
calendars: state.conf.calendars || [ ],
|
||||
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,
|
||||
Object.assign({ }, state.conf.markdown, { base_url: page_url })
|
||||
);
|
||||
return html;
|
||||
};
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export async function render_page(state: BuildState, in_file: string, out_file: string, out_url: OutFileURL, text: string, render_as_markdown: boolean, hash: string, frontmatter?: any) {
|
||||
if (render_as_markdown) {
|
||||
const opts = Object.assign({ }, state.conf.markdown, {
|
||||
// base_url: out_url.abs_url,
|
||||
});
|
||||
|
||||
text = await render_markdown_to_html(text, opts);
|
||||
}
|
||||
|
||||
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 hash_matches = file_hash_matches(state, in_file, hash);
|
||||
const rel_in_file = in_file.slice(state.conf.input.root.length);
|
||||
const old_metadata = state.old_metadata?.files?.[rel_in_file];
|
||||
const new_metadata = hash_matches ? structuredClone(old_metadata) : {
|
||||
first_seen_time: old_metadata?.first_seen_time || state.build_time.iso,
|
||||
last_build_hash: hash,
|
||||
last_updated_time: state.build_time.iso,
|
||||
};
|
||||
|
||||
const tags = state.conf.templates?.tags;
|
||||
const context = mustache_context(state, out_url.abs_url, new_metadata, frontmatter);
|
||||
const rendered = render_template(text, context, layout, structuredClone(state.partials), tags);
|
||||
await write_text(out_file, rendered);
|
||||
|
||||
state.new_metadata.files[rel_in_file] = new_metadata;
|
||||
handle_page_side_effects(state, in_file, out_file, out_url, text, frontmatter);
|
||||
}
|
||||
|
||||
function handle_page_side_effects(state: BuildState, in_file: string, out_file: string, out_url: OutFileURL, text: string, frontmatter?: any) {
|
||||
// Only actual HTML webpages are registered with things like the RSS feed; This is
|
||||
// to prevent things like CSS files showing up, which may be templated (and therefore
|
||||
// pass through this function), but are not really "pages"
|
||||
if (frontmatter && typeof frontmatter === 'object' && out_file.endsWith('.html')) {
|
||||
if (state.conf.rss) {
|
||||
for (const [index, rss_conf] of Object.entries(state.conf.rss)) {
|
||||
if (in_file.startsWith(rss_conf.in_dir + '/')) {
|
||||
const entries = (state.rss[index] = state.rss[index] || [ ]);
|
||||
handle_rss(state, rss_conf, entries, in_file, out_url, text, frontmatter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state.conf.sitemap) {
|
||||
handle_sitemap(state, out_url, frontmatter);
|
||||
}
|
||||
|
||||
if (frontmatter?.event) {
|
||||
if (state.conf.events) {
|
||||
handle_event(state, in_file, out_url, frontmatter);
|
||||
}
|
||||
|
||||
if (state.conf.calendars) {
|
||||
for (const [index, cal_conf] of Object.entries(state.conf.calendars)) {
|
||||
if (in_file.startsWith(cal_conf.in_dir + '/')) {
|
||||
const entries = (state.rss[index] = state.rss[index] || [ ]);
|
||||
handle_calendar(state, cal_conf, entries, in_file, out_url, frontmatter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handle_rss(state: BuildState, rss_conf: RSSConfig, entries: RSSEntry[], in_file: string, out_url: OutFileURL, text: string, frontmatter: FrontMatter) {
|
||||
if (frontmatter?.rss?.skip) {
|
||||
return;
|
||||
}
|
||||
|
||||
const author_or_authors = get_author(state, frontmatter);
|
||||
const authors = author_or_authors && (Array.isArray(author_or_authors) ? author_or_authors : [ author_or_authors ]);
|
||||
|
||||
entries.push({
|
||||
url: out_url.abs_url,
|
||||
in_file: in_file,
|
||||
html_content: text,
|
||||
title: frontmatter?.title,
|
||||
description: frontmatter?.description,
|
||||
authors: authors,
|
||||
tags: frontmatter?.tags,
|
||||
});
|
||||
}
|
||||
|
||||
function handle_sitemap(state: BuildState, out_url: OutFileURL, frontmatter: FrontMatter) {
|
||||
if (frontmatter?.sitemap?.skip) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.sitemap.push({
|
||||
url: out_url.abs_url,
|
||||
lastmod: state.build_time.iso,
|
||||
change_freq: frontmatter?.sitemap?.change_freq,
|
||||
priority: frontmatter?.sitemap?.priority,
|
||||
});
|
||||
}
|
||||
|
||||
function handle_event(state: BuildState, in_file: string, out_url: OutFileURL, frontmatter: FrontMatter) {
|
||||
if (! state.conf.events) {
|
||||
return;
|
||||
}
|
||||
|
||||
const author_or_authors = get_author(state, frontmatter);
|
||||
const author = Array.isArray(author_or_authors) ? author_or_authors[0] : author_or_authors;
|
||||
|
||||
if (Array.isArray(frontmatter.event)) {
|
||||
state.event_series.push({
|
||||
url: out_url.abs_url,
|
||||
in_file: in_file,
|
||||
title: frontmatter.title,
|
||||
description: frontmatter.description,
|
||||
author_name: author?.name,
|
||||
author_email: author?.email,
|
||||
entries: frontmatter.event.slice(),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
state.events.push({
|
||||
url: out_url.abs_url,
|
||||
in_file: in_file,
|
||||
title: frontmatter.event.title || frontmatter.title,
|
||||
description: frontmatter.description,
|
||||
author_name: author?.name,
|
||||
author_email: author?.email,
|
||||
start: frontmatter.event.start,
|
||||
end: frontmatter.event.end,
|
||||
time_zone: frontmatter.event.time_zone,
|
||||
});
|
||||
}
|
||||
|
||||
function handle_calendar(state: BuildState, cal_conf: CalendarConfig, entries: EventEntry[], in_file: string, out_url: OutFileURL, frontmatter: FrontMatter) {
|
||||
const author_or_authors = get_author(state, frontmatter);
|
||||
const author = Array.isArray(author_or_authors) ? author_or_authors[0] : author_or_authors;
|
||||
|
||||
if (Array.isArray(frontmatter.event)) {
|
||||
// todo: add each event to the calendar
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
entries.push({
|
||||
url: out_url.abs_url,
|
||||
in_file: in_file,
|
||||
title: frontmatter.title,
|
||||
description: frontmatter.description,
|
||||
author_name: author?.name,
|
||||
author_email: author?.email,
|
||||
start: frontmatter.event?.start,
|
||||
end: frontmatter.event?.end,
|
||||
time_zone: frontmatter.event?.time_zone,
|
||||
});
|
||||
}
|
||||
|
||||
export function config_hash_matches(state: BuildState) {
|
||||
const { old_metadata, new_metadata } = state;
|
||||
|
||||
if (! old_metadata.last_build?.config_hash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (old_metadata.last_build.config_hash !== new_metadata.last_build.config_hash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function file_hash_matches(state: BuildState, in_file: string, new_hash: string) {
|
||||
in_file = in_file.slice(state.conf.input.root.length);
|
||||
const old_hash = state.old_metadata.files[in_file]?.last_build_hash;
|
||||
|
||||
if (! old_hash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (old_hash !== new_hash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function skip_file(state: BuildState, in_file: string, out_file: string, out_url: OutFileURL, frontmatter?: any) {
|
||||
in_file = in_file.slice(state.conf.input.root.length);
|
||||
state.new_metadata.files[in_file] = structuredClone(state.old_metadata?.files?.[in_file]);
|
||||
handle_page_side_effects(state, in_file, out_file, out_url, frontmatter);
|
||||
}
|
||||
|
||||
export function get_author(state: BuildState, frontmatter?: FrontMatter) {
|
||||
if (! frontmatter?.author) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Array.isArray(frontmatter.author)) {
|
||||
const list = frontmatter.author
|
||||
.map((author) => state.conf.authors?.[author])
|
||||
.filter((author) => author);
|
||||
|
||||
if (! list.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
return state.conf.authors?.[frontmatter.author]
|
||||
}
|
209
src/build-files/icalendar.ts
Normal file
209
src/build-files/icalendar.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
|
||||
import { write_text } from '../fs';
|
||||
import { BuildState } from './state';
|
||||
import { map_input_file_to_output_file } from './helpers';
|
||||
// import { parseICS, CalendarComponent } from 'ical';
|
||||
import create_calendar, { ICalEventData, ICalCalendarData } from 'ical-generator';
|
||||
import { FrontMatterLocation } from '../template';
|
||||
|
||||
export type { ICalEventData, ICalCalendarData, ICalAttendeeData, ICalAttendeeStatus } from 'ical-generator';
|
||||
|
||||
export interface EventSeries {
|
||||
url: string;
|
||||
in_file: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
author_name?: string;
|
||||
author_email?: string;
|
||||
entries: {
|
||||
title?: string;
|
||||
start?: string;
|
||||
end?: string;
|
||||
time_zone?: `${string}/${string}`;
|
||||
location?: FrontMatterLocation;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface EventEntry {
|
||||
url: string;
|
||||
in_file: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
author_name?: string;
|
||||
author_email?: string;
|
||||
start?: string;
|
||||
end?: string;
|
||||
time_zone?: `${string}/${string}`;
|
||||
location?: FrontMatterLocation;
|
||||
}
|
||||
|
||||
export async function write_events_and_calendars_if_needed(state: BuildState) {
|
||||
if (state.conf.events) {
|
||||
for (const entry of state.events) {
|
||||
const event = icalendar_event(state, entry);
|
||||
const cal_data: ICalCalendarData = {
|
||||
prodId: {
|
||||
company: 'jbrumond.me',
|
||||
product: 'docs2website',
|
||||
language: 'EN',
|
||||
},
|
||||
// ...
|
||||
};
|
||||
|
||||
const calendar = create_icalendar(cal_data, event);
|
||||
const out_file = await map_input_file_to_output_file(state, entry.in_file, [ '.html', '.md', '.markdown' ], '.ics');
|
||||
await write_text(out_file, calendar);
|
||||
}
|
||||
|
||||
for (const series of state.event_series) {
|
||||
const events = series.entries.map((event) => {
|
||||
return icalendar_event(state, {
|
||||
url: series.url,
|
||||
in_file: series.in_file,
|
||||
title: event.title || series.title,
|
||||
description: series.description,
|
||||
author_name: series.author_name,
|
||||
author_email: series.author_email,
|
||||
start: event.start,
|
||||
end: event.end,
|
||||
time_zone: event.time_zone,
|
||||
location: event.location,
|
||||
});
|
||||
});
|
||||
|
||||
const cal_data: ICalCalendarData = {
|
||||
name: series.title,
|
||||
description: series.description,
|
||||
url: series.url,
|
||||
prodId: {
|
||||
company: 'jbrumond.me',
|
||||
product: 'docs2website',
|
||||
language: 'EN',
|
||||
},
|
||||
// ...
|
||||
};
|
||||
|
||||
const calendar = create_icalendar(cal_data, events);
|
||||
const out_file = await map_input_file_to_output_file(state, series.in_file, [ '.html', '.md', '.markdown' ], '.ics');
|
||||
await write_text(out_file, calendar);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.conf.calendars) {
|
||||
for (let index = 0; index < state.conf.calendars.length; index++) {
|
||||
const cal_conf = state.conf.calendars[index];
|
||||
const cal_entries = state.calendars[index];
|
||||
|
||||
for (const entry of cal_entries) {
|
||||
const event = icalendar_event(state, entry);
|
||||
const cal_data: ICalCalendarData = {
|
||||
name: cal_conf.title,
|
||||
prodId: {
|
||||
company: 'jbrumond.me',
|
||||
product: 'docs2website',
|
||||
language: 'EN',
|
||||
},
|
||||
// ...
|
||||
};
|
||||
|
||||
const calendar = create_icalendar(cal_data, event);
|
||||
await write_text(cal_conf.out_file, calendar);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// export function parse_icalendar(contents: string) {
|
||||
// const parsed = parseICS(contents);
|
||||
// const calendar: CalendarComponent[] = [ ];
|
||||
|
||||
// for (const data of Object.values(parsed)) {
|
||||
// calendar.push(data);
|
||||
// }
|
||||
|
||||
// return calendar;
|
||||
// }
|
||||
|
||||
export function create_icalendar(cal: ICalCalendarData, events: ICalEventData | ICalEventData[]) {
|
||||
const calendar = create_calendar(cal);
|
||||
|
||||
if (Array.isArray(events)) {
|
||||
for (const event of events) {
|
||||
calendar.createEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
calendar.createEvent(events);
|
||||
}
|
||||
|
||||
return calendar.toString();
|
||||
}
|
||||
|
||||
export function icalendar_event(state: BuildState, entry: EventEntry) : ICalEventData {
|
||||
const in_file = entry.in_file.slice(state.conf.input.root.length);
|
||||
const metadata = state.new_metadata.files[in_file];
|
||||
|
||||
return {
|
||||
id: entry.url,
|
||||
summary: entry.title,
|
||||
description: entry.description || void 0,
|
||||
start: entry.start,
|
||||
end: entry.end,
|
||||
url: entry.url,
|
||||
timezone: entry.time_zone,
|
||||
created: metadata.first_seen_time,
|
||||
lastModified: metadata.last_updated_time,
|
||||
organizer: {
|
||||
name: entry.author_name || 'Unknown',
|
||||
email: entry.author_email,
|
||||
},
|
||||
location: format_location(entry.location),
|
||||
// attendees: post.mentions.flatMap((mention) : ICalAttendeeData | ICalAttendeeData[] => {
|
||||
// if (mention.is_rsvp && mention.is_reply_to_this) {
|
||||
// const ext = mention.external as ExternalEntry;
|
||||
// const status = ext.rsvp_type === 'yes'
|
||||
// ? 'ACCEPTED' as const
|
||||
// : ext.rsvp_type === 'no'
|
||||
// ? 'DECLINED' as const
|
||||
// : ext.rsvp_type === 'maybe'
|
||||
// ? 'TENTATIVE' as const
|
||||
// : 'NEEDS-ACTION' as const;
|
||||
|
||||
// return {
|
||||
// name: mention.author_name,
|
||||
// rsvp: ext.rsvp_type === 'yes',
|
||||
// status: status as ICalAttendeeStatus
|
||||
// };
|
||||
// }
|
||||
|
||||
// return [ ];
|
||||
// }),
|
||||
};
|
||||
}
|
||||
|
||||
function format_location(loc: FrontMatterLocation) {
|
||||
if (! loc) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (loc.description) {
|
||||
if (loc.address) {
|
||||
return `${loc.description} (${loc.address})`;
|
||||
}
|
||||
|
||||
if (loc.lat && loc.long) {
|
||||
return `${loc.description} [${loc.lat}, ${loc.long}]`;
|
||||
}
|
||||
|
||||
return loc.description;
|
||||
}
|
||||
|
||||
if (loc.address) {
|
||||
return loc.address;
|
||||
}
|
||||
|
||||
if (loc.lat && loc.long) {
|
||||
return `[${loc.lat}, ${loc.long}]`;
|
||||
}
|
||||
}
|
100
src/build-files/index.ts
Normal file
100
src/build-files/index.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
|
||||
import { DateTime } from 'luxon';
|
||||
import { Config } from '../conf';
|
||||
import { build_env_scope } from '../env';
|
||||
import { load_themes } from '../themes';
|
||||
import { load_extras } from '../template';
|
||||
import { Metadata } from '../metadata';
|
||||
import { hash_obj } from '../hash';
|
||||
import { BuildState } from './state';
|
||||
import { read_json, with_default_if_missing, write_json } from '../fs';
|
||||
|
||||
import { copy_raw_files } from './raw';
|
||||
import { render_text_file_templates } from './mustache';
|
||||
import { render_markdown_files } from './markdown';
|
||||
import { render_json_schema_files } from './jsonschema';
|
||||
import { write_sitemap_if_needed } from './sitemap';
|
||||
import { write_rss_if_needed } from './rss';
|
||||
import { write_events_and_calendars_if_needed } from './icalendar';
|
||||
import { as_context_time, as_html_time } from '../time';
|
||||
|
||||
export { BuildState, ThemeGroups } from './state';
|
||||
|
||||
export async function build_docs_project(conf: Config) {
|
||||
const now = DateTime.now();
|
||||
const conf_hash = hash_obj(conf);
|
||||
const themes = await load_themes(conf);
|
||||
const get_metadata = read_json(conf.metadata, false).then(({ parsed }) => parsed);
|
||||
const metadata = await with_default_if_missing<Metadata>(get_metadata, {
|
||||
last_build: null,
|
||||
files: { },
|
||||
});
|
||||
|
||||
const state: BuildState = {
|
||||
conf,
|
||||
seen_files: new Set<string>(),
|
||||
env: build_env_scope(conf),
|
||||
layouts: Object.create(null),
|
||||
themes: themes,
|
||||
theme_groups: {
|
||||
all: Object.values(themes),
|
||||
light: [ ],
|
||||
dark: [ ],
|
||||
high_contrast: [ ],
|
||||
low_contrast: [ ],
|
||||
monochrome: [ ],
|
||||
greyscale: [ ],
|
||||
protanopia_safe: [ ],
|
||||
deuteranopia_safe: [ ],
|
||||
tritanopia_safe: [ ],
|
||||
},
|
||||
old_metadata: metadata,
|
||||
new_metadata: {
|
||||
last_build: {
|
||||
time: now.toISO(),
|
||||
config_hash: conf_hash,
|
||||
},
|
||||
files: { }
|
||||
},
|
||||
extras: await load_extras(),
|
||||
made_directories: new Set<string>(),
|
||||
rss: [ ],
|
||||
sitemap: [ ],
|
||||
events: [ ],
|
||||
event_series: [ ],
|
||||
calendars: [ ],
|
||||
build_time: as_context_time(now),
|
||||
};
|
||||
|
||||
for (const theme of Object.values(themes)) {
|
||||
for (const label of theme.labels) {
|
||||
state.theme_groups[label].push(theme);
|
||||
}
|
||||
}
|
||||
|
||||
if (conf.input.raw) {
|
||||
await copy_raw_files(state);
|
||||
}
|
||||
|
||||
if (conf.input.text) {
|
||||
await render_text_file_templates(state);
|
||||
}
|
||||
|
||||
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: other file types...
|
||||
|
||||
await write_sitemap_if_needed(state);
|
||||
await write_rss_if_needed(state);
|
||||
await write_events_and_calendars_if_needed(state);
|
||||
|
||||
// Write the updated metadata file
|
||||
await write_json(conf.metadata, state.new_metadata, true);
|
||||
}
|
||||
|
131
src/build-files/jsonschema.ts
Normal file
131
src/build-files/jsonschema.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
|
||||
import { glob } from 'glob';
|
||||
import { BuildState } from './state';
|
||||
import { read_json, write_text, read_yaml } from '../fs';
|
||||
import { build_markdown_from_json_schema } from '@doc-utils/jsonschema2markdown';
|
||||
import { stringify as to_yaml } from 'yaml';
|
||||
import { build_partials, map_input_file_to_output_file, map_output_file_to_url, render_page } from './helpers';
|
||||
|
||||
export async function render_json_schema_files(state: BuildState) {
|
||||
const promises: Promise<any>[] = [ ];
|
||||
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);
|
||||
}
|
||||
|
||||
export async function handle_json_schema_json_file(state: BuildState, in_file: string) {
|
||||
const promises: Promise<any>[] = [ ];
|
||||
|
||||
const out_file = map_input_file_to_output_file(state, in_file, [ '.json' ], '.html');
|
||||
const { frontmatter, parsed, json, hash } = await read_json(in_file, true);
|
||||
|
||||
promises.push(
|
||||
render_json_schema(state, parsed, in_file, await out_file, hash, 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);
|
||||
}
|
||||
|
||||
export async function handle_json_schema_yaml_file(state: BuildState, in_file: string) {
|
||||
const promises: Promise<any>[] = [ ];
|
||||
|
||||
const out_file = map_input_file_to_output_file(state, in_file, [ '.yaml', '.yml' ], '.html');
|
||||
const { frontmatter, parsed, yaml, hash } = await read_yaml(in_file, true);
|
||||
|
||||
promises.push(
|
||||
render_json_schema(state, parsed, in_file, await out_file, hash, 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);
|
||||
}
|
||||
|
||||
export async function render_json_schema(state: BuildState, schema: unknown, in_file: string, out_file: string, hash: string, frontmatter?: any) {
|
||||
if (frontmatter?.skip) {
|
||||
return;
|
||||
}
|
||||
|
||||
const out_url = map_output_file_to_url(state, out_file);
|
||||
|
||||
const promises: Promise<any>[] = [ ];
|
||||
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))
|
||||
);
|
||||
}
|
||||
|
||||
promises.push(
|
||||
render_page(state, in_file, out_file, out_url, markdown, true, hash, frontmatter)
|
||||
);
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
43
src/build-files/markdown.ts
Normal file
43
src/build-files/markdown.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
|
||||
import { glob } from 'glob';
|
||||
import { read_text } from '../fs';
|
||||
import { BuildState } from './state';
|
||||
import { build_partials, map_input_file_to_output_file, map_output_file_to_url, render_page } from './helpers';
|
||||
|
||||
export async function render_markdown_files(state: BuildState) {
|
||||
const promises: Promise<any>[] = [ ];
|
||||
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);
|
||||
}
|
||||
|
||||
export async function render_markdown_file(state: BuildState, in_file: string) {
|
||||
const out_file = await map_input_file_to_output_file(state, in_file, [ '.md', '.markdown' ], '.html');
|
||||
const out_url = map_output_file_to_url(state, out_file);
|
||||
const { frontmatter, text, hash } = await read_text(in_file);
|
||||
|
||||
if (frontmatter?.skip) {
|
||||
return;
|
||||
}
|
||||
|
||||
await render_page(state, in_file, out_file, out_url, text, true, hash, frontmatter);
|
||||
}
|
43
src/build-files/mustache.ts
Normal file
43
src/build-files/mustache.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
|
||||
import { glob } from 'glob';
|
||||
import { read_text } from '../fs';
|
||||
import { BuildState } from './state';
|
||||
import { build_partials, map_input_file_to_output_file, map_output_file_to_url, render_page } from './helpers';
|
||||
|
||||
export async function render_text_file_templates(state: BuildState) {
|
||||
const promises: Promise<any>[] = [ ];
|
||||
const files = await glob(state.conf.input.text, {
|
||||
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_text_file_template(state, in_file)
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
export async function render_text_file_template(state: BuildState, in_file: string) {
|
||||
const out_file = await map_input_file_to_output_file(state, in_file);
|
||||
const out_url = map_output_file_to_url(state, out_file);
|
||||
const { frontmatter, text, hash } = await read_text(in_file);
|
||||
|
||||
if (frontmatter?.skip) {
|
||||
return;
|
||||
}
|
||||
|
||||
await render_page(state, in_file, out_file, out_url, text, false, hash, frontmatter);
|
||||
}
|
32
src/build-files/raw.ts
Normal file
32
src/build-files/raw.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
import { glob } from 'glob';
|
||||
import { BuildState } from './state';
|
||||
import { promises as fs } from 'fs';
|
||||
import { map_input_file_to_output_file } from './helpers';
|
||||
|
||||
export async function copy_raw_files(state: BuildState) {
|
||||
const promises: Promise<any>[] = [ ];
|
||||
const files = await glob(state.conf.input.raw, {
|
||||
absolute: true,
|
||||
cwd: state.conf.input.root,
|
||||
});
|
||||
|
||||
for (const in_file of files) {
|
||||
if (state.seen_files.has(in_file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
state.seen_files.add(in_file);
|
||||
|
||||
const out_file = map_input_file_to_output_file(state, in_file);
|
||||
|
||||
// todo: check hashes to see if we can skip
|
||||
|
||||
promises.push(
|
||||
fs.copyFile(in_file, await out_file)
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
// todo: update metadata
|
||||
}
|
144
src/build-files/rss.ts
Normal file
144
src/build-files/rss.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
|
||||
import type { XMLBuilder } from 'xmlbuilder2/lib/interfaces';
|
||||
import { create as create_xml } from 'xmlbuilder2';
|
||||
import { BuildState } from './state';
|
||||
import { write_text } from '../fs';
|
||||
import { AuthorConfig, app_version } from '../conf';
|
||||
import { map_output_file_to_url } from './helpers';
|
||||
import { DateTime } from 'luxon';
|
||||
import { FileMetadata } from '../metadata';
|
||||
|
||||
export interface RSSEntry {
|
||||
url: string;
|
||||
in_file: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
authors?: AuthorConfig[];
|
||||
html_content?: string;
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
export async function write_rss_if_needed(state: BuildState) {
|
||||
if (! state.conf.rss) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let index = 0; index < state.conf.rss.length; index++) {
|
||||
const rss_conf = state.conf.rss[index];
|
||||
const doc = create_xml({ version: '1.0', encoding: 'UTF-8' });
|
||||
const { abs_url: self_url } = map_output_file_to_url(state, rss_conf.out_file);
|
||||
|
||||
if (rss_conf.xsl) {
|
||||
for (const url of rss_conf.xsl) {
|
||||
doc.ins('xml-stylesheet', `href="${url}" type="text/xsl"`);
|
||||
}
|
||||
}
|
||||
|
||||
const rss = doc.ele('rss', {
|
||||
version: '2.0',
|
||||
'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
|
||||
'xmlns:content': 'http://purl.org/rss/1.0/modules/content/',
|
||||
'xmlns:atom': 'http://www.w3.org/2005/Atom',
|
||||
// 'xmlns:docs2website': 'urn:uuid:7fc4e5d4-f68e-11ed-b0d0-00155ddef564',
|
||||
});
|
||||
|
||||
const channel = rss.ele('channel');
|
||||
channel.ele('generator').txt(`docs2website ${app_version}`);
|
||||
|
||||
if (rss_conf.title) {
|
||||
channel.ele('title').txt(rss_conf.title);
|
||||
}
|
||||
|
||||
if (rss_conf.link) {
|
||||
channel.ele('link').txt(rss_conf.link);
|
||||
}
|
||||
|
||||
link(channel, 'application/rss+xml', 'self', self_url);
|
||||
|
||||
if (rss_conf.alternates?.length) {
|
||||
for (const alt of rss_conf.alternates) {
|
||||
link(channel, alt.type, 'alternate', alt.href);
|
||||
}
|
||||
}
|
||||
|
||||
if (rss_conf.description) {
|
||||
channel.ele('description').txt(rss_conf.description);
|
||||
}
|
||||
|
||||
if (rss_conf.language) {
|
||||
channel.ele('language').txt(rss_conf.language);
|
||||
}
|
||||
|
||||
if (rss_conf.copyright) {
|
||||
channel.ele('copyright').txt(rss_conf.copyright);
|
||||
}
|
||||
|
||||
channel.ele('lastBuildDate').txt(state.build_time.rfc2822);
|
||||
|
||||
// channel.ele('rating').txt(''); // see: https://www.w3.org/PICS/
|
||||
// channel.ele('pubDate').txt(publish_date.toUTCString());
|
||||
// channel.ele('categories').txt('');
|
||||
// channel.ele('docs').txt('');
|
||||
// channel.ele('managingEditor').txt('james@jbrumond.me');
|
||||
// channel.ele('webMaster').txt('james@jbrumond.me');
|
||||
// channel.ele('ttl').txt('');
|
||||
// channel.ele('image').txt('');
|
||||
// channel.ele('skipHours').txt('');
|
||||
// channel.ele('skipDays').txt('');
|
||||
|
||||
let entries: (RSSEntry & { metadata: FileMetadata, first_seen: number })[] = [ ];
|
||||
|
||||
for (const entry of state.rss[index] || [ ]) {
|
||||
const in_file = entry.in_file.slice(state.conf.input.root.length);
|
||||
const metadata = state.new_metadata.files[in_file];
|
||||
entries.push({ ...entry, metadata, first_seen: DateTime.fromISO(metadata.first_seen_time).toUnixInteger() });
|
||||
}
|
||||
|
||||
entries = entries.sort((a, b) => {
|
||||
return b.first_seen - a.first_seen;
|
||||
});
|
||||
|
||||
for (const entry of entries) {
|
||||
const item = channel.ele('item');
|
||||
|
||||
item.ele('link').txt(entry.url);
|
||||
|
||||
if (entry.title) {
|
||||
item.ele('title').txt(entry.title);
|
||||
}
|
||||
|
||||
if (entry.description) {
|
||||
item.ele('description').txt(entry.description);
|
||||
}
|
||||
|
||||
if (entry.authors) {
|
||||
for (const author of entry.authors) {
|
||||
item.ele('dc:creator').ele({ $: author.name || author.url || author.email });
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.tags) {
|
||||
entry.tags.forEach((tag) => item.ele('category').txt(tag));
|
||||
}
|
||||
|
||||
item.ele('guid').txt(entry.url);
|
||||
item.ele('pubDate').txt(DateTime.fromISO(entry.metadata.first_seen_time).toRFC2822());
|
||||
|
||||
if (entry.html_content) {
|
||||
item.ele('content:encoded').ele({ $: entry.html_content });
|
||||
}
|
||||
}
|
||||
|
||||
const xml = doc.toString({
|
||||
indent: ' ',
|
||||
prettyPrint: true,
|
||||
});
|
||||
|
||||
await write_text(rss_conf.out_file, xml);
|
||||
}
|
||||
}
|
||||
|
||||
function link(channel: XMLBuilder, type: string, rel: string, href: string) {
|
||||
// channel.ele('link', { href, rel, type, });
|
||||
channel.ele('atom:link', { type, rel, href, });
|
||||
}
|
57
src/build-files/sitemap.ts
Normal file
57
src/build-files/sitemap.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
|
||||
import type { XMLBuilder } from 'xmlbuilder2/lib/interfaces';
|
||||
import { create as create_xml } from 'xmlbuilder2';
|
||||
import { BuildState } from './state';
|
||||
import { write_text } from '../fs';
|
||||
import { resolve as path_resolve } from 'path';
|
||||
|
||||
export async function write_sitemap_if_needed(state: BuildState) {
|
||||
if (! state.conf.sitemap) {
|
||||
return;
|
||||
}
|
||||
|
||||
const doc = create_xml({ version: '1.0', encoding: 'UTF-8' });
|
||||
const urlset = doc.ele('urlset', {
|
||||
'xmlns': 'http://www.sitemaps.org/schemas/sitemap/0.9',
|
||||
'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
||||
'xsi:schemaLocation': 'http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd'
|
||||
});
|
||||
|
||||
for (const entry of state.sitemap) {
|
||||
url_elem(urlset, entry.url, entry.lastmod, entry.change_freq, entry.priority);
|
||||
}
|
||||
|
||||
const out_file = state.conf.sitemap.out_file || path_resolve(state.conf.output.root, 'sitemap.xml');
|
||||
const xml = doc.toString({
|
||||
indent: ' ',
|
||||
prettyPrint: true,
|
||||
});
|
||||
|
||||
await write_text(out_file, xml);
|
||||
}
|
||||
|
||||
export interface SitemapEntry {
|
||||
url: string;
|
||||
lastmod: string;
|
||||
change_freq?: ChangeFreq;
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
export type ChangeFreq = 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
|
||||
|
||||
function url_elem(urlset: XMLBuilder, loc: string, lastmod: string, change_freq?: ChangeFreq, priority?: number) {
|
||||
const url = urlset.ele('url');
|
||||
|
||||
url.ele('loc').txt(loc);
|
||||
url.ele('lastmod').txt(lastmod);
|
||||
|
||||
if (change_freq) {
|
||||
url.ele('changefreq').txt(change_freq);
|
||||
}
|
||||
|
||||
if (priority != null) {
|
||||
url.ele('priority').txt(priority.toFixed(1));
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
41
src/build-files/state.ts
Normal file
41
src/build-files/state.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
|
||||
import type { Config } from '../conf';
|
||||
import type { Metadata } from '../metadata';
|
||||
import type { ColorTheme } from '@doc-utils/color-themes';
|
||||
import type { SitemapEntry } from './sitemap';
|
||||
import { RSSEntry } from './rss';
|
||||
import { EventEntry, EventSeries } from './icalendar';
|
||||
import { ContextTime } from '../template';
|
||||
|
||||
export interface BuildState {
|
||||
conf: Config;
|
||||
seen_files: Set<string>;
|
||||
env: Record<string, string>;
|
||||
partials?: Record<string, string>;
|
||||
layouts: Record<string, string>;
|
||||
themes: Record<string, ColorTheme>;
|
||||
theme_groups: ThemeGroups;
|
||||
extras: Record<string, string>;
|
||||
made_directories: Set<string>;
|
||||
old_metadata: Metadata;
|
||||
new_metadata: Metadata;
|
||||
rss: RSSEntry[][];
|
||||
sitemap: SitemapEntry[];
|
||||
events: EventEntry[];
|
||||
event_series: EventSeries[];
|
||||
calendars: EventEntry[][];
|
||||
build_time: ContextTime;
|
||||
}
|
||||
|
||||
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[];
|
||||
}
|
425
src/build.ts
425
src/build.ts
@@ -1,425 +0,0 @@
|
||||
|
||||
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 { DateTime } from 'luxon';
|
||||
import { stringify as to_yaml } from 'yaml';
|
||||
|
||||
import { promises as fs } from 'fs';
|
||||
import { dirname, join as path_join } from 'path';
|
||||
|
||||
import { icons } from './icons';
|
||||
import { Config } from './conf';
|
||||
import { build_env_scope } from './env';
|
||||
import { load_themes, render_theme_css_properties } from './themes';
|
||||
import { mkdirp, read_json, read_text, read_yaml, write_text } from './fs';
|
||||
import { load_layout, Context, render_template, load_partials, load_extras, FrontMatter } from './template';
|
||||
|
||||
interface BuildState {
|
||||
conf: Config;
|
||||
seen_files: Set<string>;
|
||||
env: Record<string, string>;
|
||||
partials?: Record<string, string>;
|
||||
layouts: Record<string, string>;
|
||||
themes: Record<string, ColorTheme>;
|
||||
theme_groups: ThemeGroups;
|
||||
extras: Record<string, string>;
|
||||
made_directories: Set<string>;
|
||||
build_time: {
|
||||
iso: string;
|
||||
rfc2822: string;
|
||||
};
|
||||
}
|
||||
|
||||
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<string>(),
|
||||
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<string>(),
|
||||
build_time: {
|
||||
iso: now.toISO(),
|
||||
rfc2822: now.toRFC2822(),
|
||||
},
|
||||
}
|
||||
|
||||
if (conf.input.raw) {
|
||||
await copy_raw_files(state);
|
||||
}
|
||||
|
||||
if (conf.input.text) {
|
||||
await render_text_file_templates(state);
|
||||
}
|
||||
|
||||
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<any>[] = [ ];
|
||||
const files = await glob(state.conf.input.raw, {
|
||||
absolute: true,
|
||||
cwd: state.conf.input.root,
|
||||
});
|
||||
|
||||
for (const in_file of files) {
|
||||
if (state.seen_files.has(in_file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
state.seen_files.add(in_file);
|
||||
|
||||
const out_file = map_input_file_to_output_file(state, in_file);
|
||||
|
||||
promises.push(
|
||||
fs.copyFile(in_file, await out_file)
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===== File Renderers =====
|
||||
|
||||
async function render_text_file_templates(state: BuildState) {
|
||||
const promises: Promise<any>[] = [ ];
|
||||
const files = await glob(state.conf.input.text, {
|
||||
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_text_file_template(state, in_file)
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
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, text } = await read_text(in_file);
|
||||
|
||||
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(text, context, layout, structuredClone(state.partials), tags);
|
||||
await write_text(await out_file, rendered);
|
||||
}
|
||||
|
||||
async function render_markdown_files(state: BuildState) {
|
||||
const promises: Promise<any>[] = [ ];
|
||||
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<any>[] = [ ];
|
||||
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<any>[] = [ ];
|
||||
|
||||
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<any>[] = [ ];
|
||||
|
||||
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<any>[] = [ ];
|
||||
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) {
|
||||
if (! in_file.startsWith(state.conf.input.root)) {
|
||||
throw new Error('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));
|
||||
|
||||
if (remove_exts) {
|
||||
for (const ext of remove_exts) {
|
||||
if (out_file.endsWith(ext)) {
|
||||
out_file = out_file.slice(0, -ext.length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (add_ext) {
|
||||
out_file += add_ext;
|
||||
}
|
||||
|
||||
const dir = dirname(out_file);
|
||||
|
||||
if (! state.made_directories.has(dir)) {
|
||||
state.made_directories.add(dir);
|
||||
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;
|
||||
};
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
92
src/conf.ts
92
src/conf.ts
@@ -4,17 +4,59 @@ import { parse as parse_yaml } from 'yaml';
|
||||
import { resolve as resolve_path, dirname } from 'path';
|
||||
import { MarkdownOptions } from '@doc-utils/markdown2html';
|
||||
|
||||
export const app_version = require('../package.json').version;
|
||||
|
||||
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);
|
||||
|
||||
config.title = resolve_env_var_if_needed(config.title);
|
||||
config.metadata = resolve_env_var_if_needed(config.metadata);
|
||||
config.base_url = resolve_env_var_if_needed(config.base_url);
|
||||
config.input.root = resolve_env_var_if_needed(config.input.root);
|
||||
config.output.root = resolve_env_var_if_needed(config.output.root);
|
||||
|
||||
process_markdown_config(config);
|
||||
resolve_paths(path, config);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
export interface RSSConfig {
|
||||
in_dir: string;
|
||||
out_file: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
link?: string;
|
||||
xsl?: string[];
|
||||
language?: string;
|
||||
copyright?: string;
|
||||
alternates?: {
|
||||
type: string;
|
||||
href: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface CalendarConfig {
|
||||
in_dir?: string;
|
||||
out_file?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface AuthorConfig {
|
||||
name?: string;
|
||||
email?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
title: string;
|
||||
metadata: string;
|
||||
base_url: string;
|
||||
authors?: Record<string, AuthorConfig>;
|
||||
input: {
|
||||
root: string;
|
||||
raw?: string[];
|
||||
@@ -39,13 +81,20 @@ export interface Config {
|
||||
include_yaml_and_json?: boolean;
|
||||
include_intermediate_markdown?: boolean;
|
||||
};
|
||||
markdown?: MarkdownOptions;
|
||||
sitemap?: false | {
|
||||
out_file?: string;
|
||||
};
|
||||
rss?: false | RSSConfig[];
|
||||
events?: false | {
|
||||
};
|
||||
calendars?: false | CalendarConfig[];
|
||||
markdown?: Omit<MarkdownOptions, 'base_url' | 'inline' | 'extensions'>;
|
||||
schema?: {
|
||||
//
|
||||
};
|
||||
openapi?: {
|
||||
// ;
|
||||
}
|
||||
};
|
||||
// ...
|
||||
}
|
||||
|
||||
@@ -57,6 +106,7 @@ function resolve_paths(file_path: string, config: Config) {
|
||||
const base_path = dirname(file_path);
|
||||
config.input.root = resolve_path(base_path, config.input.root);
|
||||
config.output.root = resolve_path(base_path, config.output.root);
|
||||
config.metadata = resolve_path(base_path, config.metadata);
|
||||
|
||||
if (config.templates?.layouts) {
|
||||
config.templates.layouts = resolve_path(base_path, config.templates.layouts);
|
||||
@@ -65,6 +115,36 @@ function resolve_paths(file_path: string, config: Config) {
|
||||
if (config.templates?.partials) {
|
||||
config.templates.partials = resolve_path(base_path, config.templates.partials);
|
||||
}
|
||||
|
||||
if ((config.sitemap as boolean) === true) {
|
||||
config.sitemap = { };
|
||||
}
|
||||
|
||||
if ((config.events as boolean) === true) {
|
||||
config.events = { };
|
||||
}
|
||||
|
||||
if (Array.isArray(config.rss)) {
|
||||
for (const rss_conf of config.rss) {
|
||||
if (rss_conf.in_dir) {
|
||||
rss_conf.in_dir = resolve_path(config.input.root, rss_conf.in_dir);
|
||||
rss_conf.out_file = resolve_path(config.output.root, rss_conf.out_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config.sitemap && typeof config.sitemap === 'object' && config.sitemap.out_file) {
|
||||
config.sitemap.out_file = resolve_path(config.output.root, config.sitemap.out_file);
|
||||
}
|
||||
|
||||
if (Array.isArray(config.calendars)) {
|
||||
for (const cal_conf of config.calendars) {
|
||||
if (cal_conf.in_dir) {
|
||||
cal_conf.in_dir = resolve_path(config.input.root, cal_conf.in_dir);
|
||||
cal_conf.out_file = resolve_path(config.output.root, cal_conf.out_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function process_markdown_config(config: any) {
|
||||
@@ -82,3 +162,11 @@ function process_markdown_config(config: any) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resolve_env_var_if_needed<T>(value: T | { from_env: string }) : T | string {
|
||||
if (typeof value === 'object' && 'from_env' in value) {
|
||||
return process.env[value.from_env];
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
36
src/fs.ts
36
src/fs.ts
@@ -3,6 +3,7 @@ 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';
|
||||
import { hash_str } from './hash';
|
||||
|
||||
export async function load_from_dir(dir: string, file: string) {
|
||||
if (! dir) {
|
||||
@@ -18,41 +19,58 @@ 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) {
|
||||
export async function with_default_if_missing<T>(read_promise: Promise<T>, default_value: T) : Promise<T> {
|
||||
try {
|
||||
return await read_promise;
|
||||
}
|
||||
|
||||
catch (error) {
|
||||
if (error?.code === 'ENOENT') {
|
||||
return default_value;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function read_text(file: string, check_for_frontmatter = true, compute_hash = true) {
|
||||
const text = await fs.readFile(file, 'utf8');
|
||||
const hash = compute_hash ? hash_str(text) : null;
|
||||
|
||||
if (check_for_frontmatter) {
|
||||
const { frontmatter, document } = process_frontmatter(text);
|
||||
return { frontmatter, text: document };
|
||||
return { frontmatter, text: document, hash };
|
||||
}
|
||||
|
||||
return { text, frontmatter: null };
|
||||
return { text, frontmatter: null, hash };
|
||||
}
|
||||
|
||||
export async function read_json(file: string, check_for_frontmatter = true) {
|
||||
export async function read_json(file: string, check_for_frontmatter = true, compute_hash = true) {
|
||||
const json = await fs.readFile(file, 'utf8');
|
||||
const hash = compute_hash ? hash_str(json) : null;
|
||||
|
||||
if (check_for_frontmatter) {
|
||||
const { frontmatter, document } = process_frontmatter(json);
|
||||
const parsed = JSON.parse(document);
|
||||
return { frontmatter, json: document, parsed };
|
||||
return { frontmatter, json: document, parsed, hash };
|
||||
}
|
||||
|
||||
const parsed = JSON.parse(json);
|
||||
return { json, parsed, frontmatter: null };
|
||||
return { json, parsed, frontmatter: null, hash };
|
||||
}
|
||||
|
||||
export async function read_yaml(file: string, check_for_frontmatter = true) {
|
||||
export async function read_yaml(file: string, check_for_frontmatter = true, compute_hash = true) {
|
||||
const yaml = await fs.readFile(file, 'utf8');
|
||||
const hash = compute_hash ? hash_str(yaml) : null;
|
||||
|
||||
if (check_for_frontmatter) {
|
||||
const { frontmatter, document } = process_frontmatter(yaml);
|
||||
const parsed = parse_yaml(document);
|
||||
return { frontmatter, yaml: document, parsed };
|
||||
return { frontmatter, yaml: document, parsed, hash };
|
||||
}
|
||||
|
||||
const parsed = parse_yaml(yaml);
|
||||
return { yaml, parsed, frontmatter: null };
|
||||
return { yaml, parsed, frontmatter: null, hash };
|
||||
}
|
||||
|
||||
export function write_text(file: string, text: string) {
|
||||
|
14
src/hash.ts
Normal file
14
src/hash.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
import { createHash } from 'crypto';
|
||||
|
||||
export function hash_str(str: string) {
|
||||
const hash = createHash('sha512');
|
||||
hash.update(str);
|
||||
return hash.digest('base64');
|
||||
}
|
||||
|
||||
export function hash_obj(obj: any) {
|
||||
// todo: use something more stable
|
||||
const str = JSON.stringify(obj);
|
||||
return hash_str(str);
|
||||
}
|
@@ -1,3 +1,3 @@
|
||||
|
||||
export { read_config, Config } from './conf';
|
||||
export { build_docs_project, ThemeGroups } from './build';
|
||||
export { build_docs_project, ThemeGroups } from './build-files';
|
||||
|
20
src/metadata.ts
Normal file
20
src/metadata.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
export interface Metadata {
|
||||
last_build: BuildMetadata;
|
||||
files: {
|
||||
[file_path: string]: FileMetadata;
|
||||
};
|
||||
}
|
||||
|
||||
export interface BuildMetadata {
|
||||
time: string;
|
||||
config_hash: string;
|
||||
}
|
||||
|
||||
export interface FileMetadata {
|
||||
first_seen_time: string;
|
||||
last_build_hash: string;
|
||||
last_updated_time: string;
|
||||
}
|
||||
|
||||
//
|
@@ -1,34 +1,100 @@
|
||||
|
||||
import { Config } from './conf';
|
||||
import { AuthorConfig, CalendarConfig, Config, RSSConfig } from './conf';
|
||||
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';
|
||||
import { ThemeGroups } from './build-files';
|
||||
import { ChangeFreq } from './build-files/sitemap';
|
||||
|
||||
export interface Context {
|
||||
env?: Record<string, string>;
|
||||
page?: FrontMatter;
|
||||
base_url: string;
|
||||
page_url: string;
|
||||
page_published: ContextTime;
|
||||
page_updated: ContextTime;
|
||||
site_title: string;
|
||||
author: AuthorConfig | AuthorConfig[];
|
||||
event?: ContextEvent | ContextEvent[];
|
||||
event_series?: {
|
||||
start?: ContextTime;
|
||||
end?: ContextTime;
|
||||
};
|
||||
icons: Record<string, string>;
|
||||
themes: ColorTheme[];
|
||||
theme_groups: ThemeGroups;
|
||||
build_time: {
|
||||
iso: string;
|
||||
rfc2822: string;
|
||||
};
|
||||
rss_feeds: RSSConfig[];
|
||||
calendars: CalendarConfig[];
|
||||
build_time: ContextTime;
|
||||
markdown: {
|
||||
render_inline(): MustacheRenderer;
|
||||
}
|
||||
}
|
||||
|
||||
export interface FrontMatter {
|
||||
export interface ContextTime {
|
||||
iso: string;
|
||||
rfc2822: string;
|
||||
html: string;
|
||||
}
|
||||
|
||||
export interface ContextEvent {
|
||||
title?: string;
|
||||
start?: ContextTime;
|
||||
end?: ContextTime;
|
||||
time_zone?: `${string}/${string}`;
|
||||
location?: ContextLocation;
|
||||
}
|
||||
|
||||
export interface ContextLocation {
|
||||
description?: string;
|
||||
lat?: string;
|
||||
long?: string;
|
||||
// todo: represent this better?
|
||||
address?: string;
|
||||
}
|
||||
|
||||
export interface FrontMatter {
|
||||
skip?: boolean;
|
||||
layout?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
tags?: string[];
|
||||
author?: string | string[];
|
||||
rss?: RSSFrontmatter;
|
||||
sitemap?: SitemapFrontmatter;
|
||||
event?: EventFrontmatter | EventFrontmatter[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface SitemapFrontmatter {
|
||||
skip?: boolean;
|
||||
change_freq?: ChangeFreq;
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
export interface EventFrontmatter {
|
||||
title?: string;
|
||||
start?: string;
|
||||
end?: string;
|
||||
time_zone?: `${string}/${string}`;
|
||||
location?: FrontMatterLocation;
|
||||
}
|
||||
|
||||
export interface FrontMatterLocation {
|
||||
description?: string;
|
||||
lat?: string;
|
||||
long?: string;
|
||||
// todo: represent this better?
|
||||
address?: string;
|
||||
}
|
||||
|
||||
export interface RSSFrontmatter {
|
||||
skip?: boolean;
|
||||
}
|
||||
|
||||
export function render_template(template: string, context: Context, layout?: string, partials: Record<string, string> = { }, tags?: [ string, string ]) {
|
||||
partials['.content'] = template;
|
||||
return mustache_render(layout || template, context, partials, tags);
|
||||
@@ -38,10 +104,19 @@ export async function load_extras() {
|
||||
const extras: Record<string, string> = Object.create(null);
|
||||
const extras_dir = resolve_path(__dirname, '../extras');
|
||||
const extras_files = [
|
||||
'svg-links.js',
|
||||
'components/color-scheme-toggle-button.js',
|
||||
'components/outline-button.js',
|
||||
'components/outline-inline.js',
|
||||
'prism.css',
|
||||
'typography/spacious.css',
|
||||
'typography/compact.css',
|
||||
'typography/general.css',
|
||||
'theme-animation.css',
|
||||
'forms-inputs/spacious.css',
|
||||
'forms-inputs/compact.css',
|
||||
'forms-inputs/general.css',
|
||||
'figures.css',
|
||||
];
|
||||
|
||||
const promises = extras_files.map((file) => load_from_dir(extras_dir, file));
|
||||
|
32
src/time.ts
Normal file
32
src/time.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
export function now(zone?: string) {
|
||||
return zone
|
||||
? DateTime.now().setZone(zone)
|
||||
: DateTime.now();
|
||||
}
|
||||
|
||||
export function from_iso(time: string, zone?: string) {
|
||||
return zone
|
||||
? DateTime.fromISO(time, { zone })
|
||||
: DateTime.fromISO(time);
|
||||
}
|
||||
|
||||
export function as_html_time(time: DateTime, classname = '', lang?: string, config?: Intl.DateTimeFormatOptions) {
|
||||
if (lang && config) {
|
||||
const formatter = new Intl.DateTimeFormat(lang, config);
|
||||
const formatted = formatter.format(new Date(time.toISO()));
|
||||
return `<time datetime="${time.toISO()}">${formatted}</time>`;
|
||||
}
|
||||
|
||||
return `<time class="${classname}" datetime="${time.toISO()}">${time.toRFC2822()}</time>`;
|
||||
}
|
||||
|
||||
export function as_context_time(time: DateTime, classname = '', lang?: string, config?: Intl.DateTimeFormatOptions) {
|
||||
return {
|
||||
iso: time.toISO(),
|
||||
rfc2822: time.toRFC2822(),
|
||||
html: as_html_time(time, classname, lang, config),
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user