From 0ec9e624e6172fb1c216d70ce148459a7ad385a0 Mon Sep 17 00:00:00 2001
Subject: [PATCH 0/5] Add "Pharos" AsciiDoc Parser

This proposal adds a custom extension to the Asciidoctor library that generates Nostr Knowledge Base events from an AsciiDoc document.  The `Asciidoctor` class is extended in the `Pharos` om an AsciiDoc document.  The `Pharos` class extends the `Asciidoctor` class with a custom tree parser that maps the relationships between the AsciiDoc sections and blocks.  Ind (kind 30040) and zettel (kind 30041) events are generated based on these relationships and on the content of the respective blocks and sections.  Asciidoctor generates unique IDs for each AsciiDoc block, and these unique IDs are used d tag identifiers.  Future developments will parse metadata such as author, edition, and publication date from the AsciiDoc document, and will add methods to support remapping the relationships between generated Nostr events.

git patch cover letter: Add "Pharos" AsciiDoc Parser


From a14e60652c618faad66ffac8a2a5d2bd4d69b3e1 Mon Sep 17 00:00:00 2001
From: buttercat1791 <mjjurkoic@gmail.com>
Date: Tue, 27 Aug 2024 09:29:23 -0500
Subject: [PATCH 1/5] First pass at editor page

---
 package.json                     |   1 +
 pnpm-lock.yaml                   | 477 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 src/lib/stores.ts                |   2 ++
 src/routes/new/edit/+page.svelte |  58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/routes/new/edit/+page.ts     |   0
 5 files changed, 534 insertions(+), 4 deletions(-)
 create mode 100644 src/routes/new/edit/+page.svelte
 create mode 100644 src/routes/new/edit/+page.ts

diff --git a/package.json b/package.json
index c75da1d..747591a 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
     "@sveltejs/vite-plugin-svelte": "^3.0.1",
     "@tailwindcss/forms": "^0.5.7",
     "@tailwindcss/typography": "^0.5.10",
+    "asciidoctor": "^3.0.4",
     "markdown-it": "^14.0.0",
     "markdown-it-plain-text": "^0.3.0",
     "marked": "^11.1.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1f05af9..fdd3d90 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -26,6 +26,9 @@ importers:
       '@tailwindcss/typography':
         specifier: ^0.5.10
         version: 0.5.10(tailwindcss@3.4.1)
+      asciidoctor:
+        specifier: ^3.0.4
+        version: 3.0.4(chokidar@3.5.3)
       markdown-it:
         specifier: ^14.0.0
         version: 14.0.0
@@ -92,7 +95,7 @@ importers:
         version: 4.2.9
       svelte-check:
         specifier: ^3.6.3
-        version: 3.6.3(postcss-load-config@5.0.2(jiti@1.21.0)(postcss@8.4.33))(postcss@8.4.33)(svelte@4.2.9)
+        version: 3.6.3(postcss-load-config@5.0.2(jiti@1.21.0)(postcss@8.4.33))(postcss@8.4.33)(pug@3.0.3)(svelte@4.2.9)
       tailwindcss:
         specifier: ^3.4.1
         version: 3.4.1
@@ -120,10 +123,42 @@ packages:
     resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==}
     engines: {node: '>=6.0.0'}

+  '@asciidoctor/cli@4.0.0':
+    resolution: {integrity: sha512-x2T9gW42921Zd90juEagtbViPZHNP2MWf0+6rJEkOzW7E9m3TGJtz+Guye9J0gwrpZsTMGCpfYMQy1We3X7osg==}
+    engines: {node: '>=16', npm: '>=8.0.0'}
+    hasBin: true
+    peerDependencies:
+      '@asciidoctor/core': '>=2 <4'
+
+  '@asciidoctor/core@3.0.4':
+    resolution: {integrity: sha512-41SDMi7iRRBViPe0L6VWFTe55bv6HEOJeRqMj5+E5wB1YPdUPuTucL4UAESPZM6OWmn4t/5qM5LusXomFUVwVQ==}
+    engines: {node: '>=16', npm: '>=8'}
+
+  '@asciidoctor/opal-runtime@3.0.1':
+    resolution: {integrity: sha512-iW7ACahOG0zZft4A/4CqDcc7JX+fWRNjV5tFAVkNCzwZD+EnFolPaUOPYt8jzadc0+Bgd80cQTtRMQnaaV1kkg==}
+    engines: {node: '>=16'}
+
+  '@babel/helper-string-parser@7.24.8':
+    resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-validator-identifier@7.24.7':
+    resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/parser@7.25.4':
+    resolution: {integrity: sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA==}
+    engines: {node: '>=6.0.0'}
+    hasBin: true
+
   '@babel/runtime@7.23.9':
     resolution: {integrity: sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==}
     engines: {node: '>=6.9.0'}

+  '@babel/types@7.25.4':
+    resolution: {integrity: sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ==}
+    engines: {node: '>=6.9.0'}
+
   '@esbuild/aix-ppc64@0.19.12':
     resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==}
     engines: {node: '>=12'}
@@ -547,11 +582,19 @@ packages:
   '@yr/monotone-cubic-spline@1.0.3':
     resolution: {integrity: sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==}

+  a-sync-waterfall@1.0.1:
+    resolution: {integrity: sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==}
+
   acorn-jsx@5.3.2:
     resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
     peerDependencies:
       acorn: ^6.0.0 || ^7.0.0 || ^8.0.0

+  acorn@7.4.1:
+    resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
+    engines: {node: '>=0.4.0'}
+    hasBin: true
+
   acorn@8.11.3:
     resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
     engines: {node: '>=0.4.0'}
@@ -599,6 +642,17 @@ packages:
     resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
     engines: {node: '>=8'}

+  asap@2.0.6:
+    resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
+
+  asciidoctor@3.0.4:
+    resolution: {integrity: sha512-hIc0Bx73wePxtic+vWBHOIgMfKSNiCmRz7BBfkyykXATrw20YGd5a3CozCHvqEPH+Wxp5qKD4aBsgtokez8nEA==}
+    engines: {node: '>=16', npm: '>=8'}
+    hasBin: true
+
+  assert-never@1.3.0:
+    resolution: {integrity: sha512-9Z3vxQ+berkL/JJo0dK+EY3Lp0s3NtSnP3VCLsh5HDcZPrh0M+KQRK5sWhUeyPPH+/RCxZqOxLMR+YC6vlviEQ==}
+
   assert@2.1.0:
     resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==}

@@ -606,6 +660,9 @@ packages:
     resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
     engines: {node: '>=4'}

+  async@3.2.6:
+    resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
+
   autoprefixer@10.4.17:
     resolution: {integrity: sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==}
     engines: {node: ^10 || ^12 || >=14}
@@ -620,6 +677,10 @@ packages:
   axobject-query@4.0.0:
     resolution: {integrity: sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==}

+  babel-walk@3.0.0-canary-5:
+    resolution: {integrity: sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==}
+    engines: {node: '>= 10.0.0'}
+
   balanced-match@1.0.2:
     resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}

@@ -668,10 +729,16 @@ packages:
     resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
     engines: {node: '>=10'}

+  character-parser@2.2.0:
+    resolution: {integrity: sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==}
+
   chokidar@3.5.3:
     resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
     engines: {node: '>= 8.10.0'}

+  cliui@7.0.4:
+    resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
+
   code-red@1.0.4:
     resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==}

@@ -686,6 +753,10 @@ packages:
     resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
     engines: {node: '>= 6'}

+  commander@5.1.0:
+    resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==}
+    engines: {node: '>= 6'}
+
   commander@9.5.0:
     resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==}
     engines: {node: ^12.20.0 || >=14}
@@ -693,6 +764,9 @@ packages:
   concat-map@0.0.1:
     resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}

+  constantinople@4.0.1:
+    resolution: {integrity: sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==}
+
   cookie@0.6.0:
     resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
     engines: {node: '>= 0.6'}
@@ -781,6 +855,9 @@ packages:
     resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
     engines: {node: '>=6.0.0'}

+  doctypes@1.1.0:
+    resolution: {integrity: sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==}
+
   dom-serializer@2.0.0:
     resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}

@@ -797,6 +874,11 @@ packages:
   eastasianwidth@0.2.0:
     resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}

+  ejs@3.1.10:
+    resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==}
+    engines: {node: '>=0.10.0'}
+    hasBin: true
+
   electron-to-chromium@1.4.657:
     resolution: {integrity: sha512-On2ymeleg6QbRuDk7wNgDdXtNqlJLM2w4Agx1D/RiTmItiL+a9oq5p7HUa2ZtkAtGBe/kil2dq/7rPfkbe0r5w==}

@@ -931,6 +1013,9 @@ packages:
     resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
     engines: {node: ^10.12.0 || >=12.0.0}

