@kenjura/ursa 0.58.0 → 0.59.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/CHANGELOG.md +5 -0
- package/package.json +1 -1
- package/src/helper/frontmatterTable.js +120 -0
- package/src/jobs/generate.js +7 -1
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert YAML frontmatter metadata to an HTML table
|
|
3
|
+
* and inject it into the document body after the first H1
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Convert a metadata value to a displayable string
|
|
8
|
+
* @param {any} value - The value to convert
|
|
9
|
+
* @returns {string} The displayable string
|
|
10
|
+
*/
|
|
11
|
+
function formatValue(value) {
|
|
12
|
+
if (value === null || value === undefined) {
|
|
13
|
+
return '';
|
|
14
|
+
}
|
|
15
|
+
if (Array.isArray(value)) {
|
|
16
|
+
return value.map(formatValue).join(', ');
|
|
17
|
+
}
|
|
18
|
+
if (typeof value === 'object') {
|
|
19
|
+
// For nested objects, render as a mini definition list
|
|
20
|
+
return Object.entries(value)
|
|
21
|
+
.map(([k, v]) => `<strong>${escapeHtml(k)}:</strong> ${escapeHtml(formatValue(v))}`)
|
|
22
|
+
.join('<br>');
|
|
23
|
+
}
|
|
24
|
+
return String(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Escape HTML special characters
|
|
29
|
+
* @param {string} str - String to escape
|
|
30
|
+
* @returns {string} Escaped string
|
|
31
|
+
*/
|
|
32
|
+
function escapeHtml(str) {
|
|
33
|
+
if (typeof str !== 'string') return str;
|
|
34
|
+
return str
|
|
35
|
+
.replace(/&/g, '&')
|
|
36
|
+
.replace(/</g, '<')
|
|
37
|
+
.replace(/>/g, '>')
|
|
38
|
+
.replace(/"/g, '"')
|
|
39
|
+
.replace(/'/g, ''');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Convert a key to a human-readable label
|
|
44
|
+
* @param {string} key - The metadata key
|
|
45
|
+
* @returns {string} Human-readable label
|
|
46
|
+
*/
|
|
47
|
+
function formatKey(key) {
|
|
48
|
+
// Convert camelCase or snake_case to Title Case with spaces
|
|
49
|
+
return key
|
|
50
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2') // camelCase
|
|
51
|
+
.replace(/_/g, ' ') // snake_case
|
|
52
|
+
.replace(/\b\w/g, c => c.toUpperCase()); // Title Case
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Generate an HTML table from metadata object
|
|
57
|
+
* @param {Object} metadata - The parsed YAML frontmatter
|
|
58
|
+
* @returns {string} HTML table string
|
|
59
|
+
*/
|
|
60
|
+
export function metadataToTable(metadata) {
|
|
61
|
+
if (!metadata || typeof metadata !== 'object' || Object.keys(metadata).length === 0) {
|
|
62
|
+
return '';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Filter out internal/template-related keys that shouldn't be displayed
|
|
66
|
+
const excludeKeys = ['template', 'layout', 'draft', 'published'];
|
|
67
|
+
const entries = Object.entries(metadata).filter(
|
|
68
|
+
([key]) => !excludeKeys.includes(key.toLowerCase())
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (entries.length === 0) {
|
|
72
|
+
return '';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const rows = entries.map(([key, value]) => {
|
|
76
|
+
const formattedValue = formatValue(value);
|
|
77
|
+
// Don't escape HTML in formatted value since it may contain our formatting
|
|
78
|
+
return ` <tr>
|
|
79
|
+
<th>${escapeHtml(formatKey(key))}</th>
|
|
80
|
+
<td>${formattedValue}</td>
|
|
81
|
+
</tr>`;
|
|
82
|
+
}).join('\n');
|
|
83
|
+
|
|
84
|
+
return `<table class="frontmatter-table">
|
|
85
|
+
<tbody>
|
|
86
|
+
${rows}
|
|
87
|
+
</tbody>
|
|
88
|
+
</table>`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Inject the frontmatter table into the body HTML after the first H1
|
|
93
|
+
* If no H1 is present, prepend the table to the body
|
|
94
|
+
* @param {string} bodyHtml - The rendered body HTML
|
|
95
|
+
* @param {Object} metadata - The parsed YAML frontmatter
|
|
96
|
+
* @returns {string} The body HTML with the frontmatter table injected
|
|
97
|
+
*/
|
|
98
|
+
export function injectFrontmatterTable(bodyHtml, metadata) {
|
|
99
|
+
const table = metadataToTable(metadata);
|
|
100
|
+
|
|
101
|
+
if (!table) {
|
|
102
|
+
return bodyHtml;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Look for the first closing </h1> tag
|
|
106
|
+
const h1CloseMatch = bodyHtml.match(/<\/h1>/i);
|
|
107
|
+
|
|
108
|
+
if (h1CloseMatch) {
|
|
109
|
+
// Insert the table after the first </h1>
|
|
110
|
+
const insertPosition = h1CloseMatch.index + h1CloseMatch[0].length;
|
|
111
|
+
return (
|
|
112
|
+
bodyHtml.slice(0, insertPosition) +
|
|
113
|
+
'\n' + table + '\n' +
|
|
114
|
+
bodyHtml.slice(insertPosition)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// No H1 found, prepend the table
|
|
119
|
+
return table + '\n' + bodyHtml;
|
|
120
|
+
}
|
package/src/jobs/generate.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
extractMetadata,
|
|
9
9
|
extractRawMetadata,
|
|
10
10
|
} from "../helper/metadataExtractor.js";
|
|
11
|
+
import { injectFrontmatterTable } from "../helper/frontmatterTable.js";
|
|
11
12
|
import {
|
|
12
13
|
hashContent,
|
|
13
14
|
loadHashCache,
|
|
@@ -307,13 +308,18 @@ export async function generate({
|
|
|
307
308
|
// Calculate the document's URL path (e.g., "/character/index.html")
|
|
308
309
|
const docUrlPath = '/' + dir + base + '.html';
|
|
309
310
|
|
|
310
|
-
|
|
311
|
+
let body = renderFile({
|
|
311
312
|
fileContents: rawBody,
|
|
312
313
|
type,
|
|
313
314
|
dirname: dir,
|
|
314
315
|
basename: base,
|
|
315
316
|
});
|
|
316
317
|
|
|
318
|
+
// Inject frontmatter table after first H1 (for markdown files with metadata)
|
|
319
|
+
if (type === '.md' && fileMeta) {
|
|
320
|
+
body = injectFrontmatterTable(body, fileMeta);
|
|
321
|
+
}
|
|
322
|
+
|
|
317
323
|
// Find nearest style.css or _style.css up the tree and copy to output
|
|
318
324
|
// Use cache to avoid repeated filesystem walks for same directory
|
|
319
325
|
let styleLink = "";
|