@kenjura/ursa 0.39.0 → 0.41.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,13 @@
1
+ # 0.41.0
2
+ 2025-12-13
3
+
4
+ - Fixed footer bug
5
+
6
+ # 0.40.0
7
+ 2025-12-13
8
+
9
+ - Added footer
10
+
1
11
  # 0.39.0
2
12
  2025-12-13
3
13
 
@@ -33,6 +33,9 @@
33
33
  <article id="main-content">
34
34
  ${body}
35
35
  </article>
36
+ <footer id="site-footer">
37
+ ${footer}
38
+ </footer>
36
39
  <div id="global-nav">
37
40
  </div>
38
41
 
package/meta/default.css CHANGED
@@ -432,6 +432,43 @@ article#main-content {
432
432
  }
433
433
  }
434
434
 
435
+ /* Footer styles */
436
+ footer#site-footer {
437
+ width: var(--article-width);
438
+ margin: 3rem auto 2rem auto;
439
+ padding-top: 2rem;
440
+ border-top: 1px solid rgba(128, 128, 128, 0.3);
441
+ text-align: center;
442
+
443
+ .footer-content {
444
+ margin-bottom: 1.5rem;
445
+ font-size: 0.95rem;
446
+ opacity: 0.85;
447
+ }
448
+
449
+ .footer-meta {
450
+ font-size: 0.8rem;
451
+ opacity: 0.6;
452
+ margin-bottom: 0.5rem;
453
+
454
+ a {
455
+ color: inherit;
456
+ }
457
+ }
458
+
459
+ .footer-copyright {
460
+ font-size: 0.8rem;
461
+ opacity: 0.6;
462
+ }
463
+ }
464
+
465
+ @media (max-width: 800px) {
466
+ footer#site-footer {
467
+ width: calc(100vw - 2rem);
468
+ margin: 3rem 1rem 2rem 1rem;
469
+ }
470
+ }
471
+
435
472
  @media (max-width: 800px) {
436
473
  nav#nav-global {
437
474
  display: flex;
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.39.0",
5
+ "version": "0.41.0",
6
6
  "description": "static site generator from MD/wikitext/YML",
7
7
  "main": "lib/index.js",
8
8
  "bin": {
@@ -129,7 +129,7 @@ function buildMenuData(tree, source, validPaths, parentPath = '') {
129
129
  const items = [];
130
130
 
131
131
  // Files to hide from menu by default
132
- const hiddenFiles = ['config.json', 'style.css'];
132
+ const hiddenFiles = ['config.json', 'style.css', 'footer.md'];
133
133
 
134
134
  for (const item of tree.children || []) {
135
135
  const ext = extname(item.path);
@@ -128,6 +128,9 @@ export async function generate({
128
128
 
129
129
  const menu = await getMenu(allSourceFilenames, source, validPaths);
130
130
 
131
+ // Generate footer content
132
+ const footer = await getFooter(source, _source);
133
+
131
134
  // Load content hash cache from .ursa folder in source directory
132
135
  let hashCache = new Map();
133
136
  if (!_clean) {
@@ -285,7 +288,8 @@ export async function generate({
285
288
  .replace("${transformedMetadata}", transformedMetadata)
286
289
  .replace("${body}", body)
287
290
  .replace("${embeddedStyle}", embeddedStyle)
288
- .replace("${searchIndex}", JSON.stringify(searchIndex));
291
+ .replace("${searchIndex}", JSON.stringify(searchIndex))
292
+ .replace("${footer}", footer);
289
293
 
290
294
  // Resolve links and mark broken internal links as inactive (debug mode on)
291
295
  // Pass docUrlPath so relative links can be resolved correctly
@@ -378,7 +382,8 @@ export async function generate({
378
382
  .replace("${title}", "Index")
379
383
  .replace("${meta}", "{}")
380
384
  .replace("${transformedMetadata}", "")
381
- .replace("${embeddedStyle}", "");
385
+ .replace("${embeddedStyle}", "")
386
+ .replace("${footer}", footer);
382
387
  console.log(`writing directory index to ${htmlOutputFilename}`);
383
388
  await outputFile(htmlOutputFilename, finalHtml);
384
389
  }
@@ -541,3 +546,87 @@ function addTrailingSlash(somePath) {
541
546
  if (somePath[somePath.length - 1] == "/") return somePath;
542
547
  return `${somePath}/`;
543
548
  }
549
+
550
+ /**
551
+ * Generate footer HTML from footer.md and package.json
552
+ * @param {string} source - resolved source path with trailing slash
553
+ * @param {string} _source - original source path
554
+ */
555
+ async function getFooter(source, _source) {
556
+ const footerParts = [];
557
+
558
+ // Try to read footer.md from source root
559
+ const footerPath = join(source, 'footer.md');
560
+ try {
561
+ if (existsSync(footerPath)) {
562
+ const footerMd = await readFile(footerPath, 'utf8');
563
+ const footerHtml = renderFile({ fileContents: footerMd, type: '.md' });
564
+ footerParts.push(`<div class="footer-content">${footerHtml}</div>`);
565
+ }
566
+ } catch (e) {
567
+ console.error(`Error reading footer.md: ${e.message}`);
568
+ }
569
+
570
+ // Try to read package.json from doc repo (check both source dir and parent)
571
+ let docPackage = null;
572
+ const sourceDir = resolve(_source);
573
+ const packagePaths = [
574
+ join(sourceDir, 'package.json'), // In source dir itself
575
+ join(sourceDir, '..', 'package.json'), // One level up (if docs is a subfolder)
576
+ ];
577
+
578
+ for (const packagePath of packagePaths) {
579
+ try {
580
+ if (existsSync(packagePath)) {
581
+ const packageJson = await readFile(packagePath, 'utf8');
582
+ docPackage = JSON.parse(packageJson);
583
+ console.log(`Found doc package.json at ${packagePath}`);
584
+ break;
585
+ }
586
+ } catch (e) {
587
+ // Continue to next path
588
+ }
589
+ }
590
+
591
+ // Get ursa version from ursa's own package.json
592
+ // Use import.meta.url to find the package.json relative to this file
593
+ let ursaVersion = 'unknown';
594
+ try {
595
+ // From src/jobs/generate.js, go up to package root
596
+ const currentFileUrl = new URL(import.meta.url);
597
+ const currentDir = dirname(currentFileUrl.pathname);
598
+ const ursaPackagePath = resolve(currentDir, '..', '..', 'package.json');
599
+
600
+ if (existsSync(ursaPackagePath)) {
601
+ const ursaPackageJson = await readFile(ursaPackagePath, 'utf8');
602
+ const ursaPackage = JSON.parse(ursaPackageJson);
603
+ ursaVersion = ursaPackage.version;
604
+ console.log(`Found ursa package.json at ${ursaPackagePath}, version: ${ursaVersion}`);
605
+ }
606
+ } catch (e) {
607
+ console.error(`Error reading ursa package.json: ${e.message}`);
608
+ }
609
+
610
+ // Build meta line: version, timestamp, "generated by ursa"
611
+ const metaParts = [];
612
+ if (docPackage?.version) {
613
+ metaParts.push(`v${docPackage.version}`);
614
+ }
615
+ metaParts.push(new Date().toISOString().split('T')[0]); // YYYY-MM-DD
616
+ metaParts.push(`Generated by <a href="https://www.npmjs.com/package/@kenjura/ursa">ursa</a> v${ursaVersion}`);
617
+
618
+ footerParts.push(`<div class="footer-meta">${metaParts.join(' • ')}</div>`);
619
+
620
+ // Copyright line from doc package.json
621
+ if (docPackage?.copyright) {
622
+ footerParts.push(`<div class="footer-copyright">${docPackage.copyright}</div>`);
623
+ } else if (docPackage?.author) {
624
+ const year = new Date().getFullYear();
625
+ const author = typeof docPackage.author === 'string' ? docPackage.author : docPackage.author.name;
626
+ if (author) {
627
+ footerParts.push(`<div class="footer-copyright">© ${year} ${author}</div>`);
628
+ }
629
+ }
630
+
631
+ return footerParts.join('\n');
632
+ }