+  filelist@1.0.4:
+    resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
+
   fill-range@7.0.1:
     resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
     engines: {node: '>=8'}
@@ -987,6 +1072,10 @@ packages:
   function-bind@1.1.2:
     resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}

+  get-caller-file@2.0.5:
+    resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+    engines: {node: 6.* || 8.* || >= 10.*}
+
   get-intrinsic@1.2.4:
     resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
     engines: {node: '>= 0.4'}
@@ -1010,6 +1099,12 @@ packages:

   glob@7.2.3:
     resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+    deprecated: Glob versions prior to v9 are no longer supported
+
+  glob@8.1.0:
+    resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
+    engines: {node: '>=12'}
+    deprecated: Glob versions prior to v9 are no longer supported

   globals@13.24.0:
     resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
@@ -1034,6 +1129,11 @@ packages:
   graphemer@1.4.0:
     resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}

+  handlebars@4.7.8:
+    resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==}
+    engines: {node: '>=0.4.7'}
+    hasBin: true
+
   has-flag@4.0.0:
     resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
     engines: {node: '>=8'}
@@ -1081,6 +1181,7 @@ packages:

   inflight@1.0.6:
     resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+    deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.

   inherits@2.0.4:
     resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
@@ -1100,6 +1201,9 @@ packages:
   is-core-module@2.13.1:
     resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==}

+  is-expression@4.0.0:
+    resolution: {integrity: sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==}
+
   is-extglob@2.1.1:
     resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
     engines: {node: '>=0.10.0'}
@@ -1128,9 +1232,16 @@ packages:
     resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
     engines: {node: '>=8'}

+  is-promise@2.2.2:
+    resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==}
+
   is-reference@3.0.2:
     resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==}

+  is-regex@1.1.4:
+    resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==}
+    engines: {node: '>= 0.4'}
+
   is-stream@2.0.1:
     resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
     engines: {node: '>=8'}
@@ -1149,10 +1260,18 @@ packages:
     resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==}
     engines: {node: '>=14'}

+  jake@10.9.2:
+    resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==}
+    engines: {node: '>=10'}
+    hasBin: true
+
   jiti@1.21.0:
     resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==}
     hasBin: true

+  js-stringify@1.0.2:
+    resolution: {integrity: sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==}
+
   js-yaml@4.1.0:
     resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
     hasBin: true
@@ -1166,6 +1285,9 @@ packages:
   json-stable-stringify-without-jsonify@1.0.1:
     resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}

+  jstransformer@1.0.0:
+    resolution: {integrity: sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==}
+
   keyv@4.5.4:
     resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}

@@ -1273,6 +1395,10 @@ packages:
   minimatch@3.1.2:
     resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}

+  minimatch@5.1.6:
+    resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
+    engines: {node: '>=10'}
+
   minimatch@9.0.3:
     resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
     engines: {node: '>=16 || 14 >=14.17'}
@@ -1313,6 +1439,9 @@ packages:
   natural-compare@1.4.0:
     resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}

+  neo-async@2.6.2:
+    resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
+
   next-tick@1.1.0:
     resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}

@@ -1362,6 +1491,16 @@ packages:
     resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
     engines: {node: '>=8'}

+  nunjucks@3.2.4:
+    resolution: {integrity: sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==}
+    engines: {node: '>= 6.9.0'}
+    hasBin: true
+    peerDependencies:
+      chokidar: ^3.3.0
+    peerDependenciesMeta:
+      chokidar:
+        optional: true
+
   object-assign@4.1.1:
     resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
     engines: {node: '>=0.10.0'}
@@ -1542,10 +1681,49 @@ packages:
     engines: {node: '>=14'}
     hasBin: true

+  promise@7.3.1:
+    resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==}
+
   prompts@2.4.2:
     resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
     engines: {node: '>= 6'}

+  pug-attrs@3.0.0:
+    resolution: {integrity: sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==}
+
+  pug-code-gen@3.0.3:
+    resolution: {integrity: sha512-cYQg0JW0w32Ux+XTeZnBEeuWrAY7/HNE6TWnhiHGnnRYlCgyAUPoyh9KzCMa9WhcJlJ1AtQqpEYHc+vbCzA+Aw==}
+
+  pug-error@2.1.0:
+    resolution: {integrity: sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==}
+
+  pug-filters@4.0.0:
+    resolution: {integrity: sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==}
+
+  pug-lexer@5.0.1:
+    resolution: {integrity: sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==}
+
+  pug-linker@4.0.0:
+    resolution: {integrity: sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==}
+
+  pug-load@3.0.0:
+    resolution: {integrity: sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==}
+
+  pug-parser@6.0.0:
+    resolution: {integrity: sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==}
+
+  pug-runtime@3.0.1:
+    resolution: {integrity: sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==}
+
+  pug-strip-comments@2.0.0:
+    resolution: {integrity: sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==}
+
+  pug-walk@2.0.0:
+    resolution: {integrity: sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==}
+
+  pug@3.0.3:
+    resolution: {integrity: sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==}
+
   punycode.js@2.3.1:
     resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
     engines: {node: '>=6'}
@@ -1571,6 +1749,10 @@ packages:
   regenerator-runtime@0.14.1:
     resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}

+  require-directory@2.1.1:
+    resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
+    engines: {node: '>=0.10.0'}
+
   resolve-from@4.0.0:
     resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
     engines: {node: '>=4'}
@@ -1820,10 +2002,17 @@ packages:
   tiny-glob@0.2.9:
     resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==}

+  to-fast-properties@2.0.0:
+    resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
+    engines: {node: '>=4'}
+
   to-regex-range@5.0.1:
     resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
     engines: {node: '>=8.0'}

+  token-stream@1.0.0:
+    resolution: {integrity: sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==}
+
   totalist@3.0.1:
     resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
     engines: {node: '>=6'}
@@ -1874,6 +2063,15 @@ packages:
   uc.micro@2.0.0:
     resolution: {integrity: sha512-DffL94LsNOccVn4hyfRe5rdKa273swqeA5DJpMOeFmEn1wCDc7nAbbB0gXlgBCL7TNzeTv6G7XVWzan7iJtfig==}

+  uglify-js@3.19.2:
+    resolution: {integrity: sha512-S8KA6DDI47nQXJSi2ctQ629YzwOVs+bQML6DAtvy0wgNdpi+0ySpQK0g2pxBq2xfF2z3YCscu7NNA8nXT9PlIQ==}
+    engines: {node: '>=0.8.0'}
+    hasBin: true
+
+  unxhr@1.2.0:
+    resolution: {integrity: sha512-6cGpm8NFXPD9QbSNx0cD2giy7teZ6xOkCUH3U89WKVkL9N9rBrWjlCwhR94Re18ZlAop4MOc3WU1M3Hv/bgpIw==}
+    engines: {node: '>=8.11'}
+
   update-browserslist-db@1.0.13:
     resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==}
     hasBin: true
@@ -1933,6 +2131,10 @@ packages:
       vite:
         optional: true

+  void-elements@3.1.0:
+    resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
+    engines: {node: '>=0.10.0'}
+
   web-streams-polyfill@3.3.2:
     resolution: {integrity: sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ==}
     engines: {node: '>= 8'}
@@ -1953,6 +2155,13 @@ packages:
     engines: {node: '>= 8'}
     hasBin: true

+  with@7.0.2:
+    resolution: {integrity: sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==}
+    engines: {node: '>= 10.0.0'}
+
+  wordwrap@1.0.0:
+    resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
+
   wrap-ansi@7.0.0:
     resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
     engines: {node: '>=10'}
@@ -1964,6 +2173,10 @@ packages:
   wrappy@1.0.2:
     resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}

+  y18n@5.0.8:
+    resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
+    engines: {node: '>=10'}
+
   yaeti@0.0.6:
     resolution: {integrity: sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==}
     engines: {node: '>=0.10.32'}
@@ -1979,6 +2192,14 @@ packages:
     resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==}
     engines: {node: '>= 14'}

+  yargs-parser@21.1.1:
+    resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
+    engines: {node: '>=12'}
+
+  yargs@17.3.1:
+    resolution: {integrity: sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==}
+    engines: {node: '>=12'}
+
   yocto-queue@0.1.0:
     resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
     engines: {node: '>=10'}
@@ -1994,10 +2215,39 @@ snapshots:
       '@jridgewell/gen-mapping': 0.3.3
       '@jridgewell/trace-mapping': 0.3.22

