mdsvr 2.4.0 → 2.5.0
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/dist/directory.d.ts +11 -0
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +31 -115
- package/dist/directory.js.map +1 -1
- package/dist/generators/feed.d.ts.map +1 -1
- package/dist/generators/feed.js +9 -2
- package/dist/generators/feed.js.map +1 -1
- package/dist/generators/robots.d.ts +11 -0
- package/dist/generators/robots.d.ts.map +1 -0
- package/dist/generators/robots.js +32 -0
- package/dist/generators/robots.js.map +1 -0
- package/dist/generators/search-index.d.ts.map +1 -1
- package/dist/generators/search-index.js +6 -5
- package/dist/generators/search-index.js.map +1 -1
- package/dist/generators/sitemap.d.ts +17 -1
- package/dist/generators/sitemap.d.ts.map +1 -1
- package/dist/generators/sitemap.js +119 -59
- package/dist/generators/sitemap.js.map +1 -1
- package/dist/generators/static-export.d.ts.map +1 -1
- package/dist/generators/static-export.js +66 -11
- package/dist/generators/static-export.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/link-transform.d.ts +37 -5
- package/dist/link-transform.d.ts.map +1 -1
- package/dist/link-transform.js +120 -13
- package/dist/link-transform.js.map +1 -1
- package/dist/render-page.d.ts +1 -2
- package/dist/render-page.d.ts.map +1 -1
- package/dist/render-page.js +56 -40
- package/dist/render-page.js.map +1 -1
- package/dist/renderer/markdown.d.ts +0 -16
- package/dist/renderer/markdown.d.ts.map +1 -1
- package/dist/renderer/markdown.js +6 -63
- package/dist/renderer/markdown.js.map +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +19 -1
- package/dist/router.js.map +1 -1
- package/dist/seo/extract.d.ts +26 -0
- package/dist/seo/extract.d.ts.map +1 -0
- package/dist/seo/extract.js +159 -0
- package/dist/seo/extract.js.map +1 -0
- package/dist/seo/index.d.ts +5 -0
- package/dist/seo/index.d.ts.map +1 -0
- package/dist/seo/index.js +5 -0
- package/dist/seo/index.js.map +1 -0
- package/dist/seo/jsonld.d.ts +15 -0
- package/dist/seo/jsonld.d.ts.map +1 -0
- package/dist/seo/jsonld.js +93 -0
- package/dist/seo/jsonld.js.map +1 -0
- package/dist/seo/metadata.d.ts +60 -0
- package/dist/seo/metadata.d.ts.map +1 -0
- package/dist/seo/metadata.js +144 -0
- package/dist/seo/metadata.js.map +1 -0
- package/dist/seo/url.d.ts +27 -0
- package/dist/seo/url.d.ts.map +1 -0
- package/dist/seo/url.js +76 -0
- package/dist/seo/url.js.map +1 -0
- package/dist/settings/defaults.d.ts.map +1 -1
- package/dist/settings/defaults.js +0 -2
- package/dist/settings/defaults.js.map +1 -1
- package/dist/settings/index.d.ts +1 -1
- package/dist/settings/index.d.ts.map +1 -1
- package/dist/settings/index.js +15 -0
- package/dist/settings/index.js.map +1 -1
- package/dist/settings/schema.d.ts +317 -5
- package/dist/settings/schema.d.ts.map +1 -1
- package/dist/settings/schema.js +45 -1
- package/dist/settings/schema.js.map +1 -1
- package/dist/template/analytics.d.ts +18 -0
- package/dist/template/analytics.d.ts.map +1 -0
- package/dist/template/analytics.js +83 -0
- package/dist/template/analytics.js.map +1 -0
- package/dist/template/index.d.ts +5 -0
- package/dist/template/index.d.ts.map +1 -1
- package/dist/template/index.js +59 -59
- package/dist/template/index.js.map +1 -1
- package/dist/template/search.d.ts.map +1 -1
- package/dist/template/search.js +9 -2
- package/dist/template/search.js.map +1 -1
- package/dist/template/seo.d.ts +7 -10
- package/dist/template/seo.d.ts.map +1 -1
- package/dist/template/seo.js +57 -33
- package/dist/template/seo.js.map +1 -1
- package/dist/template/sidebar.d.ts.map +1 -1
- package/dist/template/sidebar.js +11 -8
- package/dist/template/sidebar.js.map +1 -1
- package/dist/validator/index.d.ts +1 -1
- package/dist/validator/index.d.ts.map +1 -1
- package/dist/validator/index.js +21 -0
- package/dist/validator/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content extraction helpers for SEO metadata.
|
|
3
|
+
* (Moved from renderer/markdown.ts; description extraction is wired so it
|
|
4
|
+
* actually runs, and tightened to ~160 chars.)
|
|
5
|
+
*/
|
|
6
|
+
function stripFrontmatter(content) {
|
|
7
|
+
return content.replace(/^---\r?\n[\s\S]*?\r?\n---/, "");
|
|
8
|
+
}
|
|
9
|
+
function stripFencedCode(content) {
|
|
10
|
+
return content
|
|
11
|
+
.replace(/^(`{3,}|~{3,})[^\n]*\n[\s\S]*?^\1\s*$/gm, "")
|
|
12
|
+
.replace(/(`{3,}|~{3,})[\s\S]*?\1/g, "");
|
|
13
|
+
}
|
|
14
|
+
function decodeEntities(text) {
|
|
15
|
+
const entities = {
|
|
16
|
+
"&": "&",
|
|
17
|
+
"<": "<",
|
|
18
|
+
">": ">",
|
|
19
|
+
""": '"',
|
|
20
|
+
"'": "'",
|
|
21
|
+
"'": "'",
|
|
22
|
+
" ": " ",
|
|
23
|
+
};
|
|
24
|
+
return text.replace(/&(amp|lt|gt|quot|nbsp|#39|#x27);/g, (m) => entities[m] || m);
|
|
25
|
+
}
|
|
26
|
+
/** Strip inline markdown/HTML so only plain text remains. */
|
|
27
|
+
function cleanInline(text) {
|
|
28
|
+
return decodeEntities(text
|
|
29
|
+
.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1") // images → alt text
|
|
30
|
+
.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1") // links → text
|
|
31
|
+
.replace(/\[([^\]]+)\]\[[^\]]*\]/g, "$1") // ref links → text
|
|
32
|
+
.replace(/<[^>]+>/g, "") // HTML tags
|
|
33
|
+
.replace(/(\*\*|__)(.*?)\1/g, "$2") // bold
|
|
34
|
+
.replace(/\*([^*]+)\*/g, "$1") // italic
|
|
35
|
+
// _italic_ — only at word boundaries so SNAKE_CASE identifiers survive
|
|
36
|
+
.replace(/(^|\W)_([^_]+)_(?=\W|$)/g, "$1$2")
|
|
37
|
+
.replace(/~~([^~]+)~~/g, "$1") // strikethrough
|
|
38
|
+
.replace(/`([^`]+)`/g, "$1") // inline code
|
|
39
|
+
.replace(/\s+/g, " ")
|
|
40
|
+
.trim());
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Whether a trimmed line can never begin a meaningful paragraph
|
|
44
|
+
* (headings, lists, tables, blockquotes, HTML, MDX statements, nav links).
|
|
45
|
+
*/
|
|
46
|
+
function isSkippableLine(line) {
|
|
47
|
+
if (/^#{1,6}\s/.test(line))
|
|
48
|
+
return true; // heading
|
|
49
|
+
if (/^>/.test(line))
|
|
50
|
+
return true; // blockquote
|
|
51
|
+
if (/^[-*_]{3,}$/.test(line))
|
|
52
|
+
return true; // horizontal rule
|
|
53
|
+
if (/^[-*+]\s/.test(line))
|
|
54
|
+
return true; // bullet list
|
|
55
|
+
if (/^\d+\.\s/.test(line))
|
|
56
|
+
return true; // ordered list
|
|
57
|
+
if (/^\|/.test(line))
|
|
58
|
+
return true; // table row
|
|
59
|
+
const c = line[0];
|
|
60
|
+
if (c === "`" || c === "~")
|
|
61
|
+
return true; // fence remnants
|
|
62
|
+
if (c === "<")
|
|
63
|
+
return true; // HTML block/tag line
|
|
64
|
+
if (c === "!")
|
|
65
|
+
return true; // image
|
|
66
|
+
if (c === "[")
|
|
67
|
+
return true; // bare link / link reference (nav-like)
|
|
68
|
+
if (c === ":" || c === "{" || c === "}" || c === "=")
|
|
69
|
+
return true; // containers/MDX
|
|
70
|
+
if (/^(import|export)\s/.test(line))
|
|
71
|
+
return true; // MDX statements
|
|
72
|
+
if (/^\/\//.test(line))
|
|
73
|
+
return true; // JS-style comments
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
/** Truncate on a word boundary near `soft`, never exceeding `hard`. */
|
|
77
|
+
function truncate(text, soft, hard) {
|
|
78
|
+
if (text.length <= soft)
|
|
79
|
+
return text;
|
|
80
|
+
const window = text.slice(0, soft);
|
|
81
|
+
const boundary = window.lastIndexOf(" ");
|
|
82
|
+
let out = boundary > 40 ? window.slice(0, boundary) : text.slice(0, hard - 3);
|
|
83
|
+
if (out.length > hard - 3)
|
|
84
|
+
out = out.slice(0, hard - 3);
|
|
85
|
+
return `${out.trimEnd()}...`;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Extract the first H1 heading from markdown content (for SEO title fallback).
|
|
89
|
+
*/
|
|
90
|
+
export function extractFirstHeading(content) {
|
|
91
|
+
const body = stripFencedCode(stripFrontmatter(content));
|
|
92
|
+
const match = body.match(/^#[ \t]+(.+?)[ \t]*#*[ \t]*$/m);
|
|
93
|
+
return match ? cleanInline(match[1]) || null : null;
|
|
94
|
+
}
|
|
95
|
+
/** Descriptions shorter than this are treated as degenerate — keep scanning. */
|
|
96
|
+
const MIN_DESCRIPTION_LENGTH = 40;
|
|
97
|
+
/**
|
|
98
|
+
* Whether a line is a bare label like `**Làm gì:**` or `Functional:` —
|
|
99
|
+
* markdown emphasis that cleans to a short colon-terminated string.
|
|
100
|
+
*/
|
|
101
|
+
function isLabelOnly(line) {
|
|
102
|
+
const text = cleanInline(line);
|
|
103
|
+
return (text.length > 0 &&
|
|
104
|
+
text.length <= MIN_DESCRIPTION_LENGTH &&
|
|
105
|
+
text.endsWith(":"));
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Extract the first meaningful paragraph from markdown content
|
|
109
|
+
* (for the meta description fallback). Skips frontmatter, code blocks,
|
|
110
|
+
* headings, HTML, comments, and nav-like lines; drops label-only lines;
|
|
111
|
+
* requires ~40 chars of real prose (shorter paragraphs are kept only as a
|
|
112
|
+
* last resort); collapses whitespace; cuts at ~160 chars on a word
|
|
113
|
+
* boundary (hard cap 200).
|
|
114
|
+
*/
|
|
115
|
+
export function extractFirstParagraph(content) {
|
|
116
|
+
let body = stripFrontmatter(content);
|
|
117
|
+
body = stripFencedCode(body);
|
|
118
|
+
body = body.replace(/<!--[\s\S]*?-->/g, "");
|
|
119
|
+
let fallback = null;
|
|
120
|
+
let paraLines = [];
|
|
121
|
+
// Evaluate the accumulated paragraph: returns a qualifying description,
|
|
122
|
+
// records a shorter candidate as fallback, or null if the block is empty.
|
|
123
|
+
const flush = () => {
|
|
124
|
+
if (paraLines.length === 0)
|
|
125
|
+
return null;
|
|
126
|
+
const kept = paraLines.filter((l) => !isLabelOnly(l));
|
|
127
|
+
paraLines = [];
|
|
128
|
+
const text = cleanInline(kept.join(" "));
|
|
129
|
+
if (!text)
|
|
130
|
+
return null;
|
|
131
|
+
if (text.length >= MIN_DESCRIPTION_LENGTH)
|
|
132
|
+
return truncate(text, 160, 200);
|
|
133
|
+
if (!fallback || text.length > fallback.length)
|
|
134
|
+
fallback = text;
|
|
135
|
+
return null;
|
|
136
|
+
};
|
|
137
|
+
for (const rawLine of body.split("\n")) {
|
|
138
|
+
const line = rawLine.trim();
|
|
139
|
+
if (!line || isSkippableLine(line)) {
|
|
140
|
+
const result = flush();
|
|
141
|
+
if (result)
|
|
142
|
+
return result;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
paraLines.push(line);
|
|
146
|
+
}
|
|
147
|
+
const result = flush();
|
|
148
|
+
return result ?? (fallback ? truncate(fallback, 160, 200) : null);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Extract SEO data (title + description) from markdown content.
|
|
152
|
+
*/
|
|
153
|
+
export function extractSeoData(content) {
|
|
154
|
+
return {
|
|
155
|
+
title: extractFirstHeading(content),
|
|
156
|
+
description: extractFirstParagraph(content),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=extract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract.js","sourceRoot":"","sources":["../../src/seo/extract.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { normalizeUrlPath, normalizeBasePath, buildPageUrl, buildSiteUrl, toAbsoluteAssetUrl, } from "./url.js";
|
|
2
|
+
export { extractFirstHeading, extractFirstParagraph, extractSeoData, } from "./extract.js";
|
|
3
|
+
export { resolveSeoData, formatPageTitle, type SeoData, type SeoAuthor, type SeoBreadcrumb, type PageKind, type ResolveSeoInput, } from "./metadata.js";
|
|
4
|
+
export { buildJsonLd } from "./jsonld.js";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/seo/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,kBAAkB,GACnB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,cAAc,GACf,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,cAAc,EACd,eAAe,EACf,KAAK,OAAO,EACZ,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,eAAe,GACrB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { normalizeUrlPath, normalizeBasePath, buildPageUrl, buildSiteUrl, toAbsoluteAssetUrl, } from "./url.js";
|
|
2
|
+
export { extractFirstHeading, extractFirstParagraph, extractSeoData, } from "./extract.js";
|
|
3
|
+
export { resolveSeoData, formatPageTitle, } from "./metadata.js";
|
|
4
|
+
export { buildJsonLd } from "./jsonld.js";
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/seo/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,kBAAkB,GACnB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,cAAc,GACf,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,cAAc,EACd,eAAe,GAMhB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Settings } from "../settings/index.js";
|
|
2
|
+
import type { SeoData } from "./metadata.js";
|
|
3
|
+
/**
|
|
4
|
+
* Build the JSON-LD `<script>` tag for a page.
|
|
5
|
+
*
|
|
6
|
+
* - Homepage → `WebSite`
|
|
7
|
+
* - Document pages → `Article`
|
|
8
|
+
* - Nested routes → `BreadcrumbList`
|
|
9
|
+
*
|
|
10
|
+
* Fields are only emitted when real data exists (no invented dates).
|
|
11
|
+
* Returns an empty string when `seo.structuredData` is off or there is
|
|
12
|
+
* nothing worth emitting.
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildJsonLd(seo: SeoData, settings: Settings): string;
|
|
15
|
+
//# sourceMappingURL=jsonld.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonld.d.ts","sourceRoot":"","sources":["../../src/seo/jsonld.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG7C;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,CA0EpE"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { buildPageUrl, buildSiteUrl, toAbsoluteAssetUrl } from "./url.js";
|
|
2
|
+
/**
|
|
3
|
+
* Build the JSON-LD `<script>` tag for a page.
|
|
4
|
+
*
|
|
5
|
+
* - Homepage → `WebSite`
|
|
6
|
+
* - Document pages → `Article`
|
|
7
|
+
* - Nested routes → `BreadcrumbList`
|
|
8
|
+
*
|
|
9
|
+
* Fields are only emitted when real data exists (no invented dates).
|
|
10
|
+
* Returns an empty string when `seo.structuredData` is off or there is
|
|
11
|
+
* nothing worth emitting.
|
|
12
|
+
*/
|
|
13
|
+
export function buildJsonLd(seo, settings) {
|
|
14
|
+
if (!settings.seo.structuredData)
|
|
15
|
+
return "";
|
|
16
|
+
const graph = [];
|
|
17
|
+
if (seo.kind !== "generic" && seo.urlPath === "/") {
|
|
18
|
+
const site = {
|
|
19
|
+
"@type": "WebSite",
|
|
20
|
+
name: settings.site.title || seo.title,
|
|
21
|
+
};
|
|
22
|
+
const url = buildSiteUrl(settings);
|
|
23
|
+
if (url)
|
|
24
|
+
site.url = url;
|
|
25
|
+
const description = seo.description || settings.site.description;
|
|
26
|
+
if (description)
|
|
27
|
+
site.description = description;
|
|
28
|
+
// Sitelinks search box — client-side search reads the `q` query param.
|
|
29
|
+
if (settings.search.enabled && url) {
|
|
30
|
+
site.potentialAction = {
|
|
31
|
+
"@type": "SearchAction",
|
|
32
|
+
target: {
|
|
33
|
+
"@type": "EntryPoint",
|
|
34
|
+
urlTemplate: `${url.replace(/\/+$/, "")}/?q={search_term_string}`,
|
|
35
|
+
},
|
|
36
|
+
"query-input": "required name=search_term_string",
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
graph.push(site);
|
|
40
|
+
}
|
|
41
|
+
else if (seo.kind === "document" && seo.type === "article") {
|
|
42
|
+
const article = {
|
|
43
|
+
"@type": "Article",
|
|
44
|
+
headline: seo.title,
|
|
45
|
+
};
|
|
46
|
+
if (seo.description)
|
|
47
|
+
article.description = seo.description;
|
|
48
|
+
if (seo.absoluteUrl)
|
|
49
|
+
article.url = seo.absoluteUrl;
|
|
50
|
+
if (seo.image) {
|
|
51
|
+
const image = toAbsoluteAssetUrl(seo.image, settings);
|
|
52
|
+
if (image)
|
|
53
|
+
article.image = image;
|
|
54
|
+
}
|
|
55
|
+
if (seo.author) {
|
|
56
|
+
article.author = {
|
|
57
|
+
"@type": seo.author.type,
|
|
58
|
+
name: seo.author.name,
|
|
59
|
+
...(seo.author.url ? { url: seo.author.url } : {}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (seo.datePublished)
|
|
63
|
+
article.datePublished = seo.datePublished;
|
|
64
|
+
if (seo.dateModified)
|
|
65
|
+
article.dateModified = seo.dateModified;
|
|
66
|
+
graph.push(article);
|
|
67
|
+
}
|
|
68
|
+
if (seo.breadcrumbs.length > 1) {
|
|
69
|
+
graph.push({
|
|
70
|
+
"@type": "BreadcrumbList",
|
|
71
|
+
itemListElement: seo.breadcrumbs.map((crumb, i) => {
|
|
72
|
+
const item = {
|
|
73
|
+
"@type": "ListItem",
|
|
74
|
+
position: i + 1,
|
|
75
|
+
name: crumb.name,
|
|
76
|
+
};
|
|
77
|
+
const url = buildPageUrl(crumb.urlPath, settings);
|
|
78
|
+
if (url)
|
|
79
|
+
item.item = url;
|
|
80
|
+
return item;
|
|
81
|
+
}),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
if (graph.length === 0)
|
|
85
|
+
return "";
|
|
86
|
+
// `<\/` escaping prevents a literal `</script>` from breaking the tag.
|
|
87
|
+
const json = JSON.stringify({
|
|
88
|
+
"@context": "https://schema.org",
|
|
89
|
+
"@graph": graph,
|
|
90
|
+
}).replace(/<\//g, "<\\/");
|
|
91
|
+
return `<script type="application/ld+json">${json}</script>`;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=jsonld.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonld.js","sourceRoot":"","sources":["../../src/seo/jsonld.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Settings } from "../settings/index.js";
|
|
2
|
+
import type { NavItem } from "../template/sidebar.js";
|
|
3
|
+
export type PageKind = "document" | "directory" | "generic";
|
|
4
|
+
export interface SeoAuthor {
|
|
5
|
+
name: string;
|
|
6
|
+
url?: string;
|
|
7
|
+
type: "Person" | "Organization";
|
|
8
|
+
}
|
|
9
|
+
export interface SeoBreadcrumb {
|
|
10
|
+
name: string;
|
|
11
|
+
urlPath: string;
|
|
12
|
+
}
|
|
13
|
+
export interface SeoData {
|
|
14
|
+
/** Resolved page title (before `seo.titleTemplate`). */
|
|
15
|
+
title: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
image?: string;
|
|
18
|
+
/** Pixel dimensions, set only for generated OG images (known size). */
|
|
19
|
+
imageWidth?: number;
|
|
20
|
+
imageHeight?: number;
|
|
21
|
+
/** Absolute canonical URL, or null when no `site.baseUrl` is configured. */
|
|
22
|
+
absoluteUrl: string | null;
|
|
23
|
+
/** Normalized trailing-slash route. */
|
|
24
|
+
urlPath: string;
|
|
25
|
+
type: "website" | "article";
|
|
26
|
+
kind: PageKind;
|
|
27
|
+
datePublished?: string;
|
|
28
|
+
dateModified?: string;
|
|
29
|
+
author?: SeoAuthor;
|
|
30
|
+
noIndex: boolean;
|
|
31
|
+
/** Home → ancestors → current page (empty when disabled or at root). */
|
|
32
|
+
breadcrumbs: SeoBreadcrumb[];
|
|
33
|
+
/** Root-relative sitemap URL, or null when it should not be advertised. */
|
|
34
|
+
sitemapUrl: string | null;
|
|
35
|
+
}
|
|
36
|
+
export interface ResolveSeoInput {
|
|
37
|
+
frontmatter?: Record<string, unknown>;
|
|
38
|
+
/** Raw markdown/MDX source for description auto-extraction. */
|
|
39
|
+
content?: string;
|
|
40
|
+
/** Display title resolved by the caller (frontmatter → H1 → filename). */
|
|
41
|
+
title: string;
|
|
42
|
+
urlPath?: string;
|
|
43
|
+
kind?: PageKind;
|
|
44
|
+
settings: Settings;
|
|
45
|
+
sidebar?: NavItem[];
|
|
46
|
+
isStaticExport?: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Apply `seo.titleTemplate` to a page title. When the template is unset,
|
|
50
|
+
* defaults to `"<title> | <site title>"` (or the bare title when the site
|
|
51
|
+
* has no title).
|
|
52
|
+
*/
|
|
53
|
+
export declare function formatPageTitle(pageTitle: string, settings: Settings): string;
|
|
54
|
+
/**
|
|
55
|
+
* Resolve all SEO metadata for a page from frontmatter, content, and
|
|
56
|
+
* settings. Centralizes the frontmatter → extracted → site-defaults chain
|
|
57
|
+
* so templates and generators share one source of truth.
|
|
58
|
+
*/
|
|
59
|
+
export declare function resolveSeoData(input: ResolveSeoInput): SeoData;
|
|
60
|
+
//# sourceMappingURL=metadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../src/seo/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAMtD,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,WAAW,GAAG,SAAS,CAAC;AAE5D,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,GAAG,cAAc,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,OAAO;IACtB,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,SAAS,GAAG,SAAS,CAAC;IAC5B,IAAI,EAAE,QAAQ,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,wEAAwE;IACxE,WAAW,EAAE,aAAa,EAAE,CAAC;IAC7B,2EAA2E;IAC3E,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAyDD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAa7E;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAsF9D"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { getOgImageUrl } from "../og/index.js";
|
|
2
|
+
import { OG_WIDTH, OG_HEIGHT } from "../og/template.js";
|
|
3
|
+
import { extractFirstParagraph } from "./extract.js";
|
|
4
|
+
import { buildPageUrl, normalizeBasePath, normalizeUrlPath } from "./url.js";
|
|
5
|
+
function str(value) {
|
|
6
|
+
if (typeof value !== "string")
|
|
7
|
+
return undefined;
|
|
8
|
+
const s = value.trim();
|
|
9
|
+
return s || undefined;
|
|
10
|
+
}
|
|
11
|
+
function toIsoDate(value) {
|
|
12
|
+
if (value == null || value === "")
|
|
13
|
+
return undefined;
|
|
14
|
+
const d = value instanceof Date ? value : new Date(String(value));
|
|
15
|
+
return Number.isNaN(d.getTime()) ? undefined : d.toISOString().split("T")[0];
|
|
16
|
+
}
|
|
17
|
+
function resolveAuthor(author) {
|
|
18
|
+
if (typeof author === "string") {
|
|
19
|
+
const name = author.trim();
|
|
20
|
+
return name ? { name, type: "Person" } : undefined;
|
|
21
|
+
}
|
|
22
|
+
if (author && typeof author === "object") {
|
|
23
|
+
const obj = author;
|
|
24
|
+
const name = str(obj.name);
|
|
25
|
+
if (!name)
|
|
26
|
+
return undefined;
|
|
27
|
+
const type = obj.type === "Organization" ? "Organization" : "Person";
|
|
28
|
+
return { name, url: str(obj.url), type };
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
function humanizeSegment(segment) {
|
|
33
|
+
let decoded = segment;
|
|
34
|
+
try {
|
|
35
|
+
decoded = decodeURIComponent(segment);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Keep the raw segment when it is not valid percent-encoding
|
|
39
|
+
}
|
|
40
|
+
return decoded
|
|
41
|
+
.replace(/^\d+\./, "")
|
|
42
|
+
.replace(/[-_]/g, " ")
|
|
43
|
+
.replace(/\.\w+$/, "")
|
|
44
|
+
.trim()
|
|
45
|
+
.replace(/^\w/, (c) => c.toUpperCase());
|
|
46
|
+
}
|
|
47
|
+
function findNavTitle(items, urlPath) {
|
|
48
|
+
const target = urlPath.replace(/\/+$/, "") || "/";
|
|
49
|
+
for (const item of items) {
|
|
50
|
+
const href = item.href.replace(/\/+$/, "") || "/";
|
|
51
|
+
if (href === target)
|
|
52
|
+
return item.title;
|
|
53
|
+
if (item.children) {
|
|
54
|
+
const found = findNavTitle(item.children, urlPath);
|
|
55
|
+
if (found)
|
|
56
|
+
return found;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Apply `seo.titleTemplate` to a page title. When the template is unset,
|
|
63
|
+
* defaults to `"<title> | <site title>"` (or the bare title when the site
|
|
64
|
+
* has no title).
|
|
65
|
+
*/
|
|
66
|
+
export function formatPageTitle(pageTitle, settings) {
|
|
67
|
+
if (settings.seo.titleTemplate != null) {
|
|
68
|
+
return settings.seo.titleTemplate.replace("%s", pageTitle);
|
|
69
|
+
}
|
|
70
|
+
// Default: "<title> | <site title>"; skip the suffix when it would
|
|
71
|
+
// duplicate the site title (e.g. homepage H1 == site.title).
|
|
72
|
+
if (!settings.site.title ||
|
|
73
|
+
pageTitle.trim().toLowerCase() === settings.site.title.trim().toLowerCase()) {
|
|
74
|
+
return pageTitle;
|
|
75
|
+
}
|
|
76
|
+
return `${pageTitle} | ${settings.site.title}`;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Resolve all SEO metadata for a page from frontmatter, content, and
|
|
80
|
+
* settings. Centralizes the frontmatter → extracted → site-defaults chain
|
|
81
|
+
* so templates and generators share one source of truth.
|
|
82
|
+
*/
|
|
83
|
+
export function resolveSeoData(input) {
|
|
84
|
+
const { frontmatter = {}, content = "", title, settings, sidebar = [], isStaticExport = false, } = input;
|
|
85
|
+
const kind = input.kind ?? "document";
|
|
86
|
+
const urlPath = normalizeUrlPath(input.urlPath ?? "/");
|
|
87
|
+
const resolvedTitle = str(frontmatter.seoTitle) ?? title;
|
|
88
|
+
const description = str(frontmatter.description) ??
|
|
89
|
+
(content ? extractFirstParagraph(content) : null) ??
|
|
90
|
+
(settings.site.description || undefined);
|
|
91
|
+
const isHome = urlPath === "/";
|
|
92
|
+
const type = kind === "document" && !isHome ? "article" : "website";
|
|
93
|
+
let image = str(frontmatter.image);
|
|
94
|
+
let generatedOg = false;
|
|
95
|
+
if (!image && isStaticExport && settings.seo.og?.enabled) {
|
|
96
|
+
image = getOgImageUrl(urlPath, settings.generate.basePath || "", settings.seo.og.imageFormat);
|
|
97
|
+
generatedOg = true;
|
|
98
|
+
}
|
|
99
|
+
image ??= settings.seo.defaultImage;
|
|
100
|
+
const author = resolveAuthor(frontmatter.author) ??
|
|
101
|
+
(settings.site.author?.name
|
|
102
|
+
? {
|
|
103
|
+
name: settings.site.author.name,
|
|
104
|
+
url: settings.site.author.url,
|
|
105
|
+
type: "Organization",
|
|
106
|
+
}
|
|
107
|
+
: undefined);
|
|
108
|
+
const breadcrumbs = [];
|
|
109
|
+
if (settings.navigation.breadcrumbs && !isHome && kind !== "generic") {
|
|
110
|
+
breadcrumbs.push({ name: "Home", urlPath: "/" });
|
|
111
|
+
const segments = urlPath.split("/").filter(Boolean);
|
|
112
|
+
let acc = "";
|
|
113
|
+
segments.forEach((segment, i) => {
|
|
114
|
+
acc += `/${segment}`;
|
|
115
|
+
const isLast = i === segments.length - 1;
|
|
116
|
+
const name = isLast
|
|
117
|
+
? resolvedTitle
|
|
118
|
+
: (findNavTitle(sidebar, acc) ?? humanizeSegment(segment));
|
|
119
|
+
breadcrumbs.push({ name, urlPath: `${acc}/` });
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const sitemapUrl = settings.seo.generateSitemap && !(isStaticExport && !settings.site.baseUrl)
|
|
123
|
+
? `${normalizeBasePath(settings.generate.basePath)}/sitemap.xml`
|
|
124
|
+
: null;
|
|
125
|
+
return {
|
|
126
|
+
title: resolvedTitle,
|
|
127
|
+
description,
|
|
128
|
+
image,
|
|
129
|
+
imageWidth: generatedOg ? OG_WIDTH : undefined,
|
|
130
|
+
imageHeight: generatedOg ? OG_HEIGHT : undefined,
|
|
131
|
+
absoluteUrl: buildPageUrl(urlPath, settings),
|
|
132
|
+
urlPath,
|
|
133
|
+
type,
|
|
134
|
+
kind,
|
|
135
|
+
datePublished: toIsoDate(frontmatter.datePublished ?? frontmatter.date),
|
|
136
|
+
dateModified: toIsoDate(frontmatter.dateModified ?? frontmatter.updated ?? frontmatter.lastmod),
|
|
137
|
+
author,
|
|
138
|
+
noIndex: (frontmatter.noindex ?? frontmatter.noIndex) === true ||
|
|
139
|
+
(kind === "directory" && settings.seo.noIndexDirectoryPages),
|
|
140
|
+
breadcrumbs,
|
|
141
|
+
sitemapUrl,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=metadata.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata.js","sourceRoot":"","sources":["../../src/seo/metadata.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Settings } from "../settings/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Normalize a documentation route to its canonical form:
|
|
4
|
+
* leading `/`, trailing `/`, index/readme file names collapsed to the
|
|
5
|
+
* directory URL, `.md`/`.mdx` extensions stripped, `index.html` collapsed,
|
|
6
|
+
* and every path segment percent-encoded.
|
|
7
|
+
*/
|
|
8
|
+
export declare function normalizeUrlPath(urlPath: string): string;
|
|
9
|
+
/** Normalize `generate.basePath` to `"/base"` form (empty when unset). */
|
|
10
|
+
export declare function normalizeBasePath(basePath: string | undefined): string;
|
|
11
|
+
/**
|
|
12
|
+
* Build the absolute URL for a documentation route.
|
|
13
|
+
* Returns `null` when no absolute base is available (`site.baseUrl`
|
|
14
|
+
* or an explicit request `origin`).
|
|
15
|
+
*/
|
|
16
|
+
export declare function buildPageUrl(urlPath: string, settings: Settings, origin?: string): string | null;
|
|
17
|
+
/** Absolute site root URL (baseUrl + basePath + `/`), or `null`. */
|
|
18
|
+
export declare function buildSiteUrl(settings: Settings, origin?: string): string | null;
|
|
19
|
+
/**
|
|
20
|
+
* Resolve an image/asset reference to an absolute URL.
|
|
21
|
+
* Absolute http(s) URLs pass through. Site-relative paths are resolved
|
|
22
|
+
* against `site.baseUrl` + `generate.basePath` (a path that already starts
|
|
23
|
+
* with basePath is not prefixed twice). Returns `null` when the input is a
|
|
24
|
+
* relative path and no absolute base is configured.
|
|
25
|
+
*/
|
|
26
|
+
export declare function toAbsoluteAssetUrl(pathOrUrl: string, settings: Settings, origin?: string): string | null;
|
|
27
|
+
//# sourceMappingURL=url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../src/seo/url.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAgBxD;AAED,0EAA0E;AAC1E,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAItE;AAMD;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,QAAQ,EAClB,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,GAAG,IAAI,CAIf;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI/E;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,QAAQ,EAClB,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,GAAG,IAAI,CAef"}
|
package/dist/seo/url.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize a documentation route to its canonical form:
|
|
3
|
+
* leading `/`, trailing `/`, index/readme file names collapsed to the
|
|
4
|
+
* directory URL, `.md`/`.mdx` extensions stripped, `index.html` collapsed,
|
|
5
|
+
* and every path segment percent-encoded.
|
|
6
|
+
*/
|
|
7
|
+
export function normalizeUrlPath(urlPath) {
|
|
8
|
+
let p = (urlPath || "/").split("?")[0].split("#")[0];
|
|
9
|
+
if (!p.startsWith("/"))
|
|
10
|
+
p = `/${p}`;
|
|
11
|
+
const segments = p.split("/").filter((s) => s.length > 0 && s !== ".");
|
|
12
|
+
const last = segments[segments.length - 1];
|
|
13
|
+
if (last) {
|
|
14
|
+
if (/^(readme|index)\.(md|mdx|html)$/i.test(last)) {
|
|
15
|
+
segments.pop();
|
|
16
|
+
}
|
|
17
|
+
else if (/\.(md|mdx)$/i.test(last)) {
|
|
18
|
+
segments[segments.length - 1] = last.replace(/\.(md|mdx)$/i, "");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const encoded = segments.map((s) => encodeURIComponent(s));
|
|
22
|
+
return `/${encoded.join("/")}${encoded.length ? "/" : ""}`;
|
|
23
|
+
}
|
|
24
|
+
/** Normalize `generate.basePath` to `"/base"` form (empty when unset). */
|
|
25
|
+
export function normalizeBasePath(basePath) {
|
|
26
|
+
const bp = (basePath || "").trim();
|
|
27
|
+
if (!bp || bp === "/")
|
|
28
|
+
return "";
|
|
29
|
+
return `/${bp.replace(/^\/+|\/+$/g, "")}`;
|
|
30
|
+
}
|
|
31
|
+
function resolveBase(settings, origin) {
|
|
32
|
+
return (settings.site.baseUrl || origin || "").replace(/\/+$/, "");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Build the absolute URL for a documentation route.
|
|
36
|
+
* Returns `null` when no absolute base is available (`site.baseUrl`
|
|
37
|
+
* or an explicit request `origin`).
|
|
38
|
+
*/
|
|
39
|
+
export function buildPageUrl(urlPath, settings, origin) {
|
|
40
|
+
const base = resolveBase(settings, origin);
|
|
41
|
+
if (!base)
|
|
42
|
+
return null;
|
|
43
|
+
return `${base}${normalizeBasePath(settings.generate.basePath)}${normalizeUrlPath(urlPath)}`;
|
|
44
|
+
}
|
|
45
|
+
/** Absolute site root URL (baseUrl + basePath + `/`), or `null`. */
|
|
46
|
+
export function buildSiteUrl(settings, origin) {
|
|
47
|
+
const base = resolveBase(settings, origin);
|
|
48
|
+
if (!base)
|
|
49
|
+
return null;
|
|
50
|
+
return `${base}${normalizeBasePath(settings.generate.basePath)}/`;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Resolve an image/asset reference to an absolute URL.
|
|
54
|
+
* Absolute http(s) URLs pass through. Site-relative paths are resolved
|
|
55
|
+
* against `site.baseUrl` + `generate.basePath` (a path that already starts
|
|
56
|
+
* with basePath is not prefixed twice). Returns `null` when the input is a
|
|
57
|
+
* relative path and no absolute base is configured.
|
|
58
|
+
*/
|
|
59
|
+
export function toAbsoluteAssetUrl(pathOrUrl, settings, origin) {
|
|
60
|
+
if (/^https?:\/\//i.test(pathOrUrl))
|
|
61
|
+
return pathOrUrl;
|
|
62
|
+
const siteUrl = buildSiteUrl(settings, origin);
|
|
63
|
+
if (!siteUrl)
|
|
64
|
+
return null;
|
|
65
|
+
let p = pathOrUrl.replace(/^\.\//, "").replace(/^\/+/, "");
|
|
66
|
+
const bp = normalizeBasePath(settings.generate.basePath).replace(/^\//, "");
|
|
67
|
+
if (bp && p.startsWith(`${bp}/`))
|
|
68
|
+
p = p.slice(bp.length + 1);
|
|
69
|
+
const encoded = p
|
|
70
|
+
.split("/")
|
|
71
|
+
.filter(Boolean)
|
|
72
|
+
.map(encodeURIComponent)
|
|
73
|
+
.join("/");
|
|
74
|
+
return `${siteUrl}${encoded}`;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/seo/url.ts"],"names":[],"mappings":"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"}
|