@kenjura/ursa 0.43.0 → 0.44.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 CHANGED
@@ -1,3 +1,8 @@
1
+ # 0.44.0
2
+ 2025-12-16
3
+
4
+ - Added 'sections' metadata property with hierarchical section structure
5
+
1
6
  # 0.43.0
2
7
  2025-12-14
3
8
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@kenjura/ursa",
3
3
  "author": "Andrew London <andrew@kenjura.com>",
4
4
  "type": "module",
5
- "version": "0.43.0",
5
+ "version": "0.44.0",
6
6
  "description": "static site generator from MD/wikitext/YML",
7
7
  "main": "lib/index.js",
8
8
  "bin": {
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Extract sections from markdown content based on headings
3
+ * Creates a hierarchical structure of sections
4
+ */
5
+
6
+ /**
7
+ * Extract sections from markdown content
8
+ * @param {string} content - The markdown content
9
+ * @returns {Array} Array of section objects with name and optional children
10
+ */
11
+ export function extractSections(content) {
12
+ if (!content) return [];
13
+
14
+ // Match all markdown headings (# to ######)
15
+ // Handles both "# Heading" and "#Heading" formats
16
+ const headingRegex = /^(#{1,6})\s*(.+?)$/gm;
17
+
18
+ const headings = [];
19
+ let match;
20
+
21
+ while ((match = headingRegex.exec(content)) !== null) {
22
+ const level = match[1].length; // Number of # characters
23
+ const name = match[2].trim();
24
+ headings.push({ level, name });
25
+ }
26
+
27
+ if (headings.length === 0) return [];
28
+
29
+ // Build hierarchical structure
30
+ return buildSectionTree(headings);
31
+ }
32
+
33
+ /**
34
+ * Build a hierarchical tree from flat heading list
35
+ * @param {Array} headings - Array of {level, name} objects
36
+ * @returns {Array} Hierarchical section tree
37
+ */
38
+ function buildSectionTree(headings) {
39
+ const root = { level: 0, children: [] };
40
+ const stack = [root];
41
+
42
+ for (const heading of headings) {
43
+ const section = { name: heading.name, level: heading.level, children: [] };
44
+
45
+ // Pop stack until we find a parent with lower level
46
+ while (stack.length > 1 && stack[stack.length - 1].level >= heading.level) {
47
+ stack.pop();
48
+ }
49
+
50
+ // Add to parent's children
51
+ const parent = stack[stack.length - 1];
52
+ parent.children.push(section);
53
+
54
+ // Push this section onto stack (it might have children)
55
+ stack.push(section);
56
+ }
57
+
58
+ // Clean up: remove level and empty children arrays
59
+ cleanupTree(root.children);
60
+
61
+ return root.children;
62
+ }
63
+
64
+ /**
65
+ * Remove level property and empty children arrays from the tree
66
+ */
67
+ function cleanupTree(sections) {
68
+ for (const section of sections) {
69
+ delete section.level;
70
+ if (section.children && section.children.length > 0) {
71
+ cleanupTree(section.children);
72
+ } else {
73
+ delete section.children;
74
+ }
75
+ }
76
+ }
@@ -21,6 +21,7 @@ import {
21
21
  markInactiveLinks,
22
22
  } from "../helper/linkValidator.js";
23
23
  import { getAndIncrementBuildId } from "../helper/ursaConfig.js";
24
+ import { extractSections } from "../helper/sectionExtractor.js";
24
25
 
25
26
  // Helper function to build search index from processed files
26
27
  function buildSearchIndex(jsonCache, source, output) {
@@ -230,12 +231,16 @@ export async function generate({
230
231
  dirname: dir,
231
232
  basename: base,
232
233
  });
234
+ // Extract sections for markdown files
235
+ const sections = type === '.md' ? extractSections(rawBody) : [];
236
+
233
237
  jsonCache.set(file, {
234
238
  name: base,
235
239
  url,
236
240
  contents: rawBody,
237
241
  bodyHtml: body,
238
242
  metadata: meta,
243
+ sections,
239
244
  transformedMetadata: '',
240
245
  });
241
246
  return; // Skip regenerating this file
@@ -307,6 +312,10 @@ export async function generate({
307
312
  // json
308
313
 
309
314
  const jsonOutputFilename = outputFilename.replace(".html", ".json");
315
+
316
+ // Extract sections for markdown files
317
+ const sections = type === '.md' ? extractSections(rawBody) : [];
318
+
310
319
  const jsonObject = {
311
320
  name: base,
312
321
  url,
@@ -314,6 +323,7 @@ export async function generate({
314
323
  // bodyLessMeta: bodyLessMeta,
315
324
  bodyHtml: body,
316
325
  metadata: meta,
326
+ sections,
317
327
  transformedMetadata,
318
328
  // html: finalHtml,
319
329
  };