+  '@asciidoctor/cli@4.0.0(@asciidoctor/core@3.0.4)':
+    dependencies:
+      '@asciidoctor/core': 3.0.4
+      yargs: 17.3.1
+
+  '@asciidoctor/core@3.0.4':
+    dependencies:
+      '@asciidoctor/opal-runtime': 3.0.1
+      unxhr: 1.2.0
+
+  '@asciidoctor/opal-runtime@3.0.1':
+    dependencies:
+      glob: 8.1.0
+      unxhr: 1.2.0
+
+  '@babel/helper-string-parser@7.24.8': {}
+
+  '@babel/helper-validator-identifier@7.24.7': {}
+
+  '@babel/parser@7.25.4':
+    dependencies:
+      '@babel/types': 7.25.4
+
   '@babel/runtime@7.23.9':
     dependencies:
       regenerator-runtime: 0.14.1

+  '@babel/types@7.25.4':
+    dependencies:
+      '@babel/helper-string-parser': 7.24.8
+      '@babel/helper-validator-identifier': 7.24.7
+      to-fast-properties: 2.0.0
+
   '@esbuild/aix-ppc64@0.19.12':
     optional: true

@@ -2369,10 +2619,14 @@ snapshots:

   '@yr/monotone-cubic-spline@1.0.3': {}

+  a-sync-waterfall@1.0.1: {}
+
   acorn-jsx@5.3.2(acorn@8.11.3):
     dependencies:
       acorn: 8.11.3

+  acorn@7.4.1: {}
+
   acorn@8.11.3: {}

   ajv@6.12.6:
@@ -2419,6 +2673,21 @@ snapshots:

   array-union@2.1.0: {}

+  asap@2.0.6: {}
+
+  asciidoctor@3.0.4(chokidar@3.5.3):
+    dependencies:
+      '@asciidoctor/cli': 4.0.0(@asciidoctor/core@3.0.4)
+      '@asciidoctor/core': 3.0.4
+      ejs: 3.1.10
+      handlebars: 4.7.8
+      nunjucks: 3.2.4(chokidar@3.5.3)
+      pug: 3.0.3
+    transitivePeerDependencies:
+      - chokidar
+
+  assert-never@1.3.0: {}
+
   assert@2.1.0:
     dependencies:
       call-bind: 1.0.6
@@ -2431,6 +2700,8 @@ snapshots:
     dependencies:
       tslib: 2.6.2

+  async@3.2.6: {}
+
   autoprefixer@10.4.17(postcss@8.4.33):
     dependencies:
       browserslist: 4.22.3
@@ -2447,6 +2718,10 @@ snapshots:
     dependencies:
       dequal: 2.0.3

+  babel-walk@3.0.0-canary-5:
+    dependencies:
+      '@babel/types': 7.25.4
+
   balanced-match@1.0.2: {}

   binary-extensions@2.2.0: {}
@@ -2495,6 +2770,10 @@ snapshots:
       ansi-styles: 4.3.0
       supports-color: 7.2.0

+  character-parser@2.2.0:
+    dependencies:
+      is-regex: 1.1.4
+
   chokidar@3.5.3:
     dependencies:
       anymatch: 3.1.3
@@ -2507,6 +2786,12 @@ snapshots:
     optionalDependencies:
       fsevents: 2.3.3

+  cliui@7.0.4:
+    dependencies:
+      string-width: 4.2.3
+      strip-ansi: 6.0.1
+      wrap-ansi: 7.0.0
+
   code-red@1.0.4:
     dependencies:
       '@jridgewell/sourcemap-codec': 1.4.15
@@ -2523,10 +2808,17 @@ snapshots:

   commander@4.1.1: {}

+  commander@5.1.0: {}
+
   commander@9.5.0: {}

   concat-map@0.0.1: {}

+  constantinople@4.0.1:
+    dependencies:
+      '@babel/parser': 7.25.4
+      '@babel/types': 7.25.4
+
   cookie@0.6.0: {}

   cross-spawn@7.0.3:
@@ -2598,6 +2890,8 @@ snapshots:
     dependencies:
       esutils: 2.0.3

+  doctypes@1.1.0: {}
+
   dom-serializer@2.0.0:
     dependencies:
       domelementtype: 2.3.0
@@ -2618,6 +2912,10 @@ snapshots:

   eastasianwidth@0.2.0: {}

+  ejs@3.1.10:
+    dependencies:
+      jake: 10.9.2
+
   electron-to-chromium@1.4.657: {}

   emoji-regex@8.0.0: {}
@@ -2821,6 +3119,10 @@ snapshots:
     dependencies:
       flat-cache: 3.2.0

+  filelist@1.0.4:
+    dependencies:
+      minimatch: 5.1.6
+
   fill-range@7.0.1:
     dependencies:
       to-regex-range: 5.0.1
@@ -2879,6 +3181,8 @@ snapshots:

   function-bind@1.1.2: {}

+  get-caller-file@2.0.5: {}
+
   get-intrinsic@1.2.4:
     dependencies:
       es-errors: 1.3.0
@@ -2914,6 +3218,14 @@ snapshots:
       once: 1.4.0
       path-is-absolute: 1.0.1

+  glob@8.1.0:
+    dependencies:
+      fs.realpath: 1.0.0
+      inflight: 1.0.6
+      inherits: 2.0.4
+      minimatch: 5.1.6
+      once: 1.4.0
+
   globals@13.24.0:
     dependencies:
       type-fest: 0.20.2
@@ -2939,6 +3251,15 @@ snapshots:

   graphemer@1.4.0: {}

+  handlebars@4.7.8:
+    dependencies:
+      minimist: 1.2.8
+      neo-async: 2.6.2
+      source-map: 0.6.1
+      wordwrap: 1.0.0
+    optionalDependencies:
+      uglify-js: 3.19.2
+
   has-flag@4.0.0: {}

   has-property-descriptors@1.0.1:
@@ -2999,6 +3320,11 @@ snapshots:
     dependencies:
       hasown: 2.0.0

+  is-expression@4.0.0:
+    dependencies:
+      acorn: 7.4.1
+      object-assign: 4.1.1
+
   is-extglob@2.1.1: {}

   is-fullwidth-code-point@3.0.0: {}
@@ -3020,10 +3346,17 @@ snapshots:

   is-path-inside@3.0.3: {}

+  is-promise@2.2.2: {}
+
   is-reference@3.0.2:
     dependencies:
       '@types/estree': 1.0.5

+  is-regex@1.1.4:
+    dependencies:
+      call-bind: 1.0.6
+      has-tostringtag: 1.0.2
+
   is-stream@2.0.1: {}

   is-typed-array@1.1.13:
@@ -3040,8 +3373,17 @@ snapshots:
     optionalDependencies:
       '@pkgjs/parseargs': 0.11.0

+  jake@10.9.2:
+    dependencies:
+      async: 3.2.6
+      chalk: 4.1.2
+      filelist: 1.0.4
+      minimatch: 3.1.2
+
   jiti@1.21.0: {}

+  js-stringify@1.0.2: {}
+
   js-yaml@4.1.0:
     dependencies:
       argparse: 2.0.1
@@ -3052,6 +3394,11 @@ snapshots:

   json-stable-stringify-without-jsonify@1.0.1: {}

+  jstransformer@1.0.0:
+    dependencies:
+      is-promise: 2.2.2
+      promise: 7.3.1
+
   keyv@4.5.4:
     dependencies:
       json-buffer: 3.0.1
@@ -3139,6 +3486,10 @@ snapshots:
     dependencies:
       brace-expansion: 1.1.11

+  minimatch@5.1.6:
+    dependencies:
+      brace-expansion: 2.0.1
+
   minimatch@9.0.3:
     dependencies:
       brace-expansion: 2.0.1
@@ -3169,6 +3520,8 @@ snapshots:

   natural-compare@1.4.0: {}

+  neo-async@2.6.2: {}
+
   next-tick@1.1.0: {}

   node-domexception@1.0.0: {}
@@ -3218,6 +3571,14 @@ snapshots:
     dependencies:
       path-key: 3.1.1

+  nunjucks@3.2.4(chokidar@3.5.3):
+    dependencies:
+      a-sync-waterfall: 1.0.1
+      asap: 2.0.6
+      commander: 5.1.0
+    optionalDependencies:
+      chokidar: 3.5.3
+
   object-assign@4.1.1: {}

   object-hash@3.0.0: {}
@@ -3368,11 +3729,82 @@ snapshots:

   prettier@3.2.5: {}

+  promise@7.3.1:
+    dependencies:
+      asap: 2.0.6
+
   prompts@2.4.2:
     dependencies:
       kleur: 3.0.3
       sisteransi: 1.0.5

