mdorigin 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -3
- package/dist/adapters/cloudflare.js +8 -0
- package/dist/adapters/node.js +1 -0
- package/dist/cli/main.d.ts +1 -0
- package/dist/cli/main.js +1 -0
- package/dist/core/markdown.d.ts +8 -0
- package/dist/core/markdown.js +35 -0
- package/dist/core/request-handler.d.ts +1 -0
- package/dist/core/request-handler.js +323 -11
- package/dist/core/router.d.ts +1 -0
- package/dist/core/router.js +1 -1
- package/dist/core/site-config.d.ts +27 -0
- package/dist/core/site-config.js +84 -2
- package/dist/html/template-kind.d.ts +1 -1
- package/dist/html/template.d.ts +12 -1
- package/dist/html/template.js +90 -21
- package/dist/html/theme.js +288 -91
- package/dist/index-builder.js +8 -0
- package/package.json +3 -3
package/dist/core/site-config.js
CHANGED
|
@@ -32,11 +32,20 @@ export async function loadSiteConfig(options = {}) {
|
|
|
32
32
|
parsedConfig.siteDescription !== ''
|
|
33
33
|
? parsedConfig.siteDescription
|
|
34
34
|
: undefined,
|
|
35
|
+
siteUrl: normalizeSiteUrl(parsedConfig.siteUrl),
|
|
36
|
+
favicon: normalizeSiteHref(parsedConfig.favicon),
|
|
37
|
+
logo: normalizeLogo(parsedConfig.logo),
|
|
35
38
|
showDate: parsedConfig.showDate ?? true,
|
|
36
39
|
showSummary: parsedConfig.showSummary ?? true,
|
|
37
40
|
theme: isBuiltInThemeName(parsedConfig.theme) ? parsedConfig.theme : 'paper',
|
|
38
41
|
template: isTemplateName(parsedConfig.template) ? parsedConfig.template : 'document',
|
|
39
42
|
topNav: normalizeTopNav(parsedConfig.topNav),
|
|
43
|
+
footerNav: normalizeTopNav(parsedConfig.footerNav),
|
|
44
|
+
footerText: typeof parsedConfig.footerText === 'string' && parsedConfig.footerText !== ''
|
|
45
|
+
? parsedConfig.footerText
|
|
46
|
+
: undefined,
|
|
47
|
+
socialLinks: normalizeSocialLinks(parsedConfig.socialLinks),
|
|
48
|
+
editLink: normalizeEditLink(parsedConfig.editLink),
|
|
40
49
|
showHomeIndex: typeof parsedConfig.showHomeIndex === 'boolean'
|
|
41
50
|
? parsedConfig.showHomeIndex
|
|
42
51
|
: normalizeTopNav(parsedConfig.topNav).length === 0,
|
|
@@ -83,7 +92,7 @@ function isBuiltInThemeName(value) {
|
|
|
83
92
|
return value === 'paper' || value === 'atlas' || value === 'gazette';
|
|
84
93
|
}
|
|
85
94
|
function isTemplateName(value) {
|
|
86
|
-
return value === 'document' || value === '
|
|
95
|
+
return value === 'document' || value === 'catalog';
|
|
87
96
|
}
|
|
88
97
|
function isNodeNotFound(error) {
|
|
89
98
|
return (typeof error === 'object' &&
|
|
@@ -116,8 +125,81 @@ function normalizeTopNav(value) {
|
|
|
116
125
|
item.label !== '' &&
|
|
117
126
|
typeof item.href === 'string' &&
|
|
118
127
|
item.href !== '') {
|
|
119
|
-
|
|
128
|
+
const href = normalizeSiteHref(item.href);
|
|
129
|
+
return href ? [{ label: item.label, href }] : [];
|
|
120
130
|
}
|
|
121
131
|
return [];
|
|
122
132
|
});
|
|
123
133
|
}
|
|
134
|
+
function normalizeLogo(value) {
|
|
135
|
+
if (typeof value !== 'object' ||
|
|
136
|
+
value === null ||
|
|
137
|
+
!('src' in value) ||
|
|
138
|
+
typeof value.src !== 'string' ||
|
|
139
|
+
value.src === '') {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
const src = normalizeSiteHref(value.src);
|
|
143
|
+
if (!src) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
const href = 'href' in value && typeof value.href === 'string'
|
|
147
|
+
? normalizeSiteHref(value.href)
|
|
148
|
+
: undefined;
|
|
149
|
+
return {
|
|
150
|
+
src,
|
|
151
|
+
alt: 'alt' in value && typeof value.alt === 'string' && value.alt !== ''
|
|
152
|
+
? value.alt
|
|
153
|
+
: undefined,
|
|
154
|
+
href,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function normalizeSocialLinks(value) {
|
|
158
|
+
if (!Array.isArray(value)) {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
return value.flatMap((item) => {
|
|
162
|
+
if (typeof item !== 'object' ||
|
|
163
|
+
item === null ||
|
|
164
|
+
!('icon' in item) ||
|
|
165
|
+
!('label' in item) ||
|
|
166
|
+
!('href' in item) ||
|
|
167
|
+
typeof item.icon !== 'string' ||
|
|
168
|
+
item.icon === '' ||
|
|
169
|
+
typeof item.label !== 'string' ||
|
|
170
|
+
item.label === '' ||
|
|
171
|
+
typeof item.href !== 'string' ||
|
|
172
|
+
item.href === '') {
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
const href = normalizeSiteHref(item.href);
|
|
176
|
+
return href
|
|
177
|
+
? [{ icon: item.icon, label: item.label, href }]
|
|
178
|
+
: [];
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
function normalizeEditLink(value) {
|
|
182
|
+
if (typeof value !== 'object' ||
|
|
183
|
+
value === null ||
|
|
184
|
+
!('baseUrl' in value) ||
|
|
185
|
+
typeof value.baseUrl !== 'string' ||
|
|
186
|
+
value.baseUrl === '') {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
return { baseUrl: value.baseUrl };
|
|
190
|
+
}
|
|
191
|
+
function normalizeSiteUrl(value) {
|
|
192
|
+
return typeof value === 'string' && value !== '' ? value.replace(/\/+$/, '') : undefined;
|
|
193
|
+
}
|
|
194
|
+
function normalizeSiteHref(value) {
|
|
195
|
+
if (typeof value !== 'string' || value === '') {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
if (value.startsWith('/') ||
|
|
199
|
+
value.startsWith('#') ||
|
|
200
|
+
value.startsWith('//') ||
|
|
201
|
+
/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(value)) {
|
|
202
|
+
return value;
|
|
203
|
+
}
|
|
204
|
+
return `/${value.replace(/^\.?\//, '')}`;
|
|
205
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export type TemplateName = 'document' | '
|
|
1
|
+
export type TemplateName = 'document' | 'catalog';
|
package/dist/html/template.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import type { SiteNavItem } from '../core/site-config.js';
|
|
1
|
+
import type { SiteLogo, SiteNavItem, SiteSocialLink } from '../core/site-config.js';
|
|
2
|
+
import type { ManagedIndexEntry } from '../core/markdown.js';
|
|
2
3
|
import type { TemplateName } from './template-kind.js';
|
|
3
4
|
import { type BuiltInThemeName } from './theme.js';
|
|
4
5
|
export interface RenderDocumentOptions {
|
|
5
6
|
siteTitle: string;
|
|
6
7
|
siteDescription?: string;
|
|
8
|
+
siteUrl?: string;
|
|
9
|
+
favicon?: string;
|
|
10
|
+
logo?: SiteLogo;
|
|
7
11
|
title: string;
|
|
8
12
|
body: string;
|
|
9
13
|
summary?: string;
|
|
@@ -13,7 +17,14 @@ export interface RenderDocumentOptions {
|
|
|
13
17
|
theme: BuiltInThemeName;
|
|
14
18
|
template: TemplateName;
|
|
15
19
|
topNav?: SiteNavItem[];
|
|
20
|
+
footerNav?: SiteNavItem[];
|
|
21
|
+
footerText?: string;
|
|
22
|
+
socialLinks?: SiteSocialLink[];
|
|
23
|
+
editLinkHref?: string;
|
|
16
24
|
stylesheetContent?: string;
|
|
25
|
+
canonicalPath?: string;
|
|
26
|
+
alternateMarkdownPath?: string;
|
|
27
|
+
catalogEntries?: ManagedIndexEntry[];
|
|
17
28
|
}
|
|
18
29
|
export declare function renderDocument(options: RenderDocumentOptions): string;
|
|
19
30
|
export declare function escapeHtml(value: string): string;
|
package/dist/html/template.js
CHANGED
|
@@ -8,17 +8,49 @@ export function renderDocument(options) {
|
|
|
8
8
|
const summaryMeta = options.summary
|
|
9
9
|
? `<meta name="description" content="${escapeHtml(options.summary)}">`
|
|
10
10
|
: '';
|
|
11
|
+
const canonicalMeta = options.siteUrl && options.canonicalPath
|
|
12
|
+
? `<link rel="canonical" href="${escapeHtml(`${options.siteUrl}${options.canonicalPath}`)}">`
|
|
13
|
+
: '';
|
|
14
|
+
const faviconMeta = options.favicon
|
|
15
|
+
? `<link rel="icon" href="${escapeHtml(options.favicon)}">`
|
|
16
|
+
: '';
|
|
17
|
+
const alternateMarkdownMeta = options.alternateMarkdownPath
|
|
18
|
+
? `<link rel="alternate" type="text/markdown" href="${escapeHtml(options.alternateMarkdownPath)}">`
|
|
19
|
+
: '';
|
|
11
20
|
const stylesheetBlock = `<style>${getBuiltInThemeStyles(options.theme)}${options.stylesheetContent ? `\n${options.stylesheetContent}` : ''}</style>`;
|
|
12
21
|
const navBlock = options.topNav && options.topNav.length > 0
|
|
13
22
|
? `<nav class="site-nav"><ul>${options.topNav
|
|
14
23
|
.map((item) => `<li><a href="${escapeHtml(item.href)}">${escapeHtml(item.label)}</a></li>`)
|
|
15
24
|
.join('')}</ul></nav>`
|
|
16
25
|
: '';
|
|
26
|
+
const footerNavBlock = options.footerNav && options.footerNav.length > 0
|
|
27
|
+
? `<nav class="site-footer__nav"><ul>${options.footerNav
|
|
28
|
+
.map((item) => `<li><a href="${escapeHtml(item.href)}">${escapeHtml(item.label)}</a></li>`)
|
|
29
|
+
.join('')}</ul></nav>`
|
|
30
|
+
: '';
|
|
31
|
+
const socialLinksBlock = options.socialLinks && options.socialLinks.length > 0
|
|
32
|
+
? `<ul class="site-footer__social">${options.socialLinks
|
|
33
|
+
.map((item) => `<li><a href="${escapeHtml(item.href)}" aria-label="${escapeHtml(item.label)}" title="${escapeHtml(item.label)}">${renderSocialIcon(item.icon)}</a></li>`)
|
|
34
|
+
.join('')}</ul>`
|
|
35
|
+
: '';
|
|
17
36
|
const siteDescriptionBlock = siteDescription
|
|
18
37
|
? `<span>${siteDescription}</span>`
|
|
19
38
|
: '';
|
|
20
|
-
const
|
|
21
|
-
?
|
|
39
|
+
const logoBlock = options.logo
|
|
40
|
+
? `<span class="site-header__logo"><img src="${escapeHtml(options.logo.src)}" alt="${escapeHtml(options.logo.alt ?? '')}"></span>`
|
|
41
|
+
: '';
|
|
42
|
+
const brandHref = escapeHtml(options.logo?.href ?? '/');
|
|
43
|
+
const editLinkBlock = options.editLinkHref
|
|
44
|
+
? `<a class="site-footer__edit-link" href="${escapeHtml(options.editLinkHref)}">Edit this page</a>`
|
|
45
|
+
: '';
|
|
46
|
+
const footerTextBlock = options.footerText
|
|
47
|
+
? `<p class="site-footer__text">${escapeHtml(options.footerText)}</p>`
|
|
48
|
+
: '';
|
|
49
|
+
const footerBlock = footerNavBlock || socialLinksBlock || footerTextBlock || editLinkBlock
|
|
50
|
+
? `<footer class="site-footer"><div class="site-footer__inner">${footerNavBlock}${socialLinksBlock}${footerTextBlock}${editLinkBlock}</div></footer>`
|
|
51
|
+
: '';
|
|
52
|
+
const articleBody = options.template === 'catalog'
|
|
53
|
+
? renderCatalogArticle(options.body, options.catalogEntries ?? [])
|
|
22
54
|
: options.body;
|
|
23
55
|
return [
|
|
24
56
|
'<!doctype html>',
|
|
@@ -28,35 +60,21 @@ export function renderDocument(options) {
|
|
|
28
60
|
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
29
61
|
`<title>${title} | ${siteTitle}</title>`,
|
|
30
62
|
summaryMeta,
|
|
63
|
+
canonicalMeta,
|
|
64
|
+
faviconMeta,
|
|
65
|
+
alternateMarkdownMeta,
|
|
31
66
|
stylesheetBlock,
|
|
32
67
|
'</head>',
|
|
33
68
|
`<body data-theme="${options.theme}" data-template="${options.template}">`,
|
|
34
|
-
`<header class="site-header"><div class="site-header__inner"><div class="site-header__brand"><p class="site-header__title"><a href="
|
|
69
|
+
`<header class="site-header"><div class="site-header__inner"><div class="site-header__brand"><p class="site-header__title"><a href="${brandHref}">${logoBlock}<span>${siteTitle}</span></a></p>${siteDescriptionBlock}</div>${navBlock}</div></header>`,
|
|
35
70
|
'<main>',
|
|
36
71
|
`<article>${articleBody}</article>`,
|
|
37
72
|
'</main>',
|
|
73
|
+
footerBlock,
|
|
38
74
|
'</body>',
|
|
39
75
|
'</html>',
|
|
40
76
|
].join('');
|
|
41
77
|
}
|
|
42
|
-
function renderEditorialBody(options) {
|
|
43
|
-
const heroLines = [
|
|
44
|
-
'<div class="page-intro">',
|
|
45
|
-
`<p class="page-intro__eyebrow">${escapeHtml(options.siteTitle)}</p>`,
|
|
46
|
-
`<h1 class="page-intro__title">${escapeHtml(options.title)}</h1>`,
|
|
47
|
-
];
|
|
48
|
-
if (options.showSummary !== false && options.summary) {
|
|
49
|
-
heroLines.push(`<p class="page-intro__summary">${escapeHtml(options.summary)}</p>`);
|
|
50
|
-
}
|
|
51
|
-
if (options.showDate !== false && options.date) {
|
|
52
|
-
heroLines.push(`<p class="page-intro__meta">${escapeHtml(options.date)}</p>`);
|
|
53
|
-
}
|
|
54
|
-
heroLines.push('</div>');
|
|
55
|
-
return `${heroLines.join('')}<div class="page-body">${stripLeadingH1(options.body)}</div>`;
|
|
56
|
-
}
|
|
57
|
-
function stripLeadingH1(html) {
|
|
58
|
-
return html.replace(/^\s*<h1>.*?<\/h1>\s*/s, '');
|
|
59
|
-
}
|
|
60
78
|
export function escapeHtml(value) {
|
|
61
79
|
return value
|
|
62
80
|
.replaceAll('&', '&')
|
|
@@ -65,3 +83,54 @@ export function escapeHtml(value) {
|
|
|
65
83
|
.replaceAll('"', '"')
|
|
66
84
|
.replaceAll("'", ''');
|
|
67
85
|
}
|
|
86
|
+
function renderSocialIcon(icon) {
|
|
87
|
+
switch (icon) {
|
|
88
|
+
case 'github':
|
|
89
|
+
return iconSvg('M12 2C6.48 2 2 6.58 2 12.11c0 4.43 2.87 8.18 6.84 9.5.5.1.68-.22.68-.48 0-.24-.01-1.04-.01-1.89-2.78.62-3.37-1.19-3.37-1.19-.46-1.17-1.11-1.48-1.11-1.48-.91-.63.07-.62.07-.62 1 .08 1.53 1.04 1.53 1.04.9 1.54 2.35 1.09 2.92.84.09-.66.35-1.09.63-1.34-2.22-.25-4.55-1.12-4.55-4.97 0-1.1.39-2 1.03-2.71-.1-.26-.45-1.29.1-2.68 0 0 .84-.27 2.75 1.03A9.4 9.4 0 0 1 12 6.84c.85 0 1.71.12 2.51.35 1.91-1.3 2.75-1.03 2.75-1.03.55 1.39.2 2.42.1 2.68.64.71 1.03 1.61 1.03 2.71 0 3.86-2.33 4.72-4.56 4.97.36.31.68.91.68 1.84 0 1.33-.01 2.4-.01 2.73 0 .27.18.58.69.48A10.11 10.11 0 0 0 22 12.11C22 6.58 17.52 2 12 2Z');
|
|
90
|
+
case 'rss':
|
|
91
|
+
return iconSvg('M5 3a16 16 0 0 1 16 16h-3A13 13 0 0 0 5 6V3Zm0 6a10 10 0 0 1 10 10h-3a7 7 0 0 0-7-7V9Zm0 6a4 4 0 0 1 4 4H5v-4Zm0 3a1 1 0 1 0 0 2 1 1 0 0 0 0-2Z');
|
|
92
|
+
case 'npm':
|
|
93
|
+
return iconSvg('M3 7h18v10h-9v-8h-3v8H3V7Zm10 2h2v6h2V9h2v6h1V9h1V8h-8v1Z');
|
|
94
|
+
case 'x':
|
|
95
|
+
return iconSvg('M18.9 3H21l-6.87 7.85L22 21h-6.17l-4.83-6.32L5.47 21H3.36l7.35-8.4L2 3h6.32l4.37 5.77L18.9 3Zm-2.17 16h1.17L7.68 4H6.43l10.3 15Z');
|
|
96
|
+
case 'home':
|
|
97
|
+
return iconSvg('M12 3 3 10.2V21h6v-6h6v6h6V10.2L12 3Z');
|
|
98
|
+
default:
|
|
99
|
+
return `<span class="site-footer__social-label">${escapeHtml(icon.slice(0, 1).toUpperCase())}</span>`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function iconSvg(pathData) {
|
|
103
|
+
return `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="${pathData}"></path></svg>`;
|
|
104
|
+
}
|
|
105
|
+
function renderCatalogArticle(body, entries) {
|
|
106
|
+
if (entries.length === 0) {
|
|
107
|
+
return body;
|
|
108
|
+
}
|
|
109
|
+
const directories = entries.filter((entry) => entry.kind === 'directory');
|
|
110
|
+
const articles = entries.filter((entry) => entry.kind === 'article');
|
|
111
|
+
return [
|
|
112
|
+
`<div class="catalog-page__body">${body}</div>`,
|
|
113
|
+
'<section class="catalog-page" aria-label="Catalog">',
|
|
114
|
+
directories.length > 0 ? renderCatalogDirectories(directories) : '',
|
|
115
|
+
articles.length > 0 ? renderCatalogArticles(articles) : '',
|
|
116
|
+
'</section>',
|
|
117
|
+
].join('');
|
|
118
|
+
}
|
|
119
|
+
function renderCatalogDirectories(entries) {
|
|
120
|
+
return [
|
|
121
|
+
'<div class="catalog-list catalog-list--directories">',
|
|
122
|
+
...entries.map((entry) => `<a class="catalog-item catalog-item--directory" href="${escapeHtml(entry.href)}"><strong class="catalog-item__title">${escapeHtml(entry.title)}</strong>${entry.detail
|
|
123
|
+
? `<span class="catalog-item__detail">${escapeHtml(entry.detail)}</span>`
|
|
124
|
+
: '<span class="catalog-item__detail">Browse this section.</span>'}</a>`),
|
|
125
|
+
'</div>',
|
|
126
|
+
].join('');
|
|
127
|
+
}
|
|
128
|
+
function renderCatalogArticles(entries) {
|
|
129
|
+
return [
|
|
130
|
+
'<div class="catalog-list">',
|
|
131
|
+
...entries.map((entry) => `<a class="catalog-item" href="${escapeHtml(entry.href)}"><strong class="catalog-item__title">${escapeHtml(entry.title)}</strong>${entry.detail
|
|
132
|
+
? `<span class="catalog-item__detail">${escapeHtml(entry.detail)}</span>`
|
|
133
|
+
: ''}</a>`),
|
|
134
|
+
'</div>',
|
|
135
|
+
].join('');
|
|
136
|
+
}
|