+  pug-attrs@3.0.0:
+    dependencies:
+      constantinople: 4.0.1
+      js-stringify: 1.0.2
+      pug-runtime: 3.0.1
+
+  pug-code-gen@3.0.3:
+    dependencies:
+      constantinople: 4.0.1
+      doctypes: 1.1.0
+      js-stringify: 1.0.2
+      pug-attrs: 3.0.0
+      pug-error: 2.1.0
+      pug-runtime: 3.0.1
+      void-elements: 3.1.0
+      with: 7.0.2
+
+  pug-error@2.1.0: {}
+
+  pug-filters@4.0.0:
+    dependencies:
+      constantinople: 4.0.1
+      jstransformer: 1.0.0
+      pug-error: 2.1.0
+      pug-walk: 2.0.0
+      resolve: 1.22.8
+
+  pug-lexer@5.0.1:
+    dependencies:
+      character-parser: 2.2.0
+      is-expression: 4.0.0
+      pug-error: 2.1.0
+
+  pug-linker@4.0.0:
+    dependencies:
+      pug-error: 2.1.0
+      pug-walk: 2.0.0
+
+  pug-load@3.0.0:
+    dependencies:
+      object-assign: 4.1.1
+      pug-walk: 2.0.0
+
+  pug-parser@6.0.0:
+    dependencies:
+      pug-error: 2.1.0
+      token-stream: 1.0.0
+
+  pug-runtime@3.0.1: {}
+
+  pug-strip-comments@2.0.0:
+    dependencies:
+      pug-error: 2.1.0
+
+  pug-walk@2.0.0: {}
+
+  pug@3.0.3:
+    dependencies:
+      pug-code-gen: 3.0.3
+      pug-filters: 4.0.0
+      pug-lexer: 5.0.1
+      pug-linker: 4.0.0
+      pug-load: 3.0.0
+      pug-parser: 6.0.0
+      pug-runtime: 3.0.1
+      pug-strip-comments: 2.0.0
+
   punycode.js@2.3.1: {}

   punycode@2.3.1: {}
@@ -3397,6 +3829,8 @@ snapshots:

   regenerator-runtime@0.14.1: {}

+  require-directory@2.1.1: {}
+
   resolve-from@4.0.0: {}

   resolve@1.22.8:
@@ -3564,7 +3998,7 @@ snapshots:
     transitivePeerDependencies:
       - supports-color

-  svelte-check@3.6.3(postcss-load-config@5.0.2(jiti@1.21.0)(postcss@8.4.33))(postcss@8.4.33)(svelte@4.2.9):
+  svelte-check@3.6.3(postcss-load-config@5.0.2(jiti@1.21.0)(postcss@8.4.33))(postcss@8.4.33)(pug@3.0.3)(svelte@4.2.9):
     dependencies:
       '@jridgewell/trace-mapping': 0.3.22
       chokidar: 3.5.3
@@ -3573,7 +4007,7 @@ snapshots:
       picocolors: 1.0.0
       sade: 1.8.1
       svelte: 4.2.9
-      svelte-preprocess: 5.1.3(postcss-load-config@5.0.2(jiti@1.21.0)(postcss@8.4.33))(postcss@8.4.33)(svelte@4.2.9)(typescript@5.3.3)
+      svelte-preprocess: 5.1.3(postcss-load-config@5.0.2(jiti@1.21.0)(postcss@8.4.33))(postcss@8.4.33)(pug@3.0.3)(svelte@4.2.9)(typescript@5.3.3)
       typescript: 5.3.3
     transitivePeerDependencies:
       - '@babel/core'
@@ -3600,7 +4034,7 @@ snapshots:
     dependencies:
       svelte: 4.2.9

-  svelte-preprocess@5.1.3(postcss-load-config@5.0.2(jiti@1.21.0)(postcss@8.4.33))(postcss@8.4.33)(svelte@4.2.9)(typescript@5.3.3):
+  svelte-preprocess@5.1.3(postcss-load-config@5.0.2(jiti@1.21.0)(postcss@8.4.33))(postcss@8.4.33)(pug@3.0.3)(svelte@4.2.9)(typescript@5.3.3):
     dependencies:
       '@types/pug': 2.0.10
       detect-indent: 6.1.0
@@ -3611,6 +4045,7 @@ snapshots:
     optionalDependencies:
       postcss: 8.4.33
       postcss-load-config: 5.0.2(jiti@1.21.0)(postcss@8.4.33)
+      pug: 3.0.3
       typescript: 5.3.3

   svelte@4.2.9:
@@ -3707,10 +4142,14 @@ snapshots:
       globalyzer: 0.1.0
       globrex: 0.1.2

+  to-fast-properties@2.0.0: {}
+
   to-regex-range@5.0.1:
     dependencies:
       is-number: 7.0.0

+  token-stream@1.0.0: {}
+
   totalist@3.0.1: {}

   ts-api-utils@1.2.1(typescript@5.3.3):
@@ -3745,6 +4184,11 @@ snapshots:

   uc.micro@2.0.0: {}

+  uglify-js@3.19.2:
+    optional: true
+
+  unxhr@1.2.0: {}
+
   update-browserslist-db@1.0.13(browserslist@4.22.3):
     dependencies:
       browserslist: 4.22.3
@@ -3783,6 +4227,8 @@ snapshots:
     optionalDependencies:
       vite: 5.0.12

+  void-elements@3.1.0: {}
+
   web-streams-polyfill@3.3.2: {}

   websocket-polyfill@0.0.3:
@@ -3815,6 +4261,15 @@ snapshots:
     dependencies:
       isexe: 2.0.0

+  with@7.0.2:
+    dependencies:
+      '@babel/parser': 7.25.4
+      '@babel/types': 7.25.4
+      assert-never: 1.3.0
+      babel-walk: 3.0.0-canary-5
+
+  wordwrap@1.0.0: {}
+
   wrap-ansi@7.0.0:
     dependencies:
       ansi-styles: 4.3.0
@@ -3829,6 +4284,8 @@ snapshots:

   wrappy@1.0.2: {}

+  y18n@5.0.8: {}
+
   yaeti@0.0.6: {}

   yallist@4.0.0: {}
@@ -3837,4 +4294,16 @@ snapshots:

   yaml@2.3.4: {}

+  yargs-parser@21.1.1: {}
+
+  yargs@17.3.1:
+    dependencies:
+      cliui: 7.0.4
+      escalade: 3.1.2
+      get-caller-file: 2.0.5
+      require-directory: 2.1.1
+      string-width: 4.2.3
+      y18n: 5.0.8
+      yargs-parser: 21.1.1
+
   yocto-queue@0.1.0: {}
diff --git a/src/lib/stores.ts b/src/lib/stores.ts
index 19a4857..f072e5d 100644
--- a/src/lib/stores.ts
+++ b/src/lib/stores.ts
@@ -6,3 +6,5 @@ export let idList = writable<string[]>([]);
 export let alexandriaKinds = readable<number[]>([30040, 30041]);

 export let feedType = writable<FeedType>(FeedType.Relays);
+
+export let editorText = writable<string>('');
diff --git a/src/routes/new/edit/+page.svelte b/src/routes/new/edit/+page.svelte
new file mode 100644
index 0000000..b2b5e28
--- /dev/null
+++ b/src/routes/new/edit/+page.svelte
@@ -0,0 +1,58 @@
+<script lang="ts">
+  import { Input, Label, Textarea, Toolbar, ToolbarButton } from "flowbite-svelte";
+  import { CodeOutline, EyeSolid } from "flowbite-svelte-icons";
+  import { editorText } from "$lib/stores";
+  import Asciidoctor from 'asciidoctor';
+
+  const asciidoctor = Asciidoctor();
+
+  let isEditing: boolean = true;
+  let richEditorContent: string;
+
+  const showPreview = () => {
+    isEditing = false;
+    richEditorContent = asciidoctor.convert($editorText) as string;
+  };
+
+  const hidePreview = () => {
+    isEditing = false;
+  };
+</script>
+
+<main>
+  <form>
+    <div class='flex flex-col space-y-4'>
+      <div>
+        <Label for='article-title' class='mb-2'>Article Title</Label>
+        <Input type='text' id='article-title' placeholder='Title' required />
+      </div>
+      <div>
+        <Label for='article-author' class='mb-2'>Author Name</Label>
+        <Input type='text' id='article-author' placeholder='Author' required />
+      </div>
+      {#if isEditing}
+        <Textarea
+          id='article-content'
+          rows=8
+          placeholder='Write AsciiDoc content'
+          bind:value={$editorText}
+        >
+          <Toolbar slot='header' embedded>
+            <ToolbarButton name='Preview' on:click={showPreview}>
+              <EyeSolid class='w-6 h-6' />
+            </ToolbarButton>
+          </Toolbar>
+        </Textarea>
+      {:else}
+        <div>
+          <Toolbar>
+            <ToolbarButton name='Edit' on:click={hidePreview}>
+              <CodeOutline class='w-6 h-6' />
+            </ToolbarButton>
+          </Toolbar>
+          {@html richEditorContent}
+        </div>
+      {/if}
+    </div>
+  </form>
+</main>
diff --git a/src/routes/new/edit/+page.ts b/src/routes/new/edit/+page.ts
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/routes/new/edit/+page.ts
--
libgit2 1.7.2


From 7f84d4130bca98c8dda00d07de91effd9f426237 Mon Sep 17 00:00:00 2001
From: buttercat1791 <mjjurkoic@gmail.com>
Date: Fri, 6 Sep 2024 22:34:06 -0500
Subject: [PATCH 3/5] Extend Asciidoctor for custom parsing

---
 package.json      |  1 +
 pnpm-lock.yaml    | 54 +++++++++++++++++++++++++++++++++++-------------------
 src/lib/parser.ts | 31 +++++++++++++++++++++++++++++++
 3 files changed, 67 insertions(+), 19 deletions(-)
 create mode 100644 src/lib/parser.ts

diff --git a/package.json b/package.json
index 0ee7da7..55ea480 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
     "@sveltejs/adapter-auto": "^3.1.1",
     "@sveltejs/kit": "^2.4.3",
     "@types/markdown-it": "^13.0.7",
+    "@types/node": "^22.5.4",
     "@types/showdown": "^2.0.6",
     "autoprefixer": "^10.4.17",
     "eslint-plugin-svelte": "^2.35.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 686c501..72c7433 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -19,7 +19,7 @@ importers:
         version: 2.11.8
       '@sveltejs/vite-plugin-svelte':
         specifier: ^3.0.1
-        version: 3.1.2(svelte@4.2.19)(vite@5.4.2)
+        version: 3.1.2(svelte@4.2.19)(vite@5.4.2(@types/node@22.5.4))
       '@tailwindcss/forms':
         specifier: ^0.5.7
         version: 0.5.8(tailwindcss@3.4.10)
@@ -53,13 +53,16 @@ importers:
     devDependencies:
       '@sveltejs/adapter-auto':
         specifier: ^3.1.1
-        version: 3.2.4(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.2))(svelte@4.2.19)(vite@5.4.2))
+        version: 3.2.4(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.2(@types/node@22.5.4)))(svelte@4.2.19)(vite@5.4.2(@types/node@22.5.4)))
       '@sveltejs/kit':
         specifier: ^2.4.3
-        version: 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.2))(svelte@4.2.19)(vite@5.4.2)
+        version: 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.2(@types/node@22.5.4)))(svelte@4.2.19)(vite@5.4.2(@types/node@22.5.4))
       '@types/markdown-it':
         specifier: ^13.0.7
         version: 13.0.9
+      '@types/node':
+        specifier: ^22.5.4
+        version: 22.5.4
       '@types/showdown':
         specifier: ^2.0.6
         version: 2.0.6
@@ -107,7 +110,7 @@ importers:
         version: 5.5.4
       vite:
         specifier: ^5.0.12
-        version: 5.4.2
+        version: 5.4.2(@types/node@22.5.4)

 packages:

@@ -578,6 +581,9 @@ packages:
   '@types/mdurl@1.0.5':
     resolution: {integrity: sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==}

+  '@types/node@22.5.4':
+    resolution: {integrity: sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==}
+
   '@types/pug@2.0.10':
     resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==}

@@ -2073,6 +2079,9 @@ packages:
     engines: {node: '>=0.8.0'}
     hasBin: true

+  undici-types@6.19.8:
+    resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
+
   unxhr@1.2.0:
     resolution: {integrity: sha512-6cGpm8NFXPD9QbSNx0cD2giy7teZ6xOkCUH3U89WKVkL9N9rBrWjlCwhR94Re18ZlAop4MOc3WU1M3Hv/bgpIw==}
     engines: {node: '>=8.11'}
@@ -2542,14 +2551,14 @@ snapshots:
       '@noble/hashes': 1.3.1
       '@scure/base': 1.1.1

-  '@sveltejs/adapter-auto@3.2.4(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.2))(svelte@4.2.19)(vite@5.4.2))':
+  '@sveltejs/adapter-auto@3.2.4(@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.2(@types/node@22.5.4)))(svelte@4.2.19)(vite@5.4.2(@types/node@22.5.4)))':
     dependencies:
-      '@sveltejs/kit': 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.2))(svelte@4.2.19)(vite@5.4.2)
+      '@sveltejs/kit': 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.2(@types/node@22.5.4)))(svelte@4.2.19)(vite@5.4.2(@types/node@22.5.4))
       import-meta-resolve: 4.1.0

-  '@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.2))(svelte@4.2.19)(vite@5.4.2)':
+  '@sveltejs/kit@2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.2(@types/node@22.5.4)))(svelte@4.2.19)(vite@5.4.2(@types/node@22.5.4))':
     dependencies:
-      '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@4.2.19)(vite@5.4.2)
+      '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@4.2.19)(vite@5.4.2(@types/node@22.5.4))
       '@types/cookie': 0.6.0
       cookie: 0.6.0
       devalue: 5.0.0
@@ -2563,28 +2572,28 @@ snapshots:
       sirv: 2.0.4
       svelte: 4.2.19
       tiny-glob: 0.2.9
-      vite: 5.4.2
+      vite: 5.4.2(@types/node@22.5.4)

-  '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.2))(svelte@4.2.19)(vite@5.4.2)':
+  '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.2(@types/node@22.5.4)))(svelte@4.2.19)(vite@5.4.2(@types/node@22.5.4))':
     dependencies:
-      '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@4.2.19)(vite@5.4.2)
+      '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@4.2.19)(vite@5.4.2(@types/node@22.5.4))
       debug: 4.3.6
       svelte: 4.2.19
-      vite: 5.4.2
+      vite: 5.4.2(@types/node@22.5.4)
     transitivePeerDependencies:
       - supports-color

-  '@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.2)':
+  '@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.2(@types/node@22.5.4))':
     dependencies:
-      '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.2))(svelte@4.2.19)(vite@5.4.2)
+      '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.2(@types/node@22.5.4)))(svelte@4.2.19)(vite@5.4.2(@types/node@22.5.4))
       debug: 4.3.6
       deepmerge: 4.3.1
       kleur: 4.1.5
       magic-string: 0.30.11
       svelte: 4.2.19
       svelte-hmr: 0.16.0(svelte@4.2.19)
-      vite: 5.4.2
-      vitefu: 0.2.5(vite@5.4.2)
+      vite: 5.4.2(@types/node@22.5.4)
+      vitefu: 0.2.5(vite@5.4.2(@types/node@22.5.4))
     transitivePeerDependencies:
       - supports-color

@@ -2614,6 +2623,10 @@ snapshots:

   '@types/mdurl@1.0.5': {}

+  '@types/node@22.5.4':
+    dependencies:
+      undici-types: 6.19.8
+
   '@types/pug@2.0.10': {}

   '@types/resolve@1.20.2': {}
@@ -4176,6 +4189,8 @@ snapshots:
   uglify-js@3.19.3:
     optional: true

+  undici-types@6.19.8: {}
+
   unxhr@1.2.0: {}

   update-browserslist-db@1.1.0(browserslist@4.23.3):
@@ -4196,17 +4211,18 @@ snapshots:

   util-deprecate@1.0.2: {}

-  vite@5.4.2:
+  vite@5.4.2(@types/node@22.5.4):
     dependencies:
       esbuild: 0.21.5
       postcss: 8.4.44
       rollup: 4.21.2
     optionalDependencies:
+      '@types/node': 22.5.4
       fsevents: 2.3.3

-  vitefu@0.2.5(vite@5.4.2):
+  vitefu@0.2.5(vite@5.4.2(@types/node@22.5.4)):
     optionalDependencies:
-      vite: 5.4.2
+      vite: 5.4.2(@types/node@22.5.4)

   void-elements@3.1.0: {}

diff --git a/src/lib/parser.ts b/src/lib/parser.ts
new file mode 100644
index 0000000..57ae5e6
--- /dev/null
+++ b/src/lib/parser.ts
@@ -0,0 +1,31 @@
+import { Asciidoctor, Document, Extensions, type ProcessorOptions } from 'asciidoctor';
+
+class Pharos extends Asciidoctor {
+  private html?: string | Document;
+
+  constructor() {
+    super();
+    const pharos = this;
+
+    this.Extensions.register(function () {
+      const registry = this;
+      registry.treeProcessor(function () {
+        const dsl = this;
+        dsl.process(function (document) {
+          const treeProcessor = this;
+          pharos.pharosTreeProcessor(treeProcessor, document);
+        });
+      })
+    });
+  }
+
+  parse(content: string, options?: ProcessorOptions | undefined): void {
+    this.html = this.convert(content, options) as string | Document | undefined;
+    this.Block
+  }
+
+  private pharosTreeProcessor(treeProcessor: Extensions.TreeProcessor, document: Document) {
+    // Recursively map sections to their note kinds down to five levels of depth.
+    document.getSections();
+  }
+}
\ No newline at end of file
--
libgit2 1.7.2

git patch: Extend Asciidoctor for custom parsing


From 0ec9e624e6172fb1c216d70ce148459a7ad385a0 Mon Sep 17 00:00:00 2001
From: buttercat1791 <mjjurkoic@gmail.com>
Date: Tue, 10 Sep 2024 08:56:52 -0500
Subject: [PATCH 5/5] Generate Nostr events from parsed AsciiDoc block tree

---
 src/lib/parser.ts | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 169 insertions(+), 9 deletions(-)

diff --git a/src/lib/parser.ts b/src/lib/parser.ts
index 60a4b29..5e42a64 100644
--- a/src/lib/parser.ts
+++ b/src/lib/parser.ts
@@ -1,9 +1,29 @@
-import { AbstractNode, Asciidoctor, Block, Document, Extensions, Section, type ProcessorOptions } from 'asciidoctor';
+import NDK, { NDKEvent } from '@nostr-dev-kit/ndk';
+import {
+  AbstractBlock,
+  AbstractNode,
+  Asciidoctor,
+  Block,
+  Document,
+  Extensions,
+  Section,
+  type ProcessorOptions
+} from 'asciidoctor';

 export default class Pharos extends Asciidoctor {
+  private ndk: NDK;
+
+  /**
+   * The HTML content of the converted document.
+   */
   private html?: string | Document;

   /**
+   * The ID of the root node in the document.
+   */
+  private rootId?: string;
+
+  /**
    * A map of node IDs to the nodes themselves.
    */
   private nodes: Map<string, AbstractNode> = new Map<string, AbstractNode>();
@@ -18,11 +38,18 @@ export default class Pharos extends Asciidoctor {
    */
   private indices: Map<string, Set<string>> = new Map<string, Set<string>>();

-  constructor() {
+  /**
+   * A map of node IDs to the Nostr event IDs of the events they generate.
+   */
+  private eventIds: Map<string, string> = new Map<string, string>();
+
+  constructor(ndk: NDK) {
     super();
-    const pharos = this;

-    pharos.Extensions.register(function () {
+    this.ndk = ndk;
+
+    const pharos = this;
+    this.Extensions.register(function () {
       const registry = this;
       registry.treeProcessor(function () {
         const dsl = this;
@@ -36,9 +63,19 @@ export default class Pharos extends Asciidoctor {

   parse(content: string, options?: ProcessorOptions | undefined): void {
     this.html = this.convert(content, options) as string | Document | undefined;
-    this.Block
   }

+  getEvents(pubkey: string): NDKEvent[] {
+    const stack = this.stackEventNodes(this.rootId!);
+    return this.generateEvents(stack, pubkey);
+  }
+
+  getHtml(): string {
+    return this.html?.toString() || '';
+  }
+
+  // #region Tree Processor Extensions
+
   /**
    * Walks the Asciidoctor Abstract Syntax Tree (AST) and performs the following mappings:
    * - Each node ID is mapped to the node itself.
@@ -46,10 +83,10 @@ export default class Pharos extends Asciidoctor {
    * - Each ID of a node containing children is mapped to the set of IDs of its children.
    */
   private treeProcessor(treeProcessor: Extensions.TreeProcessor, document: Document) {
-    const id = document.getId();
-    this.nodes.set(id, document);
-    this.kinds.set(id, 30040);
-    this.indices.set(id, new Set<string>());
+    this.rootId = document.getId();
+    this.nodes.set(this.rootId, document);
+    this.kinds.set(this.rootId, 30040);
+    this.indices.set(this.rootId, new Set<string>());

     /** FIFO queue (uses `Array.push()` and `Array.shift()`). */
     const queue: AbstractNode[] = document.getBlocks();
@@ -128,4 +165,127 @@ export default class Pharos extends Asciidoctor {
     // Add the block to its parent index.
     this.indices.get(parentId)?.add(id);
   }
+
+  //#endregion
+
+  // #region NDKEvent Generation
+
+  /**
+   * Generates a stack of node IDs such that processing them in LIFO order will generate any events
+   * used by an index before generating that index itself.
+   * @param rootNodeId The ID of the node from which to start processing.
+   * @returns An array of node IDs in the order they should be processed to generate events.
+   */
+  private stackEventNodes(rootNodeId: string): string[] {
+    const traversalStack: string[] = [this.rootId!];
+    const eventStack: string[] = [];
+
+    while (traversalStack.length > 0) {
+      const parentId = traversalStack.pop()!;
+      eventStack.push(parentId);
+
+      if (!this.indices.has(parentId)) {
+        continue;
+      }
+
+      const childIds = Array.from(this.indices.get(parentId)!);
+      traversalStack.push(...childIds);
+    }
+
+    return eventStack;
+  }
+
+  /**
+   * Generates Nostr events for each node in the given stack.
+   * @param nodeIdStack An array of node IDs ordered such that processing them in LIFO order will
+   * produce any child event before it is required by a parent index event.
+   * @param pubkey The public key (not encoded in npub form) of the user generating the events.
+   * @returns An array of Nostr events.
+   */
+  private generateEvents(nodeIdStack: string[], pubkey: string): NDKEvent[] {
+    const events: NDKEvent[] = [];
+
+    while (nodeIdStack.length > 0) {
+      const nodeId = nodeIdStack.pop();
+      let event: NDKEvent;
+
+      switch (this.kinds.get(nodeId!)) {
+      case 30040:
+        events.push(this.generateIndexEvent(nodeId!, pubkey));
+        break;
+
+      case 30041:
+      default:
+        // Kind 30041 is currently the default contentful kind.
+        events.push(this.generateZettelEvent(nodeId!, pubkey));
+        break;
+      }
+    }
+
+    return events;
+  }
+
+  /**
+   * Generates a kind 30040 index event for the node with the given ID.
+   * @param nodeId The ID of the AsciiDoc document node from which to generate an index event.  The
+   * node ID will be used as the event's unique d tag identifier.
+   * @param pubkey The public key (not encoded in npub form) of the user generating the events.
+   * @returns An unsigned NDKEvent with the requisite tags, including e tags pointing to each of its
+   * children, and dated to the present moment.
+   */
+  private generateIndexEvent(nodeId: string, pubkey: string): NDKEvent {
+    const title = (this.nodes.get(nodeId)! as AbstractBlock).getTitle();
+    const childTags = Array.from(this.indices.get(nodeId)!)
+      .map(id => ['#e', this.eventIds.get(id)!]);
+
+    const event = new NDKEvent(this.ndk);
+    event.kind = 30040;
+    event.content = '';
+    event.tags = [
+      ['title', title!],
+      ['#d', nodeId],
+      ...childTags
+    ];
+    event.created_at = Date.now();
+    event.pubkey = pubkey;
+
+    // Event ID generation must be the last step.
+    const eventId = event.getEventHash();
+    this.eventIds.set(nodeId, eventId);
+    event.id = eventId;
+
+    return event;
+  }
+
+  /**
+   * Generates a kind 30041 zettel event for the node with the given ID.
+   * @param nodeId The ID of the AsciiDoc document node from which to generate an index event.  The
+   * node ID will be used as the event's unique d tag identifier.
+   * @param pubkey The public key (not encoded in npub form) of the user generating the events.
+   * @returns An unsigned NDKEvent containing the content of the zettel, the requisite tags, and
+   * dated to the present moment.
+   */
+  private generateZettelEvent(nodeId: string, pubkey: string): NDKEvent {
+    const title = (this.nodes.get(nodeId)! as Block).getTitle();
+    const content = (this.nodes.get(nodeId)! as Block).getSource();  // AsciiDoc source content.
+
+    const event = new NDKEvent(this.ndk);
+    event.kind = 30041;
+    event.content = content!;
+    event.tags = [
+      ['title', title!],
+      ['#d', nodeId]
+    ];
+    event.created_at = Date.now();
+    event.pubkey = pubkey;
+
+    // Event ID generation must be the last step.
+    const eventId = event.getEventHash();
+    this.eventIds.set(nodeId, eventId);
+    event.id = eventId;
+
+    return event;
+  }
+
+  // #endregion
 }
\ No newline at end of file
--
libgit2 1.7.2

git patch: Generate Nostr events from parsed AsciiDoc block tree


From f4be33f60755633e3a06a51e69473a972d80eac1 Mon Sep 17 00:00:00 2001
From: buttercat1791 <mjjurkoic@gmail.com>
Date: Sun, 8 Sep 2024 12:13:21 -0500
Subject: [PATCH 4/5] Write a custom Asciidoctor tree processor

---
 src/lib/parser.ts | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 107 insertions(+), 7 deletions(-)

diff --git a/src/lib/parser.ts b/src/lib/parser.ts
index 57ae5e6..60a4b29 100644
--- a/src/lib/parser.ts
+++ b/src/lib/parser.ts
@@ -1,19 +1,34 @@
-import { Asciidoctor, Document, Extensions, type ProcessorOptions } from 'asciidoctor';
+import { AbstractNode, Asciidoctor, Block, Document, Extensions, Section, type ProcessorOptions } from 'asciidoctor';

-class Pharos extends Asciidoctor {
+export default class Pharos extends Asciidoctor {
   private html?: string | Document;

+  /**
+   * A map of node IDs to the nodes themselves.
+   */
+  private nodes: Map<string, AbstractNode> = new Map<string, AbstractNode>();
+
+  /**
+   * A map of node IDs to the integer event kind that will be used to represent the node.
+   */
+  private kinds: Map<string, number> = new Map<string, number>();
+
+  /**
+   * A map of index IDs to the IDs of the nodes they reference.
+   */
+  private indices: Map<string, Set<string>> = new Map<string, Set<string>>();
+
   constructor() {
     super();
     const pharos = this;

-    this.Extensions.register(function () {
+    pharos.Extensions.register(function () {
       const registry = this;
       registry.treeProcessor(function () {
         const dsl = this;
         dsl.process(function (document) {
           const treeProcessor = this;
-          pharos.pharosTreeProcessor(treeProcessor, document);
+          pharos.treeProcessor(treeProcessor, document);
         });
       })
     });
@@ -24,8 +39,93 @@ class Pharos extends Asciidoctor {
     this.Block
   }

-  private pharosTreeProcessor(treeProcessor: Extensions.TreeProcessor, document: Document) {
-    // Recursively map sections to their note kinds down to five levels of depth.
-    document.getSections();
+  /**
+   * Walks the Asciidoctor Abstract Syntax Tree (AST) and performs the following mappings:
+   * - Each node ID is mapped to the node itself.
+   * - Each node ID is mapped to an integer event kind that will be used to represent the node.
+   * - Each ID of a node containing children is mapped to the set of IDs of its children.
+   */
+  private treeProcessor(treeProcessor: Extensions.TreeProcessor, document: Document) {
+    const id = document.getId();
+    this.nodes.set(id, document);
+    this.kinds.set(id, 30040);
+    this.indices.set(id, new Set<string>());
+
+    /** FIFO queue (uses `Array.push()` and `Array.shift()`). */
+    const queue: AbstractNode[] = document.getBlocks();
+
+    while (queue.length > 0) {
+      const block = queue.shift();
+      if (!block) {
+        continue;
+      }
+
+      if (block instanceof Section) {
+        const children = this.processSection(block);
+        queue.push(...children);
+      } else {
+        this.processBlock(block as Block);
+      }
+    }
+  }
+
+  /**
+   * Processes a section of the Asciidoctor AST.
+   * @param section The section to process.
+   * @returns An array of the section's child nodes.  If there are no child nodes, returns an empty
+   * array.
+   * @remarks Sections are mapped as kind 30040 indices by default.
+   */
+  private processSection(section: Section): AbstractNode[] {
+    const id = section.getId();
+
+    // Prevent duplicates.
+    if (this.nodes.has(id)) {
+      return [];
+    }
+
+    this.nodes.set(id, section);
+    this.kinds.set(id, 30040);  // Sections are indices by default.
+    this.indices.set(id, new Set<string>());
+
+    const parentId = section.getParent()?.getId();
+    if (!parentId) {
+      return [];
+    }
+
+    // Add the section to its parent index.
+    this.indices.get(parentId)?.add(id);
+
+    // Limit to 5 levels of section depth.
+    if (section.getLevel() >= 5) {
+      return [];
+    }
+
+    return section.getBlocks();
+  }
+
+  /**
+   * Processes a block of the Asciidoctor AST.
+   * @param block The block to process.
+   * @remarks Blocks are mapped as kind 30041 zettels by default.
+   */
+  private processBlock(block: Block): void {
+    const id = block.getId();
+
+    // Prevent duplicates.
+    if (this.nodes.has(id)) {
+      return;
+    }
+
+    this.nodes.set(id, block);
+    this.kinds.set(id, 30041);  // Blocks are zettels by default.
+
+    const parentId = block.getParent()?.getId();
+    if (!parentId) {
+      return;
+    }
+
+    // Add the block to its parent index.
+    this.indices.get(parentId)?.add(id);
   }
 }
\ No newline at end of file
--
libgit2 1.7.2

git patch: Write a custom Asciidoctor tree processor


For those interested, you can see my progress thus far on an AsciiDoc parser and event generator for Alexandria here:

https://gitworkshop.dev/r/naddr1qvzqqqrhnypzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqq9yzmr90pskuerjd9ssphy5dc/proposals/note187a6pmzk8py7pyz095g6jz9w38axc68yusdq0mr4kejm2v5rrj8qcm4f82

#NostrDev #Alexandria #GitCitadel


It looks like you've made author a required field, but NKBIP-01 says it's optional.
On the uploader, I have a construct_d_tag function with a fall-through statement, that adds on each additional available variable, ending with $dTagTitle . "-by-" . $dTagAuthor . "-v-" . $dTagVersion

So, something like The-fox-and-the-hound-by-Aesop-v-fourth-revised


But The-fox-and-the-hound or The-fox-and-the-hound-v-fourth-revised or The-fox-and-the-hound-by-Aesop are also correct.


Why is page.ts empty? Am I missing some information or is it just a placeholder?


Wow, five levels. Nice.


What happens if it has more than 5 sections?


I'm sorry, but I can't tell what you do with the article-title, how you get it into the d-tag and formulate the d-tag, with the title, author, and version. I must be blind. I just see a node ID, but I can't tell where it comes from or what it consists of.


Find it a bit confusing that the indices are sections and the sections are blocks, and the nodes can be indices or sections, which means that they can be sections or blocks, and the commentary  and function "zettel", but they aren't called zettel in the function.
Clashes too much with the domain, as described in NKBIP-1 and in my Uploader. Can we maybe agree on one naming convention and write it into the spec?
Also problematic because only 30041 are zettel, so adding more kinds would make it even more confusing because you'd be referring to wiki pages as "zettel".


What happens if the first section contains no entries? Seems to just return an empty relay. Shouldn't it return FALSE?


Ah, it comes from Asciidoctor. What is in it? Is it a random string UID?


Or is a wiki page actually a zettel, in that context and we need to redo the spec?
The spec calls them "section/article".


just responding as I can right now, 30041 are the Zettel analogues, not the full article


Okay, it's all a bit fuzzy. The spec assumes 30040 is always an article and each linked element is a section of the article. But we also allowed for 30040 to be a book and each linked element is then a chapter. And we allowed for 30040 to be a collection and each linked element is then an article, in it's own right. I made a 30040 that was an audiobook list and each 30041 contained an audiobook episode.

I actually prefer 30040 index and 30041 section, as we don't presume to know the content of the events, we're just describing what the "do": one contains a list of events, the other contains a portion of the content.


Yeah, bit fuzzy but that's why in some earlier conversations i made analogues to 30040s being some type of container event. They also got inspiration from lists and how you can have a top level note with other comments chained under it.

Containers can hold other containers, but collectively the pairing of 30040s with a sequence of various events constructs a modular article, not that 30040s are the modular article event.


In terms of feed based implementation, maybe some 30040s should have an explicit 'top level' indicator. That way chapters won't be seen in the feed.


Asciidoctor parses a document into blocks.  A block can be a paragraph, an image, a chart, a code block, a mathematical formula, a section, or even a whole document.

For sections, the ID consists of the underscore-separated section title.  So if you had a section header `== Part 3: The Reckoning` in your document, then Asciidoctor's ID for that section would be something like `part_3_the_reckoning`.

It does something similar to generate IDs for paragraph blocks that don't have an explicit title, but I'm not sure yet exactly what the results come out to be.

If two IDs clash, Asciidoctor appends numbers them to guarantee uniqueness.


I just haven't filled that part in, yet.  I wanted to get the parser work up so the team could look at it, because feedback on the parser may heavily impact the UI.


I don't do anything to pull author from the document yet, though I will add that soon.  Asciidoctor has some nice convenience methods that automatically parse fields like author out of the document.

Thank you for reminding me to include author and version information in the d tag, I hadn't thought about that detail yet.


It will parse the document down to 5 levels of nested sections (`===== Section`).  Currently, any sections deeper than that will be lumped into the content of the kind 30041 events generated from fifth-level blocks.

In theory, we can go arbitrarily deep, but at a certain point, we don't need that much depth in an editor workflow that is driven by direct user input (versus a programmatic upload of something large and complex, like the Bible).  Setting a cutoff makes things a bit easier for both us and, IMO, for the end user.


Recording here what we discussed in Slack:

- 'Zettel' is the term we're using specifically for kind 30041 events.  They are intended to be bite-sized notes.
- 'Index' is the term we're using for kind 30040 events.
- 'Event' is the generic term.  Wiki entries, long-form articles, indices, and zettels are all 'events.'  If we don't have a specific term for an event kind in a given instance, we'll call it an 'event.'

Terms like 'node,' 'block,' and 'section' are specific to Asciidoctor.  I can add details in the code comments explaining how we are using different terms throughout the parser logic.


Okay, sounds good. That's how I handle it, as well.


nostr:npub1wqfzz2p880wq0tumuae9lfwyhs8uz35xd0kr34zrvrwyh3kvrzuskcqsyn did you see this one?


Same, bro. 😂

We were trying to think through the nested index structure, yesterday, and it turned into a multi-hour debate in three channels, to straighten it out.

https://gitworkshop.dev/r/naddr1qq9yzmr90pskuerjd9sszrthwden5te0dehhxtnvdakqygxufnggdntuukccx2klflw3yyfgnzqd93lzjk7tpe5ycqdvaemuqcpsgqqqw7vskyayug/proposals/note187a6pmzk8py7pyz095g6jz9w38axc68yusdq0mr4kejm2v5rrj8qcm4f82


would be nice on a first pass for how to examine and test some of the features like the parser. Like general workflows and expected behaviors. How'd one go about that? nostr:nprofile1qqs06gywary09qmcp2249ztwfq3ue8wxhl2yyp3c39thzp55plvj0sgprdmhxue69uhhg6r9vehhyetnwshxummnw3erztnrdakj7qguwaehxw309a6xsetrd96xzer9dshxummnw3erztnrdakj7qgcwaehxw309ahx7um5wgh8xmmkvf5hgtngdaehgtcw45ql5 have ideas? Would Gherkin be a use case?


Yeah, we need to write some Gherkin. We can already see the problem with the naming, arising out of a lack of consensus.


I'm having a hard time figuring out by the question.

Are you asking what happens if the first  index event doesn't have any child events?

In general, each paragraph will become its own kind 30041 zettel, so if there are no subsections, the document will be turned into a single index with a bunch of one-paragraph zettels.

Does that answer your question, or did I just misunderstand it?


What if it's only

"Document metadata

=== This is a header"

And then it ends. Maybe because someone copy-pasted wrong or pushed the button too early. Or maybe they just want to create an index and update it later, with the relevant e tags.

Then it would only generate a 30040, right? Or would it return an error? Or would they need to confirm that it should only generate a 30040?


A totally empty document should throw an error.  But I think if the user tries to publish a document, as long as we can pull the required fields for the index event tags from the document, we should go ahead and publish it.  Trust that our users know what they're doing, or are at least smart enough to figure out out sooner or later.


I'm thinking through d tag conventions more, and we may have to revise our pattern somewhat.

The pattern `<title>-<author>-<version>` only really works for the root index of a whole book.  Chapters/sections within that book won't have author and version attached to them by default.  I see at least two options:

1. Carry author and version information down into the book's hierarchy, and attach it to every chapter and section index.  This makes the ids of _everything_ longer (which increases URI length within our app), but it increases the likelihood of name uniqueness even of sections within a book.

2. Don't include author and version information in the d tag identifier of every chapter and section, but include that information in the tags.  If we know the author and edition of the book within a chapter resides, for instance, we'll include it in the index event generated for that chapter.

2.5. If we take this latter approach, I'm interested in exploring adding additional fields to the d tag array.  The question is whether the first identifier in that array is _always_ used to generate note identifiers for 30000-series events, or whether the whole d tag array is used.  Perhaps we can include full author and version information in the d tag array, but not have it be mandatory for looking up an event.  The details probably depend on relay indexing and search implementations, there.

Let me know which option you prefer, or if you have other alternatives.


nostr:npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr on ngit 1.4.5, when I try to run `ngit push` or `ngit pull`, I get the following message:

> Error: cannot find proposal that matches the current branch name

The branch name on my local computer is `article-editor`, and I'm trying to push updates to the below proposal on GitWorkshop:

nostr:nevent1qvzqqqqx2ypzquqjyy5zww7uq7hehemjt7juf0q0c9rgv6lv8r2yxcxuf0rvcx9eqydhwumn8ghj7argv43kjarpv3jkctnwdaehgu339e3k7mgqyqlmhg8v2cuyncysfuk3r2gg46yl5mrgunjp5plvwkmxtdfjsvwgu45txfp


I like 2. For 2.5, is there a link to the d tag spec? The nips repo points to NIP01 but I don't see any reference to jt,


I think we were leaning into the definition for wiki pages, since that's the closest event kind.


The spec doesn't assume that a 30040 is a book, that's why it's that way. It could also be an index of articles or wiki pages from different authors. Or pages from various books from various authors in various editions.

That's why only the title is required, as everything has some sort of title.


I like #1, for cases where the lower-level stuff doesn't have their own tags.  I had to do this, when I ran into the problem that I had two different editions of one audiobook and they kept overwriting each others' events, even though I changed the version of the 30040.


Hmm. I do like 2.5 then. By default we keep the first entry as the title. Any other important searchable metadata (which can be different, depending on domain) could go there. Could this essentially combine nip36 and 54 to reduce redundancy?


Don't know. I don't fully understand 2.5. Need a map or something. 😅


I'm thinking something like:
```
"tags": [
  ["d", "chapter-1", "book:example-book", "author:John Doe", "version:1.0"],
  // other tags...
]
```


Ah, I get it, thanks. I forget that the tags are arrays.


Y'all I searched and the NIPs repo doesn't formally define the `d` tag anywhere.

That means no one can tell us we can't extend it lol.

I was thinking we use positional values to extend the `d` tag array.  Something like:

```json
"tags": [
  ["d", "war-and-peace", "leo-tolstoy", "penguin-classics-edition"]
]
```

The general format would be `["d", <title>, <author>, <edition>]` where edition is a human-readable edition name (as opposed to just a number).

The event _should_ still be addressable by `#d` and the title, just like other event kinds' d tags, but we can increase address specificity for the Nostr Knowledge Base use case.  Then we can bake this specificity into wikilinks (see NIP-54 for an existing wikilinks specification).  A document might contain `[[War and Peace. Leo Tolstoy. Penguin Classics Edition.]]` and we can specify that clients should split that at the periods and normalize it into a d tag array reference, so it becomes `["#d", "war-and-peace", "leo-tolstoy", "penguin-classics-edition"]`.  The client can then use this tag array to search its relays, find the closest match, and display to the user a hyperlink to the referenced event.

Basically, we define a citation format for NKB events.

Since the event ID is derived from a serialization of the whole event, including tags, increasing identifier specificity will always generate a unique event ID.

We'll have to experiment with how relays index events by d tag, and how they respond to queries for such events.  Maybe Stella's PHP utilities can help us with that.  Worst case, relays only support searching by the first `d` tag value and return a bunch of matching results, and then we have Alexandria walk through the results and find matches by author and edition.


You think we should fine grain a difference between [[leo-tolstoy]] the topic and [[author:leo-tolstoy]] anything written by him?


Maybe using `[[author:leo-tolstoy]]` should link to a search page showing results with that `author` tag.  We can use a similar format for wikilinks to search results or tag-based feeds.  Perhaps you want to link to a feed of literature on a specific topic.


Yeah, just allowing for a multitude of ways for human navigation. The general case would be to remove the '{noun}:' and just find anything matching the sane string across the categories.

Kind of an aside, but I'm wondering if it would make sense backend-wise if we paired an optional db for caching on relays we want to associate with to improve performance. I'd imagine without the db search performance would degrade as events increase.


The app in general probably shouldn't have its own DB, but our instance, on our website, could easily have its own DB, server, etc. to provide premium features.


GIVE ME ELASTIC SEARCH! 😂


>>The index MUST also be uniquely identifiable using a combination of the d tag's first value (usually containing the title), the pubkey, and the kind fields.<<

This is the part in the NKBIP-01 that pertains to the d tag. We could change the text to read

>>The index MUST also be uniquely identifiable using a combination of the d tag's values (at least including the first value, usually the title), the pubkey, and the kind fields.<<


That sounds good.  It gives us flexibility, but maintains basic compatibility with standard event identification by d tag.


I think the caching DB could be part of our premium offering.

The client is open-source, so anyone can run an instance.  The data?  Not everybody will have that.

We can attach a caching service to TheCitadel relay and introduce a paid tier for those who want that additional performance.


Love the idea