From 5fcfc11dfbf19a88f07bce1e0380a36c3f2579b2 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Wed, 4 Mar 2026 12:16:33 +0100 Subject: [PATCH] add unit tests --- asciidoc_testdoc.adoc | 370 + debug-asciidoc-output.adoc | 72 + debug-lists.ts | 27 + generate-test-report.js | 642 ++ generate-test-report.ts | 642 ++ jest.config.js | 25 + markdown_testdoc.md | 337 + package.json | 11 +- src/converters/to-asciidoc.js | 692 ++ src/converters/to-asciidoc.ts | 423 +- src/detector.js | 70 + src/extractors/frontmatter.js | 160 + src/extractors/frontmatter.ts | 177 + src/extractors/metadata.js | 243 + src/parser.js | 92 + src/parser.ts | 25 +- src/processors/asciidoc.js | 148 + src/processors/asciidoc.ts | 7 + src/processors/html-postprocess.js | 594 ++ src/processors/html-postprocess.ts | 326 +- src/processors/html-utils.js | 239 + src/processors/html-utils.ts | 33 + src/processors/music.js | 143 + src/processors/music.ts | 115 +- src/types.js | 14 + src/types.ts | 16 + test-parser-report.test.ts | 628 ++ test-report.html | 13415 +++++++++++++++++++++++++++ 28 files changed, 19620 insertions(+), 66 deletions(-) create mode 100644 asciidoc_testdoc.adoc create mode 100644 debug-asciidoc-output.adoc create mode 100644 debug-lists.ts create mode 100644 generate-test-report.js create mode 100644 generate-test-report.ts create mode 100644 jest.config.js create mode 100644 markdown_testdoc.md create mode 100644 src/converters/to-asciidoc.js create mode 100644 src/detector.js create mode 100644 src/extractors/frontmatter.js create mode 100644 src/extractors/frontmatter.ts create mode 100644 src/extractors/metadata.js create mode 100644 src/parser.js create mode 100644 src/processors/asciidoc.js create mode 100644 src/processors/html-postprocess.js create mode 100644 src/processors/html-utils.js create mode 100644 src/processors/music.js create mode 100644 src/types.js create mode 100644 test-parser-report.test.ts create mode 100644 test-report.html diff --git a/asciidoc_testdoc.adoc b/asciidoc_testdoc.adoc new file mode 100644 index 0000000..00c585d --- /dev/null +++ b/asciidoc_testdoc.adoc @@ -0,0 +1,370 @@ += AsciiDoc Test Document +Kismet Lee +2.9, October 31, 2021: Fall incarnation +:description: Test description +:author: Kismet Lee +:date: 2021-10-31 +:version: 2.9 +:status: Draft +:keywords: AsciiDoc, Test, Document +:category: Test +:language: English + +== Bullet list + +This is a test unordered list with mixed bullets: + +* First item with a number 2. in it +* Second item +* Third item +** Indented item +** Indented item +* Fourth item + +Another unordered list: + +* 1st item +* 2nd item +* third item containing _italic_ text +** indented item +** second indented item +* fourth item + +This is a test ordered list with indented items: + +. First item +. Second item +. Third item +.. Indented item +.. Indented item +. Fourth item + +Ordered list where everything has no number: + +. First item +. Second item +. Third item +. Fourth item + +This is a mixed list with indented items: + +. First item +. Second item +. Third item +* Indented item +* Indented item +. Fourth item + +This is another mixed list with indented items: + +* First item +* Second item +* Third item +. Indented item +. Indented item +* Fourth item + +== Headers + +=== Third-level header + +==== Fourth-level header + +===== Fifth-level header + +====== Sixth-level header + +== Media and Links + +=== Nostr address + +This should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l + +This is also plaintext: + +npub1gv069u6q7zkl393ad47xutpqmyfj0rrfrlnqnlfc2ld38k8nnl4st9wa6q + +These should be turned into links: + +nostr:naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l + +nostr:npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z + +nostr:nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj + +nostr:nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh + +nostr:note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg + +=== Hashtag + +#testhashtag at the start of the line and #inlinehashtag in the middle + +=== Wikilinks + +[[NKBIP-01|Specification]] and [[mirepoix]] + +=== URL + +https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html + +link:https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html[Welt Online link] + +this should render as plaintext: `http://www.example.com` + +this should be a hyperlink: www.example.com + +this should be a hyperlink to the http URL with the same address, so wss://theforest.nostr1.com should render like link:wss://theforest.nostr1.com[https://theforest.nostr1.com] + +=== Images + +https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png + +image::https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png[test image, width=100%] + +=== Media + +==== YouTube + +https://youtube.com/shorts/ZWfvChb-i0w + +link:https://youtube.com/shorts/ZWfvChb-i0w[image:https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png[Youtube link with pic]] + +==== Spotify + +https://open.spotify.com/episode/1GSZFA8vWltPyxYkArdRKx?si=bq6-az28TcuP596feTkRFQ + +link:https://open.spotify.com/episode/1GSZFA8vWltPyxYkArdRKx?si=bq6-az28TcuP596feTkRFQ[image:https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png[Spotify link with pic]] + +==== Audio + +https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3 + +link:https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3[image:https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png[Audio link with pic]] + +==== Video + +https://v.nostr.build/MTjaYib4upQuf8zn.mp4 + +link:https://v.nostr.build/MTjaYib4upQuf8zn.mp4[image:https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png[Video link with pic]] + +== Tables + +=== Orderly + +[cols="1,2"] +|=== +|Syntax|Description +|Header|Title +|Paragraph|Text +|=== + +=== Unorderly + +[cols="1,2"] +|=== +|Syntax|Description +|Header|Title +|Paragraph|Text +|=== + +=== With alignment + +[cols="<,^,>"] +|=== +|Syntax|Description|Test Text +|Header|Title|Here's this +|Paragraph|Text|And more +|=== + +== Code blocks + +=== json + +[source,json] +---- +{ + "id": "", + "pubkey": "", + "created_at": 1725087283, + "kind": 30040, + "tags": [ + ["d", "aesop's-fables-by-aesop"], + ["title", "Aesop's Fables"], + ["author", "Aesop"], + ], + "sig": "" +} +---- + +=== typescript + +[source,typescript] +---- +/** + * Get Nostr identifier type + */ +function getNostrType(id: string): 'npub' | 'nprofile' | 'nevent' | 'naddr' | 'note' | null { + if (id.startsWith('npub')) return 'npub'; + if (id.startsWith('nprofile')) return 'nprofile'; + if (id.startsWith('nevent')) return 'nevent'; + if (id.startsWith('naddr')) return 'naddr'; + if (id.startsWith('note')) return 'note'; + return null; +} +---- + +=== shell + +[source,shell] +---- + +mkdir new_directory +cp source.txt destination.txt + +---- + +=== LaTeX + +[source,latex] +---- +$$ +M = +\begin{bmatrix} +\frac{5}{6} & \frac{1}{6} & 0 \\[0.3em] +\frac{5}{6} & 0 & \frac{1}{6} \\[0.3em] +0 & \frac{5}{6} & \frac{1}{6} +\end{bmatrix} +$$ +---- + +[source,latex] +---- +$$ +f(x)= +\begin{cases} +1/d_{ij} & \quad \text{when $d_{ij} \leq 160$}\\ +0 & \quad \text{otherwise} +\end{cases} +$$ +---- + +=== ABC Notation + +[source,abc] +---- +X:1 +T:Ohne Titel +C:Aufgezeichnet 1784 +A:Seibis nahe Lichtenberg in Oberfranken +S:Handschrift, bezeichnet und datiert: "Heinrich Nicol Philipp zu Seibis den 30 Junius 1784" +M:4/4 +L:1/4 +K:D +dd d2 | ee e2 | fg ad | cB cA |\ +dd d2 | ee e2 | fg ad | ed/c/ d2 :| +|:\ +fg ad | cB cA | fg ad | cB cA |\ +dd d2 | ee e2 | fg ad | ed/c/ d2 :| +---- + +=== PlantUML + +[source,plantuml] +---- +@startuml +Alice -> Bob: Authentication Request +Bob --> Alice: Authentication Response +@enduml +---- + +=== BPMN + +[source,plantuml] +---- +@startbpmn +start +:Task 1; +:Task 2; +stop +@endbpmn +---- + +== LaTeX + +=== LaTeX in inline-code + +`$[ x^n + y^n = z^n \]$` and `$[\sqrt{x^2+1}\]$` and `$\color{blue}{X \sim Normal \; (\mu,\sigma^2)}$` + +== LaTeX outside of code + +This is a latex code block $$\mathbb{N} = \{ a \in \mathbb{Z} : a > 0 \}$$ and another that is an inline latex $\color{green}{X \sim Normal \; (\mu,\sigma^2)}$ and should be green + +== Footnotes + +Here's a simple footnote,footnote:[This is the first footnote.] and here's a longer one.footnote:[Here's one with multiple paragraphs and code.] + +== Anchor links + +<> + +== Formatting + +=== Strikethrough + +[line-through]#The world is flat.# We now know that the world is round. This should not be ~struck~ through. + +=== Bold + +This is *bold* text. So is this *bold* text. + +=== Italic + +This is _italic_ text. So is this _italic_ text. + +=== Task List + +* [x] Write the press release +* [ ] Update the website +* [ ] Contact the media + +=== Emoji shortcodes + +Gone camping! :tent: Be back soon. + +That is so funny! :joy: + +=== Marking and highlighting text + +I need to highlight these [highlight]#very important words#. + +=== Subscript and Superscript + +H~2~O + +X^2^ + +=== Delimiter + +based upon a - + +''' + +based upon a * + +''' + +=== Quotes + +[quote] +____ +This is a single line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj +____ + +[quote] +____ +This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj +This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj +This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj +This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj +____ diff --git a/debug-asciidoc-output.adoc b/debug-asciidoc-output.adoc new file mode 100644 index 0000000..c64ca37 --- /dev/null +++ b/debug-asciidoc-output.adoc @@ -0,0 +1,72 @@ + + +This is a test unordered list with mixed bullets: + +* First item with a number 2. in it +* Second item +* Third item + * Indented item + * Indented item +* Fourth item + + +Another unordered list: + +* 1st item +* 2nd item +* third item containing _italic_ text + * indented item + * second indented item +* fourth item + + +This is a test ordered list with indented items: + +. First item +. Second item +. Third item + . Indented item + . Indented item +. Fourth item + + +Ordered list where everything has the same number: + +. First item +. Second item +. Third item +. Fourth item + + +Ordered list that is wrongly numbered: + +. First item +. Second item +. Third item +. Fourth item + + +This is a mixed list with indented items: + +. First item +. Second item +. Third item + + * Indented item + * Indented item + +. Fourth item + + +This is another mixed list with indented items: + +* First item +* Second item +* Third item + + . Indented item + . Indented item + +* Fourth item + + diff --git a/debug-lists.ts b/debug-lists.ts new file mode 100644 index 0000000..12dc776 --- /dev/null +++ b/debug-lists.ts @@ -0,0 +1,27 @@ +import { convertToAsciidoc } from './src/converters/to-asciidoc'; +import { detectFormat } from './src/detector'; +import * as fs from 'fs'; +import * as path from 'path'; + +// Read just the list section from markdown test doc +const markdownContent = fs.readFileSync( + path.join(__dirname, 'markdown_testdoc.md'), + 'utf-8' +); + +// Extract just the list sections +const listSection = markdownContent.split('## Bullet list')[1]?.split('##')[0] || markdownContent; + +console.log('=== ORIGINAL MARKDOWN ==='); +console.log(listSection); +console.log('\n=== DETECTED FORMAT ==='); +const format = detectFormat(listSection); +console.log(format); + +console.log('\n=== CONVERTED ASCIIDOC ==='); +const asciidoc = convertToAsciidoc(listSection, format, '', {}); +console.log(asciidoc); + +// Write to file for inspection +fs.writeFileSync(path.join(__dirname, 'debug-asciidoc-output.adoc'), asciidoc); +console.log('\n=== Written to debug-asciidoc-output.adoc ==='); diff --git a/generate-test-report.js b/generate-test-report.js new file mode 100644 index 0000000..088142a --- /dev/null +++ b/generate-test-report.js @@ -0,0 +1,642 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +const parser_1 = require("./src/parser"); +const fs = __importStar(require("fs")); +const path = __importStar(require("path")); +async function main() { + const parser = new parser_1.Parser({ + linkBaseURL: 'https://example.com', + wikilinkUrl: '/events?d={dtag}', + hashtagUrl: '/notes?t={topic}', + }); + console.log('Reading test documents...'); + // Read test documents + const markdownContent = fs.readFileSync(path.join(__dirname, 'markdown_testdoc.md'), 'utf-8'); + const asciidocContent = fs.readFileSync(path.join(__dirname, 'asciidoc_testdoc.adoc'), 'utf-8'); + console.log('Parsing markdown document...'); + const markdownResult = await parser.process(markdownContent); + console.log('Parsing asciidoc document...'); + const asciidocResult = await parser.process(asciidocContent); + console.log('Generating HTML report...'); + // Generate HTML report + const htmlReport = generateHTMLReport({ + markdown: { + original: markdownContent, + result: markdownResult, + }, + asciidoc: { + original: asciidocContent, + result: asciidocResult, + }, + }); + // Write HTML report to file (force fresh write) + const reportPath = path.join(__dirname, 'test-report.html'); + // Delete old report if it exists to ensure fresh generation + if (fs.existsSync(reportPath)) { + fs.unlinkSync(reportPath); + } + fs.writeFileSync(reportPath, htmlReport, 'utf-8'); + const reportUrl = `file://${reportPath}`; + console.log(`\n✅ Test report generated successfully!`); + console.log(` File: ${reportPath}`); + console.log(` Size: ${(htmlReport.length / 1024).toFixed(2)} KB`); + console.log(` Timestamp: ${new Date().toISOString()}`); + console.log(` Open this file in your browser to view the results.\n`); +} +function generateHTMLReport(data) { + const { markdown, asciidoc } = data; + return ` + + + + + GC Parser Test Report + + + +
+

GC Parser Test Report

+

Generated: ${new Date().toLocaleString()}

+ + +
+

Markdown Document Test ✓ Parsed

+ +
+ + + + +
+ +
+
+
+
${markdown.result.nostrLinks.length}
+
Nostr Links
+
+
+
${markdown.result.wikilinks.length}
+
Wikilinks
+
+
+
${markdown.result.hashtags.length}
+
Hashtags
+
+
+
${markdown.result.links.length}
+
Links
+
+
+
${markdown.result.media.length}
+
Media URLs
+
+
+
${markdown.result.hasLaTeX ? 'Yes' : 'No'}
+
Has LaTeX
+
+
+
${markdown.result.hasMusicalNotation ? 'Yes' : 'No'}
+
Has Music
+
+
+ +

Frontmatter

+ ${markdown.result.frontmatter ? ` + + ` : '

No frontmatter found

'} +
+ +
+

Original Markdown Content

+
+
${escapeHtml(markdown.original)}
+
+
+ +
+

Rendered HTML Output

+
+ ${markdown.result.content} +
+
+ View Raw HTML +
+
${escapeHtml(markdown.result.content)}
+
+
+
+ +
+

Extracted Metadata

+ + ${markdown.result.nostrLinks.length > 0 ? ` +

Nostr Links (${markdown.result.nostrLinks.length})

+ ${markdown.result.nostrLinks.map((link) => ` +
+ ${escapeHtml(link.type)}: ${escapeHtml(link.bech32)} + ${link.text ? ` - ${escapeHtml(link.text)}` : ''} +
+ `).join('')} + ` : ''} + + ${markdown.result.wikilinks.length > 0 ? ` +

Wikilinks (${markdown.result.wikilinks.length})

+ ${markdown.result.wikilinks.map((wl) => ` +
+ ${escapeHtml(wl.original)} → dtag: ${escapeHtml(wl.dtag)} + ${wl.display ? ` (display: ${escapeHtml(wl.display)})` : ''} +
+ `).join('')} + ` : ''} + + ${markdown.result.hashtags.length > 0 ? ` +

Hashtags (${markdown.result.hashtags.length})

+ ${markdown.result.hashtags.map((tag) => ` +
+ #${escapeHtml(tag)} +
+ `).join('')} + ` : ''} + + ${markdown.result.links.length > 0 ? ` +

Links (${markdown.result.links.length})

+ ${markdown.result.links.map((link) => ` +
+ ${escapeHtml(link.text || link.url)} + ${link.isExternal ? 'External' : ''} +
+ `).join('')} + ` : ''} + + ${markdown.result.media.length > 0 ? ` +

Media URLs (${markdown.result.media.length})

+ ${markdown.result.media.map((url) => ` + + `).join('')} + ` : ''} + + ${markdown.result.tableOfContents ? ` +

Table of Contents

+
+ ${markdown.result.tableOfContents} +
+ ` : ''} +
+
+ + +
+

AsciiDoc Document Test ✓ Parsed

+ +
+ + + + +
+ +
+
+
+
${asciidoc.result.nostrLinks.length}
+
Nostr Links
+
+
+
${asciidoc.result.wikilinks.length}
+
Wikilinks
+
+
+
${asciidoc.result.hashtags.length}
+
Hashtags
+
+
+
${asciidoc.result.links.length}
+
Links
+
+
+
${asciidoc.result.media.length}
+
Media URLs
+
+
+
${asciidoc.result.hasLaTeX ? 'Yes' : 'No'}
+
Has LaTeX
+
+
+
${asciidoc.result.hasMusicalNotation ? 'Yes' : 'No'}
+
Has Music
+
+
+ +

Frontmatter

+ ${asciidoc.result.frontmatter ? ` + + ` : '

No frontmatter found

'} +
+ +
+

Original AsciiDoc Content

+
+
${escapeHtml(asciidoc.original)}
+
+
+ +
+

Rendered HTML Output

+
+ ${asciidoc.result.content} +
+
+ View Raw HTML +
+
${escapeHtml(asciidoc.result.content)}
+
+
+
+ +
+

Extracted Metadata

+ + ${asciidoc.result.nostrLinks.length > 0 ? ` +

Nostr Links (${asciidoc.result.nostrLinks.length})

+ ${asciidoc.result.nostrLinks.map((link) => ` +
+ ${escapeHtml(link.type)}: ${escapeHtml(link.bech32)} + ${link.text ? ` - ${escapeHtml(link.text)}` : ''} +
+ `).join('')} + ` : ''} + + ${asciidoc.result.wikilinks.length > 0 ? ` +

Wikilinks (${asciidoc.result.wikilinks.length})

+ ${asciidoc.result.wikilinks.map((wl) => ` +
+ ${escapeHtml(wl.original)} → dtag: ${escapeHtml(wl.dtag)} + ${wl.display ? ` (display: ${escapeHtml(wl.display)})` : ''} +
+ `).join('')} + ` : ''} + + ${asciidoc.result.hashtags.length > 0 ? ` +

Hashtags (${asciidoc.result.hashtags.length})

+ ${asciidoc.result.hashtags.map((tag) => ` +
+ #${escapeHtml(tag)} +
+ `).join('')} + ` : ''} + + ${asciidoc.result.links.length > 0 ? ` +

Links (${asciidoc.result.links.length})

+ ${asciidoc.result.links.map((link) => ` +
+ ${escapeHtml(link.text || link.url)} + ${link.isExternal ? 'External' : ''} +
+ `).join('')} + ` : ''} + + ${asciidoc.result.media.length > 0 ? ` +

Media URLs (${asciidoc.result.media.length})

+ ${asciidoc.result.media.map((url) => ` + + `).join('')} + ` : ''} + + ${asciidoc.result.tableOfContents ? ` +

Table of Contents

+
+ ${asciidoc.result.tableOfContents} +
+ ` : ''} +
+
+
+ + + +`; +} +function escapeHtml(text) { + const map = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + }; + return text.replace(/[&<>"']/g, (m) => map[m]); +} +// Run the script +main().catch((error) => { + console.error('Error generating test report:', error); + process.exit(1); +}); diff --git a/generate-test-report.ts b/generate-test-report.ts new file mode 100644 index 0000000..3ec5143 --- /dev/null +++ b/generate-test-report.ts @@ -0,0 +1,642 @@ +import { Parser } from './src/parser'; +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * Script that parses both markdown and asciidoc test documents + * and generates an HTML report showing the parsing results + */ + +interface TestData { + original: string; + result: any; +} + +interface ReportData { + markdown: TestData; + asciidoc: TestData; +} + +async function main() { + const parser = new Parser({ + linkBaseURL: 'https://example.com', + wikilinkUrl: '/events?d={dtag}', + hashtagUrl: '/notes?t={topic}', + }); + + console.log('Reading test documents...'); + + // Read test documents + const markdownContent = fs.readFileSync( + path.join(__dirname, 'markdown_testdoc.md'), + 'utf-8' + ); + const asciidocContent = fs.readFileSync( + path.join(__dirname, 'asciidoc_testdoc.adoc'), + 'utf-8' + ); + + console.log('Parsing markdown document...'); + const markdownResult = await parser.process(markdownContent); + + console.log('Parsing asciidoc document...'); + const asciidocResult = await parser.process(asciidocContent); + + console.log('Generating HTML report...'); + // Generate HTML report + const htmlReport = generateHTMLReport({ + markdown: { + original: markdownContent, + result: markdownResult, + }, + asciidoc: { + original: asciidocContent, + result: asciidocResult, + }, + }); + + // Write HTML report to file (force fresh write) + const reportPath = path.join(__dirname, 'test-report.html'); + + // Delete old report if it exists to ensure fresh generation + if (fs.existsSync(reportPath)) { + fs.unlinkSync(reportPath); + } + + fs.writeFileSync(reportPath, htmlReport, 'utf-8'); + + const reportUrl = `file://${reportPath}`; + console.log(`\n✅ Test report generated successfully!`); + console.log(` File: ${reportPath}`); + console.log(` Size: ${(htmlReport.length / 1024).toFixed(2)} KB`); + console.log(` Timestamp: ${new Date().toISOString()}`); + console.log(` Open this file in your browser to view the results.\n`); +} + +function generateHTMLReport(data: ReportData): string { + const { markdown, asciidoc } = data; + + return ` + + + + + GC Parser Test Report + + + +
+

GC Parser Test Report

+

Generated: ${new Date().toLocaleString()}

+ + +
+

Markdown Document Test ✓ Parsed

+ +
+ + + + +
+ +
+
+
+
${markdown.result.nostrLinks.length}
+
Nostr Links
+
+
+
${markdown.result.wikilinks.length}
+
Wikilinks
+
+
+
${markdown.result.hashtags.length}
+
Hashtags
+
+
+
${markdown.result.links.length}
+
Links
+
+
+
${markdown.result.media.length}
+
Media URLs
+
+
+
${markdown.result.hasLaTeX ? 'Yes' : 'No'}
+
Has LaTeX
+
+
+
${markdown.result.hasMusicalNotation ? 'Yes' : 'No'}
+
Has Music
+
+
+ +

Frontmatter

+ ${markdown.result.frontmatter ? ` + + ` : '

No frontmatter found

'} +
+ +
+

Original Markdown Content

+
+
${escapeHtml(markdown.original)}
+
+
+ +
+

Rendered HTML Output

+
+ ${markdown.result.content} +
+
+ View Raw HTML +
+
${escapeHtml(markdown.result.content)}
+
+
+
+ +
+

Extracted Metadata

+ + ${markdown.result.nostrLinks.length > 0 ? ` +

Nostr Links (${markdown.result.nostrLinks.length})

+ ${markdown.result.nostrLinks.map((link: any) => ` +
+ ${escapeHtml(link.type)}: ${escapeHtml(link.bech32)} + ${link.text ? ` - ${escapeHtml(link.text)}` : ''} +
+ `).join('')} + ` : ''} + + ${markdown.result.wikilinks.length > 0 ? ` +

Wikilinks (${markdown.result.wikilinks.length})

+ ${markdown.result.wikilinks.map((wl: any) => ` +
+ ${escapeHtml(wl.original)} → dtag: ${escapeHtml(wl.dtag)} + ${wl.display ? ` (display: ${escapeHtml(wl.display)})` : ''} +
+ `).join('')} + ` : ''} + + ${markdown.result.hashtags.length > 0 ? ` +

Hashtags (${markdown.result.hashtags.length})

+ ${markdown.result.hashtags.map((tag: string) => ` +
+ #${escapeHtml(tag)} +
+ `).join('')} + ` : ''} + + ${markdown.result.links.length > 0 ? ` +

Links (${markdown.result.links.length})

+ ${markdown.result.links.map((link: any) => ` +
+ ${escapeHtml(link.text || link.url)} + ${link.isExternal ? 'External' : ''} +
+ `).join('')} + ` : ''} + + ${markdown.result.media.length > 0 ? ` +

Media URLs (${markdown.result.media.length})

+ ${markdown.result.media.map((url: string) => ` + + `).join('')} + ` : ''} + + ${markdown.result.tableOfContents ? ` +

Table of Contents

+
+ ${markdown.result.tableOfContents} +
+ ` : ''} +
+
+ + +
+

AsciiDoc Document Test ✓ Parsed

+ +
+ + + + +
+ +
+
+
+
${asciidoc.result.nostrLinks.length}
+
Nostr Links
+
+
+
${asciidoc.result.wikilinks.length}
+
Wikilinks
+
+
+
${asciidoc.result.hashtags.length}
+
Hashtags
+
+
+
${asciidoc.result.links.length}
+
Links
+
+
+
${asciidoc.result.media.length}
+
Media URLs
+
+
+
${asciidoc.result.hasLaTeX ? 'Yes' : 'No'}
+
Has LaTeX
+
+
+
${asciidoc.result.hasMusicalNotation ? 'Yes' : 'No'}
+
Has Music
+
+
+ +

Frontmatter

+ ${asciidoc.result.frontmatter ? ` + + ` : '

No frontmatter found

'} +
+ +
+

Original AsciiDoc Content

+
+
${escapeHtml(asciidoc.original)}
+
+
+ +
+

Rendered HTML Output

+
+ ${asciidoc.result.content} +
+
+ View Raw HTML +
+
${escapeHtml(asciidoc.result.content)}
+
+
+
+ +
+

Extracted Metadata

+ + ${asciidoc.result.nostrLinks.length > 0 ? ` +

Nostr Links (${asciidoc.result.nostrLinks.length})

+ ${asciidoc.result.nostrLinks.map((link: any) => ` +
+ ${escapeHtml(link.type)}: ${escapeHtml(link.bech32)} + ${link.text ? ` - ${escapeHtml(link.text)}` : ''} +
+ `).join('')} + ` : ''} + + ${asciidoc.result.wikilinks.length > 0 ? ` +

Wikilinks (${asciidoc.result.wikilinks.length})

+ ${asciidoc.result.wikilinks.map((wl: any) => ` +
+ ${escapeHtml(wl.original)} → dtag: ${escapeHtml(wl.dtag)} + ${wl.display ? ` (display: ${escapeHtml(wl.display)})` : ''} +
+ `).join('')} + ` : ''} + + ${asciidoc.result.hashtags.length > 0 ? ` +

Hashtags (${asciidoc.result.hashtags.length})

+ ${asciidoc.result.hashtags.map((tag: string) => ` +
+ #${escapeHtml(tag)} +
+ `).join('')} + ` : ''} + + ${asciidoc.result.links.length > 0 ? ` +

Links (${asciidoc.result.links.length})

+ ${asciidoc.result.links.map((link: any) => ` +
+ ${escapeHtml(link.text || link.url)} + ${link.isExternal ? 'External' : ''} +
+ `).join('')} + ` : ''} + + ${asciidoc.result.media.length > 0 ? ` +

Media URLs (${asciidoc.result.media.length})

+ ${asciidoc.result.media.map((url: string) => ` + + `).join('')} + ` : ''} + + ${asciidoc.result.tableOfContents ? ` +

Table of Contents

+
+ ${asciidoc.result.tableOfContents} +
+ ` : ''} +
+
+
+ + + +`; +} + +function escapeHtml(text: string): string { + const map: Record = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + }; + return text.replace(/[&<>"']/g, (m) => map[m]); +} + +// Run the script +main().catch((error) => { + console.error('Error generating test report:', error); + process.exit(1); +}); diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..590e48b --- /dev/null +++ b/jest.config.js @@ -0,0 +1,25 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + roots: [''], + testMatch: ['**/*.test.ts'], + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + collectCoverageFrom: [ + 'src/**/*.ts', + '!src/**/*.d.ts', + ], + globals: { + 'ts-jest': { + tsconfig: { + target: 'ES2020', + module: 'commonjs', + lib: ['ES2020'], + types: ['node'], + strict: true, + esModuleInterop: true, + skipLibCheck: true, + forceConsistentCasingInFileNames: true, + }, + }, + }, +}; diff --git a/markdown_testdoc.md b/markdown_testdoc.md new file mode 100644 index 0000000..5d96eb0 --- /dev/null +++ b/markdown_testdoc.md @@ -0,0 +1,337 @@ +--- +# this is YAML front matter +author: James Smith +summary: This is a summary +topics: list, of, topics +variable: one +array: + - one thing + - two things + - several things +# all of this data is available to our layout +--- + +# Markdown Test Document + +## Bullet list + +This is a test unordered list with mixed bullets: +* First item with a number 2. in it +* Second item +* Third item + - Indented item + - Indented item +* Fourth item + +Another unordered list: +- 1st item +- 2nd item +- third item containing _italic_ text + - indented item + - second indented item +- fourth item + +This is a test ordered list with indented items: +1. First item +2. Second item +3. Third item + 1. Indented item + 2. Indented item +4. Fourth item + +Ordered list where everything has the same number: +1. First item +1. Second item +1. Third item +1. Fourth item + +Ordered list that is wrongly numbered: +1. First item +8. Second item +3. Third item +5. Fourth item + +This is a mixed list with indented items: +1. First item +2. Second item +3. Third item + * Indented item + * Indented item +4. Fourth item + +This is another mixed list with indented items: +- First item +- Second item +- Third item + 1. Indented item + 2. Indented item +- Fourth item + + +## Headers + +### Third-level header + +#### Fourth-level header + +##### Fifth-level header + +###### Sixth-level header + +## Media and Links + +### Nostr address + +This should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l + +This is also plaintext: + +npub1gv069u6q7zkl393ad47xutpqmyfj0rrfrlnqnlfc2ld38k8nnl4st9wa6q + +These should be turned into links: + +nostr:naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l + +nostr:npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z + +nostr:nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj + +nostr:nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh + +nostr:note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg + +### Hashtag + +#testhashtag at the start of the line and #inlinehashtag in the middle + +### Wikilinks + +[[NKBIP-01|Specification]] and [[mirepoix]] + +### URL + +https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html + +[Welt Online link](https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html) + +this should render as plaintext: `http://www.example.com` + +this should be a hyperlink: www.example.com + +this shouild be a hyperlink to the http URL with the same address, so wss://theforest.nostr1.com should render like [wss://theforest.nostr1.com](https://theforest.nostr1.com) + +### Images + +Image: https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png + +![test image](https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png) + +### Media + +#### YouTube + +https://youtube.com/shorts/ZWfvChb-i0w + +[![Youtube link with pic](https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png)](https://youtube.com/shorts/ZWfvChb-i0w) + +#### Spotify + +https://open.spotify.com/episode/1GSZFA8vWltPyxYkArdRKx?si=bq6-az28TcuP596feTkRFQ + +[![Spotify link with pic](https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png)](https://open.spotify.com/episode/1GSZFA8vWltPyxYkArdRKx?si=bq6-az28TcuP596feTkRFQ) + +#### Audio + +https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3 + +[![Audio link with pic](https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png)](https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3) + +#### Video + +https://v.nostr.build/MTjaYib4upQuf8zn.mp4 + +[![Video link with pic](https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png)](https://v.nostr.build/MTjaYib4upQuf8zn.mp4) + +## Tables + +### Orderly + +| Syntax | Description | +| ----------- | ----------- | +| Header | Title | +| Paragraph | Text | + +### Unorderly + +| Syntax | Description | +| --- | ----------- | +| Header | Title | +| Paragraph | Text | + +### With alignment + +| Syntax | Description | Test Text | +| :--- | :----: | ---: | +| Header | Title | Here's this | +| Paragraph | Text | And more | + +## Code blocks + +### json + +```json +{ + "id": "", + "pubkey": "", + "created_at": 1725087283, + "kind": 30040, + "tags": [ + ["d", "aesop's-fables-by-aesop"], + ["title", "Aesop's Fables"], + ["author", "Aesop"], + ], + "sig": "" +} +``` + +### typescript + +```typescript +/** + * Get Nostr identifier type + */ +function getNostrType(id: string): 'npub' | 'nprofile' | 'nevent' | 'naddr' | 'note' | null { + if (id.startsWith('npub')) return 'npub'; + if (id.startsWith('nprofile')) return 'nprofile'; + if (id.startsWith('nevent')) return 'nevent'; + if (id.startsWith('naddr')) return 'naddr'; + if (id.startsWith('note')) return 'note'; + return null; +} +``` + +### shell + +```shell + +mkdir new_directory +cp source.txt destination.txt + +``` + +### LaTeX + +```latex +$$ +M = +\begin{bmatrix} +\frac{5}{6} & \frac{1}{6} & 0 \\[0.3em] +\frac{5}{6} & 0 & \frac{1}{6} \\[0.3em] +0 & \frac{5}{6} & \frac{1}{6} +\end{bmatrix} +$$ +``` + +```latex +$$ +f(x)= +\begin{cases} +1/d_{ij} & \quad \text{when $d_{ij} \leq 160$}\\ +0 & \quad \text{otherwise} +\end{cases} +$$ +``` + +### ABC Notation + +```abc +X:1 +T:Ohne Titel +C:Aufgezeichnet 1784 +A:Seibis nahe Lichtenberg in Oberfranken +S:Handschrift, bezeichnet und datiert: "Heinrich Nicol Philipp zu Seibis den 30 Junius 1784" +M:4/4 +L:1/4 +K:D +dd d2 | ee e2 | fg ad | cB cA |\ +dd d2 | ee e2 | fg ad | ed/c/ d2 :| +|:\ +fg ad | cB cA | fg ad | cB cA |\ +dd d2 | ee e2 | fg ad | ed/c/ d2 :| +``` + +## LateX + +### LaTex in inline-code + +`$[ x^n + y^n = z^n \]$` and `$[\sqrt{x^2+1}\]$` and `$\color{blue}{X \sim Normal \; (\mu,\sigma^2)}$` + +## LaTex outside of code + +This is a latex code block $$\mathbb{N} = \{ a \in \mathbb{Z} : a > 0 \}$$ and another that is an inline latex $\color{green}{X \sim Normal \; (\mu,\sigma^2)}$ and should be green + +## Footnotes + +Here's a simple footnote,[^1] and here's a longer one.[^bignote] + +[^1]: This is the first footnote. + +[^bignote]: Here's one with multiple paragraphs and code. + +## Anchor links + +[Link to bullet list section](#bullet-lists) + +## Formatting + +### Strikethrough + +~~The world is flat.~~ We now know that the world is round. This should not be ~struck~ through. + +### Bold + +This is *bold* text. So is this **bold** text. + +### Italic + +This is _italic_ text. So is this __italic__ text. + +### Task List + +- [x] Write the press release +- [ ] Update the website +- [ ] Contact the media + +### Emoji shortcodes + +Gone camping! :tent: Be back soon. + +That is so funny! :joy: + +### Marking and highlighting text + +I need to highlight these ==very important words==. + +### Subscript and Superscript + +H~2~O + +X^2^ + +### Delimiter + +based upon a - + +--- + +based upon a * + +*** + +### Quotes + +> This is a single line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj + +> This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj +> This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj +> This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj \ No newline at end of file diff --git a/package.json b/package.json index b74319d..443ae6e 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "scripts": { "build": "tsc", "test": "jest", + "test:report": "ts-node generate-test-report.ts", "prepublishOnly": "npm run build" }, "keywords": [ @@ -21,13 +22,15 @@ "author": "", "license": "MIT", "dependencies": { - "@asciidoctor/core": "^3.0.4" + "@asciidoctor/core": "^3.0.4", + "node-emoji": "^2.2.0" }, "devDependencies": { + "@types/highlight.js": "^10.1.0", + "@types/jest": "^29.5.11", "@types/node": "^20.11.0", - "typescript": "^5.3.3", "jest": "^29.7.0", - "@types/jest": "^29.5.11", - "@types/highlight.js": "^10.1.0" + "ts-jest": "^29.4.6", + "typescript": "^5.3.3" } } diff --git a/src/converters/to-asciidoc.js b/src/converters/to-asciidoc.js new file mode 100644 index 0000000..bfdc851 --- /dev/null +++ b/src/converters/to-asciidoc.js @@ -0,0 +1,692 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.convertToAsciidoc = convertToAsciidoc; +const types_1 = require("../types"); +// Import node-emoji if available (optional dependency) +let emoji; +try { + emoji = require('node-emoji'); +} +catch (e) { + // node-emoji not available, emoji conversion will be skipped + emoji = null; +} +/** + * Clean URL by removing tracking parameters + * Based on jumble's cleanUrl function + */ +function cleanUrl(url) { + try { + const parsedUrl = new URL(url); + // List of tracking parameter prefixes and exact names to remove + const trackingParams = [ + // Google Analytics & Ads + 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', + 'utm_id', 'utm_source_platform', 'utm_creative_format', 'utm_marketing_tactic', + 'gclid', 'gclsrc', 'dclid', 'gbraid', 'wbraid', + // Facebook + 'fbclid', 'fb_action_ids', 'fb_action_types', 'fb_source', 'fb_ref', + // Twitter/X + 'twclid', 'twsrc', + // Microsoft/Bing + 'msclkid', 'mc_cid', 'mc_eid', + // Adobe + 'adobe_mc', 'adobe_mc_ref', 'adobe_mc_sdid', + // Mailchimp + 'mc_cid', 'mc_eid', + // HubSpot + 'hsCtaTracking', 'hsa_acc', 'hsa_cam', 'hsa_grp', 'hsa_ad', 'hsa_src', 'hsa_tgt', 'hsa_kw', 'hsa_mt', 'hsa_net', 'hsa_ver', + // Marketo + 'mkt_tok', + // YouTube + 'si', 'feature', 'kw', 'pp', + // Other common tracking + 'ref', 'referrer', 'source', 'campaign', 'medium', 'content', + 'yclid', 'srsltid', '_ga', '_gl', 'igshid', 'epik', 'pk_campaign', 'pk_kwd', + // Mobile app tracking + 'adjust_tracker', 'adjust_campaign', 'adjust_adgroup', 'adjust_creative', + // Amazon + 'tag', 'linkCode', 'creative', 'creativeASIN', 'linkId', 'ascsubtag', + // Affiliate tracking + 'aff_id', 'affiliate_id', 'aff', 'ref_', 'refer', + // Social media share tracking + 'share', 'shared', 'sharesource' + ]; + // Remove all tracking parameters + trackingParams.forEach(param => { + parsedUrl.searchParams.delete(param); + }); + // Remove any parameter that starts with utm_ or _ + Array.from(parsedUrl.searchParams.keys()).forEach(key => { + if (key.startsWith('utm_') || key.startsWith('_')) { + parsedUrl.searchParams.delete(key); + } + }); + return parsedUrl.toString(); + } + catch { + // If URL parsing fails, return original URL + return url; + } +} +/** + * Converts content to AsciiDoc format based on detected format + * This is the unified entry point - everything becomes AsciiDoc + */ +function convertToAsciidoc(content, format, linkBaseURL, options = {}) { + let asciidoc = ''; + switch (format) { + case types_1.ContentFormat.AsciiDoc: + // For AsciiDoc content, ensure proper formatting + asciidoc = content.replace(/\\n/g, '\n'); + // Ensure headers are on their own lines with proper spacing + asciidoc = asciidoc.replace(/(\S[^\n]*)\n(={1,6}\s+[^\n]+)/g, (_match, before, header) => { + return `${before}\n\n${header}`; + }); + break; + case types_1.ContentFormat.Wikipedia: + asciidoc = convertWikipediaToAsciidoc(content); + break; + case types_1.ContentFormat.Markdown: + asciidoc = convertMarkdownToAsciidoc(content); + break; + case types_1.ContentFormat.Plain: + default: + asciidoc = convertPlainTextToAsciidoc(content); + break; + } + // Process special elements for all content types + // Process wikilinks + asciidoc = processWikilinks(asciidoc, linkBaseURL); + // Process nostr: addresses if enabled + if (options.enableNostrAddresses !== false) { + asciidoc = processNostrAddresses(asciidoc, linkBaseURL); + } + // Process media URLs in markdown links/images first (before converting to AsciiDoc) + // This ensures media URLs in [text](url) or ![alt](url) format are detected + asciidoc = processMediaUrlsInMarkdown(asciidoc); + // Process media URLs (YouTube, Spotify, video, audio files) - for bare URLs + asciidoc = processMediaUrls(asciidoc); + // Process bare URLs (convert to AsciiDoc links) + asciidoc = processBareUrls(asciidoc); + // Process hashtags (after URLs to avoid conflicts) + asciidoc = processHashtags(asciidoc); + return asciidoc; +} +/** + * Converts Wikipedia markup to AsciiDoc format + * Handles Wikipedia-style headings, links, and formatting + */ +function convertWikipediaToAsciidoc(content) { + let asciidoc = content.replace(/\\n/g, '\n'); + // Convert Wikipedia headings: == Heading == to AsciiDoc == Heading + // Wikipedia uses == for level 2, === for level 3, etc. + // AsciiDoc uses = for title, == for level 1, === for level 2, etc. + // So Wikipedia level 2 (==) maps to AsciiDoc level 1 (==) + asciidoc = asciidoc.replace(/^(=+)\s+(.+?)\s+\1$/gm, (match, equals, heading) => { + const level = equals.length - 1; // Count = signs, subtract 1 for AsciiDoc mapping + const asciidocEquals = '='.repeat(level + 1); // AsciiDoc uses one more = for same level + return `${asciidocEquals} ${heading.trim()}`; + }); + // Convert Wikipedia bold: ''text'' to AsciiDoc *text* + asciidoc = asciidoc.replace(/''([^']+)''/g, '*$1*'); + // Convert Wikipedia italic: 'text' to AsciiDoc _text_ + // Be careful not to match apostrophes in words + asciidoc = asciidoc.replace(/(^|[^'])'([^']+)'([^']|$)/g, '$1_$2_$3'); + // Convert Wikipedia links: [[Page]] or [[Page|Display]] to wikilinks + // These will be processed by processWikilinks later, but we need to ensure + // they're in the right format. Wikipedia links are already in [[...]] format + // which matches our wikilink format, so they should work as-is. + // Convert Wikipedia external links: [URL text] to AsciiDoc link:URL[text] + asciidoc = asciidoc.replace(/\[(https?:\/\/[^\s\]]+)\s+([^\]]+)\]/g, 'link:$1[$2]'); + asciidoc = asciidoc.replace(/\[(https?:\/\/[^\s\]]+)\]/g, 'link:$1[$1]'); + // Convert Wikipedia lists (they use * or # similar to Markdown) + // This is handled similarly to Markdown, so we can reuse that logic + // But Wikipedia also uses : for definition lists and ; for term lists + // For now, we'll handle basic lists and let AsciiDoc handle the rest + // Convert horizontal rules: ---- to AsciiDoc ''' + asciidoc = asciidoc.replace(/^----+$/gm, "'''"); + return asciidoc; +} +/** + * Converts Markdown to AsciiDoc format + * Based on jumble's conversion patterns + */ +function convertMarkdownToAsciidoc(content) { + let asciidoc = content.replace(/\\n/g, '\n'); + // Fix spacing issues (but be careful not to break links and images) + // Process these BEFORE converting links/images to avoid conflicts + asciidoc = asciidoc.replace(/`([^`\n]+)`\s*\(([^)]+)\)/g, '`$1` ($2)'); + asciidoc = asciidoc.replace(/([a-zA-Z0-9])`([^`\n]+)`([a-zA-Z0-9])/g, '$1 `$2` $3'); + asciidoc = asciidoc.replace(/([a-zA-Z0-9])`([^`\n]+)`\s*\(/g, '$1 `$2` ('); + asciidoc = asciidoc.replace(/\)`([^`\n]+)`([a-zA-Z0-9])/g, ') `$1` $2'); + asciidoc = asciidoc.replace(/([a-zA-Z0-9])\)([a-zA-Z0-9])/g, '$1) $2'); + // Add space before == but not if it's part of a markdown link pattern + // Check that == is not immediately after ]( which would be a link + asciidoc = asciidoc.replace(/([a-zA-Z0-9])(? 🏕️) + // Only convert if node-emoji is available + if (emoji && emoji.emojify) { + asciidoc = emoji.emojify(asciidoc); + } + // Convert code blocks (handle both \n and \r\n line endings) + // Special handling for diagram languages: latex, plantuml, puml, bpmn + asciidoc = asciidoc.replace(/```(\w+)?\r?\n([\s\S]*?)\r?\n```/g, (_match, lang, code) => { + const trimmedCode = code.trim(); + if (trimmedCode.length === 0) + return ''; + const langLower = lang ? lang.toLowerCase() : ''; + // If it's a latex code block, always treat as code (not math) + if (langLower === 'latex') { + return `[source,latex]\n----\n${trimmedCode}\n----`; + } + // Handle PlantUML diagrams + if (langLower === 'plantuml' || langLower === 'puml') { + // Check if it already has @startuml/@enduml or @startbpmn/@endbpmn + if (trimmedCode.includes('@start') || trimmedCode.includes('@end')) { + return `[plantuml]\n----\n${trimmedCode}\n----`; + } + // If not, wrap it in @startuml/@enduml + return `[plantuml]\n----\n@startuml\n${trimmedCode}\n@enduml\n----`; + } + // Handle BPMN diagrams (using PlantUML BPMN syntax) + if (langLower === 'bpmn') { + // Check if it already has @startbpmn/@endbpmn + if (trimmedCode.includes('@startbpmn') && trimmedCode.includes('@endbpmn')) { + return `[plantuml]\n----\n${trimmedCode}\n----`; + } + // If not, wrap it in @startbpmn/@endbpmn + return `[plantuml]\n----\n@startbpmn\n${trimmedCode}\n@endbpmn\n----`; + } + // Check if it's ABC notation (starts with X:) + if (!lang && /^X:\s*\d+/m.test(trimmedCode)) { + // ABC notation - keep as plain text block, will be processed by music processor + return `----\n${trimmedCode}\n----`; + } + const hasCodePatterns = /[{}();=<>]|function|class|import|export|def |if |for |while |return |const |let |var |public |private |static |console\.log/.test(trimmedCode); + const isLikelyText = /^[A-Za-z\s.,!?\-'"]+$/.test(trimmedCode) && trimmedCode.length > 50; + const hasTooManySpaces = (trimmedCode.match(/\s{3,}/g) || []).length > 3; + const hasMarkdownPatterns = /^#{1,6}\s|^\*\s|^\d+\.\s|^\>\s|^\|.*\|/.test(trimmedCode); + if ((!hasCodePatterns && trimmedCode.length > 100) || isLikelyText || hasTooManySpaces || hasMarkdownPatterns) { + return _match; + } + return `[source${lang ? ',' + lang : ''}]\n----\n${trimmedCode}\n----`; + }); + // Handle inline code: LaTeX formulas in inline code should be rendered as math + // Pattern: `$formula$` should become $formula$ (math), not code + // Handle escaped brackets: `$[ ... \]$` and `$[\sqrt{...}\]$` + asciidoc = asciidoc.replace(/`(\$[^`]+\$)`/g, (match, formula) => { + // Extract the formula (remove the $ signs) + const mathContent = formula.slice(1, -1); + return `$${mathContent}$`; // Return as math, not code + }); + asciidoc = asciidoc.replace(/`([^`]+)`/g, '`$1`'); // Regular inline code + // Convert nested image links first: [![alt](img)](url) - image wrapped in link + // This must come before regular image processing + asciidoc = asciidoc.replace(/\[!\[([^\]]*)\]\(([^)]+?)\)\]\(([^)]+?)\)/g, (match, alt, imgUrl, linkUrl) => { + const cleanImgUrl = imgUrl.trim(); + const cleanLinkUrl = linkUrl.trim(); + const cleanAlt = alt.trim(); + // Check if linkUrl is a media URL + if (cleanLinkUrl.startsWith('MEDIA:')) { + return cleanLinkUrl; // Return the placeholder as-is + } + // Create a link with an image inside - don't escape brackets in URLs + // AsciiDoc can handle URLs with brackets if they're in the URL part + return `link:${cleanLinkUrl}[image:${cleanImgUrl}[${cleanAlt ? cleanAlt : 'link'}]]`; + }); + // Convert images (but not nested ones, which we already processed) + // Match: ![alt text](url) or ![](url) - handle empty alt text + // Use negative lookbehind to avoid matching nested image links + // Format: image::url[alt,width=100%] - matching jumble's format + asciidoc = asciidoc.replace(/(? { + let processedUrl = url.trim(); + const cleanAlt = alt.trim(); + // Check if it's already a MEDIA: placeholder (processed by processMediaUrlsInMarkdown) + if (processedUrl.startsWith('MEDIA:')) { + return processedUrl; // Return the placeholder as-is + } + // Clean URL (remove tracking parameters) + processedUrl = cleanUrl(processedUrl); + // Regular image - match jumble's format: image::url[alt,width=100%] + // Don't escape brackets - AsciiDoc handles URLs properly + return `image::${processedUrl}[${cleanAlt ? cleanAlt + ',' : ''}width=100%]`; + }); + // Convert anchor links: [text](#section-id) - these are internal links + asciidoc = asciidoc.replace(/(? { + const cleanText = text.trim(); + const cleanAnchor = anchor.trim(); + // AsciiDoc uses # for anchor links, but we need to normalize the anchor ID + // Convert to lowercase and replace spaces/special chars with hyphens + const normalizedAnchor = cleanAnchor.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, ''); + const escapedText = cleanText.replace(/([\[\]])/g, '\\$1'); + return `<<${normalizedAnchor},${escapedText}>>`; + }); + // Convert links (but not images or anchor links, which we already processed) + // Match: [text](url) - use negative lookbehind to avoid matching images + // Use non-greedy matching for URL to stop at first closing paren + // This ensures we don't capture trailing punctuation + asciidoc = asciidoc.replace(/(? { + let processedUrl = url.trim(); + const cleanText = text.trim(); + // Check if it's already a MEDIA: placeholder (processed by processMediaUrlsInMarkdown) + if (processedUrl.startsWith('MEDIA:')) { + return processedUrl; // Return the placeholder as-is + } + // Clean URL (remove tracking parameters) + processedUrl = cleanUrl(processedUrl); + // Handle WSS URLs: convert wss:// to https:// for display + if (processedUrl.startsWith('wss://')) { + processedUrl = processedUrl.replace(/^wss:\/\//, 'https://'); + } + // Regular link - don't escape brackets in URLs (AsciiDoc handles them) + // Only escape brackets in the link text if needed + const escapedText = cleanText.replace(/([\[\]])/g, '\\$1'); + return `link:${processedUrl}[${escapedText}]`; + }); + // Convert horizontal rules + asciidoc = asciidoc.replace(/^---$/gm, '\'\'\''); + asciidoc = asciidoc.replace(/^\*\*\*$/gm, '\'\'\''); // Also handle *** + // Convert lists - need to process them as blocks to preserve structure + // First, convert task lists (before regular lists) + // Task lists: - [x] or - [ ] or * [x] or * [ ] + asciidoc = asciidoc.replace(/^(\s*)([-*])\s+\[([ x])\]\s+(.+)$/gm, (_match, indent, bullet, checked, text) => { + // Use AsciiDoc checkbox syntax: * [x] Task text + // The checkbox will be rendered by AsciiDoctor + return `${indent}* [${checked === 'x' ? 'x' : ' '}] ${text}`; + }); + // Convert lists - process entire list blocks to ensure proper AsciiDoc formatting + // AsciiDoc lists need to be on their own lines with proper spacing + // Process lists in blocks to handle nested lists correctly + const lines = asciidoc.split('\n'); + const processedLines = []; + let inList = false; + let listType = null; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const isEmpty = line.trim() === ''; + const prevLine = i > 0 ? processedLines[processedLines.length - 1] : ''; + const prevLineIsEmpty = prevLine.trim() === ''; + // Check if this line is a list item (but not a task list, which we already processed) + const unorderedMatch = line.match(/^(\s*)([-*+])\s+(.+)$/); + const orderedMatch = line.match(/^(\s*)(\d+)\.\s+(.+)$/); + const isTaskList = line.match(/^(\s*)([-*])\s+\[([ x])\]\s+(.+)$/); + if (unorderedMatch && !isTaskList) { + const [, indent, , text] = unorderedMatch; + const indentLevel = indent.length; + // AsciiDoc uses 4 spaces per indentation level + // Markdown typically uses 2 or 4 spaces per level + // 2 spaces = 1 level (4 spaces), 4 spaces = 1 level (4 spaces) + const asciidocIndent = ' '.repeat(Math.ceil(indentLevel / 4)); + // Add blank line before list if not already in a list + // But don't add blank line if we're switching list types within the same list context + if (!inList) { + // Starting a new list - add blank line if previous line has content + if (processedLines.length > 0 && !prevLineIsEmpty) { + processedLines.push(''); + } + inList = true; + listType = 'unordered'; + } + else if (listType !== 'unordered') { + // Switching list types - don't add blank line, just change type + listType = 'unordered'; + } + processedLines.push(`${asciidocIndent}* ${text}`); + } + else if (orderedMatch) { + const [, indent, , text] = orderedMatch; + const indentLevel = indent.length; + // AsciiDoc uses 4 spaces per indentation level + // Markdown typically uses 2 or 4 spaces per level + // 2 spaces = 1 level (4 spaces), 4 spaces = 1 level (4 spaces) + const asciidocIndent = ' '.repeat(Math.ceil(indentLevel / 4)); + // Add blank line before list if not already in a list + // But don't add blank line if we're switching list types within the same list context + if (!inList) { + // Starting a new list - add blank line if previous line has content + if (processedLines.length > 0 && !prevLineIsEmpty) { + processedLines.push(''); + } + inList = true; + listType = 'ordered'; + } + else if (listType !== 'ordered') { + // Switching list types - don't add blank line, just change type + listType = 'ordered'; + } + processedLines.push(`${asciidocIndent}. ${text}`); + } + else { + // Not a list item + if (inList && !isEmpty) { + // End of list - add blank line after if the next line is not empty + if (i < lines.length - 1 && lines[i + 1].trim() !== '') { + processedLines.push(''); + } + inList = false; + listType = null; + } + processedLines.push(line); + } + } + asciidoc = processedLines.join('\n'); + // Convert blockquotes with attribution + asciidoc = asciidoc.replace(/^(>\s+.+(?:\n>\s+.+)*)/gm, (match) => { + const lines = match.split('\n').map(line => line.replace(/^>\s*/, '')); + let quoteBodyLines = []; + let attributionLine; + for (let i = lines.length - 1; i >= 0; i--) { + const line = lines[i].trim(); + if (line.startsWith('—') || line.startsWith('--')) { + attributionLine = line; + quoteBodyLines = lines.slice(0, i); + break; + } + } + const quoteContent = quoteBodyLines.filter(l => l.trim() !== '').join('\n').trim(); + if (attributionLine) { + let cleanedAttribution = attributionLine.replace(/^[—-]+/, '').trim(); + let author = ''; + let source = ''; + const linkMatch = cleanedAttribution.match(/^(.*?),?\s*link:([^[\\]]+)\[([^\\]]+)\]$/); + if (linkMatch) { + author = linkMatch[1].trim(); + source = `link:${linkMatch[2].trim()}[${linkMatch[3].trim()}]`; + } + else { + const parts = cleanedAttribution.split(',').map(p => p.trim()); + author = parts[0]; + if (parts.length > 1) { + source = parts.slice(1).join(', ').trim(); + } + } + return `[quote, ${author}, ${source}]\n____\n${quoteContent}\n____`; + } + else { + return `____\n${quoteContent}\n____`; + } + }); + // Convert tables with alignment support + asciidoc = asciidoc.replace(/(\|.*\|[\r\n]+\|[\s\-\|:]*[\r\n]+(\|.*\|[\r\n]+)*)/g, (match) => { + const lines = match.trim().split('\n').filter(line => line.trim()); + if (lines.length < 2) + return match; + const headerRow = lines[0]; + const separatorRow = lines[1]; + const dataRows = lines.slice(2); + if (!separatorRow.includes('-')) + return match; + // Parse alignment from separator row + // :--- = left, :----: = center, ---: = right, --- = default + const cells = separatorRow.split('|').filter(c => c.trim()); + const alignments = []; + cells.forEach((cell, index) => { + const trimmed = cell.trim(); + if (trimmed.startsWith(':') && trimmed.endsWith(':')) { + alignments[index] = '^'; // center (AsciiDoc uses ^ for center) + } + else if (trimmed.endsWith(':')) { + alignments[index] = '>'; // right + } + else if (trimmed.startsWith(':')) { + alignments[index] = '<'; // left (explicit) + } + else { + alignments[index] = '<'; // default left + } + }); + // Build cols attribute with alignments + const colsAttr = alignments.length > 0 + ? `[cols="${alignments.join(',')}"]` + : ''; + let tableAsciidoc = colsAttr ? `${colsAttr}\n` : ''; + tableAsciidoc += '|===\n'; + tableAsciidoc += headerRow + '\n'; + dataRows.forEach(row => { + tableAsciidoc += row + '\n'; + }); + tableAsciidoc += '|==='; + return tableAsciidoc; + }); + // Convert footnotes + const footnoteDefinitions = {}; + let tempAsciidoc = asciidoc; + tempAsciidoc = tempAsciidoc.replace(/^\[\^([^\]]+)\]:\s*([\s\S]*?)(?=\n\[\^|\n---|\n##|\n###|\n####|\n#####|\n######|$)/gm, (_, id, text) => { + footnoteDefinitions[id] = text.trim(); + return ''; + }); + asciidoc = tempAsciidoc.replace(/\[\^([^\]]+)\]/g, (match, id) => { + if (footnoteDefinitions[id]) { + return `footnote:[${footnoteDefinitions[id]}]`; + } + return match; + }); + return asciidoc; +} +/** + * Converts plain text to AsciiDoc format + * Preserves line breaks by converting single newlines to line continuations + */ +function convertPlainTextToAsciidoc(content) { + // Preserve double newlines (paragraph breaks) + // Convert single newlines to line continuations ( +\n) + return content + .replace(/\r\n/g, '\n') // Normalize line endings + .replace(/\n\n+/g, '\n\n') // Normalize multiple newlines to double + .replace(/([^\n])\n([^\n])/g, '$1 +\n$2'); // Single newlines become line continuations +} +/** + * Normalizes text to d-tag format + */ +function normalizeDtag(text) { + return text + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, ''); +} +/** + * Processes wikilinks: [[target]] or [[target|display text]] + * Converts to WIKILINK: placeholder format to protect from AsciiDoc processing + */ +function processWikilinks(content, linkBaseURL) { + // Process bookstr macro wikilinks: [[book::...]] + content = content.replace(/\[\[book::([^\]]+)\]\]/g, (_match, bookContent) => { + const cleanContent = bookContent.trim(); + return `BOOKSTR:${cleanContent}`; + }); + // Process standard wikilinks: [[Target Page]] or [[target page|see this]] + // Use placeholder format to prevent AsciiDoc from processing the brackets + content = content.replace(/\[\[([^|\]]+)(?:\|([^\]]+))?\]\]/g, (_match, target, displayText) => { + const cleanTarget = target.trim(); + const cleanDisplay = displayText ? displayText.trim() : cleanTarget; + const dTag = normalizeDtag(cleanTarget); + // Use placeholder format: WIKILINK:dtag|display + // This prevents AsciiDoc from interpreting the brackets + return `WIKILINK:${dTag}|${cleanDisplay}`; + }); + return content; +} +/** + * Processes nostr: addresses + * Only processes addresses with "nostr:" prefix - bare addresses are left as plaintext + * Converts to link:nostr:...[...] format + * Valid bech32 prefixes: npub, nprofile, nevent, naddr, note + */ +function processNostrAddresses(content, linkBaseURL) { + // Match nostr: followed by valid bech32 prefix and identifier + // Bech32 format: prefix + separator (1) + data (at least 6 chars for valid identifiers) + // Only match if it has "nostr:" prefix - bare addresses should remain as plaintext + const nostrPattern = /nostr:((?:npub|nprofile|nevent|naddr|note)1[a-z0-9]{6,})/gi; + return content.replace(nostrPattern, (_match, bech32Id) => { + return `link:nostr:${bech32Id}[${bech32Id}]`; + }); +} +/** + * Processes media URLs in markdown links and images + * Converts them to MEDIA: placeholders before markdown conversion + */ +function processMediaUrlsInMarkdown(content) { + let processed = content; + // Process YouTube URLs in markdown links: [text](youtube-url) + processed = processed.replace(/\[([^\]]+)\]\((?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:watch\?v=|embed\/|v\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})(?:[?&][^?\s<>"{}|\\^`\[\]()]*)?\)/gi, (_match, text, videoId) => { + return `MEDIA:youtube:${videoId}`; + }); + // Process Spotify URLs in markdown links: [text](spotify-url) + processed = processed.replace(/\[([^\]]+)\]\((?:https?:\/\/)?(?:open\.)?spotify\.com\/(track|album|playlist|artist|episode|show)\/([a-zA-Z0-9]+)(?:[?&][^?\s<>"{}|\\^`\[\]()]*)?\)/gi, (_match, text, type, id) => { + return `MEDIA:spotify:${type}:${id}`; + }); + // Process video files in markdown links/images: [text](video-url) or ![alt](video-url) + processed = processed.replace(/[!]?\[([^\]]*)\]\((https?:\/\/[^\s<>"{}|\\^`\[\]()]+\.(mp4|webm|ogg|m4v|mov|avi|mkv|flv|wmv))(?:\?[^\s<>"{}|\\^`\[\]()]*)?\)/gi, (_match, altOrText, url) => { + const cleanUrl = url.replace(/\?.*$/, ''); // Remove query params + return `MEDIA:video:${cleanUrl}`; + }); + // Process audio files in markdown links/images: [text](audio-url) or ![alt](audio-url) + processed = processed.replace(/[!]?\[([^\]]*)\]\((https?:\/\/[^\s<>"{}|\\^`\[\]()]+\.(mp3|m4a|ogg|wav|flac|aac|opus|wma))(?:\?[^\s<>"{}|\\^`\[\]()]*)?\)/gi, (_match, altOrText, url) => { + const cleanUrl = url.replace(/\?.*$/, ''); // Remove query params + return `MEDIA:audio:${cleanUrl}`; + }); + return processed; +} +/** + * Processes media URLs (YouTube, Spotify, video, audio files) in bare URLs + * Converts them to placeholders that will be rendered as embeds/players + */ +function processMediaUrls(content) { + // Process YouTube URLs + // Match: youtube.com/watch?v=, youtu.be/, youtube.com/embed/, youtube.com/v/ + content = content.replace(/(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:watch\?v=|embed\/|v\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})(?:[?&][^?\s<>"{}|\\^`\[\]()]*)?/gi, (match, videoId) => { + return `MEDIA:youtube:${videoId}`; + }); + // Process Spotify URLs + // Match: open.spotify.com/track/, open.spotify.com/album/, open.spotify.com/playlist/, open.spotify.com/artist/ + content = content.replace(/(?:https?:\/\/)?(?:open\.)?spotify\.com\/(track|album|playlist|artist|episode|show)\/([a-zA-Z0-9]+)(?:[?&][^?\s<>"{}|\\^`\[\]()]*)?/gi, (match, type, id) => { + return `MEDIA:spotify:${type}:${id}`; + }); + // Process video files (mp4, webm, ogg, m4v, mov, avi, etc.) + content = content.replace(/(?:https?:\/\/[^\s<>"{}|\\^`\[\]()]+)\.(mp4|webm|ogg|m4v|mov|avi|mkv|flv|wmv)(?:\?[^\s<>"{}|\\^`\[\]()]*)?/gi, (match, ext) => { + const url = match.replace(/\?.*$/, ''); // Remove query params for cleaner URL + return `MEDIA:video:${url}`; + }); + // Process audio files (mp3, m4a, ogg, wav, flac, aac, etc.) + content = content.replace(/(?:https?:\/\/[^\s<>"{}|\\^`\[\]()]+)\.(mp3|m4a|ogg|wav|flac|aac|opus|wma)(?:\?[^\s<>"{}|\\^`\[\]()]*)?/gi, (match, ext) => { + const url = match.replace(/\?.*$/, ''); // Remove query params for cleaner URL + return `MEDIA:audio:${url}`; + }); + return content; +} +/** + * Processes bare URLs and converts them to AsciiDoc links + * Matches http://, https://, wss://, and www. URLs that aren't already in markdown links + * Also handles bare image URLs (converts to images) + * Skips URLs inside code blocks (---- blocks) and inline code (backticks) + */ +function processBareUrls(content) { + // Protect code blocks and inline code from URL processing + // We'll process URLs, then restore code blocks + const codeBlockPlaceholders = []; + const inlineCodePlaceholders = []; + // Replace code blocks with placeholders + content = content.replace(/\[source[^\]]*\]\n----\n([\s\S]*?)\n----/g, (match, code) => { + const placeholder = `__CODEBLOCK_${codeBlockPlaceholders.length}__`; + codeBlockPlaceholders.push(match); + return placeholder; + }); + // Also handle plain code blocks (without [source]) + content = content.replace(/----\n([\s\S]*?)\n----/g, (match, code) => { + // Check if this is already a placeholder + if (match.includes('__CODEBLOCK_')) { + return match; + } + const placeholder = `__CODEBLOCK_${codeBlockPlaceholders.length}__`; + codeBlockPlaceholders.push(match); + return placeholder; + }); + // Replace inline code with placeholders + content = content.replace(/`([^`]+)`/g, (match, code) => { + const placeholder = `__INLINECODE_${inlineCodePlaceholders.length}__`; + inlineCodePlaceholders.push(match); + return placeholder; + }); + // First, handle bare image URLs (before regular URLs) + // Match image URLs: .jpg, .png, .gif, .webp, .svg, etc. + // Format: image::url[width=100%] - matching jumble's format + const imageUrlPattern = /(?"{}|\\^`\[\]()]+\.(jpe?g|png|gif|webp|svg|bmp|ico))(?:\?[^\s<>"{}|\\^`\[\]()]*)?/gi; + content = content.replace(imageUrlPattern, (match, url) => { + // Clean URL (remove tracking parameters) + const cleanedUrl = cleanUrl(url); + // Don't escape brackets - AsciiDoc handles URLs properly + return `image::${cleanedUrl}[width=100%]`; + }); + // Match URLs that aren't already in markdown link format + // Pattern: http://, https://, wss://, or www. followed by valid URL characters + // Use word boundary to avoid matching URLs that are part of other text + // Don't match if immediately after colon-space (like "hyperlink: www.example.com") + const urlPattern = /(?"{}|\\^`\[\]()]+|wss:\/\/[^\s<>"{}|\\^`\[\]()]+|www\.[^\s<>"{}|\\^`\[\]()]+)/gi; + content = content.replace(urlPattern, (match, url) => { + // Skip if this URL was already converted to an image + if (match.includes('image::')) { + return match; + } + // Ensure URL starts with http:// or https:// + let fullUrl = url; + if (url.startsWith('www.')) { + fullUrl = 'https://' + url; + } + else if (url.startsWith('wss://')) { + // Convert wss:// to https:// for display + fullUrl = url.replace(/^wss:\/\//, 'https://'); + } + // Clean URL (remove tracking parameters) + fullUrl = cleanUrl(fullUrl); + // Don't escape brackets in URLs - AsciiDoc handles them properly + // The URL is in the link: part, brackets in URLs are valid + // Use proper AsciiDoc link syntax: link:url[text] + return `link:${fullUrl}[${url}]`; + }); + // Restore inline code + inlineCodePlaceholders.forEach((code, index) => { + content = content.replace(`__INLINECODE_${index}__`, code); + }); + // Restore code blocks + codeBlockPlaceholders.forEach((code, index) => { + content = content.replace(`__CODEBLOCK_${index}__`, code); + }); + return content; +} +/** + * Processes hashtags + * Converts to hashtag:tag[#tag] format + * Handles hashtags at the beginning of lines to prevent line breaks + */ +function processHashtags(content) { + // Match # followed by word characters + // Match at word boundary OR at start of line OR after whitespace + // This ensures we don't match # in URLs or code, but do match at line start + return content.replace(/(^|\s|>)#([a-zA-Z0-9_]+)(?![a-zA-Z0-9_])/g, (match, before, hashtag) => { + const normalizedHashtag = hashtag.toLowerCase(); + // Preserve the space or line start before the hashtag to prevent line breaks + // Add a zero-width space or ensure proper spacing + const prefix = before === '' ? '' : before; + return `${prefix}hashtag:${normalizedHashtag}[#${hashtag}]`; + }); +} diff --git a/src/converters/to-asciidoc.ts b/src/converters/to-asciidoc.ts index 380bb63..54ba9c1 100644 --- a/src/converters/to-asciidoc.ts +++ b/src/converters/to-asciidoc.ts @@ -1,5 +1,89 @@ import { ContentFormat } from '../types'; +// Import node-emoji if available (optional dependency) +let emoji: any; +try { + emoji = require('node-emoji'); +} catch (e) { + // node-emoji not available, emoji conversion will be skipped + emoji = null; +} + +/** + * Clean URL by removing tracking parameters + * Based on jumble's cleanUrl function + */ +function cleanUrl(url: string): string { + try { + const parsedUrl = new URL(url); + + // List of tracking parameter prefixes and exact names to remove + const trackingParams = [ + // Google Analytics & Ads + 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', + 'utm_id', 'utm_source_platform', 'utm_creative_format', 'utm_marketing_tactic', + 'gclid', 'gclsrc', 'dclid', 'gbraid', 'wbraid', + + // Facebook + 'fbclid', 'fb_action_ids', 'fb_action_types', 'fb_source', 'fb_ref', + + // Twitter/X + 'twclid', 'twsrc', + + // Microsoft/Bing + 'msclkid', 'mc_cid', 'mc_eid', + + // Adobe + 'adobe_mc', 'adobe_mc_ref', 'adobe_mc_sdid', + + // Mailchimp + 'mc_cid', 'mc_eid', + + // HubSpot + 'hsCtaTracking', 'hsa_acc', 'hsa_cam', 'hsa_grp', 'hsa_ad', 'hsa_src', 'hsa_tgt', 'hsa_kw', 'hsa_mt', 'hsa_net', 'hsa_ver', + + // Marketo + 'mkt_tok', + + // YouTube + 'si', 'feature', 'kw', 'pp', + + // Other common tracking + 'ref', 'referrer', 'source', 'campaign', 'medium', 'content', + 'yclid', 'srsltid', '_ga', '_gl', 'igshid', 'epik', 'pk_campaign', 'pk_kwd', + + // Mobile app tracking + 'adjust_tracker', 'adjust_campaign', 'adjust_adgroup', 'adjust_creative', + + // Amazon + 'tag', 'linkCode', 'creative', 'creativeASIN', 'linkId', 'ascsubtag', + + // Affiliate tracking + 'aff_id', 'affiliate_id', 'aff', 'ref_', 'refer', + + // Social media share tracking + 'share', 'shared', 'sharesource' + ]; + + // Remove all tracking parameters + trackingParams.forEach(param => { + parsedUrl.searchParams.delete(param); + }); + + // Remove any parameter that starts with utm_ or _ + Array.from(parsedUrl.searchParams.keys()).forEach(key => { + if (key.startsWith('utm_') || key.startsWith('_')) { + parsedUrl.searchParams.delete(key); + } + }); + + return parsedUrl.toString(); + } catch { + // If URL parsing fails, return original URL + return url; + } +} + export interface ConvertOptions { enableNostrAddresses?: boolean; } @@ -146,14 +230,55 @@ function convertMarkdownToAsciidoc(content: string): string { asciidoc = asciidoc.replace(/\*(.+?)\*/g, '_$1_'); // Italic asciidoc = asciidoc.replace(/_(.+?)_/g, '_$1_'); // Italic asciidoc = asciidoc.replace(/~~(.+?)~~/g, '[line-through]#$1#'); // Strikethrough + asciidoc = asciidoc.replace(/==(.+?)==/g, '[highlight]#$1#'); // Text highlighting (GFM) asciidoc = asciidoc.replace(/~(.+?)~/g, '[subscript]#$1#'); // Subscript asciidoc = asciidoc.replace(/\^(.+?)\^/g, '[superscript]#$1#'); // Superscript + // Convert emoji shortcodes to Unicode (e.g., :tent: -> 🏕️) + // Only convert if node-emoji is available + if (emoji && emoji.emojify) { + asciidoc = emoji.emojify(asciidoc); + } + // Convert code blocks (handle both \n and \r\n line endings) + // Special handling for diagram languages: latex, plantuml, puml, bpmn asciidoc = asciidoc.replace(/```(\w+)?\r?\n([\s\S]*?)\r?\n```/g, (_match, lang, code) => { const trimmedCode = code.trim(); if (trimmedCode.length === 0) return ''; + const langLower = lang ? lang.toLowerCase() : ''; + + // If it's a latex code block, always treat as code (not math) + if (langLower === 'latex') { + return `[source,latex]\n----\n${trimmedCode}\n----`; + } + + // Handle PlantUML diagrams + if (langLower === 'plantuml' || langLower === 'puml') { + // Check if it already has @startuml/@enduml or @startbpmn/@endbpmn + if (trimmedCode.includes('@start') || trimmedCode.includes('@end')) { + return `[plantuml]\n----\n${trimmedCode}\n----`; + } + // If not, wrap it in @startuml/@enduml + return `[plantuml]\n----\n@startuml\n${trimmedCode}\n@enduml\n----`; + } + + // Handle BPMN diagrams (using PlantUML BPMN syntax) + if (langLower === 'bpmn') { + // Check if it already has @startbpmn/@endbpmn + if (trimmedCode.includes('@startbpmn') && trimmedCode.includes('@endbpmn')) { + return `[plantuml]\n----\n${trimmedCode}\n----`; + } + // If not, wrap it in @startbpmn/@endbpmn + return `[plantuml]\n----\n@startbpmn\n${trimmedCode}\n@endbpmn\n----`; + } + + // Check if it's ABC notation (starts with X:) + if (!lang && /^X:\s*\d+/m.test(trimmedCode)) { + // ABC notation - keep as plain text block, will be processed by music processor + return `----\n${trimmedCode}\n----`; + } + const hasCodePatterns = /[{}();=<>]|function|class|import|export|def |if |for |while |return |const |let |var |public |private |static |console\.log/.test(trimmedCode); const isLikelyText = /^[A-Za-z\s.,!?\-'"]+$/.test(trimmedCode) && trimmedCode.length > 50; const hasTooManySpaces = (trimmedCode.match(/\s{3,}/g) || []).length > 3; @@ -165,55 +290,186 @@ function convertMarkdownToAsciidoc(content: string): string { return `[source${lang ? ',' + lang : ''}]\n----\n${trimmedCode}\n----`; }); - asciidoc = asciidoc.replace(/`([^`]+)`/g, '`$1`'); // Inline code - asciidoc = asciidoc.replace(/`\$([^$]+)\$`/g, '`$\\$1\\$$`'); // Preserve LaTeX in code + + // Handle inline code: LaTeX formulas in inline code should be rendered as math + // Pattern: `$formula$` should become $formula$ (math), not code + // Handle escaped brackets: `$[ ... \]$` and `$[\sqrt{...}\]$` + asciidoc = asciidoc.replace(/`(\$[^`]+\$)`/g, (match, formula) => { + // Extract the formula (remove the $ signs) + const mathContent = formula.slice(1, -1); + return `$${mathContent}$`; // Return as math, not code + }); + asciidoc = asciidoc.replace(/`([^`]+)`/g, '`$1`'); // Regular inline code + + // Convert nested image links first: [![alt](img)](url) - image wrapped in link + // This must come before regular image processing + asciidoc = asciidoc.replace(/\[!\[([^\]]*)\]\(([^)]+?)\)\]\(([^)]+?)\)/g, (match, alt, imgUrl, linkUrl) => { + const cleanImgUrl = imgUrl.trim(); + const cleanLinkUrl = linkUrl.trim(); + const cleanAlt = alt.trim(); + + // Check if linkUrl is a media URL + if (cleanLinkUrl.startsWith('MEDIA:')) { + return cleanLinkUrl; // Return the placeholder as-is + } + + // Create a link with an image inside - don't escape brackets in URLs + // AsciiDoc can handle URLs with brackets if they're in the URL part + return `link:${cleanLinkUrl}[image:${cleanImgUrl}[${cleanAlt ? cleanAlt : 'link'}]]`; + }); - // Convert images first (before links, since images are links with ! prefix) + // Convert images (but not nested ones, which we already processed) // Match: ![alt text](url) or ![](url) - handle empty alt text - // Use non-greedy matching to stop at first closing paren - asciidoc = asciidoc.replace(/!\[([^\]]*)\]\(([^)]+?)\)/g, (match, alt, url) => { - const cleanUrl = url.trim(); + // Use negative lookbehind to avoid matching nested image links + // Format: image::url[alt,width=100%] - matching jumble's format + asciidoc = asciidoc.replace(/(? { + let processedUrl = url.trim(); const cleanAlt = alt.trim(); // Check if it's already a MEDIA: placeholder (processed by processMediaUrlsInMarkdown) - if (cleanUrl.startsWith('MEDIA:')) { - return cleanUrl; // Return the placeholder as-is + if (processedUrl.startsWith('MEDIA:')) { + return processedUrl; // Return the placeholder as-is } - // Regular image - escape special characters in URL for AsciiDoc - const escapedUrl = cleanUrl.replace(/([\[\]])/g, '\\$1'); - return `image::${escapedUrl}[${cleanAlt ? cleanAlt + ', ' : ''}width=100%]`; + // Clean URL (remove tracking parameters) + processedUrl = cleanUrl(processedUrl); + + // Regular image - match jumble's format: image::url[alt,width=100%] + // Don't escape brackets - AsciiDoc handles URLs properly + return `image::${processedUrl}[${cleanAlt ? cleanAlt + ',' : ''}width=100%]`; + }); + + // Convert anchor links: [text](#section-id) - these are internal links + asciidoc = asciidoc.replace(/(? { + const cleanText = text.trim(); + const cleanAnchor = anchor.trim(); + // AsciiDoc uses # for anchor links, but we need to normalize the anchor ID + // Convert to lowercase and replace spaces/special chars with hyphens + const normalizedAnchor = cleanAnchor.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, ''); + const escapedText = cleanText.replace(/([\[\]])/g, '\\$1'); + return `<<${normalizedAnchor},${escapedText}>>`; }); - // Convert links (but not images, which we already processed) + // Convert links (but not images or anchor links, which we already processed) // Match: [text](url) - use negative lookbehind to avoid matching images // Use non-greedy matching for URL to stop at first closing paren // This ensures we don't capture trailing punctuation asciidoc = asciidoc.replace(/(? { - const cleanUrl = url.trim(); + let processedUrl = url.trim(); const cleanText = text.trim(); // Check if it's already a MEDIA: placeholder (processed by processMediaUrlsInMarkdown) - if (cleanUrl.startsWith('MEDIA:')) { - return cleanUrl; // Return the placeholder as-is + if (processedUrl.startsWith('MEDIA:')) { + return processedUrl; // Return the placeholder as-is } - // Regular link - escape special AsciiDoc characters in both URL and text - const escapedUrl = cleanUrl.replace(/([\[\]])/g, '\\$1'); + // Clean URL (remove tracking parameters) + processedUrl = cleanUrl(processedUrl); + + // Handle WSS URLs: convert wss:// to https:// for display + if (processedUrl.startsWith('wss://')) { + processedUrl = processedUrl.replace(/^wss:\/\//, 'https://'); + } + + // Regular link - don't escape brackets in URLs (AsciiDoc handles them) + // Only escape brackets in the link text if needed const escapedText = cleanText.replace(/([\[\]])/g, '\\$1'); - return `link:${escapedUrl}[${escapedText}]`; + return `link:${processedUrl}[${escapedText}]`; }); // Convert horizontal rules asciidoc = asciidoc.replace(/^---$/gm, '\'\'\''); + asciidoc = asciidoc.replace(/^\*\*\*$/gm, '\'\'\''); // Also handle *** + + // Convert lists - need to process them as blocks to preserve structure + // First, convert task lists (before regular lists) + // Task lists: - [x] or - [ ] or * [x] or * [ ] + asciidoc = asciidoc.replace(/^(\s*)([-*])\s+\[([ x])\]\s+(.+)$/gm, (_match, indent, bullet, checked, text) => { + // Use AsciiDoc checkbox syntax: * [x] Task text + // The checkbox will be rendered by AsciiDoctor + return `${indent}* [${checked === 'x' ? 'x' : ' '}] ${text}`; + }); - // Convert unordered lists - asciidoc = asciidoc.replace(/^(\s*)\*\s+(.+)$/gm, '$1* $2'); - asciidoc = asciidoc.replace(/^(\s*)-\s+(.+)$/gm, '$1* $2'); - asciidoc = asciidoc.replace(/^(\s*)\+\s+(.+)$/gm, '$1* $2'); - - // Convert ordered lists - asciidoc = asciidoc.replace(/^(\s*)\d+\.\s+(.+)$/gm, '$1. $2'); + // Convert lists - process entire list blocks to ensure proper AsciiDoc formatting + // AsciiDoc lists need to be on their own lines with proper spacing + // Process lists in blocks to handle nested lists correctly + const lines = asciidoc.split('\n'); + const processedLines: string[] = []; + let inList = false; + let listType: 'unordered' | 'ordered' | null = null; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const isEmpty = line.trim() === ''; + const prevLine = i > 0 ? processedLines[processedLines.length - 1] : ''; + const prevLineIsEmpty = prevLine.trim() === ''; + + // Check if this line is a list item (but not a task list, which we already processed) + const unorderedMatch = line.match(/^(\s*)([-*+])\s+(.+)$/); + const orderedMatch = line.match(/^(\s*)(\d+)\.\s+(.+)$/); + const isTaskList = line.match(/^(\s*)([-*])\s+\[([ x])\]\s+(.+)$/); + + if (unorderedMatch && !isTaskList) { + const [, indent, , text] = unorderedMatch; + const indentLevel = indent.length; + // AsciiDoc uses 4 spaces per indentation level + // Markdown typically uses 2 or 4 spaces per level + // 2 spaces = 1 level (4 spaces), 4 spaces = 1 level (4 spaces) + const asciidocIndent = ' '.repeat(Math.ceil(indentLevel / 4)); + + // Add blank line before list if not already in a list + // But don't add blank line if we're switching list types within the same list context + if (!inList) { + // Starting a new list - add blank line if previous line has content + if (processedLines.length > 0 && !prevLineIsEmpty) { + processedLines.push(''); + } + inList = true; + listType = 'unordered'; + } else if (listType !== 'unordered') { + // Switching list types - don't add blank line, just change type + listType = 'unordered'; + } + + processedLines.push(`${asciidocIndent}* ${text}`); + } else if (orderedMatch) { + const [, indent, , text] = orderedMatch; + const indentLevel = indent.length; + // AsciiDoc uses 4 spaces per indentation level + // Markdown typically uses 2 or 4 spaces per level + // 2 spaces = 1 level (4 spaces), 4 spaces = 1 level (4 spaces) + const asciidocIndent = ' '.repeat(Math.ceil(indentLevel / 4)); + + // Add blank line before list if not already in a list + // But don't add blank line if we're switching list types within the same list context + if (!inList) { + // Starting a new list - add blank line if previous line has content + if (processedLines.length > 0 && !prevLineIsEmpty) { + processedLines.push(''); + } + inList = true; + listType = 'ordered'; + } else if (listType !== 'ordered') { + // Switching list types - don't add blank line, just change type + listType = 'ordered'; + } + + processedLines.push(`${asciidocIndent}. ${text}`); + } else { + // Not a list item + if (inList && !isEmpty) { + // End of list - add blank line after if the next line is not empty + if (i < lines.length - 1 && lines[i + 1].trim() !== '') { + processedLines.push(''); + } + inList = false; + listType = null; + } + processedLines.push(line); + } + } + + asciidoc = processedLines.join('\n'); // Convert blockquotes with attribution asciidoc = asciidoc.replace(/^(>\s+.+(?:\n>\s+.+)*)/gm, (match) => { @@ -258,8 +514,8 @@ function convertMarkdownToAsciidoc(content: string): string { } }); - // Convert tables - asciidoc = asciidoc.replace(/(\|.*\|[\r\n]+\|[\s\-\|]*[\r\n]+(\|.*\|[\r\n]+)*)/g, (match) => { + // Convert tables with alignment support + asciidoc = asciidoc.replace(/(\|.*\|[\r\n]+\|[\s\-\|:]*[\r\n]+(\|.*\|[\r\n]+)*)/g, (match) => { const lines = match.trim().split('\n').filter(line => line.trim()); if (lines.length < 2) return match; @@ -269,7 +525,31 @@ function convertMarkdownToAsciidoc(content: string): string { if (!separatorRow.includes('-')) return match; - let tableAsciidoc = '[cols="1,1"]\n|===\n'; + // Parse alignment from separator row + // :--- = left, :----: = center, ---: = right, --- = default + const cells = separatorRow.split('|').filter(c => c.trim()); + const alignments: string[] = []; + + cells.forEach((cell, index) => { + const trimmed = cell.trim(); + if (trimmed.startsWith(':') && trimmed.endsWith(':')) { + alignments[index] = '^'; // center (AsciiDoc uses ^ for center) + } else if (trimmed.endsWith(':')) { + alignments[index] = '>'; // right + } else if (trimmed.startsWith(':')) { + alignments[index] = '<'; // left (explicit) + } else { + alignments[index] = '<'; // default left + } + }); + + // Build cols attribute with alignments + const colsAttr = alignments.length > 0 + ? `[cols="${alignments.join(',')}"]` + : ''; + + let tableAsciidoc = colsAttr ? `${colsAttr}\n` : ''; + tableAsciidoc += '|===\n'; tableAsciidoc += headerRow + '\n'; dataRows.forEach(row => { tableAsciidoc += row + '\n'; @@ -349,12 +629,14 @@ function processWikilinks(content: string, linkBaseURL: string): string { /** * Processes nostr: addresses + * Only processes addresses with "nostr:" prefix - bare addresses are left as plaintext * Converts to link:nostr:...[...] format * Valid bech32 prefixes: npub, nprofile, nevent, naddr, note */ function processNostrAddresses(content: string, linkBaseURL: string): string { // Match nostr: followed by valid bech32 prefix and identifier // Bech32 format: prefix + separator (1) + data (at least 6 chars for valid identifiers) + // Only match if it has "nostr:" prefix - bare addresses should remain as plaintext const nostrPattern = /nostr:((?:npub|nprofile|nevent|naddr|note)1[a-z0-9]{6,})/gi; return content.replace(nostrPattern, (_match, bech32Id) => { return `link:nostr:${bech32Id}[${bech32Id}]`; @@ -427,26 +709,93 @@ function processMediaUrls(content: string): string { /** * Processes bare URLs and converts them to AsciiDoc links - * Matches http://, https://, and www. URLs that aren't already in markdown links + * Matches http://, https://, wss://, and www. URLs that aren't already in markdown links + * Also handles bare image URLs (converts to images) + * Skips URLs inside code blocks (---- blocks) and inline code (backticks) */ function processBareUrls(content: string): string { + // Protect code blocks and inline code from URL processing + // We'll process URLs, then restore code blocks + const codeBlockPlaceholders: string[] = []; + const inlineCodePlaceholders: string[] = []; + + // Replace code blocks with placeholders + content = content.replace(/\[source[^\]]*\]\n----\n([\s\S]*?)\n----/g, (match, code) => { + const placeholder = `__CODEBLOCK_${codeBlockPlaceholders.length}__`; + codeBlockPlaceholders.push(match); + return placeholder; + }); + + // Also handle plain code blocks (without [source]) + content = content.replace(/----\n([\s\S]*?)\n----/g, (match, code) => { + // Check if this is already a placeholder + if (match.includes('__CODEBLOCK_')) { + return match; + } + const placeholder = `__CODEBLOCK_${codeBlockPlaceholders.length}__`; + codeBlockPlaceholders.push(match); + return placeholder; + }); + + // Replace inline code with placeholders + content = content.replace(/`([^`]+)`/g, (match, code) => { + const placeholder = `__INLINECODE_${inlineCodePlaceholders.length}__`; + inlineCodePlaceholders.push(match); + return placeholder; + }); + + // First, handle bare image URLs (before regular URLs) + // Match image URLs: .jpg, .png, .gif, .webp, .svg, etc. + // Format: image::url[width=100%] - matching jumble's format + const imageUrlPattern = /(?"{}|\\^`\[\]()]+\.(jpe?g|png|gif|webp|svg|bmp|ico))(?:\?[^\s<>"{}|\\^`\[\]()]*)?/gi; + content = content.replace(imageUrlPattern, (match, url) => { + // Clean URL (remove tracking parameters) + const cleanedUrl = cleanUrl(url); + // Don't escape brackets - AsciiDoc handles URLs properly + return `image::${cleanedUrl}[width=100%]`; + }); + // Match URLs that aren't already in markdown link format - // Pattern: http://, https://, or www. followed by valid URL characters - // Use negative lookbehind to avoid matching URLs inside parentheses (markdown links) - // Match URLs that are not preceded by ]( (which would be a markdown link) - const urlPattern = /(?"{}|\\^`\[\]()]+|www\.[^\s<>"{}|\\^`\[\]()]+)/gi; + // Pattern: http://, https://, wss://, or www. followed by valid URL characters + // Use word boundary to avoid matching URLs that are part of other text + // Don't match if immediately after colon-space (like "hyperlink: www.example.com") + const urlPattern = /(?"{}|\\^`\[\]()]+|wss:\/\/[^\s<>"{}|\\^`\[\]()]+|www\.[^\s<>"{}|\\^`\[\]()]+)/gi; - return content.replace(urlPattern, (match, url) => { + content = content.replace(urlPattern, (match, url) => { + // Skip if this URL was already converted to an image + if (match.includes('image::')) { + return match; + } + // Ensure URL starts with http:// or https:// let fullUrl = url; if (url.startsWith('www.')) { fullUrl = 'https://' + url; + } else if (url.startsWith('wss://')) { + // Convert wss:// to https:// for display + fullUrl = url.replace(/^wss:\/\//, 'https://'); } - // Escape special AsciiDoc characters - const escapedUrl = fullUrl.replace(/([\[\]])/g, '\\$1'); - return `link:${escapedUrl}[${url}]`; + // Clean URL (remove tracking parameters) + fullUrl = cleanUrl(fullUrl); + + // Don't escape brackets in URLs - AsciiDoc handles them properly + // The URL is in the link: part, brackets in URLs are valid + // Use proper AsciiDoc link syntax: link:url[text] + return `link:${fullUrl}[${url}]`; }); + + // Restore inline code + inlineCodePlaceholders.forEach((code, index) => { + content = content.replace(`__INLINECODE_${index}__`, code); + }); + + // Restore code blocks + codeBlockPlaceholders.forEach((code, index) => { + content = content.replace(`__CODEBLOCK_${index}__`, code); + }); + + return content; } /** diff --git a/src/detector.js b/src/detector.js new file mode 100644 index 0000000..ece781f --- /dev/null +++ b/src/detector.js @@ -0,0 +1,70 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.detectFormat = detectFormat; +const types_1 = require("./types"); +/** + * Detects the content format based on content patterns + */ +function detectFormat(content) { + // Check for AsciiDoc indicators + const asciidocIndicators = [ + '= ', // Title + '== ', // Section + '=== ', // Subsection + 'include::', // Include directive + 'image::', // Image block + '[source', // Source block + '----', // Listing block + '....', // Literal block + '|===', // Table + 'link:', // AsciiDoc link format + 'wikilink:', // Wikilink macro + 'hashtag:', // Hashtag macro + ]; + let asciidocScore = 0; + for (const indicator of asciidocIndicators) { + if (content.includes(indicator)) { + asciidocScore++; + } + } + // Check for Wikipedia markup indicators (== Heading == format) + const wikipediaIndicators = [ + /^==+\s+.+?\s+==+$/m, // Wikipedia headings: == Heading == + /\[\[[^\]]+\]\]/, // Wikipedia links: [[Page]] + /''[^']+''/, // Wikipedia bold: ''text'' + /'[^']+'/, // Wikipedia italic: 'text' + ]; + let wikipediaScore = 0; + for (const indicator of wikipediaIndicators) { + if (indicator.test(content)) { + wikipediaScore++; + } + } + // Check for Markdown indicators (more specific patterns to avoid false positives) + const markdownIndicators = [ + /^#{1,6}\s+/m, // Heading at start of line + /```[\s\S]*?```/, // Code block + /\*\*[^*]+\*\*/, // Bold text + /^[-*+]\s+/m, // List item at start of line + /!\[[^\]]*\]\([^)]+\)/, // Image syntax + /\[[^\]]+\]\([^)]+\)/, // Link syntax + ]; + let markdownScore = 0; + for (const indicator of markdownIndicators) { + if (indicator.test(content)) { + markdownScore++; + } + } + // Determine format based on scores + // Wikipedia format takes precedence if detected (it's more specific) + if (wikipediaScore > 0 && wikipediaScore >= 2) { + return types_1.ContentFormat.Wikipedia; + } + else if (asciidocScore > markdownScore && asciidocScore >= 2) { + return types_1.ContentFormat.AsciiDoc; + } + else if (markdownScore > 0) { + return types_1.ContentFormat.Markdown; + } + return types_1.ContentFormat.Plain; +} diff --git a/src/extractors/frontmatter.js b/src/extractors/frontmatter.js new file mode 100644 index 0000000..3265847 --- /dev/null +++ b/src/extractors/frontmatter.js @@ -0,0 +1,160 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.extractFrontmatter = extractFrontmatter; +/** + * Extracts front matter from content + * Handles both YAML front matter (--- ... ---) and AsciiDoc document header attributes (:key: value) + * Returns the front matter object and the content + * For YAML: removes front matter from content + * For AsciiDoc: removes header from content and extracts as metadata (prevents header from appearing in rendered output) + */ +function extractFrontmatter(content) { + // First, try to match YAML front matter: ---\n...\n--- + const yamlFrontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/; + const yamlMatch = content.match(yamlFrontmatterRegex); + if (yamlMatch) { + const yamlContent = yamlMatch[1]; + const contentWithoutFrontmatter = yamlMatch[2]; + // Simple YAML parser for basic key-value pairs and arrays + // This is a basic implementation - for complex YAML, consider using a library + const frontmatter = {}; + const lines = yamlContent.split('\n'); + let currentKey = null; + let inArray = false; + let arrayKey = null; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const trimmed = line.trim(); + // Skip empty lines and comments + if (!trimmed || trimmed.startsWith('#')) { + if (inArray && trimmed === '') { + // Empty line might end the array + inArray = false; + arrayKey = null; + } + continue; + } + // Array item (line starting with -) + if (trimmed.startsWith('- ')) { + const item = trimmed.substring(2).trim(); + const cleanItem = item.replace(/^["']|["']$/g, ''); + if (arrayKey && frontmatter[arrayKey]) { + frontmatter[arrayKey].push(cleanItem); + } + else if (currentKey) { + // Start new array + arrayKey = currentKey; + inArray = true; + frontmatter[currentKey] = [cleanItem]; + } + continue; + } + // Key-value pair + const keyValueMatch = trimmed.match(/^(\w+):\s*(.+)$/); + if (keyValueMatch) { + const key = keyValueMatch[1]; + let value = keyValueMatch[2].trim(); + // Remove quotes if present + if ((value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'"))) { + value = value.slice(1, -1); + } + frontmatter[key] = value; + currentKey = key; + inArray = false; + arrayKey = null; + continue; + } + } + return { frontmatter: Object.keys(frontmatter).length > 0 ? frontmatter : undefined, content: contentWithoutFrontmatter }; + } + // If no YAML front matter, try to extract AsciiDoc document header attributes + // AsciiDoc format: = Title\nAuthor\nRevision\n:attribute: value\n... + // Match header lines until we hit a blank line (which separates header from body) + // The header consists of: title line, optional author/revision lines, and attribute lines + const lines = content.split('\n'); + let headerEndIndex = 0; + // Find where the header ends (first blank line after title/attributes) + if (lines[0] && lines[0].match(/^=+\s+/)) { + // We have a title line, now find where header ends + let i = 1; + // Skip author and revision lines (non-empty lines that don't start with :) + while (i < lines.length && lines[i].trim() && !lines[i].trim().startsWith(':')) { + i++; + } + // Now skip attribute lines (lines starting with :) + while (i < lines.length && lines[i].trim().startsWith(':')) { + i++; + } + // Skip the blank line that separates header from body + if (i < lines.length && lines[i].trim() === '') { + i++; + } + headerEndIndex = i; + } + // If we found a header, extract it + if (headerEndIndex > 0) { + const headerLines = lines.slice(0, headerEndIndex); + const headerContent = headerLines.join('\n'); + const contentWithoutHeader = lines.slice(headerEndIndex).join('\n'); + const frontmatter = {}; + const headerLinesArray = headerContent.split('\n'); + // Extract title (first line starting with =) + const titleMatch = headerLinesArray[0].match(/^=+\s+(.+)$/); + if (titleMatch) { + frontmatter.title = titleMatch[1].trim(); + } + // Extract author (line after title, if it doesn't start with :) + if (headerLinesArray.length > 1 && !headerLinesArray[1].trim().startsWith(':')) { + const authorLine = headerLinesArray[1].trim(); + if (authorLine && !authorLine.match(/^[\d.,\s:]+$/)) { + // Not a revision line (which has numbers, commas, colons) + frontmatter.author = authorLine; + } + } + // Extract revision (line with version, date, remark format: "2.9, October 31, 2021: Fall incarnation") + for (let i = 1; i < headerLinesArray.length; i++) { + const line = headerLinesArray[i].trim(); + if (line.match(/^[\d.,\s:]+$/)) { + // This looks like a revision line + const revisionMatch = line.match(/^([^,]+),\s*([^:]+)(?::\s*(.+))?$/); + if (revisionMatch) { + frontmatter.version = revisionMatch[1].trim(); + frontmatter.date = revisionMatch[2].trim(); + if (revisionMatch[3]) { + frontmatter.revision = revisionMatch[3].trim(); + } + } + break; + } + } + // Extract AsciiDoc attributes (:key: value) + for (const line of headerLinesArray) { + const trimmed = line.trim(); + if (trimmed.startsWith(':') && trimmed.includes(':')) { + const attrMatch = trimmed.match(/^:([^:]+):\s*(.+)$/); + if (attrMatch) { + const key = attrMatch[1].trim(); + let value = attrMatch[2].trim(); + // Remove quotes if present + if ((value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'"))) { + value = value.slice(1, -1); + } + // Handle comma-separated values (like keywords) + if (value.includes(',') && !value.includes(' ')) { + frontmatter[key] = value.split(',').map((v) => v.trim()); + } + else { + frontmatter[key] = value; + } + } + } + } + // For AsciiDoc, remove the header from content to prevent it from appearing in rendered output + // AsciiDoctor can work without the header, and we've already extracted the metadata + return { frontmatter: Object.keys(frontmatter).length > 0 ? frontmatter : undefined, content: contentWithoutHeader }; + } + // No front matter found + return { content }; +} diff --git a/src/extractors/frontmatter.ts b/src/extractors/frontmatter.ts new file mode 100644 index 0000000..5d7e53e --- /dev/null +++ b/src/extractors/frontmatter.ts @@ -0,0 +1,177 @@ +/** + * Extracts front matter from content + * Handles both YAML front matter (--- ... ---) and AsciiDoc document header attributes (:key: value) + * Returns the front matter object and the content + * For YAML: removes front matter from content + * For AsciiDoc: removes header from content and extracts as metadata (prevents header from appearing in rendered output) + */ +export function extractFrontmatter(content: string): { frontmatter?: Record; content: string } { + // First, try to match YAML front matter: ---\n...\n--- + const yamlFrontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/; + const yamlMatch = content.match(yamlFrontmatterRegex); + + if (yamlMatch) { + const yamlContent = yamlMatch[1]; + const contentWithoutFrontmatter = yamlMatch[2]; + + // Simple YAML parser for basic key-value pairs and arrays + // This is a basic implementation - for complex YAML, consider using a library + const frontmatter: Record = {}; + const lines = yamlContent.split('\n'); + let currentKey: string | null = null; + let inArray = false; + let arrayKey: string | null = null; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const trimmed = line.trim(); + + // Skip empty lines and comments + if (!trimmed || trimmed.startsWith('#')) { + if (inArray && trimmed === '') { + // Empty line might end the array + inArray = false; + arrayKey = null; + } + continue; + } + + // Array item (line starting with -) + if (trimmed.startsWith('- ')) { + const item = trimmed.substring(2).trim(); + const cleanItem = item.replace(/^["']|["']$/g, ''); + + if (arrayKey && frontmatter[arrayKey]) { + frontmatter[arrayKey].push(cleanItem); + } else if (currentKey) { + // Start new array + arrayKey = currentKey; + inArray = true; + frontmatter[currentKey] = [cleanItem]; + } + continue; + } + + // Key-value pair + const keyValueMatch = trimmed.match(/^(\w+):\s*(.+)$/); + if (keyValueMatch) { + const key = keyValueMatch[1]; + let value = keyValueMatch[2].trim(); + + // Remove quotes if present + if ((value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'"))) { + value = value.slice(1, -1); + } + + frontmatter[key] = value; + currentKey = key; + inArray = false; + arrayKey = null; + continue; + } + } + + return { frontmatter: Object.keys(frontmatter).length > 0 ? frontmatter : undefined, content: contentWithoutFrontmatter }; + } + + // If no YAML front matter, try to extract AsciiDoc document header attributes + // AsciiDoc format: = Title\nAuthor\nRevision\n:attribute: value\n... + // Match header lines until we hit a blank line (which separates header from body) + // The header consists of: title line, optional author/revision lines, and attribute lines + const lines = content.split('\n'); + let headerEndIndex = 0; + + // Find where the header ends (first blank line after title/attributes) + if (lines[0] && lines[0].match(/^=+\s+/)) { + // We have a title line, now find where header ends + let i = 1; + // Skip author and revision lines (non-empty lines that don't start with :) + while (i < lines.length && lines[i].trim() && !lines[i].trim().startsWith(':')) { + i++; + } + // Now skip attribute lines (lines starting with :) + while (i < lines.length && lines[i].trim().startsWith(':')) { + i++; + } + // Skip the blank line that separates header from body + if (i < lines.length && lines[i].trim() === '') { + i++; + } + headerEndIndex = i; + } + + // If we found a header, extract it + if (headerEndIndex > 0) { + const headerLines = lines.slice(0, headerEndIndex); + const headerContent = headerLines.join('\n'); + const contentWithoutHeader = lines.slice(headerEndIndex).join('\n'); + + const frontmatter: Record = {}; + const headerLinesArray = headerContent.split('\n'); + + // Extract title (first line starting with =) + const titleMatch = headerLinesArray[0].match(/^=+\s+(.+)$/); + if (titleMatch) { + frontmatter.title = titleMatch[1].trim(); + } + + // Extract author (line after title, if it doesn't start with :) + if (headerLinesArray.length > 1 && !headerLinesArray[1].trim().startsWith(':')) { + const authorLine = headerLinesArray[1].trim(); + if (authorLine && !authorLine.match(/^[\d.,\s:]+$/)) { + // Not a revision line (which has numbers, commas, colons) + frontmatter.author = authorLine; + } + } + + // Extract revision (line with version, date, remark format: "2.9, October 31, 2021: Fall incarnation") + for (let i = 1; i < headerLinesArray.length; i++) { + const line = headerLinesArray[i].trim(); + if (line.match(/^[\d.,\s:]+$/)) { + // This looks like a revision line + const revisionMatch = line.match(/^([^,]+),\s*([^:]+)(?::\s*(.+))?$/); + if (revisionMatch) { + frontmatter.version = revisionMatch[1].trim(); + frontmatter.date = revisionMatch[2].trim(); + if (revisionMatch[3]) { + frontmatter.revision = revisionMatch[3].trim(); + } + } + break; + } + } + + // Extract AsciiDoc attributes (:key: value) + for (const line of headerLinesArray) { + const trimmed = line.trim(); + if (trimmed.startsWith(':') && trimmed.includes(':')) { + const attrMatch = trimmed.match(/^:([^:]+):\s*(.+)$/); + if (attrMatch) { + const key = attrMatch[1].trim(); + let value = attrMatch[2].trim(); + + // Remove quotes if present + if ((value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'"))) { + value = value.slice(1, -1); + } + + // Handle comma-separated values (like keywords) + if (value.includes(',') && !value.includes(' ')) { + frontmatter[key] = value.split(',').map((v: string) => v.trim()); + } else { + frontmatter[key] = value; + } + } + } + } + + // For AsciiDoc, remove the header from content to prevent it from appearing in rendered output + // AsciiDoctor can work without the header, and we've already extracted the metadata + return { frontmatter: Object.keys(frontmatter).length > 0 ? frontmatter : undefined, content: contentWithoutHeader }; + } + + // No front matter found + return { content }; +} diff --git a/src/extractors/metadata.js b/src/extractors/metadata.js new file mode 100644 index 0000000..9c18aea --- /dev/null +++ b/src/extractors/metadata.js @@ -0,0 +1,243 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.extractMetadata = extractMetadata; +/** + * Extracts metadata from content before processing + */ +function extractMetadata(content, linkBaseURL) { + return { + nostrLinks: extractNostrLinks(content), + wikilinks: extractWikilinks(content), + hashtags: extractHashtags(content), + links: extractLinks(content, linkBaseURL), + media: extractMedia(content), + }; +} +/** + * Extract Nostr links from content + */ +function extractNostrLinks(content) { + const nostrLinks = []; + const seen = new Set(); + // Extract nostr: prefixed links (valid bech32 format) + const nostrMatches = content.match(/nostr:((?:npub|nprofile|nevent|naddr|note)1[a-z0-9]{6,})/gi) || []; + nostrMatches.forEach(match => { + const id = match.substring(6); // Remove 'nostr:' + const type = getNostrType(id); + if (type && !seen.has(id)) { + seen.add(id); + nostrLinks.push({ + type, + id, + text: match, + bech32: id, + }); + } + }); + return nostrLinks; +} +/** + * Extract wikilinks from content + */ +function extractWikilinks(content) { + const wikilinks = []; + const seen = new Set(); + // Match [[target]] or [[target|display]] + const wikilinkPattern = /\[\[([^|\]]+)(?:\|([^\]]+))?\]\]/g; + let match; + while ((match = wikilinkPattern.exec(content)) !== null) { + const target = match[1].trim(); + const display = match[2] ? match[2].trim() : target; + const dtag = normalizeDtag(target); + const key = `${dtag}|${display}`; + if (!seen.has(key)) { + seen.add(key); + wikilinks.push({ + dtag, + display, + original: match[0], + }); + } + } + return wikilinks; +} +/** + * Extract hashtags from content + * Excludes hashtags in URLs, code blocks, and inline code + */ +function extractHashtags(content) { + const hashtags = []; + const seen = new Set(); + // Remove code blocks first to avoid matching inside them + const codeBlockPattern = /```[\s\S]*?```/g; + const inlineCodePattern = /`[^`]+`/g; + const urlPattern = /https?:\/\/[^\s<>"']+/g; + let processedContent = content + .replace(codeBlockPattern, '') // Remove code blocks + .replace(inlineCodePattern, '') // Remove inline code + .replace(urlPattern, ''); // Remove URLs + // Extract hashtags: #hashtag (word boundary to avoid matching in URLs) + const hashtagPattern = /\B#([a-zA-Z0-9_]+)/g; + let match; + while ((match = hashtagPattern.exec(processedContent)) !== null) { + const tag = match[1].toLowerCase(); + if (!seen.has(tag)) { + hashtags.push(tag); + seen.add(tag); + } + } + return hashtags; +} +/** + * Extract regular links from content + */ +function extractLinks(content, linkBaseURL) { + const links = []; + const seen = new Set(); + // Extract markdown links: [text](url) - optimized to avoid double matching + const markdownLinkPattern = /\[([^\]]+)\]\(([^)]+)\)/g; + let markdownMatch; + while ((markdownMatch = markdownLinkPattern.exec(content)) !== null) { + const [, text, url] = markdownMatch; + if (!seen.has(url) && !isNostrUrl(url)) { + seen.add(url); + links.push({ + url, + text, + isExternal: isExternalUrl(url, linkBaseURL), + }); + } + } + // Extract asciidoc links: link:url[text] - optimized to avoid double matching + const asciidocLinkPattern = /link:([^\[]+)\[([^\]]+)\]/g; + let asciidocMatch; + while ((asciidocMatch = asciidocLinkPattern.exec(content)) !== null) { + const [, url, text] = asciidocMatch; + if (!seen.has(url) && !isNostrUrl(url)) { + seen.add(url); + links.push({ + url, + text, + isExternal: isExternalUrl(url, linkBaseURL), + }); + } + } + // Extract raw URLs (basic pattern) + const urlPattern = /https?:\/\/[^\s<>"']+/g; + const rawUrls = content.match(urlPattern) || []; + rawUrls.forEach(url => { + if (!seen.has(url) && !isNostrUrl(url)) { + seen.add(url); + links.push({ + url, + text: url, + isExternal: isExternalUrl(url, linkBaseURL), + }); + } + }); + return links; +} +/** + * Extract media URLs from content + */ +function extractMedia(content) { + const media = []; + const seen = new Set(); + // Extract markdown images: ![alt](url) - optimized to avoid double matching + const markdownImagePattern = /!\[[^\]]*\]\(([^)]+)\)/g; + let markdownImageMatch; + while ((markdownImageMatch = markdownImagePattern.exec(content)) !== null) { + const url = markdownImageMatch[1]; + if (url && !seen.has(url)) { + if (isImageUrl(url) || isVideoUrl(url)) { + media.push(url); + seen.add(url); + } + } + } + // Extract asciidoc images: image::url[alt] - optimized to avoid double matching + const asciidocImagePattern = /image::([^\[]+)\[/g; + let asciidocImageMatch; + while ((asciidocImageMatch = asciidocImagePattern.exec(content)) !== null) { + const url = asciidocImageMatch[1]; + if (url && !seen.has(url)) { + if (isImageUrl(url) || isVideoUrl(url)) { + media.push(url); + seen.add(url); + } + } + } + // Extract raw image/video URLs + const urlPattern = /https?:\/\/[^\s<>"']+/g; + const rawUrls = content.match(urlPattern) || []; + rawUrls.forEach(url => { + if (!seen.has(url) && (isImageUrl(url) || isVideoUrl(url))) { + media.push(url); + seen.add(url); + } + }); + return media; +} +/** + * Get Nostr identifier type + */ +function getNostrType(id) { + if (id.startsWith('npub')) + return 'npub'; + if (id.startsWith('nprofile')) + return 'nprofile'; + if (id.startsWith('nevent')) + return 'nevent'; + if (id.startsWith('naddr')) + return 'naddr'; + if (id.startsWith('note')) + return 'note'; + return null; +} +/** + * Normalize text to d-tag format + */ +function normalizeDtag(text) { + return text + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, ''); +} +/** + * Check if URL is external + */ +function isExternalUrl(url, linkBaseURL) { + if (!linkBaseURL) + return true; + try { + // Use a simple string-based check for Node.js compatibility + // Extract hostname from URL string + const urlMatch = url.match(/^https?:\/\/([^\/]+)/); + const baseMatch = linkBaseURL.match(/^https?:\/\/([^\/]+)/); + if (urlMatch && baseMatch) { + return urlMatch[1] !== baseMatch[1]; + } + return true; + } + catch { + return true; + } +} +/** + * Check if URL is a Nostr URL + */ +function isNostrUrl(url) { + return url.startsWith('nostr:') || getNostrType(url) !== null; +} +/** + * Check if URL is an image + */ +function isImageUrl(url) { + return /\.(jpeg|jpg|png|gif|webp|svg)$/i.test(url); +} +/** + * Check if URL is a video + */ +function isVideoUrl(url) { + return /\.(mp4|webm|ogg)$/i.test(url); +} diff --git a/src/parser.js b/src/parser.js new file mode 100644 index 0000000..7896c68 --- /dev/null +++ b/src/parser.js @@ -0,0 +1,92 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Parser = void 0; +exports.defaultOptions = defaultOptions; +exports.process = process; +const detector_1 = require("./detector"); +const to_asciidoc_1 = require("./converters/to-asciidoc"); +const asciidoc_1 = require("./processors/asciidoc"); +const metadata_1 = require("./extractors/metadata"); +const frontmatter_1 = require("./extractors/frontmatter"); +/** + * Default parser options + */ +function defaultOptions() { + return { + linkBaseURL: '', + enableAsciiDoc: true, + enableMarkdown: true, + enableCodeHighlighting: true, + enableLaTeX: true, + enableMusicalNotation: true, + enableNostrAddresses: true, + }; +} +/** + * Main parser for Nostr event content + * Handles multiple content formats: AsciiDoc, Markdown, code syntax, + * LaTeX, musical notation, and nostr: prefixed addresses + * + * Everything is converted to AsciiDoc first, then processed through AsciiDoctor + */ +class Parser { + constructor(options = {}) { + const defaults = defaultOptions(); + this.options = { + linkBaseURL: options.linkBaseURL ?? defaults.linkBaseURL ?? '', + enableAsciiDoc: options.enableAsciiDoc ?? defaults.enableAsciiDoc ?? true, + enableMarkdown: options.enableMarkdown ?? defaults.enableMarkdown ?? true, + enableCodeHighlighting: options.enableCodeHighlighting ?? defaults.enableCodeHighlighting ?? true, + enableLaTeX: options.enableLaTeX ?? defaults.enableLaTeX ?? true, + enableMusicalNotation: options.enableMusicalNotation ?? defaults.enableMusicalNotation ?? true, + enableNostrAddresses: options.enableNostrAddresses ?? defaults.enableNostrAddresses ?? true, + wikilinkUrl: options.wikilinkUrl ?? defaults.wikilinkUrl, + hashtagUrl: options.hashtagUrl ?? defaults.hashtagUrl, + }; + } + /** + * Process Nostr event content and return HTML + * Automatically detects the content format and processes accordingly + * Everything is converted to AsciiDoc first, then processed through AsciiDoctor + */ + async process(content) { + // Extract frontmatter first (before any other processing) + const { frontmatter, content: contentWithoutFrontmatter } = (0, frontmatter_1.extractFrontmatter)(content); + // Extract metadata from content (after removing frontmatter) + const metadata = (0, metadata_1.extractMetadata)(contentWithoutFrontmatter, this.options.linkBaseURL); + // Detect content format (on content without frontmatter) + const format = (0, detector_1.detectFormat)(contentWithoutFrontmatter); + // Convert everything to AsciiDoc format first + const asciidocContent = (0, to_asciidoc_1.convertToAsciidoc)(contentWithoutFrontmatter, format, this.options.linkBaseURL, { + enableNostrAddresses: this.options.enableNostrAddresses, + }); + // Process through AsciiDoctor + const result = await (0, asciidoc_1.processAsciidoc)(asciidocContent, { + enableCodeHighlighting: this.options.enableCodeHighlighting, + enableLaTeX: this.options.enableLaTeX, + enableMusicalNotation: this.options.enableMusicalNotation, + originalContent: contentWithoutFrontmatter, // Pass original for LaTeX detection + linkBaseURL: this.options.linkBaseURL, // Pass linkBaseURL for link processing + wikilinkUrl: this.options.wikilinkUrl, // Pass wikilink URL format + hashtagUrl: this.options.hashtagUrl, // Pass hashtag URL format + }); + // Combine with extracted metadata and frontmatter + return { + ...result, + frontmatter, + nostrLinks: metadata.nostrLinks, + wikilinks: metadata.wikilinks, + hashtags: metadata.hashtags, + links: metadata.links, + media: metadata.media, + }; + } +} +exports.Parser = Parser; +/** + * Convenience function to process content with default options + */ +async function process(content, options) { + const parser = new Parser(options); + return parser.process(content); +} diff --git a/src/parser.ts b/src/parser.ts index b3420c1..19896e1 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -3,6 +3,7 @@ import { detectFormat } from './detector'; import { convertToAsciidoc } from './converters/to-asciidoc'; import { processAsciidoc } from './processors/asciidoc'; import { extractMetadata } from './extractors/metadata'; +import { extractFrontmatter } from './extractors/frontmatter'; /** * Default parser options @@ -27,7 +28,7 @@ export function defaultOptions(): ParserOptions { * Everything is converted to AsciiDoc first, then processed through AsciiDoctor */ export class Parser { - private options: Required; + private options: Required> & Pick; constructor(options: ParserOptions = {}) { const defaults = defaultOptions(); @@ -39,6 +40,8 @@ export class Parser { enableLaTeX: options.enableLaTeX ?? defaults.enableLaTeX ?? true, enableMusicalNotation: options.enableMusicalNotation ?? defaults.enableMusicalNotation ?? true, enableNostrAddresses: options.enableNostrAddresses ?? defaults.enableNostrAddresses ?? true, + wikilinkUrl: options.wikilinkUrl ?? defaults.wikilinkUrl, + hashtagUrl: options.hashtagUrl ?? defaults.hashtagUrl, }; } @@ -48,15 +51,18 @@ export class Parser { * Everything is converted to AsciiDoc first, then processed through AsciiDoctor */ async process(content: string): Promise { - // Extract metadata from original content (before conversion) - const metadata = extractMetadata(content, this.options.linkBaseURL); + // Extract frontmatter first (before any other processing) + const { frontmatter, content: contentWithoutFrontmatter } = extractFrontmatter(content); + + // Extract metadata from content (after removing frontmatter) + const metadata = extractMetadata(contentWithoutFrontmatter, this.options.linkBaseURL); - // Detect content format - const format = detectFormat(content); + // Detect content format (on content without frontmatter) + const format = detectFormat(contentWithoutFrontmatter); // Convert everything to AsciiDoc format first const asciidocContent = convertToAsciidoc( - content, + contentWithoutFrontmatter, format, this.options.linkBaseURL, { @@ -71,14 +77,17 @@ export class Parser { enableCodeHighlighting: this.options.enableCodeHighlighting, enableLaTeX: this.options.enableLaTeX, enableMusicalNotation: this.options.enableMusicalNotation, - originalContent: content, // Pass original for LaTeX detection + originalContent: contentWithoutFrontmatter, // Pass original for LaTeX detection linkBaseURL: this.options.linkBaseURL, // Pass linkBaseURL for link processing + wikilinkUrl: this.options.wikilinkUrl, // Pass wikilink URL format + hashtagUrl: this.options.hashtagUrl, // Pass hashtag URL format } ); - // Combine with extracted metadata + // Combine with extracted metadata and frontmatter return { ...result, + frontmatter, nostrLinks: metadata.nostrLinks, wikilinks: metadata.wikilinks, hashtags: metadata.hashtags, diff --git a/src/processors/asciidoc.js b/src/processors/asciidoc.js new file mode 100644 index 0000000..fcc3611 --- /dev/null +++ b/src/processors/asciidoc.js @@ -0,0 +1,148 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.processAsciidoc = processAsciidoc; +const core_1 = __importDefault(require("@asciidoctor/core")); +const html_utils_1 = require("./html-utils"); +const html_postprocess_1 = require("./html-postprocess"); +const asciidoctorInstance = (0, core_1.default)(); +/** + * Processes AsciiDoc content to HTML using AsciiDoctor + * Uses AsciiDoctor's built-in highlight.js and LaTeX support + */ +async function processAsciidoc(content, options = {}) { + const { enableCodeHighlighting = true, enableLaTeX = true, enableMusicalNotation = true, } = options; + // Check if content starts with level 3+ headers + // Asciidoctor article doctype requires level 1 (=) or level 2 (==) before level 3 (===) + // If content starts with level 3+, use book doctype + const firstHeaderMatch = content.match(/^(={1,6})\s+/m); + let doctype = 'article'; + if (firstHeaderMatch) { + const firstHeaderLevel = firstHeaderMatch[1].length; + if (firstHeaderLevel >= 3) { + doctype = 'book'; + } + } + try { + const result = asciidoctorInstance.convert(content, { + safe: 'safe', + backend: 'html5', + doctype: doctype, + attributes: { + 'showtitle': true, + 'sectanchors': true, + 'sectlinks': true, + 'toc': 'left', + 'toclevels': 6, + 'toc-title': 'Table of Contents', + 'source-highlighter': enableCodeHighlighting ? 'highlight.js' : 'none', + 'stem': enableLaTeX ? 'latexmath' : 'none', + 'plantuml': 'plantuml', // Enable PlantUML diagram support + 'data-uri': true, + 'imagesdir': '', + 'linkcss': false, + 'stylesheet': '', + 'stylesdir': '', + 'prewrap': true, + 'sectnums': false, + 'sectnumlevels': 6, + 'experimental': true, + 'compat-mode': false, + 'attribute-missing': 'warn', + 'attribute-undefined': 'warn', + 'skip-front-matter': true, + 'source-indent': 0, + 'indent': 0, + 'tabsize': 2, + 'tabwidth': 2, + 'hardbreaks': false, + 'paragraph-rewrite': 'normal', + 'sectids': true, + 'idprefix': '', + 'idseparator': '-', + 'sectidprefix': '', + 'sectidseparator': '-' + } + }); + const htmlString = typeof result === 'string' ? result : result.toString(); + // Extract table of contents from HTML + const { toc, contentWithoutTOC } = (0, html_utils_1.extractTOC)(htmlString); + // Sanitize HTML to prevent XSS + const sanitized = (0, html_utils_1.sanitizeHTML)(contentWithoutTOC); + // Post-process HTML: convert macros to HTML, add styling, etc. + const processed = (0, html_postprocess_1.postProcessHtml)(sanitized, { + enableMusicalNotation, + linkBaseURL: options.linkBaseURL, + wikilinkUrl: options.wikilinkUrl, + hashtagUrl: options.hashtagUrl, + }); + // Process links: add target="_blank" to external links + const processedWithLinks = options.linkBaseURL + ? (0, html_utils_1.processLinks)(processed, options.linkBaseURL) + : processed; + // Also process TOC + const tocSanitized = (0, html_utils_1.sanitizeHTML)(toc); + const tocProcessed = (0, html_postprocess_1.postProcessHtml)(tocSanitized, { + enableMusicalNotation: false, // Don't process music in TOC + linkBaseURL: options.linkBaseURL, + wikilinkUrl: options.wikilinkUrl, + hashtagUrl: options.hashtagUrl, + }); + // Process links in TOC as well + const tocProcessedWithLinks = options.linkBaseURL + ? (0, html_utils_1.processLinks)(tocProcessed, options.linkBaseURL) + : tocProcessed; + // Check for LaTeX in original content (more reliable than checking HTML) + const contentToCheck = options.originalContent || content; + const hasLaTeX = enableLaTeX && hasMathContent(contentToCheck); + // Check for musical notation in processed HTML + const hasMusicalNotation = enableMusicalNotation && (/class="abc-notation"|class="lilypond-notation"|class="chord"|class="musicxml-notation"/.test(processed)); + return { + content: processedWithLinks, + tableOfContents: tocProcessedWithLinks, + hasLaTeX, + hasMusicalNotation, + nostrLinks: [], // Will be populated by metadata extraction + wikilinks: [], + hashtags: [], + links: [], + media: [], + }; + } + catch (error) { + // Fallback to plain text with error logging + const errorMessage = error instanceof Error ? error.message : String(error); + // Use process.stderr.write for Node.js compatibility instead of console.error + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const nodeProcess = globalThis.process; + if (nodeProcess?.stderr) { + nodeProcess.stderr.write(`Error processing AsciiDoc: ${errorMessage}\n`); + } + // Escape HTML in content for safe display + const escapedContent = (0, html_utils_1.sanitizeHTML)(content); + return { + content: `

${escapedContent}

`, + tableOfContents: '', + hasLaTeX: false, + hasMusicalNotation: false, + nostrLinks: [], + wikilinks: [], + hashtags: [], + links: [], + media: [], + }; + } +} +/** + * Check if content has LaTeX math + * Based on jumble's detection pattern + */ +function hasMathContent(content) { + // Check for inline math: $...$ or \(...\) + const inlineMath = /\$[^$]+\$|\\\([^)]+\\\)/.test(content); + // Check for block math: $$...$$ or \[...\] + const blockMath = /\$\$[\s\S]*?\$\$|\\\[[\s\S]*?\\\]/.test(content); + return inlineMath || blockMath; +} diff --git a/src/processors/asciidoc.ts b/src/processors/asciidoc.ts index dfca98f..14fd66a 100644 --- a/src/processors/asciidoc.ts +++ b/src/processors/asciidoc.ts @@ -11,6 +11,8 @@ export interface ProcessOptions { enableMusicalNotation?: boolean; originalContent?: string; // Original content for LaTeX detection linkBaseURL?: string; // Base URL for link processing + wikilinkUrl?: string | ((dtag: string) => string); // Custom URL format for wikilinks + hashtagUrl?: string | ((topic: string) => string); // Custom URL format for hashtags } /** @@ -54,6 +56,7 @@ export async function processAsciidoc( 'toc-title': 'Table of Contents', 'source-highlighter': enableCodeHighlighting ? 'highlight.js' : 'none', 'stem': enableLaTeX ? 'latexmath' : 'none', + 'plantuml': 'plantuml', // Enable PlantUML diagram support 'data-uri': true, 'imagesdir': '', 'linkcss': false, @@ -93,6 +96,8 @@ export async function processAsciidoc( const processed = postProcessHtml(sanitized, { enableMusicalNotation, linkBaseURL: options.linkBaseURL, + wikilinkUrl: options.wikilinkUrl, + hashtagUrl: options.hashtagUrl, }); // Process links: add target="_blank" to external links @@ -105,6 +110,8 @@ export async function processAsciidoc( const tocProcessed = postProcessHtml(tocSanitized, { enableMusicalNotation: false, // Don't process music in TOC linkBaseURL: options.linkBaseURL, + wikilinkUrl: options.wikilinkUrl, + hashtagUrl: options.hashtagUrl, }); // Process links in TOC as well diff --git a/src/processors/html-postprocess.js b/src/processors/html-postprocess.js new file mode 100644 index 0000000..3ddef1c --- /dev/null +++ b/src/processors/html-postprocess.js @@ -0,0 +1,594 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.postProcessHtml = postProcessHtml; +const music_1 = require("./music"); +/** + * Post-processes HTML output from AsciiDoctor + * Converts AsciiDoc macros to HTML with data attributes and CSS classes + */ +function postProcessHtml(html, options = {}) { + let processed = html; + // Convert bookstr markers to HTML placeholders + processed = processed.replace(/BOOKSTR:([^<>\s]+)/g, (_match, bookContent) => { + const escaped = bookContent.replace(/"/g, '"').replace(/'/g, '''); + return ``; + }); + // Convert hashtag links to HTML + processed = processed.replace(/hashtag:([^[]+)\[([^\]]+)\]/g, (_match, normalizedHashtag, displayText) => { + // HTML escape the display text + const escapedDisplay = displayText + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + // If hashtagUrl is configured, make it a clickable link + if (options.hashtagUrl) { + let url; + if (typeof options.hashtagUrl === 'function') { + url = options.hashtagUrl(normalizedHashtag); + } + else { + // String template with {topic} placeholder + url = options.hashtagUrl.replace(/{topic}/g, normalizedHashtag); + } + // Escape URL for HTML attribute + const escapedUrl = url.replace(/"/g, '"').replace(/'/g, '''); + return `${escapedDisplay}`; + } + else { + // Default: Use span instead of tag - same color as links but no underline and not clickable + return `${escapedDisplay}`; + } + }); + // Convert WIKILINK:dtag|display placeholder format to HTML + // Match WIKILINK:dtag|display, ensuring we don't match across HTML tags + processed = processed.replace(/WIKILINK:([^|<>]+)\|([^<>\s]+)/g, (_match, dTag, displayText) => { + const escapedDtag = dTag.trim().replace(/"/g, '"'); + const escapedDisplay = displayText.trim() + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + // Generate URL using custom format or default + let url; + if (options.wikilinkUrl) { + if (typeof options.wikilinkUrl === 'function') { + url = options.wikilinkUrl(dTag.trim()); + } + else { + // String template with {dtag} placeholder + url = options.wikilinkUrl.replace(/{dtag}/g, dTag.trim()); + } + } + else { + // Default format + url = `/events?d=${escapedDtag}`; + } + // Escape URL for HTML attribute + const escapedUrl = url.replace(/"/g, '"').replace(/'/g, '''); + return `${escapedDisplay}`; + }); + // Convert nostr: links to HTML + processed = processed.replace(/link:nostr:([^[]+)\[([^\]]+)\]/g, (_match, bech32Id, displayText) => { + const nostrType = getNostrType(bech32Id); + if (nostrType === 'nevent' || nostrType === 'naddr' || nostrType === 'note') { + // Render as embedded event placeholder + const escaped = bech32Id.replace(/"/g, '"'); + return `
Loading embedded event...
`; + } + else if (nostrType === 'npub' || nostrType === 'nprofile') { + // Render as user handle + const escaped = bech32Id.replace(/"/g, '"'); + return `@${displayText}`; + } + else { + // Fallback to regular link + const escaped = bech32Id.replace(/"/g, '"'); + return `${displayText}`; + } + }); + // Convert any leftover link: macros that AsciiDoctor didn't convert + // This handles cases where AsciiDoctor couldn't parse the link (e.g., link text with special chars) + // Pattern: link:url[text] where url is http/https and text can contain any characters + processed = processed.replace(/link:(https?:\/\/[^\[]+)\[([^\]]+)\]/g, (_match, url, text) => { + // Escape URL and text for HTML attributes + const escapedUrl = url.replace(/"/g, '"').replace(/'/g, '''); + const escapedText = text + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + // Check if link text contains wss:// or ws:// - these are relay URLs, don't add OpenGraph + const isRelayUrl = /wss?:\/\//i.test(text); + if (isRelayUrl) { + // Simple link without OpenGraph wrapper + return `${escapedText} `; + } + else { + // Regular link - will be processed by OpenGraph handler if external + return `${escapedText} `; + } + }); + // Process media URLs (YouTube, Spotify, video, audio) + processed = processMedia(processed); + // Process OpenGraph links (external links that should have rich previews) + processed = processOpenGraphLinks(processed, options.linkBaseURL); + // Process images: add max-width styling and data attributes + processed = processImages(processed); + // Process musical notation if enabled + if (options.enableMusicalNotation) { + processed = (0, music_1.processMusicalNotation)(processed); + } + // Clean up any leftover markdown syntax + processed = cleanupMarkdown(processed); + // Add styling classes + processed = addStylingClasses(processed); + // Hide raw ToC text + processed = hideRawTocText(processed); + return processed; +} +/** + * Get Nostr identifier type + */ +function getNostrType(id) { + if (id.startsWith('npub')) + return 'npub'; + if (id.startsWith('nprofile')) + return 'nprofile'; + if (id.startsWith('nevent')) + return 'nevent'; + if (id.startsWith('naddr')) + return 'naddr'; + if (id.startsWith('note')) + return 'note'; + return null; +} +/** + * Process media URLs (YouTube, Spotify, video, audio) + * Converts MEDIA: placeholders to HTML embeds/players + */ +function processMedia(html) { + let processed = html; + // Process YouTube embeds + processed = processed.replace(/MEDIA:youtube:([a-zA-Z0-9_-]+)/g, (_match, videoId) => { + const escapedId = videoId.replace(/"/g, '"'); + return `
+ +
`; + }); + // Process Spotify embeds + processed = processed.replace(/MEDIA:spotify:(track|album|playlist|artist|episode|show):([a-zA-Z0-9]+)/g, (_match, type, id) => { + const escapedType = type.replace(/"/g, '"'); + const escapedId = id.replace(/"/g, '"'); + return `
+ +
`; + }); + // Process video files + processed = processed.replace(/MEDIA:video:(https?:\/\/[^\s<>"{}|\\^`\[\]()]+)/g, (_match, url) => { + const escapedUrl = url + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + return `
+ +
`; + }); + // Process audio files + processed = processed.replace(/MEDIA:audio:(https?:\/\/[^\s<>"{}|\\^`\[\]()]+)/g, (_match, url) => { + const escapedUrl = url + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + return `
+ +
`; + }); + return processed; +} +/** + * Process OpenGraph links - mark external links for OpenGraph preview fetching + */ +function processOpenGraphLinks(html, linkBaseURL) { + // First, clean up any corrupted HTML fragments that might interfere + // Remove "link:" prefixes that appear before links (AsciiDoc syntax that shouldn't be in HTML) + // This happens when AsciiDoctor doesn't fully convert link:url[text] syntax or when + // there's literal text like "should render like link:" before an anchor tag + let processed = html; + // Remove "link:" that appears immediately before anchor tags (most common case) + // Match "link:" followed by optional whitespace and then \s])link:([a-zA-Z0-9])/gi, '$1$2'); + // Also handle cases where "link:" appears with whitespace before anchor tags + processed = processed.replace(/\s+link:\s*(?= { + // If href contains HTML tags, extract just the URL part + const urlMatch = corruptedHref.match(/(https?:\/\/[^\s<>"']+)/i); + if (urlMatch) { + return `href="${urlMatch[1]}"`; + } + return match; // If we can't fix it, leave it (will be skipped by validation) + }); + // Clean up any malformed anchor tag fragments that might cause issues + processed = processed.replace(/]*<[^"'>]*)["']/gi, (match, corruptedHref) => { + // Skip corrupted anchor tags - they'll be handled by the main regex with validation + return match; + }); + // Clean up links inside code blocks - AsciiDoctor creates them but they should be plain text + // Remove tags inside blocks, keeping only the link text + processed = processed.replace(/]*>([\s\S]*?)<\/code>/gi, (match, content) => { + // Remove any tags inside code blocks, keeping only the text content + const cleaned = content.replace(/]*>(.*?)<\/a>/gi, '$1'); + return `${cleaned}`; + }); + // Also clean up links inside pre blocks + processed = processed.replace(/]*>([\s\S]*?)<\/pre>/gi, (match, content) => { + const cleaned = content.replace(/]*>(.*?)<\/a>/gi, '$1'); + return `
${cleaned}
`; + }); + // Now protect code blocks and pre blocks by replacing them with placeholders + const codeBlockPlaceholders = []; + const preBlockPlaceholders = []; + // Replace pre blocks first (they can contain code blocks) + processed = processed.replace(/]*>([\s\S]*?)<\/pre>/gi, (match) => { + const placeholder = `__PREBLOCK_${preBlockPlaceholders.length}__`; + preBlockPlaceholders.push(match); + return placeholder; + }); + // Replace code blocks + processed = processed.replace(/]*>([\s\S]*?)<\/code>/gi, (match) => { + const placeholder = `__CODEBLOCK_${codeBlockPlaceholders.length}__`; + codeBlockPlaceholders.push(match); + return placeholder; + }); + // Extract base domain from linkBaseURL if provided + let baseDomain = null; + if (linkBaseURL) { + try { + const urlMatch = linkBaseURL.match(/^https?:\/\/([^\/]+)/); + if (urlMatch) { + baseDomain = urlMatch[1]; + } + } + catch { + // Ignore parsing errors + } + } + // Before processing, remove any corrupted opengraph containers that might have been created + // These have malformed data-og-url attributes containing HTML fragments + // Match all spans with data-og-url and check if they're corrupted + // Use a pattern that matches spans with data-og-url, then check the attribute value + processed = processed.replace(/]*data-og-url=["']([^"']+)["'][^>]*>[\s\S]*?<\/span>/gi, (match) => { + // This span has a corrupted data-og-url (contains <) + // Extract the clean URL from the beginning of the attribute value + const dataOgUrlMatch = match.match(/data-og-url=["']([^"']+)["']/i); + if (dataOgUrlMatch && dataOgUrlMatch[1]) { + // Extract just the URL part (everything before the first <) + const urlMatch = dataOgUrlMatch[1].match(/(https?:\/\/[^\s<>"']+)/i); + if (urlMatch) { + const cleanUrl = urlMatch[1]; + // Extract the link text from inside the span + const linkMatch = match.match(/]*>(.*?)<\/a>/i); + const linkText = linkMatch ? linkMatch[1] : cleanUrl; + // Return a clean opengraph container with the fixed URL + const escapedUrl = cleanUrl.replace(/"/g, '"').replace(/'/g, '''); + return ` +
${linkText} + + `; + } + // If we can't extract a clean URL, just remove the corrupted span and keep any text + const textMatch = match.match(/>([^<]+) tag with proper structure + processed = processed.replace(/]*\s+)?href\s*=\s*["'](https?:\/\/[^"']{1,2048})["']([^>]*?)>(.*?)<\/a>/gis, (match, before, href, after, linkText) => { + // CRITICAL: Validate href FIRST - if it contains ANY HTML tags or fragments, skip immediately + // This prevents corrupted HTML from being created + if (!href) { + return match; // Skip if no href + } + // Skip if href contains HTML tags or looks corrupted - be very strict + // Check for common HTML fragments that indicate corruption + if (href.includes('<') || href.includes('>') || href.includes('href=') || href.includes('') || href.includes('"']+$/i.test(href)) { + return match; // Skip if href doesn't match clean URL pattern + } + // Validate href is a proper URL (starts with http:// or https:// and doesn't contain invalid chars) + if (!/^https?:\/\/[^\s<>"']+$/i.test(href)) { + return match; // Skip if href doesn't match URL pattern + } + // Skip if the match contains unclosed tags or corrupted HTML + const openATags = (match.match(//g) || []).length; + if (openATags !== closeATags || openATags !== 1) { + return match; // Multiple or mismatched tags = corrupted + } + // Skip if match contains nested HTML that looks corrupted + if (match.includes('href="') && match.split('href="').length > 2) { + return match; // Multiple href attributes = corrupted + } + // Skip if it's already a media embed, nostr link, wikilink, or opengraph link + if (match.includes('class="wikilink"') || + match.includes('class="nostr-link"') || + match.includes('class="opengraph-link"') || + match.includes('data-embedded-note') || + match.includes('youtube-embed') || + match.includes('spotify-embed') || + match.includes('media-embed') || + match.includes('opengraph-link-container')) { + return match; + } + // Skip if it's a media file URL + if (/\.(mp4|webm|ogg|m4v|mov|avi|mkv|flv|wmv|mp3|m4a|wav|flac|aac|opus|wma|jpeg|jpg|png|gif|webp|svg)$/i.test(href)) { + return match; + } + // Skip if it's YouTube or Spotify (already handled as media) + if (/youtube\.com|youtu\.be|spotify\.com/i.test(href)) { + return match; + } + // Skip if link text contains wss:// or ws:// - these are relay URLs, not web pages + // They don't need OpenGraph previews + if (/wss?:\/\//i.test(linkText)) { + return match; + } + // Check if it's an external link (not same domain) + let isExternal = true; + if (baseDomain) { + try { + const hrefMatch = href.match(/^https?:\/\/([^\/]+)/); + if (hrefMatch && hrefMatch[1] === baseDomain) { + isExternal = false; + } + } + catch { + // If parsing fails, assume external + } + } + // Only process external links + if (!isExternal) { + return match; + } + // Escape the URL for data attribute + const escapedUrl = href + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, '''); + // Add data attribute for OpenGraph fetching and wrap in container + // The actual OpenGraph fetching will be done client-side via JavaScript + return ` + ${linkText} + + `; + }); + // Restore code blocks + codeBlockPlaceholders.forEach((codeBlock, index) => { + processed = processed.replace(`__CODEBLOCK_${index}__`, codeBlock); + }); + // Restore pre blocks + preBlockPlaceholders.forEach((preBlock, index) => { + processed = processed.replace(`__PREBLOCK_${index}__`, preBlock); + }); + return processed; +} +/** + * Process images: add max-width styling and data attributes + */ +function processImages(html) { + const imageUrls = []; + const imageUrlRegex = /]+src=["']([^"']+)["'][^>]*>/gi; + let match; + while ((match = imageUrlRegex.exec(html)) !== null) { + const url = match[1]; + if (url && !imageUrls.includes(url)) { + imageUrls.push(url); + } + } + return html.replace(/]+)>/gi, (imgTag, attributes) => { + const srcMatch = attributes.match(/src=["']([^"']+)["']/i); + if (!srcMatch) + return imgTag; + const src = srcMatch[1]; + const currentIndex = imageUrls.indexOf(src); + let updatedAttributes = attributes; + if (updatedAttributes.match(/class=["']/i)) { + updatedAttributes = updatedAttributes.replace(/class=["']([^"']*)["']/i, (_match, classes) => { + const cleanedClasses = classes.replace(/max-w-\[?[^\s\]]+\]?/g, '').trim(); + const newClasses = cleanedClasses + ? `${cleanedClasses} max-w-[400px] object-contain cursor-zoom-in` + : 'max-w-[400px] object-contain cursor-zoom-in'; + return `class="${newClasses}"`; + }); + } + else { + updatedAttributes += ` class="max-w-[400px] h-auto object-contain cursor-zoom-in"`; + } + updatedAttributes += ` data-asciidoc-image="true" data-image-index="${currentIndex}" data-image-src="${src.replace(/"/g, '"')}"`; + return ``; + }); +} +/** + * Clean URL by removing tracking parameters + * Based on jumble's cleanUrl function + */ +function cleanUrl(url) { + try { + const parsedUrl = new URL(url); + // List of tracking parameter prefixes and exact names to remove + const trackingParams = [ + // Google Analytics & Ads + 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', + 'utm_id', 'utm_source_platform', 'utm_creative_format', 'utm_marketing_tactic', + 'gclid', 'gclsrc', 'dclid', 'gbraid', 'wbraid', + // Facebook + 'fbclid', 'fb_action_ids', 'fb_action_types', 'fb_source', 'fb_ref', + // Twitter/X + 'twclid', 'twsrc', + // Microsoft/Bing + 'msclkid', 'mc_cid', 'mc_eid', + // Adobe + 'adobe_mc', 'adobe_mc_ref', 'adobe_mc_sdid', + // Mailchimp + 'mc_cid', 'mc_eid', + // HubSpot + 'hsCtaTracking', 'hsa_acc', 'hsa_cam', 'hsa_grp', 'hsa_ad', 'hsa_src', 'hsa_tgt', 'hsa_kw', 'hsa_mt', 'hsa_net', 'hsa_ver', + // Marketo + 'mkt_tok', + // YouTube + 'si', 'feature', 'kw', 'pp', + // Other common tracking + 'ref', 'referrer', 'source', 'campaign', 'medium', 'content', + 'yclid', 'srsltid', '_ga', '_gl', 'igshid', 'epik', 'pk_campaign', 'pk_kwd', + // Mobile app tracking + 'adjust_tracker', 'adjust_campaign', 'adjust_adgroup', 'adjust_creative', + // Amazon + 'tag', 'linkCode', 'creative', 'creativeASIN', 'linkId', 'ascsubtag', + // Affiliate tracking + 'aff_id', 'affiliate_id', 'aff', 'ref_', 'refer', + // Social media share tracking + 'share', 'shared', 'sharesource' + ]; + // Remove all tracking parameters + trackingParams.forEach(param => { + parsedUrl.searchParams.delete(param); + }); + // Remove any parameter that starts with utm_ or _ + Array.from(parsedUrl.searchParams.keys()).forEach(key => { + if (key.startsWith('utm_') || key.startsWith('_')) { + parsedUrl.searchParams.delete(key); + } + }); + return parsedUrl.toString(); + } + catch { + // If URL parsing fails, return original URL + return url; + } +} +/** + * Clean up leftover markdown syntax + */ +function cleanupMarkdown(html) { + let cleaned = html; + // Clean up markdown image syntax + cleaned = cleaned.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_match, alt, url) => { + const altText = alt || ''; + // Clean URL (remove tracking parameters) + const cleanedUrl = cleanUrl(url); + // Escape for HTML attribute + const escapedUrl = cleanedUrl.replace(/"/g, '"').replace(/'/g, '''); + return `${altText}`; + }); + // Clean up markdown link syntax + cleaned = cleaned.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, text, url) => { + if (cleaned.includes(`href="${url}"`)) { + return _match; + } + // Clean URL (remove tracking parameters) + const cleanedUrl = cleanUrl(url); + // Escape for HTML attribute + const escapedUrl = cleanedUrl.replace(/"/g, '"').replace(/'/g, '''); + // Escape text for HTML + const escapedText = text.replace(/&/g, '&').replace(//g, '>'); + return `${escapedText} `; + }); + return cleaned; +} +/** + * Add proper CSS classes for styling + */ +function addStylingClasses(html) { + let styled = html; + // Add strikethrough styling + styled = styled.replace(/([^<]+)<\/span>/g, '$1'); + // Add subscript styling + styled = styled.replace(/([^<]+)<\/span>/g, '$1'); + // Add superscript styling + styled = styled.replace(/([^<]+)<\/span>/g, '$1'); + // Add code highlighting classes + styled = styled.replace(/
/g, '
');
+    styled = styled.replace(//g, '');
+    return styled;
+}
+/**
+ * Hide raw AsciiDoc ToC text
+ */
+function hideRawTocText(html) {
+    let cleaned = html;
+    cleaned = cleaned.replace(/]*>.*?Table of Contents.*?\(\d+\).*?<\/h[1-6]>/gi, '');
+    cleaned = cleaned.replace(/]*>.*?Table of Contents.*?\(\d+\).*?<\/p>/gi, '');
+    cleaned = cleaned.replace(/]*>.*?Assumptions.*?\[n=0\].*?<\/p>/gi, '');
+    return cleaned;
+}
diff --git a/src/processors/html-postprocess.ts b/src/processors/html-postprocess.ts
index 4a9b5b6..643fc72 100644
--- a/src/processors/html-postprocess.ts
+++ b/src/processors/html-postprocess.ts
@@ -3,6 +3,10 @@ import { processMusicalNotation } from './music';
 export interface PostProcessOptions {
   enableMusicalNotation?: boolean;
   linkBaseURL?: string;
+  /** Custom URL format for wikilinks */
+  wikilinkUrl?: string | ((dtag: string) => string);
+  /** Custom URL format for hashtags */
+  hashtagUrl?: string | ((topic: string) => string);
 }
 
 /**
@@ -18,7 +22,7 @@ export function postProcessHtml(html: string, options: PostProcessOptions = {}):
     return ``;
   });
 
-  // Convert hashtag links to HTML (styled like links but not clickable)
+  // Convert hashtag links to HTML
   processed = processed.replace(/hashtag:([^[]+)\[([^\]]+)\]/g, (_match, normalizedHashtag, displayText) => {
     // HTML escape the display text
     const escapedDisplay = displayText
@@ -27,8 +31,25 @@ export function postProcessHtml(html: string, options: PostProcessOptions = {}):
       .replace(/>/g, '>')
       .replace(/"/g, '"')
       .replace(/'/g, ''');
-    // Use span instead of  tag - same color as links but no underline and not clickable
-    return `${escapedDisplay}`;
+    
+    // If hashtagUrl is configured, make it a clickable link
+    if (options.hashtagUrl) {
+      let url: string;
+      if (typeof options.hashtagUrl === 'function') {
+        url = options.hashtagUrl(normalizedHashtag);
+      } else {
+        // String template with {topic} placeholder
+        url = options.hashtagUrl.replace(/{topic}/g, normalizedHashtag);
+      }
+      
+      // Escape URL for HTML attribute
+      const escapedUrl = url.replace(/"/g, '"').replace(/'/g, ''');
+      
+      return `${escapedDisplay}`;
+    } else {
+      // Default: Use span instead of  tag - same color as links but no underline and not clickable
+      return `${escapedDisplay}`;
+    }
   });
 
   // Convert WIKILINK:dtag|display placeholder format to HTML
@@ -42,10 +63,24 @@ export function postProcessHtml(html: string, options: PostProcessOptions = {}):
       .replace(/"/g, '"')
       .replace(/'/g, ''');
     
-    // Always use relative URL for wikilinks (works on any domain)
-    const url = `/events?d=${escapedDtag}`;
+    // Generate URL using custom format or default
+    let url: string;
+    if (options.wikilinkUrl) {
+      if (typeof options.wikilinkUrl === 'function') {
+        url = options.wikilinkUrl(dTag.trim());
+      } else {
+        // String template with {dtag} placeholder
+        url = options.wikilinkUrl.replace(/{dtag}/g, dTag.trim());
+      }
+    } else {
+      // Default format
+      url = `/events?d=${escapedDtag}`;
+    }
+    
+    // Escape URL for HTML attribute
+    const escapedUrl = url.replace(/"/g, '"').replace(/'/g, ''');
     
-    return `${escapedDisplay}`;
+    return `${escapedDisplay}`;
   });
 
   // Convert nostr: links to HTML
@@ -67,6 +102,31 @@ export function postProcessHtml(html: string, options: PostProcessOptions = {}):
     }
   });
 
+  // Convert any leftover link: macros that AsciiDoctor didn't convert
+  // This handles cases where AsciiDoctor couldn't parse the link (e.g., link text with special chars)
+  // Pattern: link:url[text] where url is http/https and text can contain any characters
+  processed = processed.replace(/link:(https?:\/\/[^\[]+)\[([^\]]+)\]/g, (_match, url, text) => {
+    // Escape URL and text for HTML attributes
+    const escapedUrl = url.replace(/"/g, '"').replace(/'/g, ''');
+    const escapedText = text
+      .replace(/&/g, '&')
+      .replace(//g, '>')
+      .replace(/"/g, '"')
+      .replace(/'/g, ''');
+    
+    // Check if link text contains wss:// or ws:// - these are relay URLs, don't add OpenGraph
+    const isRelayUrl = /wss?:\/\//i.test(text);
+    
+    if (isRelayUrl) {
+      // Simple link without OpenGraph wrapper
+      return `${escapedText} `;
+    } else {
+      // Regular link - will be processed by OpenGraph handler if external
+      return `${escapedText} `;
+    }
+  });
+
   // Process media URLs (YouTube, Spotify, video, audio)
   processed = processMedia(processed);
 
@@ -192,7 +252,71 @@ function processMedia(html: string): string {
  * Process OpenGraph links - mark external links for OpenGraph preview fetching
  */
 function processOpenGraphLinks(html: string, linkBaseURL?: string): string {
+  // First, clean up any corrupted HTML fragments that might interfere
+  // Remove "link:" prefixes that appear before links (AsciiDoc syntax that shouldn't be in HTML)
+  // This happens when AsciiDoctor doesn't fully convert link:url[text] syntax or when
+  // there's literal text like "should render like link:" before an anchor tag
   let processed = html;
+  
+  // Remove "link:" that appears immediately before anchor tags (most common case)
+  // Match "link:" followed by optional whitespace and then \s])link:([a-zA-Z0-9])/gi, '$1$2');
+  
+  // Also handle cases where "link:" appears with whitespace before anchor tags
+  processed = processed.replace(/\s+link:\s*(?= {
+    // If href contains HTML tags, extract just the URL part
+    const urlMatch = corruptedHref.match(/(https?:\/\/[^\s<>"']+)/i);
+    if (urlMatch) {
+      return `href="${urlMatch[1]}"`;
+    }
+    return match; // If we can't fix it, leave it (will be skipped by validation)
+  });
+  
+  // Clean up any malformed anchor tag fragments that might cause issues
+  processed = processed.replace(/]*<[^"'>]*)["']/gi, (match, corruptedHref) => {
+    // Skip corrupted anchor tags - they'll be handled by the main regex with validation
+    return match;
+  });
+  
+  // Clean up links inside code blocks - AsciiDoctor creates them but they should be plain text
+  // Remove  tags inside  blocks, keeping only the link text
+  processed = processed.replace(/]*>([\s\S]*?)<\/code>/gi, (match, content) => {
+    // Remove any  tags inside code blocks, keeping only the text content
+    const cleaned = content.replace(/]*>(.*?)<\/a>/gi, '$1');
+    return `${cleaned}`;
+  });
+  
+  // Also clean up links inside pre blocks
+  processed = processed.replace(/]*>([\s\S]*?)<\/pre>/gi, (match, content) => {
+    const cleaned = content.replace(/]*>(.*?)<\/a>/gi, '$1');
+    return `
${cleaned}
`; + }); + + // Now protect code blocks and pre blocks by replacing them with placeholders + const codeBlockPlaceholders: string[] = []; + const preBlockPlaceholders: string[] = []; + + // Replace pre blocks first (they can contain code blocks) + processed = processed.replace(/]*>([\s\S]*?)<\/pre>/gi, (match) => { + const placeholder = `__PREBLOCK_${preBlockPlaceholders.length}__`; + preBlockPlaceholders.push(match); + return placeholder; + }); + + // Replace code blocks + processed = processed.replace(/]*>([\s\S]*?)<\/code>/gi, (match) => { + const placeholder = `__CODEBLOCK_${codeBlockPlaceholders.length}__`; + codeBlockPlaceholders.push(match); + return placeholder; + }); // Extract base domain from linkBaseURL if provided let baseDomain: string | null = null; @@ -206,11 +330,88 @@ function processOpenGraphLinks(html: string, linkBaseURL?: string): string { // Ignore parsing errors } } - + + // Before processing, remove any corrupted opengraph containers that might have been created + // These have malformed data-og-url attributes containing HTML fragments + // Match all spans with data-og-url and check if they're corrupted + // Use a pattern that matches spans with data-og-url, then check the attribute value + processed = processed.replace(/]*data-og-url=["']([^"']+)["'][^>]*>[\s\S]*?<\/span>/gi, (match) => { + // This span has a corrupted data-og-url (contains <) + // Extract the clean URL from the beginning of the attribute value + const dataOgUrlMatch = match.match(/data-og-url=["']([^"']+)["']/i); + if (dataOgUrlMatch && dataOgUrlMatch[1]) { + // Extract just the URL part (everything before the first <) + const urlMatch = dataOgUrlMatch[1].match(/(https?:\/\/[^\s<>"']+)/i); + if (urlMatch) { + const cleanUrl = urlMatch[1]; + // Extract the link text from inside the span + const linkMatch = match.match(/]*>(.*?)<\/a>/i); + const linkText = linkMatch ? linkMatch[1] : cleanUrl; + // Return a clean opengraph container with the fixed URL + const escapedUrl = cleanUrl.replace(/"/g, '"').replace(/'/g, '''); + return ` +
${linkText} + + `; + } + // If we can't extract a clean URL, just remove the corrupted span and keep any text + const textMatch = match.match(/>([^<]+)]*?)href\s*=\s*["'](https?:\/\/[^"']+)["']([^>]*?)>(.*?)<\/a>/gis, (match, before, href, after, linkText) => { + // Use a stricter regex that only matches valid, complete anchor tags + // The regex must match a complete tag with proper structure + processed = processed.replace(/]*\s+)?href\s*=\s*["'](https?:\/\/[^"']{1,2048})["']([^>]*?)>(.*?)<\/a>/gis, (match, before, href, after, linkText) => { + // CRITICAL: Validate href FIRST - if it contains ANY HTML tags or fragments, skip immediately + // This prevents corrupted HTML from being created + if (!href) { + return match; // Skip if no href + } + + // Skip if href contains HTML tags or looks corrupted - be very strict + // Check for common HTML fragments that indicate corruption + if (href.includes('<') || href.includes('>') || href.includes('href=') || href.includes('') || href.includes('"']+$/i.test(href)) { + return match; // Skip if href doesn't match clean URL pattern + } + + // Validate href is a proper URL (starts with http:// or https:// and doesn't contain invalid chars) + if (!/^https?:\/\/[^\s<>"']+$/i.test(href)) { + return match; // Skip if href doesn't match URL pattern + } + + // Skip if the match contains unclosed tags or corrupted HTML + const openATags = (match.match(//g) || []).length; + if (openATags !== closeATags || openATags !== 1) { + return match; // Multiple or mismatched tags = corrupted + } + + // Skip if match contains nested HTML that looks corrupted + if (match.includes('href="') && match.split('href="').length > 2) { + return match; // Multiple href attributes = corrupted + } + // Skip if it's already a media embed, nostr link, wikilink, or opengraph link if (match.includes('class="wikilink"') || match.includes('class="nostr-link"') || @@ -233,6 +434,12 @@ function processOpenGraphLinks(html: string, linkBaseURL?: string): string { return match; } + // Skip if link text contains wss:// or ws:// - these are relay URLs, not web pages + // They don't need OpenGraph previews + if (/wss?:\/\//i.test(linkText)) { + return match; + } + // Check if it's an external link (not same domain) let isExternal = true; if (baseDomain) { @@ -260,7 +467,7 @@ function processOpenGraphLinks(html: string, linkBaseURL?: string): string { // Add data attribute for OpenGraph fetching and wrap in container // The actual OpenGraph fetching will be done client-side via JavaScript return ` - ${linkText} + ${linkText}
) + // Extract everything from X: up to (but not including) </code> or </pre> + const abcMatch = decoded.match(/^(X:\s*\d+[\s\S]*?)(?=<\/code>|<\/pre>)/); + if (abcMatch) { + let cleanAbc = abcMatch[1].trim(); + // Remove any trailing HTML entities + cleanAbc = cleanAbc.replace(/<.*$/, '').trim(); + // Validate it's reasonable ABC notation + if (cleanAbc.length > 10 && cleanAbc.length < 2000 && cleanAbc.match(/^X:\s*\d+/m)) { + // Return clean code block - the processing step will wrap it in abc-notation div + return `
${cleanAbc}
`; + } + } + // If extraction fails, just remove the corrupted div and return empty code block + // This prevents the corrupted data from being rendered + return `
`; + } + return match; + }); + // Process ABC notation blocks - ONLY code blocks explicitly marked with language-abc class + // These come from: [source,abc], [source, abc], [abc] in AsciiDoc, or ```abc in Markdown + // We do NOT auto-detect ABC notation - it must be explicitly marked + html = html.replace(/]*>]*class="[^"]*language-abc[^"]*"[^>]*>([\s\S]*?)<\/code><\/pre>/gi, (match, codeContent) => { + // Skip if already processed or corrupted + if (codeContent.includes('abc-notation') || + codeContent.includes('class="abc-notation"') || + codeContent.includes('') || + codeContent.length > 5000) { + return match; + } + // Extract ABC content from the code block + let abcContent = codeContent + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/'/g, "'") + .replace(///g, '/'); + // Remove any HTML tags + abcContent = abcContent.replace(/<[^>]+>/g, '').trim(); + // Only process if it looks like valid ABC notation (starts with X:) + // Since this is explicitly marked as ABC, we trust it's ABC notation + if (abcContent.match(/^X:\s*\d+/m) && + abcContent.length < 3000 && + !abcContent.includes(' 200) { + break; + } + abcLines.push(line); + if (abcLines.join('\n').length > 2000) { + break; + } + } + const cleanAbc = abcLines.join('\n').trim(); + if (cleanAbc.match(/^X:\s*\d+/m) && cleanAbc.length > 10 && cleanAbc.length < 2000) { + return `
${match}
`; + } + } + return match; + }); + // Process LilyPond notation blocks + const lilypondPattern = /(\\relative[^}]+})/gs; + html = html.replace(lilypondPattern, (match) => { + const lilypondContent = match.trim(); + return `
${lilypondContent}
`; + }); + // Process inline chord notation: [C], [Am], [F#m7], etc. + const chordPattern = /\[([A-G][#b]?m?[0-9]?[^\[\]]*)\]/g; + html = html.replace(chordPattern, (match, chord) => { + return `[${chord}]`; + }); + // Process MusicXML-like notation + const musicxmlPattern = /(]*>.*?<\/music>)/gs; + html = html.replace(musicxmlPattern, (match) => { + const musicxmlContent = match.trim(); + return `
${musicxmlContent}
`; + }); + return html; +} +/** + * Escapes a string for use in HTML attributes + */ +function escapeForAttr(text) { + return text + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(//g, '>') + .replace(/\n/g, ' ') + .replace(/\r/g, ''); +} diff --git a/src/processors/music.ts b/src/processors/music.ts index 1617df1..a6ea4a7 100644 --- a/src/processors/music.ts +++ b/src/processors/music.ts @@ -3,11 +3,116 @@ * Wraps musical notation in appropriate HTML for rendering */ export function processMusicalNotation(html: string): string { - // Process ABC notation blocks - const abcBlockPattern = /(X:\s*\d+[^\n]*\n(?:[^\n]+\n)*)/gs; - html = html.replace(abcBlockPattern, (match) => { - const abcContent = match.trim(); - return `
${abcContent}
`; + // First, clean up any corrupted abc-notation divs with very long data-abc attributes + // These were created by a buggy regex that matched the entire HTML document + html = html.replace(/]*class="[^"]*abc-notation[^"]*"[^>]*data-abc="([^"]{500,})"[^>]*>([\s\S]*?)<\/div>/gi, (match, dataAbc, content) => { + // This is corrupted - extract just the ABC notation from the beginning + let decoded = dataAbc + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, "'"); + + // Find the actual ABC notation (starts with X:) + const abcMatch = decoded.match(/^(X:\s*\d+[\s\S]{0,2000}?)(?:\n[^XTCMALK]|<|<\/|sect|div|pre|code)/); + if (abcMatch) { + const cleanAbc = abcMatch[1].trim(); + return `
${content}
`; + } + // If we can't extract clean ABC, remove the div entirely + return content; + }); + + // Clean up code blocks that contain corrupted abc-notation divs inside them + // The corrupted structure is:
...
+ html = html.replace(/]*>]*class="[^"]*language-abc[^"]*"[^>]*>([\s\S]*?)<\/code><\/pre>/gi, (match, codeContent) => { + // Check if codeContent contains an abc-notation div with a very long data-abc attribute (>500 chars = corrupted) + const longDataAbcMatch = codeContent.match(/]*class="[^"]*abc-notation[^"]*"[^>]*data-abc="([^"]{500,})"/i); + if (longDataAbcMatch) { + // Extract just the ABC notation from the beginning of the corrupted data-abc value + let decoded = longDataAbcMatch[1] + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, "'"); + + // The ABC notation ends where the HTML document starts (
or
) + // Extract everything from X: up to (but not including) </code> or </pre> + const abcMatch = decoded.match(/^(X:\s*\d+[\s\S]*?)(?=<\/code>|<\/pre>)/); + if (abcMatch) { + let cleanAbc = abcMatch[1].trim(); + // Remove any trailing HTML entities + cleanAbc = cleanAbc.replace(/<.*$/, '').trim(); + // Validate it's reasonable ABC notation + if (cleanAbc.length > 10 && cleanAbc.length < 2000 && cleanAbc.match(/^X:\s*\d+/m)) { + // Return clean code block - the processing step will wrap it in abc-notation div + return `
${cleanAbc}
`; + } + } + // If extraction fails, just remove the corrupted div and return empty code block + // This prevents the corrupted data from being rendered + return `
`; + } + return match; + }); + + // Process ABC notation blocks - ONLY code blocks explicitly marked with language-abc class + // These come from: [source,abc], [source, abc], [abc] in AsciiDoc, or ```abc in Markdown + // We do NOT auto-detect ABC notation - it must be explicitly marked + html = html.replace(/]*>]*class="[^"]*language-abc[^"]*"[^>]*>([\s\S]*?)<\/code><\/pre>/gi, (match, codeContent) => { + // Skip if already processed or corrupted + if (codeContent.includes('abc-notation') || + codeContent.includes('class="abc-notation"') || + codeContent.includes('') || + codeContent.length > 5000) { + return match; + } + + // Extract ABC content from the code block + let abcContent = codeContent + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/'/g, "'") + .replace(///g, '/'); + + // Remove any HTML tags + abcContent = abcContent.replace(/<[^>]+>/g, '').trim(); + + // Only process if it looks like valid ABC notation (starts with X:) + // Since this is explicitly marked as ABC, we trust it's ABC notation + if (abcContent.match(/^X:\s*\d+/m) && + abcContent.length < 3000 && + !abcContent.includes(' 200) { + break; + } + abcLines.push(line); + if (abcLines.join('\n').length > 2000) { + break; + } + } + const cleanAbc = abcLines.join('\n').trim(); + if (cleanAbc.match(/^X:\s*\d+/m) && cleanAbc.length > 10 && cleanAbc.length < 2000) { + return `
${match}
`; + } + } + return match; }); // Process LilyPond notation blocks diff --git a/src/types.js b/src/types.js new file mode 100644 index 0000000..ef14866 --- /dev/null +++ b/src/types.js @@ -0,0 +1,14 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ContentFormat = void 0; +/** + * Detected content format + */ +var ContentFormat; +(function (ContentFormat) { + ContentFormat["Unknown"] = "unknown"; + ContentFormat["AsciiDoc"] = "asciidoc"; + ContentFormat["Markdown"] = "markdown"; + ContentFormat["Wikipedia"] = "wikipedia"; + ContentFormat["Plain"] = "plain"; +})(ContentFormat || (exports.ContentFormat = ContentFormat = {})); diff --git a/src/types.ts b/src/types.ts index da1b350..0b87f30 100644 --- a/src/types.ts +++ b/src/types.ts @@ -16,6 +16,20 @@ export interface ParserOptions { enableMusicalNotation?: boolean; /** Enable nostr: address processing (default: true) */ enableNostrAddresses?: boolean; + /** + * Custom URL format for wikilinks. Can be: + * - A string template with {dtag} placeholder: "/d/{dtag}" or "/events?d={dtag}" + * - A function that takes dtag and returns URL: (dtag: string) => `/d/${dtag}` + * Default: "/events?d={dtag}" + */ + wikilinkUrl?: string | ((dtag: string) => string); + /** + * Custom URL format for hashtags. Can be: + * - A string template with {topic} placeholder: "/notes?t={topic}" or "/hashtag/{topic}" + * - A function that takes topic (hashtag without #) and returns URL: (topic: string) => `/notes?t=${topic}` + * Default: undefined (hashtags rendered as non-clickable spans) + */ + hashtagUrl?: string | ((topic: string) => string); } /** @@ -49,6 +63,8 @@ export interface ProcessResult { hasLaTeX: boolean; /** Indicates if musical notation was found */ hasMusicalNotation: boolean; + /** Extracted YAML front matter (if present) */ + frontmatter?: Record; /** Extracted Nostr links */ nostrLinks: NostrLink[]; /** Extracted wikilinks */ diff --git a/test-parser-report.test.ts b/test-parser-report.test.ts new file mode 100644 index 0000000..823066c --- /dev/null +++ b/test-parser-report.test.ts @@ -0,0 +1,628 @@ +import { Parser } from './src/parser'; +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * Test that parses both markdown and asciidoc test documents + * and generates an HTML report showing the parsing results + */ +describe('Parser Test Report', () => { + const parser = new Parser({ + linkBaseURL: 'https://example.com', + wikilinkUrl: '/events?d={dtag}', + hashtagUrl: '/notes?t={topic}', + }); + + test('Generate HTML test report for markdown and asciidoc documents', async () => { + // Read test documents + const markdownContent = fs.readFileSync( + path.join(__dirname, 'markdown_testdoc.md'), + 'utf-8' + ); + const asciidocContent = fs.readFileSync( + path.join(__dirname, 'asciidoc_testdoc.adoc'), + 'utf-8' + ); + + // Parse both documents + const markdownResult = await parser.process(markdownContent); + const asciidocResult = await parser.process(asciidocContent); + + // Generate HTML report + const htmlReport = generateHTMLReport({ + markdown: { + original: markdownContent, + result: markdownResult, + }, + asciidoc: { + original: asciidocContent, + result: asciidocResult, + }, + }); + + // Write HTML report to file + const reportPath = path.join(__dirname, 'test-report.html'); + fs.writeFileSync(reportPath, htmlReport, 'utf-8'); + + console.log(`\n✅ Test report generated: ${reportPath}`); + console.log(` Open this file in your browser to view the results.\n`); + + // Basic assertions to ensure parsing worked + expect(markdownResult.content).toBeTruthy(); + expect(asciidocResult.content).toBeTruthy(); + expect(markdownResult.content.length).toBeGreaterThan(0); + expect(asciidocResult.content.length).toBeGreaterThan(0); + }); +}); + +interface TestData { + original: string; + result: any; +} + +interface ReportData { + markdown: TestData; + asciidoc: TestData; +} + +function generateHTMLReport(data: ReportData): string { + const { markdown, asciidoc } = data; + + return ` + + + + + GC Parser Test Report + + + +
+

GC Parser Test Report

+

Generated: ${new Date().toLocaleString()}

+ + +
+

Markdown Document Test ✓ Parsed

+ +
+ + + + +
+ +
+
+
+
${markdown.result.nostrLinks.length}
+
Nostr Links
+
+
+
${markdown.result.wikilinks.length}
+
Wikilinks
+
+
+
${markdown.result.hashtags.length}
+
Hashtags
+
+
+
${markdown.result.links.length}
+
Links
+
+
+
${markdown.result.media.length}
+
Media URLs
+
+
+
${markdown.result.hasLaTeX ? 'Yes' : 'No'}
+
Has LaTeX
+
+
+
${markdown.result.hasMusicalNotation ? 'Yes' : 'No'}
+
Has Music
+
+
+ +

Frontmatter

+ ${markdown.result.frontmatter ? ` + + ` : '

No frontmatter found

'} +
+ +
+

Original Markdown Content

+
+
${escapeHtml(markdown.original)}
+
+
+ +
+

Rendered HTML Output

+
+ ${markdown.result.content} +
+
+ View Raw HTML +
+
${escapeHtml(markdown.result.content)}
+
+
+
+ +
+
+ + +
+

AsciiDoc Document Test ✓ Parsed

+ +
+ + + + +
+ +
+
+
+
${asciidoc.result.nostrLinks.length}
+
Nostr Links
+
+
+
${asciidoc.result.wikilinks.length}
+
Wikilinks
+
+
+
${asciidoc.result.hashtags.length}
+
Hashtags
+
+
+
${asciidoc.result.links.length}
+
Links
+
+
+
${asciidoc.result.media.length}
+
Media URLs
+
+
+
${asciidoc.result.hasLaTeX ? 'Yes' : 'No'}
+
Has LaTeX
+
+
+
${asciidoc.result.hasMusicalNotation ? 'Yes' : 'No'}
+
Has Music
+
+
+ +

Frontmatter

+ ${asciidoc.result.frontmatter ? ` + + ` : '

No frontmatter found

'} +
+ +
+

Original AsciiDoc Content

+
+
${escapeHtml(asciidoc.original)}
+
+
+ +
+

Rendered HTML Output

+
+ ${asciidoc.result.content} +
+
+ View Raw HTML +
+
${escapeHtml(asciidoc.result.content)}
+
+
+
+ +
+

Extracted Metadata

+ + ${asciidoc.result.nostrLinks.length > 0 ? ` +

Nostr Links (${asciidoc.result.nostrLinks.length})

+ ${asciidoc.result.nostrLinks.map(link => ` +
+ ${escapeHtml(link.type)}: ${escapeHtml(link.bech32)} + ${link.text ? ` - ${escapeHtml(link.text)}` : ''} +
+ `).join('')} + ` : ''} + + ${asciidoc.result.wikilinks.length > 0 ? ` +

Wikilinks (${asciidoc.result.wikilinks.length})

+ ${asciidoc.result.wikilinks.map(wl => ` +
+ ${escapeHtml(wl.original)} → dtag: ${escapeHtml(wl.dtag)} + ${wl.display ? ` (display: ${escapeHtml(wl.display)})` : ''} +
+ `).join('')} + ` : ''} + + ${asciidoc.result.hashtags.length > 0 ? ` +

Hashtags (${asciidoc.result.hashtags.length})

+ ${asciidoc.result.hashtags.map(tag => ` +
+ #${escapeHtml(tag)} +
+ `).join('')} + ` : ''} + + ${asciidoc.result.links.length > 0 ? ` +

Links (${asciidoc.result.links.length})

+ ${asciidoc.result.links.map(link => ` +
+ ${escapeHtml(link.text || link.url)} + ${link.isExternal ? 'External' : ''} +
+ `).join('')} + ` : ''} + + ${asciidoc.result.media.length > 0 ? ` +

Media URLs (${asciidoc.result.media.length})

+ ${asciidoc.result.media.map(url => ` + + `).join('')} + ` : ''} + + ${asciidoc.result.tableOfContents ? ` +

Table of Contents

+
+ ${asciidoc.result.tableOfContents} +
+ ` : ''} +
+
+
+ + + +`; +} + +function escapeHtml(text: string): string { + const map: Record = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + }; + return text.replace(/[&<>"']/g, (m) => map[m]); +} diff --git a/test-report.html b/test-report.html new file mode 100644 index 0000000..c14cd16 --- /dev/null +++ b/test-report.html @@ -0,0 +1,13415 @@ + + + + + + GC Parser Test Report + + + +
+

GC Parser Test Report

+

Generated: 4.3.2026, 12:15:49

+ + +
+

Markdown Document Test ✓ Parsed

+ +
+ + + + +
+ +
+
+
+
5
+
Nostr Links
+
+
+
2
+
Wikilinks
+
+
+
3
+
Hashtags
+
+
+
18
+
Links
+
+
+
3
+
Media URLs
+
+
+
Yes
+
Has LaTeX
+
+
+
Yes
+
Has Music
+
+
+ +

Frontmatter

+ + + +
+ +
+

Original Markdown Content

+
+
---
+# this is YAML front matter
+author: James Smith
+summary: This is a summary
+topics: list, of, topics
+variable: one
+array:
+  - one thing
+  - two things
+  - several things
+# all of this data is available to our layout
+---
+
+# Markdown Test Document
+
+## Bullet list
+
+This is a test unordered list with mixed bullets:
+* First item with a number 2. in it
+* Second item
+* Third item
+    - Indented item
+    - Indented item
+* Fourth item 
+
+Another unordered list:
+- 1st item
+- 2nd item
+- third item containing _italic_ text
+  - indented item
+  - second indented item
+- fourth item
+
+This is a test ordered list with indented items:
+1. First item
+2. Second item
+3. Third item
+    1. Indented item
+    2. Indented item
+4. Fourth item
+
+Ordered list where everything has the same number:
+1. First item
+1. Second item
+1. Third item
+1. Fourth item 
+
+Ordered list that is wrongly numbered:
+1. First item
+8. Second item
+3. Third item
+5. Fourth item
+
+This is a mixed list with indented items:
+1. First item
+2. Second item
+3. Third item
+    * Indented item
+    * Indented item
+4. Fourth item
+
+This is another mixed list with indented items:
+- First item
+- Second item
+- Third item
+    1. Indented item
+    2. Indented item
+- Fourth item
+
+
+## Headers
+
+### Third-level header
+
+#### Fourth-level header
+
+##### Fifth-level header
+
+###### Sixth-level header
+
+## Media and Links
+
+### Nostr address
+
+This should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l
+
+This is also plaintext:
+
+npub1gv069u6q7zkl393ad47xutpqmyfj0rrfrlnqnlfc2ld38k8nnl4st9wa6q
+
+These should be turned into links:
+
+nostr:naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l
+
+nostr:npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z
+
+nostr:nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj
+
+nostr:nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh
+
+nostr:note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg
+
+### Hashtag
+
+#testhashtag at the start of the line and #inlinehashtag in the middle
+
+### Wikilinks
+
+[[NKBIP-01|Specification]] and [[mirepoix]]
+
+### URL
+
+https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html
+
+[Welt Online link](https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html)
+
+this should render as plaintext: `http://www.example.com`
+
+this should be a hyperlink: www.example.com
+
+this shouild be a hyperlink to the http URL with the same address, so wss://theforest.nostr1.com should render like [wss://theforest.nostr1.com](https://theforest.nostr1.com)
+
+### Images
+
+Image: https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png
+
+![test image](https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png)
+
+### Media
+
+#### YouTube
+
+https://youtube.com/shorts/ZWfvChb-i0w
+
+[![Youtube link with pic](https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png)](https://youtube.com/shorts/ZWfvChb-i0w)
+
+#### Spotify
+
+https://open.spotify.com/episode/1GSZFA8vWltPyxYkArdRKx?si=bq6-az28TcuP596feTkRFQ
+
+[![Spotify link with pic](https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png)](https://open.spotify.com/episode/1GSZFA8vWltPyxYkArdRKx?si=bq6-az28TcuP596feTkRFQ)
+
+#### Audio
+
+https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3
+
+[![Audio link with pic](https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png)](https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3)
+
+#### Video
+
+https://v.nostr.build/MTjaYib4upQuf8zn.mp4
+
+[![Video link with pic](https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png)](https://v.nostr.build/MTjaYib4upQuf8zn.mp4)
+
+## Tables
+
+### Orderly
+
+| Syntax      | Description |
+| ----------- | ----------- |
+| Header      | Title       |
+| Paragraph   | Text        |
+
+### Unorderly
+
+| Syntax | Description |
+| --- | ----------- |
+| Header | Title |
+| Paragraph | Text |
+
+### With alignment
+
+| Syntax      | Description | Test Text     |
+| :---        |    :----:   |          ---: |
+| Header      | Title       | Here's this   |
+| Paragraph   | Text        | And more      |
+
+## Code blocks
+
+### json
+
+```json
+{
+    "id": "<event_id>",
+    "pubkey": "<event_originator_pubkey>",
+    "created_at": 1725087283,
+    "kind": 30040,
+    "tags": [
+        ["d", "aesop's-fables-by-aesop"],
+        ["title", "Aesop's Fables"],
+        ["author", "Aesop"],
+    ],
+    "sig": "<event_signature>"
+}
+```
+
+### typescript
+
+```typescript
+/**
+ * Get Nostr identifier type
+ */
+function getNostrType(id: string): 'npub' | 'nprofile' | 'nevent' | 'naddr' | 'note' | null {
+  if (id.startsWith('npub')) return 'npub';
+  if (id.startsWith('nprofile')) return 'nprofile';
+  if (id.startsWith('nevent')) return 'nevent';
+  if (id.startsWith('naddr')) return 'naddr';
+  if (id.startsWith('note')) return 'note';
+  return null;
+}
+```
+
+### shell
+
+```shell
+
+mkdir new_directory
+cp source.txt destination.txt
+
+```
+
+### LaTeX
+
+```latex
+$$
+M = 
+\begin{bmatrix}
+\frac{5}{6} & \frac{1}{6} & 0 \\[0.3em]
+\frac{5}{6} & 0 & \frac{1}{6} \\[0.3em]
+0 & \frac{5}{6} & \frac{1}{6}
+\end{bmatrix}
+$$
+```
+
+```latex
+$$
+f(x)=
+\begin{cases}
+1/d_{ij} & \quad \text{when $d_{ij} \leq 160$}\\ 
+0 & \quad \text{otherwise}
+\end{cases}
+$$
+```
+
+### ABC Notation
+
+```abc
+X:1
+T:Ohne Titel
+C:Aufgezeichnet 1784
+A:Seibis nahe Lichtenberg in Oberfranken
+S:Handschrift, bezeichnet und datiert: "Heinrich Nicol Philipp zu Seibis den 30 Junius 1784"
+M:4/4
+L:1/4
+K:D
+dd d2 | ee e2 | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+|:\
+fg ad | cB cA | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+```
+
+## LateX
+
+### LaTex in inline-code
+
+`$[ x^n + y^n = z^n \]$` and `$[\sqrt{x^2+1}\]$` and `$\color{blue}{X \sim Normal \; (\mu,\sigma^2)}$`
+
+## LaTex outside of code
+
+This is a latex code block $$\mathbb{N} = \{ a \in \mathbb{Z} : a > 0 \}$$ and another that is an inline latex $\color{green}{X \sim Normal \; (\mu,\sigma^2)}$ and should be green
+
+## Footnotes
+
+Here's a simple footnote,[^1] and here's a longer one.[^bignote]
+
+[^1]: This is the first footnote.
+
+[^bignote]: Here's one with multiple paragraphs and code.
+
+## Anchor links
+
+[Link to bullet list section](#bullet-lists)
+
+## Formatting
+
+### Strikethrough 
+
+~~The world is flat.~~ We now know that the world is round. This should not be ~struck~ through.
+
+### Bold
+
+This is *bold* text. So is this **bold** text.
+
+### Italic
+
+This is _italic_ text. So is this __italic__ text.
+
+### Task List
+
+- [x] Write the press release
+- [ ] Update the website
+- [ ] Contact the media
+
+### Emoji shortcodes
+
+Gone camping! :tent: Be back soon.
+
+That is so funny! :joy:
+
+### Marking and highlighting text
+
+I need to highlight these ==very important words==.
+
+### Subscript and Superscript
+
+H~2~O
+
+X^2^
+
+### Delimiter
+
+based upon a -
+
+---
+
+based upon a *
+
+***
+
+### Quotes
+
+> This is a single line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd  kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj 
+
+> This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd  kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj 
+> This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd  kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj 
+> This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd  kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj 
+
+
+ +
+

Rendered HTML Output

+
+

Markdown Test Document

+ +
+

Bullet list

+
+
+

This is a test unordered list with mixed bullets: +* First item with a number 2. in it +* Second item +* Third item + - Indented item + - Indented item +* Fourth item

+
+
+

Another unordered list: +- 1st item +- 2nd item +- third item containing italic text + - indented item + - second indented item +- fourth item

+
+
+

This is a test ordered list with indented items: +1. First item +2. Second item +3. Third item + 1. Indented item + 2. Indented item +4. Fourth item

+
+
+

Ordered list where everything has the same number: +1. First item +1. Second item +1. Third item +1. Fourth item

+
+
+

Ordered list that is wrongly numbered: +1. First item +8. Second item +3. Third item +5. Fourth item

+
+
+

This is a mixed list with indented items: +1. First item +2. Second item +3. Third item + * Indented item + * Indented item +4. Fourth item

+
+
+

This is another mixed list with indented items: +- First item +- Second item +- Third item + 1. Indented item + 2. Indented item +- Fourth item

+
+
+
+ +
+ +
+
+

Nostr address

+
+

This should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l

+
+
+

This is also plaintext:

+
+
+

npub1gv069u6q7zkl393ad47xutpqmyfj0rrfrlnqnlfc2ld38k8nnl4st9wa6q

+
+
+

These should be turned into links:

+
+ + + + + +
+
+

Hashtag

+
+

#testhashtag at the start of the line and #inlinehashtag in the middle

+
+
+ +
+

URL

+ + +
+

this should render as plaintext: http://www.example.com

+
+
+

this should be a hyperlink: www.example.com

+
+
+

this shouild be a hyperlink to the http URL with the same address, so wss://theforest.nostr1.com should render like " target="_blank" rel="noreferrer noopener" class="break-words inline-flex items-baseline gap-1"><a href="https://theforest.nostr1.com/">wss://theforest.nostr1.com</a> ( + https://theforest.nostr1.com +

+ )

+
+
+ + +
+
+
+

Tables

+
+
+

Orderly

+
+

| Syntax | Description | +| ----------- | ----------- | +| Header | Title | +| Paragraph | Text |

+
+
+
+

Unorderly

+
+

| Syntax | Description | +| --- | ----------- | +| Header | Title | +| Paragraph | Text |

+
+
+
+

With alignment

+
+

| Syntax | Description | Test Text | +| :--- | :----: | ---: | +| Header | Title | Here_s this | +| Paragraph | Text | And more |

+
+
+
+
+
+

Code blocks

+
+
+

json

+
+
+
{
+    "id": "<event_id>",
+    "pubkey": "<event_originator_pubkey>",
+    "created_at": 1725087283,
+    "kind": 30040,
+    "tags": [
+        ["d", "aesop_s-fables-by-aesop"],
+        ["title", "Aesop_s Fables"],
+        ["author", "Aesop"],
+    ],
+    "sig": "<event_signature>"
+}
+
+
+
+
+

typescript

+
+
+
/**
+ * Get Nostr identifier type
+ */
+function getNostrType(id: string): _npub_ | _nprofile_ | _nevent_ | _naddr_ | _note_ | null {
+  if (id.startsWith(_npub_)) return _npub_;
+  if (id.startsWith(_nprofile_)) return _nprofile_;
+  if (id.startsWith(_nevent_)) return _nevent_;
+  if (id.startsWith(_naddr_)) return _naddr_;
+  if (id.startsWith(_note_)) return _note_;
+  return null;
+}
+
+
+
+
+

shell

+
+
+
mkdir new_directory
+cp source.txt destination.txt
+
+
+
+
+

LaTeX

+
+
+
$
+M =
+\begin{bmatrix}
+\frac{5}{6} & \frac{1}{6} & 0 \\[0.3em]
+\frac{5}{6} & 0 & \frac{1}{6} \\[0.3em]
+0 & \frac{5}{6} & \frac{1}{6}
+\end{bmatrix}
+$
+
+
+
+
+
$
+f(x)=
+\begin{cases}
+1/d_{ij} & \quad \text{when $d_{ij} \leq 160$}\\
+0 & \quad \text{otherwise}
+\end{cases}
+$
+
+
+
+
+

ABC Notation

+
+
+
X:1
+T:Ohne Titel
+C:Aufgezeichnet 1784
+A:Seibis nahe Lichtenberg in Oberfranken
+S:Handschrift, bezeichnet und datiert: "Heinrich Nicol Philipp zu Seibis den 30 Junius 1784"
+M:4/4
+L:1/4
+K:D
+dd d2 | ee e2 | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+|:\
+fg ad | cB cA | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+
+
+
+
+
+
+

LateX

+
+
+

LaTex in inline-code

+
+

$[ x^n + y^n = z^n \]$ and $[\sqrt{x^2+1}\]$ and $\color{blue}{X \sim Normal \; (\mu,\sigma^2)}$

+
+
+
+
+
+

LaTex outside of code

+
+
+

This is a latex code block \mathbb{N} = \{ a \in \mathbb{Z} : a > 0 \} and another that is an inline latex $\color{green}{X \sim Normal \; (\mu,\sigma^2)}$ and should be green

+
+
+
+
+

Footnotes

+
+
+

Here_s a simple footnote,[^1] and here_s a longer one.[^bignote]

+
+
+

[^1]: This is the first footnote.

+
+
+

[^bignote]: Here_s one with multiple paragraphs and code.

+
+
+
+ +
+

Formatting

+
+
+

Strikethrough

+
+

~~The world is flat.~~ We now know that the world is round. This should not be struck through.

+
+
+
+

Bold

+
+

This is bold text. So is this bold text.

+
+
+
+

Italic

+
+

This is italic text. So is this italic text.

+
+
+
+

Task List

+
+
    +
  • +

    ✓ Write the press release

    +
  • +
  • +

    ❏ Update the website

    +
  • +
  • +

    ❏ Contact the media

    +
  • +
+
+
+
+

Emoji shortcodes

+
+

Gone camping! :tent: Be back soon.

+
+
+

That is so funny! :joy:

+
+
+
+

Marking and highlighting text

+
+

I need to highlight these ==very important words==.

+
+
+
+

Subscript and Superscript

+
+

H2O

+
+
+

X2

+
+
+
+

Delimiter

+
+

based upon a -

+
+
+
+

based upon a *

+
+
+
+
+

Quotes

+
+
+
+

This is a single line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj

+
+
+
+
+
+
+

This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj +This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj +This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj

+
+
+
+
+
+
+
+
+ View Raw HTML +
+
<h1>Markdown Test Document</h1>
+
+<div class="sect1">
+<h2 id="bullet-list"><a class="anchor" href="#bullet-list"></a><a class="link" href="#bullet-list">Bullet list</a></h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>This is a test unordered list with mixed bullets:
+* First item with a number 2. in it
+* Second item
+* Third item
+    - Indented item
+    - Indented item
+* Fourth item</p>
+</div>
+<div class="paragraph">
+<p>Another unordered list:
+- 1st item
+- 2nd item
+- third item containing <em>italic</em> text
+  - indented item
+  - second indented item
+- fourth item</p>
+</div>
+<div class="paragraph">
+<p>This is a test ordered list with indented items:
+1. First item
+2. Second item
+3. Third item
+    1. Indented item
+    2. Indented item
+4. Fourth item</p>
+</div>
+<div class="paragraph">
+<p>Ordered list where everything has the same number:
+1. First item
+1. Second item
+1. Third item
+1. Fourth item</p>
+</div>
+<div class="paragraph">
+<p>Ordered list that is wrongly numbered:
+1. First item
+8. Second item
+3. Third item
+5. Fourth item</p>
+</div>
+<div class="paragraph">
+<p>This is a mixed list with indented items:
+1. First item
+2. Second item
+3. Third item
+    * Indented item
+    * Indented item
+4. Fourth item</p>
+</div>
+<div class="paragraph">
+<p>This is another mixed list with indented items:
+- First item
+- Second item
+- Third item
+    1. Indented item
+    2. Indented item
+- Fourth item</p>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="headers"><a class="anchor" href="#headers"></a><a class="link" href="#headers">Headers</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="third-level-header"><a class="anchor" href="#third-level-header"></a><a class="link" href="#third-level-header">Third-level header</a></h3>
+<div class="sect3">
+<h4 id="fourth-level-header"><a class="anchor" href="#fourth-level-header"></a><a class="link" href="#fourth-level-header">Fourth-level header</a></h4>
+<div class="sect4">
+<h5 id="fifth-level-header"><a class="anchor" href="#fifth-level-header"></a><a class="link" href="#fifth-level-header">Fifth-level header</a></h5>
+<div class="sect5">
+<h6 id="sixth-level-header"><a class="anchor" href="#sixth-level-header"></a><a class="link" href="#sixth-level-header">Sixth-level header</a></h6>
+
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="media-and-links"><a class="anchor" href="#media-and-links"></a><a class="link" href="#media-and-links">Media and Links</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="nostr-address"><a class="anchor" href="#nostr-address"></a><a class="link" href="#nostr-address">Nostr address</a></h3>
+<div class="paragraph">
+<p>This should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l</p>
+</div>
+<div class="paragraph">
+<p>This is also plaintext:</p>
+</div>
+<div class="paragraph">
+<p>npub1gv069u6q7zkl393ad47xutpqmyfj0rrfrlnqnlfc2ld38k8nnl4st9wa6q</p>
+</div>
+<div class="paragraph">
+<p>These should be turned into links:</p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l">naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z">npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj">nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh">nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg">note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg</a></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="hashtag"><a class="anchor" href="#hashtag"></a><a class="link" href="#hashtag">Hashtag</a></h3>
+<div class="paragraph">
+<p><a class="hashtag-link text-primary-600 dark:text-primary-500 hover:underline" data-topic="testhashtag" data-url="/notes?t=testhashtag" href="/notes?t=testhashtag">#testhashtag</a> at the start of the line and <a class="hashtag-link text-primary-600 dark:text-primary-500 hover:underline" data-topic="inlinehashtag" data-url="/notes?t=inlinehashtag" href="/notes?t=inlinehashtag">#inlinehashtag</a> in the middle</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="wikilinks"><a class="anchor" href="#wikilinks"></a><a class="link" href="#wikilinks">Wikilinks</a></h3>
+<div class="paragraph">
+<p><a class="wikilink text-primary-600 dark:text-primary-500 hover:underline" data-dtag="nkbip-01" data-url="/events?d=nkbip-01" href="/events?d=nkbip-01">Specification</a> and <a class="wikilink text-primary-600 dark:text-primary-500 hover:underline" data-dtag="mirepoix" data-url="/events?d=mirepoix" href="/events?d=mirepoix">mirepoix</a></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="url"><a class="anchor" href="#url"></a><a class="link" href="#url">URL</a></h3>
+<div class="paragraph">
+<p><span class="opengraph-link-container" data-og-url="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html">
+      <a href="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span></p>
+</div>
+<div class="paragraph">
+<p><a href="<a href=&quot;https://<a href=&quot;https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html&quot;>www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html</a>" rel="noreferrer noopener" class="break-words inline-flex items-baseline gap-1">Welt Online link <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a></p>
+</div>
+<div class=&quot;paragraph&quot;>
+<p>this should render as plaintext: <code>http://www.example.com</code></p>
+</div>
+<div class=&quot;paragraph&quot;>
+<p>this should be a hyperlink: www.example.com</p>
+</div>
+<div class=&quot;paragraph&quot;>
+<p>this shouild be a hyperlink to the http URL with the same address, so <a href=&quot;https://theforest.nostr1.com/&quot;>wss://theforest.nostr1.com</a> should render like " target="_blank" rel="noreferrer noopener" class="break-words inline-flex items-baseline gap-1">&lt;a href=&quot;https://theforest.nostr1.com/&quot;&gt;wss://theforest.nostr1.com&lt;/a&gt; <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>(<span class="opengraph-link-container" data-og-url="https://theforest.nostr1.com">
+      <a href="https://theforest.nostr1.com" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">https://theforest.nostr1.com <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>)</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="images"><a class="anchor" href="#images"></a><a class="link" href="#images">Images</a></h3>
+<div class="paragraph">
+<p>Image: image::<a href="https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png" target="_blank" rel="noopener noreferrer">https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png</a>[width=100%]</p>
+</div>
+<div class="paragraph">
+<p><img src="<a href=&quot;https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png&quot; class=&quot;bare&quot;>https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png</a>" alt="test image" class="max-w-[400px] object-contain my-0" /></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="media"><a class="anchor" href="#media"></a><a class="link" href="#media">Media</a></h3>
+<div class="sect3">
+<h4 id="youtube"><a class="anchor" href="#youtube"></a><a class="link" href="#youtube">YouTube</a></h4>
+<div class="paragraph">
+<p><a href="https://youtube.com/shorts/ZWfvChb-i0w" target="_blank" rel="noopener noreferrer">https://youtube.com/shorts/ZWfvChb-i0w</a></p>
+</div>
+<div class="paragraph">
+<p>[<img src="<a href=&quot;https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&quot; class=&quot;bare&quot;>https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png</a>" alt="Youtube link with pic" class="max-w-[400px] object-contain my-0" />](<a href="https://youtube.com/shorts/ZWfvChb-i0w" class="bare" target="_blank" rel="noopener noreferrer">https://youtube.com/shorts/ZWfvChb-i0w</a>)</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="spotify"><a class="anchor" href="#spotify"></a><a class="link" href="#spotify">Spotify</a></h4>
+<div class="paragraph">
+<p><div class="media-embed spotify-embed" style="margin: 1rem 0;">
+      <iframe 
+        style="border-radius: 12px; width: 100%; max-width: 100%;" 
+        src="https://open.spotify.com/embed/episode/1GSZFA8vWltPyxYkArdRKx?utm_source=generator" 
+        width="100%" 
+        height="352" 
+        frameborder="0" 
+        allowfullscreen="" 
+        allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" 
+        loading="lazy">
+      </iframe>
+    </div></p>
+</div>
+<div class="paragraph">
+<p>[<img src="<a href=&quot;https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&quot; class=&quot;bare&quot;>https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png</a>" alt="Spotify link with pic" class="max-w-[400px] object-contain my-0" />](<div class="media-embed spotify-embed" style="margin: 1rem 0;">
+      <iframe 
+        style="border-radius: 12px; width: 100%; max-width: 100%;" 
+        src="https://open.spotify.com/embed/episode/1GSZFA8vWltPyxYkArdRKx?utm_source=generator" 
+        width="100%" 
+        height="352" 
+        frameborder="0" 
+        allowfullscreen="" 
+        allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" 
+        loading="lazy">
+      </iframe>
+    </div>)</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="audio"><a class="anchor" href="#audio"></a><a class="link" href="#audio">Audio</a></h4>
+<div class="paragraph">
+<p>MEDIA:audio:<a href="https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3" target="_blank" rel="noopener noreferrer">https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3</a></p>
+</div>
+<div class="paragraph">
+<p>[!<span class="chord" data-chord="Audio link with pic">[Audio link with pic]</span>(<a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png" class="bare" target="_blank" rel="noopener noreferrer">https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png</a>)](MEDIA:audio:<a href="https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3" target="_blank" rel="noopener noreferrer">https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3</a>)</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="video"><a class="anchor" href="#video"></a><a class="link" href="#video">Video</a></h4>
+<div class="paragraph">
+<p>MEDIA:video:<a href="https://v.nostr.build/MTjaYib4upQuf8zn.mp4" target="_blank" rel="noopener noreferrer">https://v.nostr.build/MTjaYib4upQuf8zn.mp4</a></p>
+</div>
+<div class="paragraph">
+<p>[<img src="<a href=&quot;https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&quot; class=&quot;bare&quot;>https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png</a>" alt="Video link with pic" class="max-w-[400px] object-contain my-0" />](MEDIA:video:<a href="https://v.nostr.build/MTjaYib4upQuf8zn.mp4" target="_blank" rel="noopener noreferrer">https://v.nostr.build/MTjaYib4upQuf8zn.mp4</a>)</p>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="tables"><a class="anchor" href="#tables"></a><a class="link" href="#tables">Tables</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="orderly"><a class="anchor" href="#orderly"></a><a class="link" href="#orderly">Orderly</a></h3>
+<div class="paragraph">
+<p>| Syntax      | Description |
+| ----------- | ----------- |
+| Header      | Title       |
+| Paragraph   | Text        |</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="unorderly"><a class="anchor" href="#unorderly"></a><a class="link" href="#unorderly">Unorderly</a></h3>
+<div class="paragraph">
+<p>| Syntax | Description |
+| --- | ----------- |
+| Header | Title |
+| Paragraph | Text |</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="with-alignment"><a class="anchor" href="#with-alignment"></a><a class="link" href="#with-alignment">With alignment</a></h3>
+<div class="paragraph">
+<p>| Syntax      | Description | Test Text     |
+| :---        |    :----:   |          ---: |
+| Header      | Title       | Here_s this   |
+| Paragraph   | Text        | And more      |</p>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="code-blocks"><a class="anchor" href="#code-blocks"></a><a class="link" href="#code-blocks">Code blocks</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="json"><a class="anchor" href="#json"></a><a class="link" href="#json">json</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>{
+    "id": "&lt;event_id&gt;",
+    "pubkey": "&lt;event_originator_pubkey&gt;",
+    "created_at": 1725087283,
+    "kind": 30040,
+    "tags": [
+        ["d", "aesop_s-fables-by-aesop"],
+        ["title", "Aesop_s Fables"],
+        ["author", "Aesop"],
+    ],
+    "sig": "&lt;event_signature&gt;"
+}</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="typescript"><a class="anchor" href="#typescript"></a><a class="link" href="#typescript">typescript</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>/**
+ * Get Nostr identifier type
+ */
+function getNostrType(id: string): _npub_ | _nprofile_ | _nevent_ | _naddr_ | _note_ | null {
+  if (id.startsWith(_npub_)) return _npub_;
+  if (id.startsWith(_nprofile_)) return _nprofile_;
+  if (id.startsWith(_nevent_)) return _nevent_;
+  if (id.startsWith(_naddr_)) return _naddr_;
+  if (id.startsWith(_note_)) return _note_;
+  return null;
+}</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="shell"><a class="anchor" href="#shell"></a><a class="link" href="#shell">shell</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>mkdir new_directory
+cp source.txt destination.txt</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="latex"><a class="anchor" href="#latex"></a><a class="link" href="#latex">LaTeX</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>$
+M =
+\begin{bmatrix}
+\frac{5}{6} &amp; \frac{1}{6} &amp; 0 \\[0.3em]
+\frac{5}{6} &amp; 0 &amp; \frac{1}{6} \\[0.3em]
+0 &amp; \frac{5}{6} &amp; \frac{1}{6}
+\end{bmatrix}
+$</code></pre>
+</div>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre><code>$
+f(x)=
+\begin{cases}
+1/d_{ij} &amp; \quad \text{when $d_{ij} \leq 160$}\\
+0 &amp; \quad \text{otherwise}
+\end{cases}
+$</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="abc-notation"><a class="anchor" href="#abc-notation"></a><a class="link" href="#abc-notation">ABC Notation</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>X:1
+T:Ohne Titel
+C:Aufgezeichnet 1784
+A:Seibis nahe Lichtenberg in Oberfranken
+S:Handschrift, bezeichnet und datiert: "Heinrich Nicol Philipp zu Seibis den 30 Junius 1784"
+M:4/4
+L:1/4
+K:D
+dd d2 | ee e2 | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+|:\
+fg ad | cB cA | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|</code></pre>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="latex-2"><a class="anchor" href="#latex-2"></a><a class="link" href="#latex-2">LateX</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="latex-in-inline-code"><a class="anchor" href="#latex-in-inline-code"></a><a class="link" href="#latex-in-inline-code">LaTex in inline-code</a></h3>
+<div class="paragraph">
+<p><code>$[ x^n + y^n = z^n \]$</code> and <code>$[\sqrt{x^2+1}\]$</code> and <code>$\color{blue}{X \sim Normal \; (\mu,\sigma^2)}$</code></p>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="latex-outside-of-code"><a class="anchor" href="#latex-outside-of-code"></a><a class="link" href="#latex-outside-of-code">LaTex outside of code</a></h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>This is a latex code block \mathbb{N} = \{ a \in \mathbb{Z} : a &gt; 0 \} and another that is an inline latex $\color{green}{X \sim Normal \; (\mu,\sigma^2)}$ and should be green</p>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="footnotes"><a class="anchor" href="#footnotes"></a><a class="link" href="#footnotes">Footnotes</a></h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>Here_s a simple footnote,[^1] and here_s a longer one.[^bignote]</p>
+</div>
+<div class="paragraph">
+<p>[^1]: This is the first footnote.</p>
+</div>
+<div class="paragraph">
+<p>[^bignote]: Here_s one with multiple paragraphs and code.</p>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="anchor-links"><a class="anchor" href="#anchor-links"></a><a class="link" href="#anchor-links">Anchor links</a></h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p><a href="#bullet-lists" rel="noreferrer noopener" class="break-words inline-flex items-baseline gap-1">Link to bullet list section <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a></p>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="formatting"><a class="anchor" href="#formatting"></a><a class="link" href="#formatting">Formatting</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="strikethrough"><a class="anchor" href="#strikethrough"></a><a class="link" href="#strikethrough">Strikethrough</a></h3>
+<div class="paragraph">
+<p>~~The world is flat.~~ We now know that the world is round. This should not be <sub>struck</sub> through.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="bold"><a class="anchor" href="#bold"></a><a class="link" href="#bold">Bold</a></h3>
+<div class="paragraph">
+<p>This is <strong>bold</strong> text. So is this <strong>bold</strong> text.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="italic"><a class="anchor" href="#italic"></a><a class="link" href="#italic">Italic</a></h3>
+<div class="paragraph">
+<p>This is <em>italic</em> text. So is this <em>italic</em> text.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="task-list"><a class="anchor" href="#task-list"></a><a class="link" href="#task-list">Task List</a></h3>
+<div class="ulist checklist">
+<ul class="checklist">
+<li>
+<p>&#10003; Write the press release</p>
+</li>
+<li>
+<p>&#10063; Update the website</p>
+</li>
+<li>
+<p>&#10063; Contact the media</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect2">
+<h3 id="emoji-shortcodes"><a class="anchor" href="#emoji-shortcodes"></a><a class="link" href="#emoji-shortcodes">Emoji shortcodes</a></h3>
+<div class="paragraph">
+<p>Gone camping! :tent: Be back soon.</p>
+</div>
+<div class="paragraph">
+<p>That is so funny! :joy:</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="marking-and-highlighting-text"><a class="anchor" href="#marking-and-highlighting-text"></a><a class="link" href="#marking-and-highlighting-text">Marking and highlighting text</a></h3>
+<div class="paragraph">
+<p>I need to highlight these ==very important words==.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="subscript-and-superscript"><a class="anchor" href="#subscript-and-superscript"></a><a class="link" href="#subscript-and-superscript">Subscript and Superscript</a></h3>
+<div class="paragraph">
+<p>H<sub>2</sub>O</p>
+</div>
+<div class="paragraph">
+<p>X<sup>2</sup></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="delimiter"><a class="anchor" href="#delimiter"></a><a class="link" href="#delimiter">Delimiter</a></h3>
+<div class="paragraph">
+<p>based upon a -</p>
+</div>
+<hr>
+<div class="paragraph">
+<p>based upon a *</p>
+</div>
+<hr>
+</div>
+<div class="sect2">
+<h3 id="quotes"><a class="anchor" href="#quotes"></a><a class="link" href="#quotes">Quotes</a></h3>
+<div class="quoteblock">
+<blockquote>
+<div class="paragraph">
+<p>This is a single line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd  kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj</p>
+</div>
+</blockquote>
+</div>
+<div class="quoteblock">
+<blockquote>
+<div class="paragraph">
+<p>This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd  kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj
+This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd  kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj
+This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd  kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj</p>
+</div>
+</blockquote>
+</div>
+</div>
+</div>
+</div>
+
+
+
+ +
+

Extracted Metadata

+ + +

Nostr Links (5)

+ +
+ naddr: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l + - nostr:naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l +
+ +
+ npub: npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z + - nostr:npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z +
+ +
+ nevent: nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj + - nostr:nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj +
+ +
+ nprofile: nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh + - nostr:nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh +
+ +
+ note: note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg + - nostr:note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg +
+ + + + +

Wikilinks (2)

+ +
+ [[NKBIP-01|Specification]] → dtag: nkbip-01 + (display: Specification) +
+ +
+ [[mirepoix]] → dtag: mirepoix + (display: mirepoix) +
+ + + + +

Hashtags (3)

+ +
+ #testhashtag +
+ +
+ #inlinehashtag +
+ +
+ #bullet +
+ + + + +

Links (18)

+ +
+ Welt Online link + External +
+ + + +
+ test image + External +
+ +
+ ![Youtube link with pic + External +
+ + + + + + + +
+ http://www.example.com` + External +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Media URLs (3)

+ + + + + + + + + + +

Table of Contents

+ + +
+
+ + +
+

AsciiDoc Document Test ✓ Parsed

+ +
+ + + + +
+ +
+
+
+
5
+
Nostr Links
+
+
+
2
+
Wikilinks
+
+
+
4
+
Hashtags
+
+
+
15
+
Links
+
+
+
2
+
Media URLs
+
+
+
Yes
+
Has LaTeX
+
+
+
No
+
Has Music
+
+
+ +

Frontmatter

+ + + +
+ +
+

Original AsciiDoc Content

+
+
= AsciiDoc Test Document
+Kismet Lee 
+2.9, October 31, 2021: Fall incarnation
+:description: Test description
+:author: Kismet Lee
+:date: 2021-10-31
+:version: 2.9
+:status: Draft
+:keywords: AsciiDoc, Test, Document
+:category: Test
+:language: English
+
+== Bullet list
+
+This is a test unordered list with mixed bullets:
+
+* First item with a number 2. in it
+* Second item
+* Third item
+** Indented item
+** Indented item
+* Fourth item
+
+Another unordered list:
+
+* 1st item
+* 2nd item
+* third item containing _italic_ text
+** indented item
+** second indented item
+* fourth item
+
+This is a test ordered list with indented items:
+
+. First item
+. Second item
+. Third item
+.. Indented item
+.. Indented item
+. Fourth item
+
+Ordered list where everything has no number:
+
+. First item
+. Second item
+. Third item
+. Fourth item
+
+This is a mixed list with indented items:
+
+. First item
+. Second item
+. Third item
+* Indented item
+* Indented item
+. Fourth item
+
+This is another mixed list with indented items:
+
+* First item
+* Second item
+* Third item
+. Indented item
+. Indented item
+* Fourth item
+
+== Headers
+
+=== Third-level header
+
+==== Fourth-level header
+
+===== Fifth-level header
+
+====== Sixth-level header
+
+== Media and Links
+
+=== Nostr address
+
+This should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l
+
+This is also plaintext:
+
+npub1gv069u6q7zkl393ad47xutpqmyfj0rrfrlnqnlfc2ld38k8nnl4st9wa6q
+
+These should be turned into links:
+
+nostr:naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l
+
+nostr:npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z
+
+nostr:nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj
+
+nostr:nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh
+
+nostr:note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg
+
+=== Hashtag
+
+#testhashtag at the start of the line and #inlinehashtag in the middle
+
+=== Wikilinks
+
+[[NKBIP-01|Specification]] and [[mirepoix]]
+
+=== URL
+
+https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html
+
+link:https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html[Welt Online link]
+
+this should render as plaintext: `http://www.example.com`
+
+this should be a hyperlink: www.example.com
+
+this should be a hyperlink to the http URL with the same address, so wss://theforest.nostr1.com should render like link:wss://theforest.nostr1.com[https://theforest.nostr1.com]
+
+=== Images
+
+https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png
+
+image::https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png[test image, width=100%]
+
+=== Media
+
+==== YouTube
+
+https://youtube.com/shorts/ZWfvChb-i0w
+
+link:https://youtube.com/shorts/ZWfvChb-i0w[image:https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png[Youtube link with pic]]
+
+==== Spotify
+
+https://open.spotify.com/episode/1GSZFA8vWltPyxYkArdRKx?si=bq6-az28TcuP596feTkRFQ
+
+link:https://open.spotify.com/episode/1GSZFA8vWltPyxYkArdRKx?si=bq6-az28TcuP596feTkRFQ[image:https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png[Spotify link with pic]]
+
+==== Audio
+
+https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3
+
+link:https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3[image:https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png[Audio link with pic]]
+
+==== Video
+
+https://v.nostr.build/MTjaYib4upQuf8zn.mp4
+
+link:https://v.nostr.build/MTjaYib4upQuf8zn.mp4[image:https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png[Video link with pic]]
+
+== Tables
+
+=== Orderly
+
+[cols="1,2"]
+|===
+|Syntax|Description
+|Header|Title
+|Paragraph|Text
+|===
+
+=== Unorderly
+
+[cols="1,2"]
+|===
+|Syntax|Description
+|Header|Title
+|Paragraph|Text
+|===
+
+=== With alignment
+
+[cols="<,^,>"]
+|===
+|Syntax|Description|Test Text
+|Header|Title|Here's this
+|Paragraph|Text|And more
+|===
+
+== Code blocks
+
+=== json
+
+[source,json]
+----
+{
+    "id": "<event_id>",
+    "pubkey": "<event_originator_pubkey>",
+    "created_at": 1725087283,
+    "kind": 30040,
+    "tags": [
+        ["d", "aesop's-fables-by-aesop"],
+        ["title", "Aesop's Fables"],
+        ["author", "Aesop"],
+    ],
+    "sig": "<event_signature>"
+}
+----
+
+=== typescript
+
+[source,typescript]
+----
+/**
+ * Get Nostr identifier type
+ */
+function getNostrType(id: string): 'npub' | 'nprofile' | 'nevent' | 'naddr' | 'note' | null {
+  if (id.startsWith('npub')) return 'npub';
+  if (id.startsWith('nprofile')) return 'nprofile';
+  if (id.startsWith('nevent')) return 'nevent';
+  if (id.startsWith('naddr')) return 'naddr';
+  if (id.startsWith('note')) return 'note';
+  return null;
+}
+----
+
+=== shell
+
+[source,shell]
+----
+
+mkdir new_directory
+cp source.txt destination.txt
+
+----
+
+=== LaTeX
+
+[source,latex]
+----
+$$
+M = 
+\begin{bmatrix}
+\frac{5}{6} & \frac{1}{6} & 0 \\[0.3em]
+\frac{5}{6} & 0 & \frac{1}{6} \\[0.3em]
+0 & \frac{5}{6} & \frac{1}{6}
+\end{bmatrix}
+$$
+----
+
+[source,latex]
+----
+$$
+f(x)=
+\begin{cases}
+1/d_{ij} & \quad \text{when $d_{ij} \leq 160$}\\ 
+0 & \quad \text{otherwise}
+\end{cases}
+$$
+----
+
+=== ABC Notation
+
+[source,abc]
+----
+X:1
+T:Ohne Titel
+C:Aufgezeichnet 1784
+A:Seibis nahe Lichtenberg in Oberfranken
+S:Handschrift, bezeichnet und datiert: "Heinrich Nicol Philipp zu Seibis den 30 Junius 1784"
+M:4/4
+L:1/4
+K:D
+dd d2 | ee e2 | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+|:\
+fg ad | cB cA | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+----
+
+=== PlantUML
+
+[source,plantuml]
+----
+@startuml
+Alice -> Bob: Authentication Request
+Bob --> Alice: Authentication Response
+@enduml
+----
+
+=== BPMN
+
+[source,plantuml]
+----
+@startbpmn
+start
+:Task 1;
+:Task 2;
+stop
+@endbpmn
+----
+
+== LaTeX
+
+=== LaTeX in inline-code
+
+`$[ x^n + y^n = z^n \]$` and `$[\sqrt{x^2+1}\]$` and `$\color{blue}{X \sim Normal \; (\mu,\sigma^2)}$`
+
+== LaTeX outside of code
+
+This is a latex code block $$\mathbb{N} = \{ a \in \mathbb{Z} : a > 0 \}$$ and another that is an inline latex $\color{green}{X \sim Normal \; (\mu,\sigma^2)}$ and should be green
+
+== Footnotes
+
+Here's a simple footnote,footnote:[This is the first footnote.] and here's a longer one.footnote:[Here's one with multiple paragraphs and code.]
+
+== Anchor links
+
+<<bullet-list,Link to bullet list section>>
+
+== Formatting
+
+=== Strikethrough
+
+[line-through]#The world is flat.# We now know that the world is round. This should not be ~struck~ through.
+
+=== Bold
+
+This is *bold* text. So is this *bold* text.
+
+=== Italic
+
+This is _italic_ text. So is this _italic_ text.
+
+=== Task List
+
+* [x] Write the press release
+* [ ] Update the website
+* [ ] Contact the media
+
+=== Emoji shortcodes
+
+Gone camping! :tent: Be back soon.
+
+That is so funny! :joy:
+
+=== Marking and highlighting text
+
+I need to highlight these [highlight]#very important words#.
+
+=== Subscript and Superscript
+
+H~2~O
+
+X^2^
+
+=== Delimiter
+
+based upon a -
+
+'''
+
+based upon a *
+
+'''
+
+=== Quotes
+
+[quote]
+____
+This is a single line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd  kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj
+____
+
+[quote]
+____
+This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd  kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj
+This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd  kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj
+This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd  kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj
+This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd  kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj
+____
+
+
+
+ +
+

Rendered HTML Output

+
+
+

Bullet list

+
+
+

This is a test unordered list with mixed bullets:

+
+
+
    +
  • +

    First item with a number 2. in it

    +
  • +
  • +

    Second item

    +
  • +
  • +

    Third item

    +
    +
      +
    • +

      Indented item

      +
    • +
    • +

      Indented item

      +
    • +
    +
    +
  • +
  • +

    Fourth item

    +
  • +
+
+
+

Another unordered list:

+
+
+
    +
  • +

    1st item

    +
  • +
  • +

    2nd item

    +
  • +
  • +

    third item containing italic text

    +
    +
      +
    • +

      indented item

      +
    • +
    • +

      second indented item

      +
    • +
    +
    +
  • +
  • +

    fourth item

    +
  • +
+
+
+

This is a test ordered list with indented items:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
    +
      +
    1. +

      Indented item

      +
    2. +
    3. +

      Indented item

      +
    4. +
    +
    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

Ordered list where everything has no number:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

This is a mixed list with indented items:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
    +
      +
    • +

      Indented item

      +
    • +
    • +

      Indented item

      +
    • +
    +
    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

This is another mixed list with indented items:

+
+
+
    +
  • +

    First item

    +
  • +
  • +

    Second item

    +
  • +
  • +

    Third item

    +
    +
      +
    1. +

      Indented item

      +
    2. +
    3. +

      Indented item

      +
    4. +
    +
    +
  • +
  • +

    Fourth item

    +
  • +
+
+
+
+ +
+ +
+
+

Nostr address

+
+

This should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l

+
+
+

This is also plaintext:

+
+
+

npub1gv069u6q7zkl393ad47xutpqmyfj0rrfrlnqnlfc2ld38k8nnl4st9wa6q

+
+
+

These should be turned into links:

+
+ + + + + +
+
+

Hashtag

+
+

#testhashtag at the start of the line and #inlinehashtag in the middle

+
+
+ + +
+ + +
+

Video

+ +
+

link:MEDIA:video:https://v.nostr.build/MTjaYib4upQuf8zn.mp4[ + Video link with pic +

+ ]

+
+
+
+
+
+
+

Tables

+
+
+

Orderly

+ ++++ + + + + + + + + + + + + + + +

Syntax

Description

Header

Title

Paragraph

Text

+
+
+

Unorderly

+ ++++ + + + + + + + + + + + + + + +

Syntax

Description

Header

Title

Paragraph

Text

+
+
+

With alignment

+ +++++ + + + + + + + + + + + + + + + + + +

Syntax

Description

Test Text

Header

Title

Here_s this

Paragraph

Text

And more

+
+
+
+
+

Code blocks

+
+
+

json

+
+
+
'''
+{
+    "id": "<event_id>",
+    "pubkey": "<event_originator_pubkey>",
+    "created_at": 1725087283,
+    "kind": 30040,
+    "tags": [
+        ["d", "aesop_s-fables-by-aesop"],
+        ["title", "Aesop_s Fables"],
+        ["author", "Aesop"],
+    ],
+    "sig": "<event_signature>"
+}
+'''
+
+
+
+
+

typescript

+
+
+
'''
+/**
+ * Get Nostr identifier type
+ */
+function getNostrType(id: string): _npub_ | _nprofile_ | _nevent_ | _naddr_ | _note_ | null {
+  if (id.startsWith(_npub_)) return _npub_;
+  if (id.startsWith(_nprofile_)) return _nprofile_;
+  if (id.startsWith(_nevent_)) return _nevent_;
+  if (id.startsWith(_naddr_)) return _naddr_;
+  if (id.startsWith(_note_)) return _note_;
+  return null;
+}
+'''
+
+
+
+
+

shell

+
+
+
'''
+
+
+
+

mkdir new_directory +cp source.txt destination.txt

+
+
+
+
+

LaTeX

+
+
+
'''
+$
+M =
+\begin{bmatrix}
+\frac{5}{6} & \frac{1}{6} & 0 \\[0.3em]
+\frac{5}{6} & 0 & \frac{1}{6} \\[0.3em]
+0 & \frac{5}{6} & \frac{1}{6}
+\end{bmatrix}
+$
+'''
+
+
+
+
+
'''
+$
+f(x)=
+\begin{cases}
+1/d_{ij} & \quad \text{when $d_{ij} \leq 160$}\\
+0 & \quad \text{otherwise}
+\end{cases}
+$
+'''
+
+
+
+
+

ABC Notation

+
+
+
'''
+X:1
+T:Ohne Titel
+C:Aufgezeichnet 1784
+A:Seibis nahe Lichtenberg in Oberfranken
+S:Handschrift, bezeichnet und datiert: "Heinrich Nicol Philipp zu Seibis den 30 Junius 1784"
+M:4/4
+L:1/4
+K:D
+dd d2 | ee e2 | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+|:\
+fg ad | cB cA | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+'''
+
+
+
+
+

PlantUML

+
+
+
'''
+@startuml
+Alice -> Bob: Authentication Request
+Bob --> Alice: Authentication Response
+@enduml
+'''
+
+
+
+
+

BPMN

+
+
+
'''
+@startbpmn
+start
+:Task 1;
+:Task 2;
+stop
+@endbpmn
+'''
+
+
+
+
+
+
+

LaTeX

+
+
+

LaTeX in inline-code

+
+

`$[ x^n + y^n = z^n \]== Bullet list

+
+
+

This is a test unordered list with mixed bullets:

+
+
+
    +
  • +

    First item with a number 2. in it

    +
  • +
  • +

    Second item

    +
  • +
  • +

    Third item

    +
    +
      +
    • +

      Indented item

      +
    • +
    • +

      Indented item

      +
    • +
    +
    +
  • +
  • +

    Fourth item

    +
  • +
+
+
+

Another unordered list:

+
+
+
    +
  • +

    1st item

    +
  • +
  • +

    2nd item

    +
  • +
  • +

    third item containing italic text

    +
    +
      +
    • +

      indented item

      +
    • +
    • +

      second indented item

      +
    • +
    +
    +
  • +
  • +

    fourth item

    +
  • +
+
+
+

This is a test ordered list with indented items:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
    +
      +
    1. +

      Indented item

      +
    2. +
    3. +

      Indented item

      +
    4. +
    +
    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

Ordered list where everything has no number:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

This is a mixed list with indented items:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
    +
      +
    • +

      Indented item

      +
    • +
    • +

      Indented item

      +
    • +
    +
    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

This is another mixed list with indented items:

+
+
+
    +
  • +

    First item

    +
  • +
  • +

    Second item

    +
  • +
  • +

    Third item

    +
    +
      +
    1. +

      Indented item

      +
    2. +
    3. +

      Indented item

      +
    4. +
    +
    +
  • +
  • +

    Fourth item

    +
  • +
+
+
+
+
+ +
+ +
+
+

Nostr address

+
+

This should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l

+
+
+

This is also plaintext:

+
+
+

npub1gv069u6q7zkl393ad47xutpqmyfj0rrfrlnqnlfc2ld38k8nnl4st9wa6q

+
+
+

These should be turned into links:

+
+ + + + + +
+
+

Hashtag

+
+

#testhashtag at the start of the line and #inlinehashtag in the middle

+
+
+ + +
+ + +
+

Video

+ +
+

link:MEDIA:video:https://v.nostr.build/MTjaYib4upQuf8zn.mp4[ + Video link with pic +

+ ]

+
+
+
+
+
+
+

Tables

+
+
+

Orderly

+ ++++ + + + + + + + + + + + + + + +

Syntax

Description

Header

Title

Paragraph

Text

+
+
+

Unorderly

+ ++++ + + + + + + + + + + + + + + +

Syntax

Description

Header

Title

Paragraph

Text

+
+
+

With alignment

+ +++++ + + + + + + + + + + + + + + + + + +

Syntax

Description

Test Text

Header

Title

Here_s this

Paragraph

Text

And more

+
+
+
+
+

Code blocks

+
+
+

json

+
+
+
'''
+{
+    "id": "<event_id>",
+    "pubkey": "<event_originator_pubkey>",
+    "created_at": 1725087283,
+    "kind": 30040,
+    "tags": [
+        ["d", "aesop_s-fables-by-aesop"],
+        ["title", "Aesop_s Fables"],
+        ["author", "Aesop"],
+    ],
+    "sig": "<event_signature>"
+}
+'''
+
+
+
+
+

typescript

+
+
+
'''
+/**
+ * Get Nostr identifier type
+ */
+function getNostrType(id: string): _npub_ | _nprofile_ | _nevent_ | _naddr_ | _note_ | null {
+  if (id.startsWith(_npub_)) return _npub_;
+  if (id.startsWith(_nprofile_)) return _nprofile_;
+  if (id.startsWith(_nevent_)) return _nevent_;
+  if (id.startsWith(_naddr_)) return _naddr_;
+  if (id.startsWith(_note_)) return _note_;
+  return null;
+}
+'''
+
+
+
+
+

shell

+
+
+
'''
+
+
+
+

mkdir new_directory +cp source.txt destination.txt

+
+
+
+
+

LaTeX

+
+
+
'''
+$
+M =
+\begin{bmatrix}
+\frac{5}{6} & \frac{1}{6} & 0 \\[0.3em]
+\frac{5}{6} & 0 & \frac{1}{6} \\[0.3em]
+0 & \frac{5}{6} & \frac{1}{6}
+\end{bmatrix}
+$
+'''
+
+
+
+
+
'''
+$
+f(x)=
+\begin{cases}
+1/d_{ij} & \quad \text{when $d_{ij} \leq 160$}\\
+0 & \quad \text{otherwise}
+\end{cases}
+$
+'''
+
+
+
+
+

ABC Notation

+
+
+
'''
+X:1
+T:Ohne Titel
+C:Aufgezeichnet 1784
+A:Seibis nahe Lichtenberg in Oberfranken
+S:Handschrift, bezeichnet und datiert: "Heinrich Nicol Philipp zu Seibis den 30 Junius 1784"
+M:4/4
+L:1/4
+K:D
+dd d2 | ee e2 | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+|:\
+fg ad | cB cA | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+'''
+
+
+
+
+

PlantUML

+
+
+
'''
+@startuml
+Alice -> Bob: Authentication Request
+Bob --> Alice: Authentication Response
+@enduml
+'''
+
+
+
+
+

BPMN

+
+
+
'''
+@startbpmn
+start
+:Task 1;
+:Task 2;
+stop
+@endbpmn
+'''
+
+
+
+
+
+
+

LaTeX

+
+
+

LaTeX in inline-code

+
+
+
and `$[\sqrt{x^2+1}\]== Bullet list
+
+
+
+

This is a test unordered list with mixed bullets:

+
+
+
    +
  • +

    First item with a number 2. in it

    +
  • +
  • +

    Second item

    +
  • +
  • +

    Third item

    +
    +
      +
    • +

      Indented item

      +
    • +
    • +

      Indented item

      +
    • +
    +
    +
  • +
  • +

    Fourth item

    +
  • +
+
+
+

Another unordered list:

+
+
+
    +
  • +

    1st item

    +
  • +
  • +

    2nd item

    +
  • +
  • +

    third item containing italic text

    +
    +
      +
    • +

      indented item

      +
    • +
    • +

      second indented item

      +
    • +
    +
    +
  • +
  • +

    fourth item

    +
  • +
+
+
+

This is a test ordered list with indented items:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
    +
      +
    1. +

      Indented item

      +
    2. +
    3. +

      Indented item

      +
    4. +
    +
    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

Ordered list where everything has no number:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

This is a mixed list with indented items:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
    +
      +
    • +

      Indented item

      +
    • +
    • +

      Indented item

      +
    • +
    +
    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

This is another mixed list with indented items:

+
+
+
    +
  • +

    First item

    +
  • +
  • +

    Second item

    +
  • +
  • +

    Third item

    +
    +
      +
    1. +

      Indented item

      +
    2. +
    3. +

      Indented item

      +
    4. +
    +
    +
  • +
  • +

    Fourth item

    +
  • +
+
+
+
+
+ +
+ +
+
+

Nostr address

+
+

This should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l

+
+
+

This is also plaintext:

+
+
+

npub1gv069u6q7zkl393ad47xutpqmyfj0rrfrlnqnlfc2ld38k8nnl4st9wa6q

+
+
+

These should be turned into links:

+
+ + + + + +
+
+

Hashtag

+
+

#testhashtag at the start of the line and #inlinehashtag in the middle

+
+
+ + +
+ + +
+

Video

+ +
+

link:MEDIA:video:https://v.nostr.build/MTjaYib4upQuf8zn.mp4[ + Video link with pic +

+ ]

+
+
+
+ + +
+

Tables

+
+
+

Orderly

+ ++++ + + + + + + + + + + + + + + +

Syntax

Description

Header

Title

Paragraph

Text

+
+
+

Unorderly

+ ++++ + + + + + + + + + + + + + + +

Syntax

Description

Header

Title

Paragraph

Text

+
+
+

With alignment

+ +++++ + + + + + + + + + + + + + + + + + +

Syntax

Description

Test Text

Header

Title

Here_s this

Paragraph

Text

And more

+
+
+
+
+

Code blocks

+
+
+

json

+
+
+
'''
+{
+    "id": "<event_id>",
+    "pubkey": "<event_originator_pubkey>",
+    "created_at": 1725087283,
+    "kind": 30040,
+    "tags": [
+        ["d", "aesop_s-fables-by-aesop"],
+        ["title", "Aesop_s Fables"],
+        ["author", "Aesop"],
+    ],
+    "sig": "<event_signature>"
+}
+'''
+
+
+
+
+

typescript

+
+
+
'''
+/**
+ * Get Nostr identifier type
+ */
+function getNostrType(id: string): _npub_ | _nprofile_ | _nevent_ | _naddr_ | _note_ | null {
+  if (id.startsWith(_npub_)) return _npub_;
+  if (id.startsWith(_nprofile_)) return _nprofile_;
+  if (id.startsWith(_nevent_)) return _nevent_;
+  if (id.startsWith(_naddr_)) return _naddr_;
+  if (id.startsWith(_note_)) return _note_;
+  return null;
+}
+'''
+
+
+
+
+

shell

+
+
+
'''
+
+
+
+

mkdir new_directory +cp source.txt destination.txt

+
+
+
+
+

LaTeX

+
+
+
'''
+$
+M =
+\begin{bmatrix}
+\frac{5}{6} & \frac{1}{6} & 0 \\[0.3em]
+\frac{5}{6} & 0 & \frac{1}{6} \\[0.3em]
+0 & \frac{5}{6} & \frac{1}{6}
+\end{bmatrix}
+$
+'''
+
+
+
+
+
'''
+$
+f(x)=
+\begin{cases}
+1/d_{ij} & \quad \text{when $d_{ij} \leq 160$}\\
+0 & \quad \text{otherwise}
+\end{cases}
+$
+'''
+
+
+
+
+

ABC Notation

+
+
+
'''
+X:1
+T:Ohne Titel
+C:Aufgezeichnet 1784
+A:Seibis nahe Lichtenberg in Oberfranken
+S:Handschrift, bezeichnet und datiert: "Heinrich Nicol Philipp zu Seibis den 30 Junius 1784"
+M:4/4
+L:1/4
+K:D
+dd d2 | ee e2 | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+|:\
+fg ad | cB cA | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+'''
+
+
+
+
+

PlantUML

+
+
+
'''
+@startuml
+Alice -> Bob: Authentication Request
+Bob --> Alice: Authentication Response
+@enduml
+'''
+
+
+
+
+

BPMN

+
+
+
'''
+@startbpmn
+start
+:Task 1;
+:Task 2;
+stop
+@endbpmn
+'''
+
+
+
+
+
+
+

LaTeX

+
+
+

LaTeX in inline-code

+
+

`$[ x^n + y^n = z^n \]== Bullet list

+
+
+

This is a test unordered list with mixed bullets:

+
+
+
    +
  • +

    First item with a number 2. in it

    +
  • +
  • +

    Second item

    +
  • +
  • +

    Third item

    +
    +
      +
    • +

      Indented item

      +
    • +
    • +

      Indented item

      +
    • +
    +
    +
  • +
  • +

    Fourth item

    +
  • +
+
+
+

Another unordered list:

+
+
+
    +
  • +

    1st item

    +
  • +
  • +

    2nd item

    +
  • +
  • +

    third item containing italic text

    +
    +
      +
    • +

      indented item

      +
    • +
    • +

      second indented item

      +
    • +
    +
    +
  • +
  • +

    fourth item

    +
  • +
+
+
+

This is a test ordered list with indented items:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
    +
      +
    1. +

      Indented item

      +
    2. +
    3. +

      Indented item

      +
    4. +
    +
    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

Ordered list where everything has no number:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

This is a mixed list with indented items:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
    +
      +
    • +

      Indented item

      +
    • +
    • +

      Indented item

      +
    • +
    +
    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

This is another mixed list with indented items:

+
+
+
    +
  • +

    First item

    +
  • +
  • +

    Second item

    +
  • +
  • +

    Third item

    +
    +
      +
    1. +

      Indented item

      +
    2. +
    3. +

      Indented item

      +
    4. +
    +
    +
  • +
  • +

    Fourth item

    +
  • +
+
+
+
+
+ +
+ +
+
+

Nostr address

+
+

This should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l

+
+
+

This is also plaintext:

+
+
+

npub1gv069u6q7zkl393ad47xutpqmyfj0rrfrlnqnlfc2ld38k8nnl4st9wa6q

+
+
+

These should be turned into links:

+
+ + + + + +
+
+

Hashtag

+
+

#testhashtag at the start of the line and #inlinehashtag in the middle

+
+
+ + +
+ + +
+

Video

+ +
+

link:MEDIA:video:https://v.nostr.build/MTjaYib4upQuf8zn.mp4[ + Video link with pic +

+ ]

+
+
+
+ + +
+

Tables

+
+
+

Orderly

+ ++++ + + + + + + + + + + + + + + +

Syntax

Description

Header

Title

Paragraph

Text

+
+
+

Unorderly

+ ++++ + + + + + + + + + + + + + + +

Syntax

Description

Header

Title

Paragraph

Text

+
+
+

With alignment

+ +++++ + + + + + + + + + + + + + + + + + +

Syntax

Description

Test Text

Header

Title

Here_s this

Paragraph

Text

And more

+
+
+
+
+

Code blocks

+
+
+

json

+
+
+
'''
+{
+    "id": "<event_id>",
+    "pubkey": "<event_originator_pubkey>",
+    "created_at": 1725087283,
+    "kind": 30040,
+    "tags": [
+        ["d", "aesop_s-fables-by-aesop"],
+        ["title", "Aesop_s Fables"],
+        ["author", "Aesop"],
+    ],
+    "sig": "<event_signature>"
+}
+'''
+
+
+
+
+

typescript

+
+
+
'''
+/**
+ * Get Nostr identifier type
+ */
+function getNostrType(id: string): _npub_ | _nprofile_ | _nevent_ | _naddr_ | _note_ | null {
+  if (id.startsWith(_npub_)) return _npub_;
+  if (id.startsWith(_nprofile_)) return _nprofile_;
+  if (id.startsWith(_nevent_)) return _nevent_;
+  if (id.startsWith(_naddr_)) return _naddr_;
+  if (id.startsWith(_note_)) return _note_;
+  return null;
+}
+'''
+
+
+
+
+

shell

+
+
+
'''
+
+
+
+

mkdir new_directory +cp source.txt destination.txt

+
+
+
+
+

LaTeX

+
+
+
'''
+$
+M =
+\begin{bmatrix}
+\frac{5}{6} & \frac{1}{6} & 0 \\[0.3em]
+\frac{5}{6} & 0 & \frac{1}{6} \\[0.3em]
+0 & \frac{5}{6} & \frac{1}{6}
+\end{bmatrix}
+$
+'''
+
+
+
+
+
'''
+$
+f(x)=
+\begin{cases}
+1/d_{ij} & \quad \text{when $d_{ij} \leq 160$}\\
+0 & \quad \text{otherwise}
+\end{cases}
+$
+'''
+
+
+
+
+

ABC Notation

+
+
+
'''
+X:1
+T:Ohne Titel
+C:Aufgezeichnet 1784
+A:Seibis nahe Lichtenberg in Oberfranken
+S:Handschrift, bezeichnet und datiert: "Heinrich Nicol Philipp zu Seibis den 30 Junius 1784"
+M:4/4
+L:1/4
+K:D
+dd d2 | ee e2 | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+|:\
+fg ad | cB cA | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+'''
+
+
+
+
+

PlantUML

+
+
+
'''
+@startuml
+Alice -> Bob: Authentication Request
+Bob --> Alice: Authentication Response
+@enduml
+'''
+
+
+
+
+

BPMN

+
+
+
'''
+@startbpmn
+start
+:Task 1;
+:Task 2;
+stop
+@endbpmn
+'''
+
+
+
+
+
+
+

LaTeX

+
+
+

LaTeX in inline-code

+
+
+
and  and `$\color{blue}{X \sim Normal \; (\mu,\sigma^2)}== Bullet list
+
+
+
+

This is a test unordered list with mixed bullets:

+
+
+
    +
  • +

    First item with a number 2. in it

    +
  • +
  • +

    Second item

    +
  • +
  • +

    Third item

    +
    +
      +
    • +

      Indented item

      +
    • +
    • +

      Indented item

      +
    • +
    +
    +
  • +
  • +

    Fourth item

    +
  • +
+
+
+

Another unordered list:

+
+
+
    +
  • +

    1st item

    +
  • +
  • +

    2nd item

    +
  • +
  • +

    third item containing italic text

    +
    +
      +
    • +

      indented item

      +
    • +
    • +

      second indented item

      +
    • +
    +
    +
  • +
  • +

    fourth item

    +
  • +
+
+
+

This is a test ordered list with indented items:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
    +
      +
    1. +

      Indented item

      +
    2. +
    3. +

      Indented item

      +
    4. +
    +
    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

Ordered list where everything has no number:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

This is a mixed list with indented items:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
    +
      +
    • +

      Indented item

      +
    • +
    • +

      Indented item

      +
    • +
    +
    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

This is another mixed list with indented items:

+
+
+
    +
  • +

    First item

    +
  • +
  • +

    Second item

    +
  • +
  • +

    Third item

    +
    +
      +
    1. +

      Indented item

      +
    2. +
    3. +

      Indented item

      +
    4. +
    +
    +
  • +
  • +

    Fourth item

    +
  • +
+
+
+
+
+ +
+ +
+
+

Nostr address

+
+

This should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l

+
+
+

This is also plaintext:

+
+
+

npub1gv069u6q7zkl393ad47xutpqmyfj0rrfrlnqnlfc2ld38k8nnl4st9wa6q

+
+
+

These should be turned into links:

+
+ + + + + +
+
+

Hashtag

+
+

#testhashtag at the start of the line and #inlinehashtag in the middle

+
+
+ + +
+ + +
+

Video

+ +
+

link:MEDIA:video:https://v.nostr.build/MTjaYib4upQuf8zn.mp4[ + Video link with pic +

+ ]

+
+
+
+ + +
+

Tables

+
+
+

Orderly

+ ++++ + + + + + + + + + + + + + + +

Syntax

Description

Header

Title

Paragraph

Text

+
+
+

Unorderly

+ ++++ + + + + + + + + + + + + + + +

Syntax

Description

Header

Title

Paragraph

Text

+
+
+

With alignment

+ +++++ + + + + + + + + + + + + + + + + + +

Syntax

Description

Test Text

Header

Title

Here_s this

Paragraph

Text

And more

+
+
+
+
+

Code blocks

+
+
+

json

+
+
+
'''
+{
+    "id": "<event_id>",
+    "pubkey": "<event_originator_pubkey>",
+    "created_at": 1725087283,
+    "kind": 30040,
+    "tags": [
+        ["d", "aesop_s-fables-by-aesop"],
+        ["title", "Aesop_s Fables"],
+        ["author", "Aesop"],
+    ],
+    "sig": "<event_signature>"
+}
+'''
+
+
+
+
+

typescript

+
+
+
'''
+/**
+ * Get Nostr identifier type
+ */
+function getNostrType(id: string): _npub_ | _nprofile_ | _nevent_ | _naddr_ | _note_ | null {
+  if (id.startsWith(_npub_)) return _npub_;
+  if (id.startsWith(_nprofile_)) return _nprofile_;
+  if (id.startsWith(_nevent_)) return _nevent_;
+  if (id.startsWith(_naddr_)) return _naddr_;
+  if (id.startsWith(_note_)) return _note_;
+  return null;
+}
+'''
+
+
+
+
+

shell

+
+
+
'''
+
+
+
+

mkdir new_directory +cp source.txt destination.txt

+
+
+
+
+

LaTeX

+
+
+
'''
+$
+M =
+\begin{bmatrix}
+\frac{5}{6} & \frac{1}{6} & 0 \\[0.3em]
+\frac{5}{6} & 0 & \frac{1}{6} \\[0.3em]
+0 & \frac{5}{6} & \frac{1}{6}
+\end{bmatrix}
+$
+'''
+
+
+
+
+
'''
+$
+f(x)=
+\begin{cases}
+1/d_{ij} & \quad \text{when $d_{ij} \leq 160$}\\
+0 & \quad \text{otherwise}
+\end{cases}
+$
+'''
+
+
+
+
+

ABC Notation

+
+
+
'''
+X:1
+T:Ohne Titel
+C:Aufgezeichnet 1784
+A:Seibis nahe Lichtenberg in Oberfranken
+S:Handschrift, bezeichnet und datiert: "Heinrich Nicol Philipp zu Seibis den 30 Junius 1784"
+M:4/4
+L:1/4
+K:D
+dd d2 | ee e2 | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+|:\
+fg ad | cB cA | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+'''
+
+
+
+
+

PlantUML

+
+
+
'''
+@startuml
+Alice -> Bob: Authentication Request
+Bob --> Alice: Authentication Response
+@enduml
+'''
+
+
+
+
+

BPMN

+
+
+
'''
+@startbpmn
+start
+:Task 1;
+:Task 2;
+stop
+@endbpmn
+'''
+
+
+
+
+
+
+

LaTeX

+
+
+

LaTeX in inline-code

+
+

`$[ x^n + y^n = z^n \]== Bullet list

+
+
+

This is a test unordered list with mixed bullets:

+
+
+
    +
  • +

    First item with a number 2. in it

    +
  • +
  • +

    Second item

    +
  • +
  • +

    Third item

    +
    +
      +
    • +

      Indented item

      +
    • +
    • +

      Indented item

      +
    • +
    +
    +
  • +
  • +

    Fourth item

    +
  • +
+
+
+

Another unordered list:

+
+
+
    +
  • +

    1st item

    +
  • +
  • +

    2nd item

    +
  • +
  • +

    third item containing italic text

    +
    +
      +
    • +

      indented item

      +
    • +
    • +

      second indented item

      +
    • +
    +
    +
  • +
  • +

    fourth item

    +
  • +
+
+
+

This is a test ordered list with indented items:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
    +
      +
    1. +

      Indented item

      +
    2. +
    3. +

      Indented item

      +
    4. +
    +
    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

Ordered list where everything has no number:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

This is a mixed list with indented items:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
    +
      +
    • +

      Indented item

      +
    • +
    • +

      Indented item

      +
    • +
    +
    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

This is another mixed list with indented items:

+
+
+
    +
  • +

    First item

    +
  • +
  • +

    Second item

    +
  • +
  • +

    Third item

    +
    +
      +
    1. +

      Indented item

      +
    2. +
    3. +

      Indented item

      +
    4. +
    +
    +
  • +
  • +

    Fourth item

    +
  • +
+
+
+
+
+ +
+ +
+
+

Nostr address

+
+

This should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l

+
+
+

This is also plaintext:

+
+
+

npub1gv069u6q7zkl393ad47xutpqmyfj0rrfrlnqnlfc2ld38k8nnl4st9wa6q

+
+
+

These should be turned into links:

+
+ + + + + +
+
+

Hashtag

+
+

#testhashtag at the start of the line and #inlinehashtag in the middle

+
+
+ + +
+ + +
+

Video

+ +
+

link:MEDIA:video:https://v.nostr.build/MTjaYib4upQuf8zn.mp4[ + Video link with pic +

+ ]

+
+
+
+ + +
+

Tables

+
+
+

Orderly

+ ++++ + + + + + + + + + + + + + + +

Syntax

Description

Header

Title

Paragraph

Text

+
+
+

Unorderly

+ ++++ + + + + + + + + + + + + + + +

Syntax

Description

Header

Title

Paragraph

Text

+
+
+

With alignment

+ +++++ + + + + + + + + + + + + + + + + + +

Syntax

Description

Test Text

Header

Title

Here_s this

Paragraph

Text

And more

+
+
+
+
+

Code blocks

+
+
+

json

+
+
+
'''
+{
+    "id": "<event_id>",
+    "pubkey": "<event_originator_pubkey>",
+    "created_at": 1725087283,
+    "kind": 30040,
+    "tags": [
+        ["d", "aesop_s-fables-by-aesop"],
+        ["title", "Aesop_s Fables"],
+        ["author", "Aesop"],
+    ],
+    "sig": "<event_signature>"
+}
+'''
+
+
+
+
+

typescript

+
+
+
'''
+/**
+ * Get Nostr identifier type
+ */
+function getNostrType(id: string): _npub_ | _nprofile_ | _nevent_ | _naddr_ | _note_ | null {
+  if (id.startsWith(_npub_)) return _npub_;
+  if (id.startsWith(_nprofile_)) return _nprofile_;
+  if (id.startsWith(_nevent_)) return _nevent_;
+  if (id.startsWith(_naddr_)) return _naddr_;
+  if (id.startsWith(_note_)) return _note_;
+  return null;
+}
+'''
+
+
+
+
+

shell

+
+
+
'''
+
+
+
+

mkdir new_directory +cp source.txt destination.txt

+
+
+
+
+

LaTeX

+
+
+
'''
+$
+M =
+\begin{bmatrix}
+\frac{5}{6} & \frac{1}{6} & 0 \\[0.3em]
+\frac{5}{6} & 0 & \frac{1}{6} \\[0.3em]
+0 & \frac{5}{6} & \frac{1}{6}
+\end{bmatrix}
+$
+'''
+
+
+
+
+
'''
+$
+f(x)=
+\begin{cases}
+1/d_{ij} & \quad \text{when $d_{ij} \leq 160$}\\
+0 & \quad \text{otherwise}
+\end{cases}
+$
+'''
+
+
+
+
+

ABC Notation

+
+
+
'''
+X:1
+T:Ohne Titel
+C:Aufgezeichnet 1784
+A:Seibis nahe Lichtenberg in Oberfranken
+S:Handschrift, bezeichnet und datiert: "Heinrich Nicol Philipp zu Seibis den 30 Junius 1784"
+M:4/4
+L:1/4
+K:D
+dd d2 | ee e2 | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+|:\
+fg ad | cB cA | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+'''
+
+
+
+
+

PlantUML

+
+
+
'''
+@startuml
+Alice -> Bob: Authentication Request
+Bob --> Alice: Authentication Response
+@enduml
+'''
+
+
+
+
+

BPMN

+
+
+
'''
+@startbpmn
+start
+:Task 1;
+:Task 2;
+stop
+@endbpmn
+'''
+
+
+
+
+
+
+

LaTeX

+
+
+

LaTeX in inline-code

+
+
+
and `$[\sqrt{x^2+1}\]== Bullet list
+
+
+
+

This is a test unordered list with mixed bullets:

+
+
+
    +
  • +

    First item with a number 2. in it

    +
  • +
  • +

    Second item

    +
  • +
  • +

    Third item

    +
    +
      +
    • +

      Indented item

      +
    • +
    • +

      Indented item

      +
    • +
    +
    +
  • +
  • +

    Fourth item

    +
  • +
+
+
+

Another unordered list:

+
+
+
    +
  • +

    1st item

    +
  • +
  • +

    2nd item

    +
  • +
  • +

    third item containing italic text

    +
    +
      +
    • +

      indented item

      +
    • +
    • +

      second indented item

      +
    • +
    +
    +
  • +
  • +

    fourth item

    +
  • +
+
+
+

This is a test ordered list with indented items:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
    +
      +
    1. +

      Indented item

      +
    2. +
    3. +

      Indented item

      +
    4. +
    +
    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

Ordered list where everything has no number:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

This is a mixed list with indented items:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
    +
      +
    • +

      Indented item

      +
    • +
    • +

      Indented item

      +
    • +
    +
    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

This is another mixed list with indented items:

+
+
+
    +
  • +

    First item

    +
  • +
  • +

    Second item

    +
  • +
  • +

    Third item

    +
    +
      +
    1. +

      Indented item

      +
    2. +
    3. +

      Indented item

      +
    4. +
    +
    +
  • +
  • +

    Fourth item

    +
  • +
+
+
+
+
+ +
+ +
+
+

Nostr address

+
+

This should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l

+
+
+

This is also plaintext:

+
+
+

npub1gv069u6q7zkl393ad47xutpqmyfj0rrfrlnqnlfc2ld38k8nnl4st9wa6q

+
+
+

These should be turned into links:

+
+ + + + + +
+
+

Hashtag

+
+

#testhashtag at the start of the line and #inlinehashtag in the middle

+
+
+ + +
+ + +
+

Video

+ +
+

link:MEDIA:video:https://v.nostr.build/MTjaYib4upQuf8zn.mp4[ + Video link with pic +

+ ]

+
+
+
+ + +
+

Tables

+
+
+

Orderly

+ ++++ + + + + + + + + + + + + + + +

Syntax

Description

Header

Title

Paragraph

Text

+
+
+

Unorderly

+ ++++ + + + + + + + + + + + + + + +

Syntax

Description

Header

Title

Paragraph

Text

+
+
+

With alignment

+ +++++ + + + + + + + + + + + + + + + + + +

Syntax

Description

Test Text

Header

Title

Here_s this

Paragraph

Text

And more

+
+
+
+
+

Code blocks

+
+
+

json

+
+
+
'''
+{
+    "id": "<event_id>",
+    "pubkey": "<event_originator_pubkey>",
+    "created_at": 1725087283,
+    "kind": 30040,
+    "tags": [
+        ["d", "aesop_s-fables-by-aesop"],
+        ["title", "Aesop_s Fables"],
+        ["author", "Aesop"],
+    ],
+    "sig": "<event_signature>"
+}
+'''
+
+
+
+
+

typescript

+
+
+
'''
+/**
+ * Get Nostr identifier type
+ */
+function getNostrType(id: string): _npub_ | _nprofile_ | _nevent_ | _naddr_ | _note_ | null {
+  if (id.startsWith(_npub_)) return _npub_;
+  if (id.startsWith(_nprofile_)) return _nprofile_;
+  if (id.startsWith(_nevent_)) return _nevent_;
+  if (id.startsWith(_naddr_)) return _naddr_;
+  if (id.startsWith(_note_)) return _note_;
+  return null;
+}
+'''
+
+
+
+
+

shell

+
+
+
'''
+
+
+
+

mkdir new_directory +cp source.txt destination.txt

+
+
+
+
+

LaTeX

+
+
+
'''
+$
+M =
+\begin{bmatrix}
+\frac{5}{6} & \frac{1}{6} & 0 \\[0.3em]
+\frac{5}{6} & 0 & \frac{1}{6} \\[0.3em]
+0 & \frac{5}{6} & \frac{1}{6}
+\end{bmatrix}
+$
+'''
+
+
+
+
+
'''
+$
+f(x)=
+\begin{cases}
+1/d_{ij} & \quad \text{when $d_{ij} \leq 160$}\\
+0 & \quad \text{otherwise}
+\end{cases}
+$
+'''
+
+
+
+
+

ABC Notation

+
+
+
'''
+X:1
+T:Ohne Titel
+C:Aufgezeichnet 1784
+A:Seibis nahe Lichtenberg in Oberfranken
+S:Handschrift, bezeichnet und datiert: "Heinrich Nicol Philipp zu Seibis den 30 Junius 1784"
+M:4/4
+L:1/4
+K:D
+dd d2 | ee e2 | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+|:\
+fg ad | cB cA | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+'''
+
+
+
+
+

PlantUML

+
+
+
'''
+@startuml
+Alice -> Bob: Authentication Request
+Bob --> Alice: Authentication Response
+@enduml
+'''
+
+
+
+
+

BPMN

+
+
+
'''
+@startbpmn
+start
+:Task 1;
+:Task 2;
+stop
+@endbpmn
+'''
+
+
+
+
+
+
+

LaTeX

+
+
+

LaTeX in inline-code

+
+

`$[ x^n + y^n = z^n \]== Bullet list

+
+
+

This is a test unordered list with mixed bullets:

+
+
+
    +
  • +

    First item with a number 2. in it

    +
  • +
  • +

    Second item

    +
  • +
  • +

    Third item

    +
    +
      +
    • +

      Indented item

      +
    • +
    • +

      Indented item

      +
    • +
    +
    +
  • +
  • +

    Fourth item

    +
  • +
+
+
+

Another unordered list:

+
+
+
    +
  • +

    1st item

    +
  • +
  • +

    2nd item

    +
  • +
  • +

    third item containing italic text

    +
    +
      +
    • +

      indented item

      +
    • +
    • +

      second indented item

      +
    • +
    +
    +
  • +
  • +

    fourth item

    +
  • +
+
+
+

This is a test ordered list with indented items:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
    +
      +
    1. +

      Indented item

      +
    2. +
    3. +

      Indented item

      +
    4. +
    +
    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

Ordered list where everything has no number:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

This is a mixed list with indented items:

+
+
+
    +
  1. +

    First item

    +
  2. +
  3. +

    Second item

    +
  4. +
  5. +

    Third item

    +
    +
      +
    • +

      Indented item

      +
    • +
    • +

      Indented item

      +
    • +
    +
    +
  6. +
  7. +

    Fourth item

    +
  8. +
+
+
+

This is another mixed list with indented items:

+
+
+
    +
  • +

    First item

    +
  • +
  • +

    Second item

    +
  • +
  • +

    Third item

    +
    +
      +
    1. +

      Indented item

      +
    2. +
    3. +

      Indented item

      +
    4. +
    +
    +
  • +
  • +

    Fourth item

    +
  • +
+
+
+
+
+ +
+ +
+
+

Nostr address

+
+

This should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l

+
+
+

This is also plaintext:

+
+
+

npub1gv069u6q7zkl393ad47xutpqmyfj0rrfrlnqnlfc2ld38k8nnl4st9wa6q

+
+
+

These should be turned into links:

+
+ + + + + +
+
+

Hashtag

+
+

#testhashtag at the start of the line and #inlinehashtag in the middle

+
+
+ + +
+ + +
+

Video

+ +
+

link:MEDIA:video:https://v.nostr.build/MTjaYib4upQuf8zn.mp4[ + Video link with pic +

+ ]

+
+
+
+ + +
+

Tables

+
+
+

Orderly

+ ++++ + + + + + + + + + + + + + + +

Syntax

Description

Header

Title

Paragraph

Text

+
+
+

Unorderly

+ ++++ + + + + + + + + + + + + + + +

Syntax

Description

Header

Title

Paragraph

Text

+
+
+

With alignment

+ +++++ + + + + + + + + + + + + + + + + + +

Syntax

Description

Test Text

Header

Title

Here_s this

Paragraph

Text

And more

+
+
+
+
+

Code blocks

+
+
+

json

+
+
+
'''
+{
+    "id": "<event_id>",
+    "pubkey": "<event_originator_pubkey>",
+    "created_at": 1725087283,
+    "kind": 30040,
+    "tags": [
+        ["d", "aesop_s-fables-by-aesop"],
+        ["title", "Aesop_s Fables"],
+        ["author", "Aesop"],
+    ],
+    "sig": "<event_signature>"
+}
+'''
+
+
+
+
+

typescript

+
+
+
'''
+/**
+ * Get Nostr identifier type
+ */
+function getNostrType(id: string): _npub_ | _nprofile_ | _nevent_ | _naddr_ | _note_ | null {
+  if (id.startsWith(_npub_)) return _npub_;
+  if (id.startsWith(_nprofile_)) return _nprofile_;
+  if (id.startsWith(_nevent_)) return _nevent_;
+  if (id.startsWith(_naddr_)) return _naddr_;
+  if (id.startsWith(_note_)) return _note_;
+  return null;
+}
+'''
+
+
+
+
+

shell

+
+
+
'''
+
+
+
+

mkdir new_directory +cp source.txt destination.txt

+
+
+
+
+

LaTeX

+
+
+
'''
+$
+M =
+\begin{bmatrix}
+\frac{5}{6} & \frac{1}{6} & 0 \\[0.3em]
+\frac{5}{6} & 0 & \frac{1}{6} \\[0.3em]
+0 & \frac{5}{6} & \frac{1}{6}
+\end{bmatrix}
+$
+'''
+
+
+
+
+
'''
+$
+f(x)=
+\begin{cases}
+1/d_{ij} & \quad \text{when $d_{ij} \leq 160$}\\
+0 & \quad \text{otherwise}
+\end{cases}
+$
+'''
+
+
+
+
+

ABC Notation

+
+
+
'''
+X:1
+T:Ohne Titel
+C:Aufgezeichnet 1784
+A:Seibis nahe Lichtenberg in Oberfranken
+S:Handschrift, bezeichnet und datiert: "Heinrich Nicol Philipp zu Seibis den 30 Junius 1784"
+M:4/4
+L:1/4
+K:D
+dd d2 | ee e2 | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+|:\
+fg ad | cB cA | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+'''
+
+
+
+
+

PlantUML

+
+
+
'''
+@startuml
+Alice -> Bob: Authentication Request
+Bob --> Alice: Authentication Response
+@enduml
+'''
+
+
+
+
+

BPMN

+
+
+
'''
+@startbpmn
+start
+:Task 1;
+:Task 2;
+stop
+@endbpmn
+'''
+
+
+
+
+
+
+

LaTeX

+
+
+

LaTeX in inline-code

+
+
+
and  and
+
+
+
+
+
+
+

LaTeX outside of code

+
+
+

This is a latex code block \mathbb{N} = \{ a \in \mathbb{Z} : a > 0 \} and another that is an inline latex $\color{green}{X \sim Normal \; (\mu,\sigma^2)}$ and should be green

+
+
+
+
+

Footnotes

+
+
+

Here_s a simple footnote,[1] and here_s a longer one.[2]

+
+
+
+ +
+

Formatting

+
+
+

Strikethrough

+
+

The world is flat. We now know that the world is round. This should not be struck through.

+
+
+
+

Bold

+
+

This is bold text. So is this bold text.

+
+
+
+

Italic

+
+

This is italic text. So is this italic text.

+
+
+
+

Task List

+
+
    +
  • +

    ✓ Write the press release

    +
  • +
  • +

    ❏ Update the website

    +
  • +
  • +

    ❏ Contact the media

    +
  • +
+
+
+
+

Emoji shortcodes

+
+

Gone camping! :tent: Be back soon.

+
+
+

That is so funny! :joy:

+
+
+
+

Marking and highlighting text

+
+

I need to highlight these very important words.

+
+
+
+

Subscript and Superscript

+
+

H2O

+
+
+

X2

+
+
+
+

Delimiter

+
+

based upon a -

+
+
+

_*

+
+
+

based upon a *

+
+
+

*_

+
+
+
+

Quotes

+
+
+
+

This is a single line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj

+
+
+
+
+
+
+

This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj +This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj +This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj +This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj

+
+
+
+
+
+
+
+
+
+1. This is the first footnote. +
+
+2. Here_s one with multiple paragraphs and code. +
+
+ +
+ View Raw HTML +
+
<div class="sect1">
+<h2 id="bullet-list"><a class="anchor" href="#bullet-list"></a><a class="link" href="#bullet-list">Bullet list</a></h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>This is a test unordered list with mixed bullets:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>First item with a number 2. in it</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Another unordered list:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>1st item</p>
+</li>
+<li>
+<p>2nd item</p>
+</li>
+<li>
+<p>third item containing <em>italic</em> text</p>
+<div class="ulist">
+<ul>
+<li>
+<p>indented item</p>
+</li>
+<li>
+<p>second indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>fourth item</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>This is a test ordered list with indented items:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="olist loweralpha">
+<ol class="loweralpha" type="a">
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ol>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>Ordered list where everything has no number:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>This is a mixed list with indented items:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>This is another mixed list with indented items:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ol>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="headers"><a class="anchor" href="#headers"></a><a class="link" href="#headers">Headers</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="third-level-header"><a class="anchor" href="#third-level-header"></a><a class="link" href="#third-level-header">Third-level header</a></h3>
+<div class="sect3">
+<h4 id="fourth-level-header"><a class="anchor" href="#fourth-level-header"></a><a class="link" href="#fourth-level-header">Fourth-level header</a></h4>
+<div class="sect4">
+<h5 id="fifth-level-header"><a class="anchor" href="#fifth-level-header"></a><a class="link" href="#fifth-level-header">Fifth-level header</a></h5>
+<div class="sect5">
+<h6 id="sixth-level-header"><a class="anchor" href="#sixth-level-header"></a><a class="link" href="#sixth-level-header">Sixth-level header</a></h6>
+
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="media-and-links"><a class="anchor" href="#media-and-links"></a><a class="link" href="#media-and-links">Media and Links</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="nostr-address"><a class="anchor" href="#nostr-address"></a><a class="link" href="#nostr-address">Nostr address</a></h3>
+<div class="paragraph">
+<p>This should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l</p>
+</div>
+<div class="paragraph">
+<p>This is also plaintext:</p>
+</div>
+<div class="paragraph">
+<p>npub1gv069u6q7zkl393ad47xutpqmyfj0rrfrlnqnlfc2ld38k8nnl4st9wa6q</p>
+</div>
+<div class="paragraph">
+<p>These should be turned into links:</p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l">naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z">npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj">nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh">nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg">note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg</a></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="hashtag"><a class="anchor" href="#hashtag"></a><a class="link" href="#hashtag">Hashtag</a></h3>
+<div class="paragraph">
+<p><a class="hashtag-link text-primary-600 dark:text-primary-500 hover:underline" data-topic="testhashtag" data-url="/notes?t=testhashtag" href="/notes?t=testhashtag">#testhashtag</a> at the start of the line and <a class="hashtag-link text-primary-600 dark:text-primary-500 hover:underline" data-topic="inlinehashtag" data-url="/notes?t=inlinehashtag" href="/notes?t=inlinehashtag">#inlinehashtag</a> in the middle</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="wikilinks"><a class="anchor" href="#wikilinks"></a><a class="link" href="#wikilinks">Wikilinks</a></h3>
+<div class="paragraph">
+<p><a class="wikilink text-primary-600 dark:text-primary-500 hover:underline" data-dtag="nkbip-01" data-url="/events?d=nkbip-01" href="/events?d=nkbip-01">Specification</a> and <a class="wikilink text-primary-600 dark:text-primary-500 hover:underline" data-dtag="mirepoix" data-url="/events?d=mirepoix" href="/events?d=mirepoix">mirepoix</a></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="url"><a class="anchor" href="#url"></a><a class="link" href="#url">URL</a></h3>
+<div class="paragraph">
+<p><span class="opengraph-link-container" data-og-url="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html">
+      <a href="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span></p>
+</div>
+<div class="paragraph">
+<p><span class="opengraph-link-container" data-og-url="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html">
+      <a href="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>[Welt Online link]</p>
+</div>
+<div class="paragraph">
+<p>this should render as plaintext: <code>http://www.example.com</code></p>
+</div>
+<div class="paragraph">
+<p>this should be a hyperlink: www.example.com</p>
+</div>
+<div class="paragraph">
+<p>this should be a hyperlink to the http URL with the same address, so <a href="https://theforest.nostr1.com/" target="_blank" rel="noopener noreferrer">wss://theforest.nostr1.com</a> should render like <a href="https://theforest.nostr1.com"https://theforest.nostr1.com" target="_blank" rel="noopener noreferrer">wss://theforest.nostr1.com<a href="https://theforest.nostr1.com" target="_blank" rel="noreferrer noopener" class="break-words inline-flex items-baseline gap-1">width=100%&quot;&gt;
+&lt;/div&gt;
+&lt;/div&gt;
+&lt;div class=&quot;imageblock&quot;&gt;
+&lt;div class=&quot;content&quot;&gt;
+&lt;img src=&quot;image::https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png&quot; alt=&quot;https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>[width=100%][test image" width="100%">
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="media"><a class="anchor" href="#media"></a><a class="link" href="#media">Media</a></h3>
+<div class="sect3">
+<h4 id="youtube"><a class="anchor" href="#youtube"></a><a class="link" href="#youtube">YouTube</a></h4>
+<div class="paragraph">
+<p><a href="https://youtube.com/shorts/ZWfvChb-i0w" target="_blank" rel="noopener noreferrer">https://youtube.com/shorts/ZWfvChb-i0w</a></p>
+</div>
+<div class="paragraph">
+<p><a href="https://youtube.com/shorts/ZWfvChb-i0w" target="_blank" rel="noopener noreferrer">https://youtube.com/shorts/ZWfvChb-i0w</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Youtube link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="spotify"><a class="anchor" href="#spotify"></a><a class="link" href="#spotify">Spotify</a></h4>
+<div class="paragraph">
+<p><div class="media-embed spotify-embed" style="margin: 1rem 0;">
+      <iframe 
+        style="border-radius: 12px; width: 100%; max-width: 100%;" 
+        src="https://open.spotify.com/embed/episode/1GSZFA8vWltPyxYkArdRKx?utm_source=generator" 
+        width="100%" 
+        height="352" 
+        frameborder="0" 
+        allowfullscreen="" 
+        allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" 
+        loading="lazy">
+      </iframe>
+    </div></p>
+</div>
+<div class="paragraph">
+<p><a href="<div class="media-embed spotify-embed" style="margin: 1rem 0;">
+      <iframe 
+        style="border-radius: 12px; width: 100%; max-width: 100%;" 
+        src="https://open.spotify.com/embed/episode/1GSZFA8vWltPyxYkArdRKx?utm_source=generator" 
+        width="100%" 
+        height="352" 
+        frameborder="0" 
+        allowfullscreen="" 
+        allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" 
+        loading="lazy">
+      </iframe>
+    </div>"><span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Spotify link with pic&lt;/a&gt; <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span></p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="audio"><a class="anchor" href="#audio"></a><a class="link" href="#audio">Audio</a></h4>
+<div class="paragraph">
+<p>MEDIA:audio:<a href="https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3" target="_blank" rel="noopener noreferrer">https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3</a></p>
+</div>
+<div class="paragraph">
+<p>link:MEDIA:audio:<a href="https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3" target="_blank" rel="noopener noreferrer">https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Audio link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="video"><a class="anchor" href="#video"></a><a class="link" href="#video">Video</a></h4>
+<div class="paragraph">
+<p>MEDIA:video:<a href="https://v.nostr.build/MTjaYib4upQuf8zn.mp4" target="_blank" rel="noopener noreferrer">https://v.nostr.build/MTjaYib4upQuf8zn.mp4</a></p>
+</div>
+<div class="paragraph">
+<p>link:MEDIA:video:<a href="https://v.nostr.build/MTjaYib4upQuf8zn.mp4" target="_blank" rel="noopener noreferrer">https://v.nostr.build/MTjaYib4upQuf8zn.mp4</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Video link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="tables"><a class="anchor" href="#tables"></a><a class="link" href="#tables">Tables</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="orderly"><a class="anchor" href="#orderly"></a><a class="link" href="#orderly">Orderly</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 66.6667%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Description</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Title</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Text</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect2">
+<h3 id="unorderly"><a class="anchor" href="#unorderly"></a><a class="link" href="#unorderly">Unorderly</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 66.6667%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Description</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Title</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Text</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect2">
+<h3 id="with-alignment"><a class="anchor" href="#with-alignment"></a><a class="link" href="#with-alignment">With alignment</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Description</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">Test Text</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Title</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">Here_s this</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Text</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">And more</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="code-blocks"><a class="anchor" href="#code-blocks"></a><a class="link" href="#code-blocks">Code blocks</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="json"><a class="anchor" href="#json"></a><a class="link" href="#json">json</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+{
+    "id": "&lt;event_id&gt;",
+    "pubkey": "&lt;event_originator_pubkey&gt;",
+    "created_at": 1725087283,
+    "kind": 30040,
+    "tags": [
+        ["d", "aesop_s-fables-by-aesop"],
+        ["title", "Aesop_s Fables"],
+        ["author", "Aesop"],
+    ],
+    "sig": "&lt;event_signature&gt;"
+}
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="typescript"><a class="anchor" href="#typescript"></a><a class="link" href="#typescript">typescript</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+/**
+ * Get Nostr identifier type
+ */
+function getNostrType(id: string): _npub_ | _nprofile_ | _nevent_ | _naddr_ | _note_ | null {
+  if (id.startsWith(_npub_)) return _npub_;
+  if (id.startsWith(_nprofile_)) return _nprofile_;
+  if (id.startsWith(_nevent_)) return _nevent_;
+  if (id.startsWith(_naddr_)) return _naddr_;
+  if (id.startsWith(_note_)) return _note_;
+  return null;
+}
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="shell"><a class="anchor" href="#shell"></a><a class="link" href="#shell">shell</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>mkdir new_directory
+cp source.txt destination.txt</p>
+</div>
+<hr>
+</div>
+<div class="sect2">
+<h3 id="latex"><a class="anchor" href="#latex"></a><a class="link" href="#latex">LaTeX</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+$
+M =
+\begin{bmatrix}
+\frac{5}{6} &amp; \frac{1}{6} &amp; 0 \\[0.3em]
+\frac{5}{6} &amp; 0 &amp; \frac{1}{6} \\[0.3em]
+0 &amp; \frac{5}{6} &amp; \frac{1}{6}
+\end{bmatrix}
+$
+'''</code></pre>
+</div>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+$
+f(x)=
+\begin{cases}
+1/d_{ij} &amp; \quad \text{when $d_{ij} \leq 160$}\\
+0 &amp; \quad \text{otherwise}
+\end{cases}
+$
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="abc-notation"><a class="anchor" href="#abc-notation"></a><a class="link" href="#abc-notation">ABC Notation</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+X:1
+T:Ohne Titel
+C:Aufgezeichnet 1784
+A:Seibis nahe Lichtenberg in Oberfranken
+S:Handschrift, bezeichnet und datiert: "Heinrich Nicol Philipp zu Seibis den 30 Junius 1784"
+M:4/4
+L:1/4
+K:D
+dd d2 | ee e2 | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+|:\
+fg ad | cB cA | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="plantuml"><a class="anchor" href="#plantuml"></a><a class="link" href="#plantuml">PlantUML</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+@startuml
+Alice -&gt; Bob: Authentication Request
+Bob --&gt; Alice: Authentication Response
+@enduml
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="bpmn"><a class="anchor" href="#bpmn"></a><a class="link" href="#bpmn">BPMN</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+@startbpmn
+start
+:Task 1;
+:Task 2;
+stop
+@endbpmn
+'''</code></pre>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="latex-2"><a class="anchor" href="#latex-2"></a><a class="link" href="#latex-2">LaTeX</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="latex-in-inline-code"><a class="anchor" href="#latex-in-inline-code"></a><a class="link" href="#latex-in-inline-code">LaTeX in inline-code</a></h3>
+<div class="paragraph">
+<p>`$[ x^n + y^n = z^n \]== Bullet list</p>
+</div>
+<div class="paragraph">
+<p>This is a test unordered list with mixed bullets:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>First item with a number 2. in it</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Another unordered list:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>1st item</p>
+</li>
+<li>
+<p>2nd item</p>
+</li>
+<li>
+<p>third item containing <em>italic</em> text</p>
+<div class="ulist">
+<ul>
+<li>
+<p>indented item</p>
+</li>
+<li>
+<p>second indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>fourth item</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>This is a test ordered list with indented items:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="olist loweralpha">
+<ol class="loweralpha" type="a">
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ol>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>Ordered list where everything has no number:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>This is a mixed list with indented items:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>This is another mixed list with indented items:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ol>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="headers-2"><a class="anchor" href="#headers-2"></a><a class="link" href="#headers-2">Headers</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="third-level-header-2"><a class="anchor" href="#third-level-header-2"></a><a class="link" href="#third-level-header-2">Third-level header</a></h3>
+<div class="sect3">
+<h4 id="fourth-level-header-2"><a class="anchor" href="#fourth-level-header-2"></a><a class="link" href="#fourth-level-header-2">Fourth-level header</a></h4>
+<div class="sect4">
+<h5 id="fifth-level-header-2"><a class="anchor" href="#fifth-level-header-2"></a><a class="link" href="#fifth-level-header-2">Fifth-level header</a></h5>
+<div class="sect5">
+<h6 id="sixth-level-header-2"><a class="anchor" href="#sixth-level-header-2"></a><a class="link" href="#sixth-level-header-2">Sixth-level header</a></h6>
+
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="media-and-links-2"><a class="anchor" href="#media-and-links-2"></a><a class="link" href="#media-and-links-2">Media and Links</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="nostr-address-2"><a class="anchor" href="#nostr-address-2"></a><a class="link" href="#nostr-address-2">Nostr address</a></h3>
+<div class="paragraph">
+<p>This should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l</p>
+</div>
+<div class="paragraph">
+<p>This is also plaintext:</p>
+</div>
+<div class="paragraph">
+<p>npub1gv069u6q7zkl393ad47xutpqmyfj0rrfrlnqnlfc2ld38k8nnl4st9wa6q</p>
+</div>
+<div class="paragraph">
+<p>These should be turned into links:</p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l">naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z">npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj">nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh">nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg">note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg</a></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="hashtag-2"><a class="anchor" href="#hashtag-2"></a><a class="link" href="#hashtag-2">Hashtag</a></h3>
+<div class="paragraph">
+<p><a class="hashtag-link text-primary-600 dark:text-primary-500 hover:underline" data-topic="testhashtag" data-url="/notes?t=testhashtag" href="/notes?t=testhashtag">#testhashtag</a> at the start of the line and <a class="hashtag-link text-primary-600 dark:text-primary-500 hover:underline" data-topic="inlinehashtag" data-url="/notes?t=inlinehashtag" href="/notes?t=inlinehashtag">#inlinehashtag</a> in the middle</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="wikilinks-2"><a class="anchor" href="#wikilinks-2"></a><a class="link" href="#wikilinks-2">Wikilinks</a></h3>
+<div class="paragraph">
+<p><a class="wikilink text-primary-600 dark:text-primary-500 hover:underline" data-dtag="nkbip-01" data-url="/events?d=nkbip-01" href="/events?d=nkbip-01">Specification</a> and <a class="wikilink text-primary-600 dark:text-primary-500 hover:underline" data-dtag="mirepoix" data-url="/events?d=mirepoix" href="/events?d=mirepoix">mirepoix</a></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="url-2"><a class="anchor" href="#url-2"></a><a class="link" href="#url-2">URL</a></h3>
+<div class="paragraph">
+<p><span class="opengraph-link-container" data-og-url="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html">
+      <a href="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span></p>
+</div>
+<div class="paragraph">
+<p><span class="opengraph-link-container" data-og-url="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html">
+      <a href="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>[Welt Online link]</p>
+</div>
+<div class="paragraph">
+<p>this should render as plaintext: <code>http://www.example.com</code></p>
+</div>
+<div class="paragraph">
+<p>this should be a hyperlink: www.example.com</p>
+</div>
+<div class="paragraph">
+<p>this should be a hyperlink to the http URL with the same address, so <a href="https://theforest.nostr1.com/" target="_blank" rel="noopener noreferrer">wss://theforest.nostr1.com</a> should render like <a href="https://theforest.nostr1.com"https://theforest.nostr1.com" target="_blank" rel="noopener noreferrer">wss://theforest.nostr1.com<a href="https://theforest.nostr1.com" target="_blank" rel="noreferrer noopener" class="break-words inline-flex items-baseline gap-1">width=100%&quot;&gt;
+&lt;/div&gt;
+&lt;/div&gt;
+&lt;div class=&quot;imageblock&quot;&gt;
+&lt;div class=&quot;content&quot;&gt;
+&lt;img src=&quot;image::https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png&quot; alt=&quot;https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>[width=100%][test image" width="100%">
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="media-2"><a class="anchor" href="#media-2"></a><a class="link" href="#media-2">Media</a></h3>
+<div class="sect3">
+<h4 id="youtube-2"><a class="anchor" href="#youtube-2"></a><a class="link" href="#youtube-2">YouTube</a></h4>
+<div class="paragraph">
+<p><a href="https://youtube.com/shorts/ZWfvChb-i0w" target="_blank" rel="noopener noreferrer">https://youtube.com/shorts/ZWfvChb-i0w</a></p>
+</div>
+<div class="paragraph">
+<p><a href="https://youtube.com/shorts/ZWfvChb-i0w" target="_blank" rel="noopener noreferrer">https://youtube.com/shorts/ZWfvChb-i0w</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Youtube link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="spotify-2"><a class="anchor" href="#spotify-2"></a><a class="link" href="#spotify-2">Spotify</a></h4>
+<div class="paragraph">
+<p><div class="media-embed spotify-embed" style="margin: 1rem 0;">
+      <iframe 
+        style="border-radius: 12px; width: 100%; max-width: 100%;" 
+        src="https://open.spotify.com/embed/episode/1GSZFA8vWltPyxYkArdRKx?utm_source=generator" 
+        width="100%" 
+        height="352" 
+        frameborder="0" 
+        allowfullscreen="" 
+        allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" 
+        loading="lazy">
+      </iframe>
+    </div></p>
+</div>
+<div class="paragraph">
+<p><a href="<div class="media-embed spotify-embed" style="margin: 1rem 0;">
+      <iframe 
+        style="border-radius: 12px; width: 100%; max-width: 100%;" 
+        src="https://open.spotify.com/embed/episode/1GSZFA8vWltPyxYkArdRKx?utm_source=generator" 
+        width="100%" 
+        height="352" 
+        frameborder="0" 
+        allowfullscreen="" 
+        allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" 
+        loading="lazy">
+      </iframe>
+    </div>"><span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Spotify link with pic&lt;/a&gt; <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span></p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="audio-2"><a class="anchor" href="#audio-2"></a><a class="link" href="#audio-2">Audio</a></h4>
+<div class="paragraph">
+<p>MEDIA:audio:<a href="https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3" target="_blank" rel="noopener noreferrer">https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3</a></p>
+</div>
+<div class="paragraph">
+<p>link:MEDIA:audio:<a href="https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3" target="_blank" rel="noopener noreferrer">https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Audio link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="video-2"><a class="anchor" href="#video-2"></a><a class="link" href="#video-2">Video</a></h4>
+<div class="paragraph">
+<p>MEDIA:video:<a href="https://v.nostr.build/MTjaYib4upQuf8zn.mp4" target="_blank" rel="noopener noreferrer">https://v.nostr.build/MTjaYib4upQuf8zn.mp4</a></p>
+</div>
+<div class="paragraph">
+<p>link:MEDIA:video:<a href="https://v.nostr.build/MTjaYib4upQuf8zn.mp4" target="_blank" rel="noopener noreferrer">https://v.nostr.build/MTjaYib4upQuf8zn.mp4</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Video link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="tables-2"><a class="anchor" href="#tables-2"></a><a class="link" href="#tables-2">Tables</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="orderly-2"><a class="anchor" href="#orderly-2"></a><a class="link" href="#orderly-2">Orderly</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 66.6667%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Description</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Title</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Text</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect2">
+<h3 id="unorderly-2"><a class="anchor" href="#unorderly-2"></a><a class="link" href="#unorderly-2">Unorderly</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 66.6667%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Description</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Title</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Text</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect2">
+<h3 id="with-alignment-2"><a class="anchor" href="#with-alignment-2"></a><a class="link" href="#with-alignment-2">With alignment</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Description</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">Test Text</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Title</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">Here_s this</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Text</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">And more</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="code-blocks-2"><a class="anchor" href="#code-blocks-2"></a><a class="link" href="#code-blocks-2">Code blocks</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="json-2"><a class="anchor" href="#json-2"></a><a class="link" href="#json-2">json</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+{
+    "id": "&lt;event_id&gt;",
+    "pubkey": "&lt;event_originator_pubkey&gt;",
+    "created_at": 1725087283,
+    "kind": 30040,
+    "tags": [
+        ["d", "aesop_s-fables-by-aesop"],
+        ["title", "Aesop_s Fables"],
+        ["author", "Aesop"],
+    ],
+    "sig": "&lt;event_signature&gt;"
+}
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="typescript-2"><a class="anchor" href="#typescript-2"></a><a class="link" href="#typescript-2">typescript</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+/**
+ * Get Nostr identifier type
+ */
+function getNostrType(id: string): _npub_ | _nprofile_ | _nevent_ | _naddr_ | _note_ | null {
+  if (id.startsWith(_npub_)) return _npub_;
+  if (id.startsWith(_nprofile_)) return _nprofile_;
+  if (id.startsWith(_nevent_)) return _nevent_;
+  if (id.startsWith(_naddr_)) return _naddr_;
+  if (id.startsWith(_note_)) return _note_;
+  return null;
+}
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="shell-2"><a class="anchor" href="#shell-2"></a><a class="link" href="#shell-2">shell</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>mkdir new_directory
+cp source.txt destination.txt</p>
+</div>
+<hr>
+</div>
+<div class="sect2">
+<h3 id="latex-3"><a class="anchor" href="#latex-3"></a><a class="link" href="#latex-3">LaTeX</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+$
+M =
+\begin{bmatrix}
+\frac{5}{6} &amp; \frac{1}{6} &amp; 0 \\[0.3em]
+\frac{5}{6} &amp; 0 &amp; \frac{1}{6} \\[0.3em]
+0 &amp; \frac{5}{6} &amp; \frac{1}{6}
+\end{bmatrix}
+$
+'''</code></pre>
+</div>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+$
+f(x)=
+\begin{cases}
+1/d_{ij} &amp; \quad \text{when $d_{ij} \leq 160$}\\
+0 &amp; \quad \text{otherwise}
+\end{cases}
+$
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="abc-notation-2"><a class="anchor" href="#abc-notation-2"></a><a class="link" href="#abc-notation-2">ABC Notation</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+X:1
+T:Ohne Titel
+C:Aufgezeichnet 1784
+A:Seibis nahe Lichtenberg in Oberfranken
+S:Handschrift, bezeichnet und datiert: "Heinrich Nicol Philipp zu Seibis den 30 Junius 1784"
+M:4/4
+L:1/4
+K:D
+dd d2 | ee e2 | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+|:\
+fg ad | cB cA | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="plantuml-2"><a class="anchor" href="#plantuml-2"></a><a class="link" href="#plantuml-2">PlantUML</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+@startuml
+Alice -&gt; Bob: Authentication Request
+Bob --&gt; Alice: Authentication Response
+@enduml
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="bpmn-2"><a class="anchor" href="#bpmn-2"></a><a class="link" href="#bpmn-2">BPMN</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+@startbpmn
+start
+:Task 1;
+:Task 2;
+stop
+@endbpmn
+'''</code></pre>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="latex-4"><a class="anchor" href="#latex-4"></a><a class="link" href="#latex-4">LaTeX</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="latex-in-inline-code-2"><a class="anchor" href="#latex-in-inline-code-2"></a><a class="link" href="#latex-in-inline-code-2">LaTeX in inline-code</a></h3>
+<div class="literalblock">
+<div class="content">
+<pre>and `$[\sqrt{x^2+1}\]== Bullet list</pre>
+</div>
+</div>
+<div class="paragraph">
+<p>This is a test unordered list with mixed bullets:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>First item with a number 2. in it</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Another unordered list:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>1st item</p>
+</li>
+<li>
+<p>2nd item</p>
+</li>
+<li>
+<p>third item containing <em>italic</em> text</p>
+<div class="ulist">
+<ul>
+<li>
+<p>indented item</p>
+</li>
+<li>
+<p>second indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>fourth item</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>This is a test ordered list with indented items:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="olist loweralpha">
+<ol class="loweralpha" type="a">
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ol>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>Ordered list where everything has no number:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>This is a mixed list with indented items:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>This is another mixed list with indented items:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ol>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="headers-3"><a class="anchor" href="#headers-3"></a><a class="link" href="#headers-3">Headers</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="third-level-header-3"><a class="anchor" href="#third-level-header-3"></a><a class="link" href="#third-level-header-3">Third-level header</a></h3>
+<div class="sect3">
+<h4 id="fourth-level-header-3"><a class="anchor" href="#fourth-level-header-3"></a><a class="link" href="#fourth-level-header-3">Fourth-level header</a></h4>
+<div class="sect4">
+<h5 id="fifth-level-header-3"><a class="anchor" href="#fifth-level-header-3"></a><a class="link" href="#fifth-level-header-3">Fifth-level header</a></h5>
+<div class="sect5">
+<h6 id="sixth-level-header-3"><a class="anchor" href="#sixth-level-header-3"></a><a class="link" href="#sixth-level-header-3">Sixth-level header</a></h6>
+
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="media-and-links-3"><a class="anchor" href="#media-and-links-3"></a><a class="link" href="#media-and-links-3">Media and Links</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="nostr-address-3"><a class="anchor" href="#nostr-address-3"></a><a class="link" href="#nostr-address-3">Nostr address</a></h3>
+<div class="paragraph">
+<p>This should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l</p>
+</div>
+<div class="paragraph">
+<p>This is also plaintext:</p>
+</div>
+<div class="paragraph">
+<p>npub1gv069u6q7zkl393ad47xutpqmyfj0rrfrlnqnlfc2ld38k8nnl4st9wa6q</p>
+</div>
+<div class="paragraph">
+<p>These should be turned into links:</p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l">naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z">npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj">nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh">nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg">note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg</a></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="hashtag-3"><a class="anchor" href="#hashtag-3"></a><a class="link" href="#hashtag-3">Hashtag</a></h3>
+<div class="paragraph">
+<p><a class="hashtag-link text-primary-600 dark:text-primary-500 hover:underline" data-topic="testhashtag" data-url="/notes?t=testhashtag" href="/notes?t=testhashtag">#testhashtag</a> at the start of the line and <a class="hashtag-link text-primary-600 dark:text-primary-500 hover:underline" data-topic="inlinehashtag" data-url="/notes?t=inlinehashtag" href="/notes?t=inlinehashtag">#inlinehashtag</a> in the middle</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="wikilinks-3"><a class="anchor" href="#wikilinks-3"></a><a class="link" href="#wikilinks-3">Wikilinks</a></h3>
+<div class="paragraph">
+<p><a class="wikilink text-primary-600 dark:text-primary-500 hover:underline" data-dtag="nkbip-01" data-url="/events?d=nkbip-01" href="/events?d=nkbip-01">Specification</a> and <a class="wikilink text-primary-600 dark:text-primary-500 hover:underline" data-dtag="mirepoix" data-url="/events?d=mirepoix" href="/events?d=mirepoix">mirepoix</a></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="url-3"><a class="anchor" href="#url-3"></a><a class="link" href="#url-3">URL</a></h3>
+<div class="paragraph">
+<p><span class="opengraph-link-container" data-og-url="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html">
+      <a href="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span></p>
+</div>
+<div class="paragraph">
+<p><span class="opengraph-link-container" data-og-url="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html">
+      <a href="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>[Welt Online link]</p>
+</div>
+<div class="paragraph">
+<p>this should render as plaintext: <code>http://www.example.com</code></p>
+</div>
+<div class="paragraph">
+<p>this should be a hyperlink: www.example.com</p>
+</div>
+<div class="paragraph">
+<p>this should be a hyperlink to the http URL with the same address, so <a href="https://theforest.nostr1.com/" target="_blank" rel="noopener noreferrer">wss://theforest.nostr1.com</a> should render like <a href="https://theforest.nostr1.com"https://theforest.nostr1.com" target="_blank" rel="noopener noreferrer">wss://theforest.nostr1.com<a href="https://theforest.nostr1.com" target="_blank" rel="noreferrer noopener" class="break-words inline-flex items-baseline gap-1">width=100%&quot;&gt;
+&lt;/div&gt;
+&lt;/div&gt;
+&lt;div class=&quot;imageblock&quot;&gt;
+&lt;div class=&quot;content&quot;&gt;
+&lt;img src=&quot;image::https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png&quot; alt=&quot;https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>[width=100%][test image" width="100%">
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="media-3"><a class="anchor" href="#media-3"></a><a class="link" href="#media-3">Media</a></h3>
+<div class="sect3">
+<h4 id="youtube-3"><a class="anchor" href="#youtube-3"></a><a class="link" href="#youtube-3">YouTube</a></h4>
+<div class="paragraph">
+<p><a href="https://youtube.com/shorts/ZWfvChb-i0w" target="_blank" rel="noopener noreferrer">https://youtube.com/shorts/ZWfvChb-i0w</a></p>
+</div>
+<div class="paragraph">
+<p><a href="https://youtube.com/shorts/ZWfvChb-i0w" target="_blank" rel="noopener noreferrer">https://youtube.com/shorts/ZWfvChb-i0w</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Youtube link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="spotify-3"><a class="anchor" href="#spotify-3"></a><a class="link" href="#spotify-3">Spotify</a></h4>
+<div class="paragraph">
+<p><div class="media-embed spotify-embed" style="margin: 1rem 0;">
+      <iframe 
+        style="border-radius: 12px; width: 100%; max-width: 100%;" 
+        src="https://open.spotify.com/embed/episode/1GSZFA8vWltPyxYkArdRKx?utm_source=generator" 
+        width="100%" 
+        height="352" 
+        frameborder="0" 
+        allowfullscreen="" 
+        allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" 
+        loading="lazy">
+      </iframe>
+    </div></p>
+</div>
+<div class="paragraph">
+<p><a href="<div class="media-embed spotify-embed" style="margin: 1rem 0;">
+      <iframe 
+        style="border-radius: 12px; width: 100%; max-width: 100%;" 
+        src="https://open.spotify.com/embed/episode/1GSZFA8vWltPyxYkArdRKx?utm_source=generator" 
+        width="100%" 
+        height="352" 
+        frameborder="0" 
+        allowfullscreen="" 
+        allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" 
+        loading="lazy">
+      </iframe>
+    </div>"><span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Spotify link with pic&lt;/a&gt; <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span></p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="audio-3"><a class="anchor" href="#audio-3"></a><a class="link" href="#audio-3">Audio</a></h4>
+<div class="paragraph">
+<p>MEDIA:audio:<a href="https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3" target="_blank" rel="noopener noreferrer">https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3</a></p>
+</div>
+<div class="paragraph">
+<p>link:MEDIA:audio:<a href="https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3" target="_blank" rel="noopener noreferrer">https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Audio link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="video-3"><a class="anchor" href="#video-3"></a><a class="link" href="#video-3">Video</a></h4>
+<div class="paragraph">
+<p>MEDIA:video:<a href="https://v.nostr.build/MTjaYib4upQuf8zn.mp4" target="_blank" rel="noopener noreferrer">https://v.nostr.build/MTjaYib4upQuf8zn.mp4</a></p>
+</div>
+<div class="paragraph">
+<p>link:MEDIA:video:<a href="https://v.nostr.build/MTjaYib4upQuf8zn.mp4" target="_blank" rel="noopener noreferrer">https://v.nostr.build/MTjaYib4upQuf8zn.mp4</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Video link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="tables-3"><a class="anchor" href="#tables-3"></a><a class="link" href="#tables-3">Tables</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="orderly-3"><a class="anchor" href="#orderly-3"></a><a class="link" href="#orderly-3">Orderly</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 66.6667%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Description</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Title</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Text</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect2">
+<h3 id="unorderly-3"><a class="anchor" href="#unorderly-3"></a><a class="link" href="#unorderly-3">Unorderly</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 66.6667%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Description</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Title</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Text</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect2">
+<h3 id="with-alignment-3"><a class="anchor" href="#with-alignment-3"></a><a class="link" href="#with-alignment-3">With alignment</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Description</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">Test Text</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Title</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">Here_s this</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Text</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">And more</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="code-blocks-3"><a class="anchor" href="#code-blocks-3"></a><a class="link" href="#code-blocks-3">Code blocks</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="json-3"><a class="anchor" href="#json-3"></a><a class="link" href="#json-3">json</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+{
+    "id": "&lt;event_id&gt;",
+    "pubkey": "&lt;event_originator_pubkey&gt;",
+    "created_at": 1725087283,
+    "kind": 30040,
+    "tags": [
+        ["d", "aesop_s-fables-by-aesop"],
+        ["title", "Aesop_s Fables"],
+        ["author", "Aesop"],
+    ],
+    "sig": "&lt;event_signature&gt;"
+}
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="typescript-3"><a class="anchor" href="#typescript-3"></a><a class="link" href="#typescript-3">typescript</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+/**
+ * Get Nostr identifier type
+ */
+function getNostrType(id: string): _npub_ | _nprofile_ | _nevent_ | _naddr_ | _note_ | null {
+  if (id.startsWith(_npub_)) return _npub_;
+  if (id.startsWith(_nprofile_)) return _nprofile_;
+  if (id.startsWith(_nevent_)) return _nevent_;
+  if (id.startsWith(_naddr_)) return _naddr_;
+  if (id.startsWith(_note_)) return _note_;
+  return null;
+}
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="shell-3"><a class="anchor" href="#shell-3"></a><a class="link" href="#shell-3">shell</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>mkdir new_directory
+cp source.txt destination.txt</p>
+</div>
+<hr>
+</div>
+<div class="sect2">
+<h3 id="latex-5"><a class="anchor" href="#latex-5"></a><a class="link" href="#latex-5">LaTeX</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+$
+M =
+\begin{bmatrix}
+\frac{5}{6} &amp; \frac{1}{6} &amp; 0 \\[0.3em]
+\frac{5}{6} &amp; 0 &amp; \frac{1}{6} \\[0.3em]
+0 &amp; \frac{5}{6} &amp; \frac{1}{6}
+\end{bmatrix}
+$
+'''</code></pre>
+</div>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+$
+f(x)=
+\begin{cases}
+1/d_{ij} &amp; \quad \text{when $d_{ij} \leq 160$}\\
+0 &amp; \quad \text{otherwise}
+\end{cases}
+$
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="abc-notation-3"><a class="anchor" href="#abc-notation-3"></a><a class="link" href="#abc-notation-3">ABC Notation</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+X:1
+T:Ohne Titel
+C:Aufgezeichnet 1784
+A:Seibis nahe Lichtenberg in Oberfranken
+S:Handschrift, bezeichnet und datiert: "Heinrich Nicol Philipp zu Seibis den 30 Junius 1784"
+M:4/4
+L:1/4
+K:D
+dd d2 | ee e2 | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+|:\
+fg ad | cB cA | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="plantuml-3"><a class="anchor" href="#plantuml-3"></a><a class="link" href="#plantuml-3">PlantUML</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+@startuml
+Alice -&gt; Bob: Authentication Request
+Bob --&gt; Alice: Authentication Response
+@enduml
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="bpmn-3"><a class="anchor" href="#bpmn-3"></a><a class="link" href="#bpmn-3">BPMN</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+@startbpmn
+start
+:Task 1;
+:Task 2;
+stop
+@endbpmn
+'''</code></pre>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="latex-6"><a class="anchor" href="#latex-6"></a><a class="link" href="#latex-6">LaTeX</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="latex-in-inline-code-3"><a class="anchor" href="#latex-in-inline-code-3"></a><a class="link" href="#latex-in-inline-code-3">LaTeX in inline-code</a></h3>
+<div class="paragraph">
+<p>`$[ x^n + y^n = z^n \]== Bullet list</p>
+</div>
+<div class="paragraph">
+<p>This is a test unordered list with mixed bullets:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>First item with a number 2. in it</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Another unordered list:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>1st item</p>
+</li>
+<li>
+<p>2nd item</p>
+</li>
+<li>
+<p>third item containing <em>italic</em> text</p>
+<div class="ulist">
+<ul>
+<li>
+<p>indented item</p>
+</li>
+<li>
+<p>second indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>fourth item</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>This is a test ordered list with indented items:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="olist loweralpha">
+<ol class="loweralpha" type="a">
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ol>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>Ordered list where everything has no number:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>This is a mixed list with indented items:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>This is another mixed list with indented items:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ol>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="headers-4"><a class="anchor" href="#headers-4"></a><a class="link" href="#headers-4">Headers</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="third-level-header-4"><a class="anchor" href="#third-level-header-4"></a><a class="link" href="#third-level-header-4">Third-level header</a></h3>
+<div class="sect3">
+<h4 id="fourth-level-header-4"><a class="anchor" href="#fourth-level-header-4"></a><a class="link" href="#fourth-level-header-4">Fourth-level header</a></h4>
+<div class="sect4">
+<h5 id="fifth-level-header-4"><a class="anchor" href="#fifth-level-header-4"></a><a class="link" href="#fifth-level-header-4">Fifth-level header</a></h5>
+<div class="sect5">
+<h6 id="sixth-level-header-4"><a class="anchor" href="#sixth-level-header-4"></a><a class="link" href="#sixth-level-header-4">Sixth-level header</a></h6>
+
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="media-and-links-4"><a class="anchor" href="#media-and-links-4"></a><a class="link" href="#media-and-links-4">Media and Links</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="nostr-address-4"><a class="anchor" href="#nostr-address-4"></a><a class="link" href="#nostr-address-4">Nostr address</a></h3>
+<div class="paragraph">
+<p>This should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l</p>
+</div>
+<div class="paragraph">
+<p>This is also plaintext:</p>
+</div>
+<div class="paragraph">
+<p>npub1gv069u6q7zkl393ad47xutpqmyfj0rrfrlnqnlfc2ld38k8nnl4st9wa6q</p>
+</div>
+<div class="paragraph">
+<p>These should be turned into links:</p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l">naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z">npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj">nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh">nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg">note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg</a></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="hashtag-4"><a class="anchor" href="#hashtag-4"></a><a class="link" href="#hashtag-4">Hashtag</a></h3>
+<div class="paragraph">
+<p><a class="hashtag-link text-primary-600 dark:text-primary-500 hover:underline" data-topic="testhashtag" data-url="/notes?t=testhashtag" href="/notes?t=testhashtag">#testhashtag</a> at the start of the line and <a class="hashtag-link text-primary-600 dark:text-primary-500 hover:underline" data-topic="inlinehashtag" data-url="/notes?t=inlinehashtag" href="/notes?t=inlinehashtag">#inlinehashtag</a> in the middle</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="wikilinks-4"><a class="anchor" href="#wikilinks-4"></a><a class="link" href="#wikilinks-4">Wikilinks</a></h3>
+<div class="paragraph">
+<p><a class="wikilink text-primary-600 dark:text-primary-500 hover:underline" data-dtag="nkbip-01" data-url="/events?d=nkbip-01" href="/events?d=nkbip-01">Specification</a> and <a class="wikilink text-primary-600 dark:text-primary-500 hover:underline" data-dtag="mirepoix" data-url="/events?d=mirepoix" href="/events?d=mirepoix">mirepoix</a></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="url-4"><a class="anchor" href="#url-4"></a><a class="link" href="#url-4">URL</a></h3>
+<div class="paragraph">
+<p><span class="opengraph-link-container" data-og-url="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html">
+      <a href="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span></p>
+</div>
+<div class="paragraph">
+<p><span class="opengraph-link-container" data-og-url="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html">
+      <a href="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>[Welt Online link]</p>
+</div>
+<div class="paragraph">
+<p>this should render as plaintext: <code>http://www.example.com</code></p>
+</div>
+<div class="paragraph">
+<p>this should be a hyperlink: www.example.com</p>
+</div>
+<div class="paragraph">
+<p>this should be a hyperlink to the http URL with the same address, so <a href="https://theforest.nostr1.com/" target="_blank" rel="noopener noreferrer">wss://theforest.nostr1.com</a> should render like <a href="https://theforest.nostr1.com"https://theforest.nostr1.com" target="_blank" rel="noopener noreferrer">wss://theforest.nostr1.com<a href="https://theforest.nostr1.com" target="_blank" rel="noreferrer noopener" class="break-words inline-flex items-baseline gap-1">width=100%&quot;&gt;
+&lt;/div&gt;
+&lt;/div&gt;
+&lt;div class=&quot;imageblock&quot;&gt;
+&lt;div class=&quot;content&quot;&gt;
+&lt;img src=&quot;image::https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png&quot; alt=&quot;https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>[width=100%][test image" width="100%">
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="media-4"><a class="anchor" href="#media-4"></a><a class="link" href="#media-4">Media</a></h3>
+<div class="sect3">
+<h4 id="youtube-4"><a class="anchor" href="#youtube-4"></a><a class="link" href="#youtube-4">YouTube</a></h4>
+<div class="paragraph">
+<p><a href="https://youtube.com/shorts/ZWfvChb-i0w" target="_blank" rel="noopener noreferrer">https://youtube.com/shorts/ZWfvChb-i0w</a></p>
+</div>
+<div class="paragraph">
+<p><a href="https://youtube.com/shorts/ZWfvChb-i0w" target="_blank" rel="noopener noreferrer">https://youtube.com/shorts/ZWfvChb-i0w</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Youtube link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="spotify-4"><a class="anchor" href="#spotify-4"></a><a class="link" href="#spotify-4">Spotify</a></h4>
+<div class="paragraph">
+<p><div class="media-embed spotify-embed" style="margin: 1rem 0;">
+      <iframe 
+        style="border-radius: 12px; width: 100%; max-width: 100%;" 
+        src="https://open.spotify.com/embed/episode/1GSZFA8vWltPyxYkArdRKx?utm_source=generator" 
+        width="100%" 
+        height="352" 
+        frameborder="0" 
+        allowfullscreen="" 
+        allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" 
+        loading="lazy">
+      </iframe>
+    </div></p>
+</div>
+<div class="paragraph">
+<p><a href="<div class="media-embed spotify-embed" style="margin: 1rem 0;">
+      <iframe 
+        style="border-radius: 12px; width: 100%; max-width: 100%;" 
+        src="https://open.spotify.com/embed/episode/1GSZFA8vWltPyxYkArdRKx?utm_source=generator" 
+        width="100%" 
+        height="352" 
+        frameborder="0" 
+        allowfullscreen="" 
+        allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" 
+        loading="lazy">
+      </iframe>
+    </div>"><span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Spotify link with pic&lt;/a&gt; <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span></p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="audio-4"><a class="anchor" href="#audio-4"></a><a class="link" href="#audio-4">Audio</a></h4>
+<div class="paragraph">
+<p>MEDIA:audio:<a href="https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3" target="_blank" rel="noopener noreferrer">https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3</a></p>
+</div>
+<div class="paragraph">
+<p>link:MEDIA:audio:<a href="https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3" target="_blank" rel="noopener noreferrer">https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Audio link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="video-4"><a class="anchor" href="#video-4"></a><a class="link" href="#video-4">Video</a></h4>
+<div class="paragraph">
+<p>MEDIA:video:<a href="https://v.nostr.build/MTjaYib4upQuf8zn.mp4" target="_blank" rel="noopener noreferrer">https://v.nostr.build/MTjaYib4upQuf8zn.mp4</a></p>
+</div>
+<div class="paragraph">
+<p>link:MEDIA:video:<a href="https://v.nostr.build/MTjaYib4upQuf8zn.mp4" target="_blank" rel="noopener noreferrer">https://v.nostr.build/MTjaYib4upQuf8zn.mp4</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Video link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="tables-4"><a class="anchor" href="#tables-4"></a><a class="link" href="#tables-4">Tables</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="orderly-4"><a class="anchor" href="#orderly-4"></a><a class="link" href="#orderly-4">Orderly</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 66.6667%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Description</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Title</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Text</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect2">
+<h3 id="unorderly-4"><a class="anchor" href="#unorderly-4"></a><a class="link" href="#unorderly-4">Unorderly</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 66.6667%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Description</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Title</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Text</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect2">
+<h3 id="with-alignment-4"><a class="anchor" href="#with-alignment-4"></a><a class="link" href="#with-alignment-4">With alignment</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Description</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">Test Text</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Title</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">Here_s this</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Text</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">And more</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="code-blocks-4"><a class="anchor" href="#code-blocks-4"></a><a class="link" href="#code-blocks-4">Code blocks</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="json-4"><a class="anchor" href="#json-4"></a><a class="link" href="#json-4">json</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+{
+    "id": "&lt;event_id&gt;",
+    "pubkey": "&lt;event_originator_pubkey&gt;",
+    "created_at": 1725087283,
+    "kind": 30040,
+    "tags": [
+        ["d", "aesop_s-fables-by-aesop"],
+        ["title", "Aesop_s Fables"],
+        ["author", "Aesop"],
+    ],
+    "sig": "&lt;event_signature&gt;"
+}
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="typescript-4"><a class="anchor" href="#typescript-4"></a><a class="link" href="#typescript-4">typescript</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+/**
+ * Get Nostr identifier type
+ */
+function getNostrType(id: string): _npub_ | _nprofile_ | _nevent_ | _naddr_ | _note_ | null {
+  if (id.startsWith(_npub_)) return _npub_;
+  if (id.startsWith(_nprofile_)) return _nprofile_;
+  if (id.startsWith(_nevent_)) return _nevent_;
+  if (id.startsWith(_naddr_)) return _naddr_;
+  if (id.startsWith(_note_)) return _note_;
+  return null;
+}
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="shell-4"><a class="anchor" href="#shell-4"></a><a class="link" href="#shell-4">shell</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>mkdir new_directory
+cp source.txt destination.txt</p>
+</div>
+<hr>
+</div>
+<div class="sect2">
+<h3 id="latex-7"><a class="anchor" href="#latex-7"></a><a class="link" href="#latex-7">LaTeX</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+$
+M =
+\begin{bmatrix}
+\frac{5}{6} &amp; \frac{1}{6} &amp; 0 \\[0.3em]
+\frac{5}{6} &amp; 0 &amp; \frac{1}{6} \\[0.3em]
+0 &amp; \frac{5}{6} &amp; \frac{1}{6}
+\end{bmatrix}
+$
+'''</code></pre>
+</div>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+$
+f(x)=
+\begin{cases}
+1/d_{ij} &amp; \quad \text{when $d_{ij} \leq 160$}\\
+0 &amp; \quad \text{otherwise}
+\end{cases}
+$
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="abc-notation-4"><a class="anchor" href="#abc-notation-4"></a><a class="link" href="#abc-notation-4">ABC Notation</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+X:1
+T:Ohne Titel
+C:Aufgezeichnet 1784
+A:Seibis nahe Lichtenberg in Oberfranken
+S:Handschrift, bezeichnet und datiert: "Heinrich Nicol Philipp zu Seibis den 30 Junius 1784"
+M:4/4
+L:1/4
+K:D
+dd d2 | ee e2 | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+|:\
+fg ad | cB cA | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="plantuml-4"><a class="anchor" href="#plantuml-4"></a><a class="link" href="#plantuml-4">PlantUML</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+@startuml
+Alice -&gt; Bob: Authentication Request
+Bob --&gt; Alice: Authentication Response
+@enduml
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="bpmn-4"><a class="anchor" href="#bpmn-4"></a><a class="link" href="#bpmn-4">BPMN</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+@startbpmn
+start
+:Task 1;
+:Task 2;
+stop
+@endbpmn
+'''</code></pre>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="latex-8"><a class="anchor" href="#latex-8"></a><a class="link" href="#latex-8">LaTeX</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="latex-in-inline-code-4"><a class="anchor" href="#latex-in-inline-code-4"></a><a class="link" href="#latex-in-inline-code-4">LaTeX in inline-code</a></h3>
+<div class="literalblock">
+<div class="content">
+<pre>and  and `$\color{blue}{X \sim Normal \; (\mu,\sigma^2)}== Bullet list</pre>
+</div>
+</div>
+<div class="paragraph">
+<p>This is a test unordered list with mixed bullets:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>First item with a number 2. in it</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Another unordered list:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>1st item</p>
+</li>
+<li>
+<p>2nd item</p>
+</li>
+<li>
+<p>third item containing <em>italic</em> text</p>
+<div class="ulist">
+<ul>
+<li>
+<p>indented item</p>
+</li>
+<li>
+<p>second indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>fourth item</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>This is a test ordered list with indented items:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="olist loweralpha">
+<ol class="loweralpha" type="a">
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ol>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>Ordered list where everything has no number:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>This is a mixed list with indented items:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>This is another mixed list with indented items:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ol>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="headers-5"><a class="anchor" href="#headers-5"></a><a class="link" href="#headers-5">Headers</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="third-level-header-5"><a class="anchor" href="#third-level-header-5"></a><a class="link" href="#third-level-header-5">Third-level header</a></h3>
+<div class="sect3">
+<h4 id="fourth-level-header-5"><a class="anchor" href="#fourth-level-header-5"></a><a class="link" href="#fourth-level-header-5">Fourth-level header</a></h4>
+<div class="sect4">
+<h5 id="fifth-level-header-5"><a class="anchor" href="#fifth-level-header-5"></a><a class="link" href="#fifth-level-header-5">Fifth-level header</a></h5>
+<div class="sect5">
+<h6 id="sixth-level-header-5"><a class="anchor" href="#sixth-level-header-5"></a><a class="link" href="#sixth-level-header-5">Sixth-level header</a></h6>
+
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="media-and-links-5"><a class="anchor" href="#media-and-links-5"></a><a class="link" href="#media-and-links-5">Media and Links</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="nostr-address-5"><a class="anchor" href="#nostr-address-5"></a><a class="link" href="#nostr-address-5">Nostr address</a></h3>
+<div class="paragraph">
+<p>This should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l</p>
+</div>
+<div class="paragraph">
+<p>This is also plaintext:</p>
+</div>
+<div class="paragraph">
+<p>npub1gv069u6q7zkl393ad47xutpqmyfj0rrfrlnqnlfc2ld38k8nnl4st9wa6q</p>
+</div>
+<div class="paragraph">
+<p>These should be turned into links:</p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l">naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z">npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj">nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh">nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg">note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg</a></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="hashtag-5"><a class="anchor" href="#hashtag-5"></a><a class="link" href="#hashtag-5">Hashtag</a></h3>
+<div class="paragraph">
+<p><a class="hashtag-link text-primary-600 dark:text-primary-500 hover:underline" data-topic="testhashtag" data-url="/notes?t=testhashtag" href="/notes?t=testhashtag">#testhashtag</a> at the start of the line and <a class="hashtag-link text-primary-600 dark:text-primary-500 hover:underline" data-topic="inlinehashtag" data-url="/notes?t=inlinehashtag" href="/notes?t=inlinehashtag">#inlinehashtag</a> in the middle</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="wikilinks-5"><a class="anchor" href="#wikilinks-5"></a><a class="link" href="#wikilinks-5">Wikilinks</a></h3>
+<div class="paragraph">
+<p><a class="wikilink text-primary-600 dark:text-primary-500 hover:underline" data-dtag="nkbip-01" data-url="/events?d=nkbip-01" href="/events?d=nkbip-01">Specification</a> and <a class="wikilink text-primary-600 dark:text-primary-500 hover:underline" data-dtag="mirepoix" data-url="/events?d=mirepoix" href="/events?d=mirepoix">mirepoix</a></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="url-5"><a class="anchor" href="#url-5"></a><a class="link" href="#url-5">URL</a></h3>
+<div class="paragraph">
+<p><span class="opengraph-link-container" data-og-url="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html">
+      <a href="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span></p>
+</div>
+<div class="paragraph">
+<p><span class="opengraph-link-container" data-og-url="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html">
+      <a href="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>[Welt Online link]</p>
+</div>
+<div class="paragraph">
+<p>this should render as plaintext: <code>http://www.example.com</code></p>
+</div>
+<div class="paragraph">
+<p>this should be a hyperlink: www.example.com</p>
+</div>
+<div class="paragraph">
+<p>this should be a hyperlink to the http URL with the same address, so <a href="https://theforest.nostr1.com/" target="_blank" rel="noopener noreferrer">wss://theforest.nostr1.com</a> should render like <a href="https://theforest.nostr1.com"https://theforest.nostr1.com" target="_blank" rel="noopener noreferrer">wss://theforest.nostr1.com<a href="https://theforest.nostr1.com" target="_blank" rel="noreferrer noopener" class="break-words inline-flex items-baseline gap-1">width=100%&quot;&gt;
+&lt;/div&gt;
+&lt;/div&gt;
+&lt;div class=&quot;imageblock&quot;&gt;
+&lt;div class=&quot;content&quot;&gt;
+&lt;img src=&quot;image::https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png&quot; alt=&quot;https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>[width=100%][test image" width="100%">
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="media-5"><a class="anchor" href="#media-5"></a><a class="link" href="#media-5">Media</a></h3>
+<div class="sect3">
+<h4 id="youtube-5"><a class="anchor" href="#youtube-5"></a><a class="link" href="#youtube-5">YouTube</a></h4>
+<div class="paragraph">
+<p><a href="https://youtube.com/shorts/ZWfvChb-i0w" target="_blank" rel="noopener noreferrer">https://youtube.com/shorts/ZWfvChb-i0w</a></p>
+</div>
+<div class="paragraph">
+<p><a href="https://youtube.com/shorts/ZWfvChb-i0w" target="_blank" rel="noopener noreferrer">https://youtube.com/shorts/ZWfvChb-i0w</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Youtube link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="spotify-5"><a class="anchor" href="#spotify-5"></a><a class="link" href="#spotify-5">Spotify</a></h4>
+<div class="paragraph">
+<p><div class="media-embed spotify-embed" style="margin: 1rem 0;">
+      <iframe 
+        style="border-radius: 12px; width: 100%; max-width: 100%;" 
+        src="https://open.spotify.com/embed/episode/1GSZFA8vWltPyxYkArdRKx?utm_source=generator" 
+        width="100%" 
+        height="352" 
+        frameborder="0" 
+        allowfullscreen="" 
+        allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" 
+        loading="lazy">
+      </iframe>
+    </div></p>
+</div>
+<div class="paragraph">
+<p><a href="<div class="media-embed spotify-embed" style="margin: 1rem 0;">
+      <iframe 
+        style="border-radius: 12px; width: 100%; max-width: 100%;" 
+        src="https://open.spotify.com/embed/episode/1GSZFA8vWltPyxYkArdRKx?utm_source=generator" 
+        width="100%" 
+        height="352" 
+        frameborder="0" 
+        allowfullscreen="" 
+        allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" 
+        loading="lazy">
+      </iframe>
+    </div>"><span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Spotify link with pic&lt;/a&gt; <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span></p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="audio-5"><a class="anchor" href="#audio-5"></a><a class="link" href="#audio-5">Audio</a></h4>
+<div class="paragraph">
+<p>MEDIA:audio:<a href="https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3" target="_blank" rel="noopener noreferrer">https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3</a></p>
+</div>
+<div class="paragraph">
+<p>link:MEDIA:audio:<a href="https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3" target="_blank" rel="noopener noreferrer">https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Audio link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="video-5"><a class="anchor" href="#video-5"></a><a class="link" href="#video-5">Video</a></h4>
+<div class="paragraph">
+<p>MEDIA:video:<a href="https://v.nostr.build/MTjaYib4upQuf8zn.mp4" target="_blank" rel="noopener noreferrer">https://v.nostr.build/MTjaYib4upQuf8zn.mp4</a></p>
+</div>
+<div class="paragraph">
+<p>link:MEDIA:video:<a href="https://v.nostr.build/MTjaYib4upQuf8zn.mp4" target="_blank" rel="noopener noreferrer">https://v.nostr.build/MTjaYib4upQuf8zn.mp4</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Video link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="tables-5"><a class="anchor" href="#tables-5"></a><a class="link" href="#tables-5">Tables</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="orderly-5"><a class="anchor" href="#orderly-5"></a><a class="link" href="#orderly-5">Orderly</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 66.6667%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Description</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Title</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Text</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect2">
+<h3 id="unorderly-5"><a class="anchor" href="#unorderly-5"></a><a class="link" href="#unorderly-5">Unorderly</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 66.6667%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Description</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Title</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Text</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect2">
+<h3 id="with-alignment-5"><a class="anchor" href="#with-alignment-5"></a><a class="link" href="#with-alignment-5">With alignment</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Description</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">Test Text</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Title</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">Here_s this</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Text</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">And more</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="code-blocks-5"><a class="anchor" href="#code-blocks-5"></a><a class="link" href="#code-blocks-5">Code blocks</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="json-5"><a class="anchor" href="#json-5"></a><a class="link" href="#json-5">json</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+{
+    "id": "&lt;event_id&gt;",
+    "pubkey": "&lt;event_originator_pubkey&gt;",
+    "created_at": 1725087283,
+    "kind": 30040,
+    "tags": [
+        ["d", "aesop_s-fables-by-aesop"],
+        ["title", "Aesop_s Fables"],
+        ["author", "Aesop"],
+    ],
+    "sig": "&lt;event_signature&gt;"
+}
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="typescript-5"><a class="anchor" href="#typescript-5"></a><a class="link" href="#typescript-5">typescript</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+/**
+ * Get Nostr identifier type
+ */
+function getNostrType(id: string): _npub_ | _nprofile_ | _nevent_ | _naddr_ | _note_ | null {
+  if (id.startsWith(_npub_)) return _npub_;
+  if (id.startsWith(_nprofile_)) return _nprofile_;
+  if (id.startsWith(_nevent_)) return _nevent_;
+  if (id.startsWith(_naddr_)) return _naddr_;
+  if (id.startsWith(_note_)) return _note_;
+  return null;
+}
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="shell-5"><a class="anchor" href="#shell-5"></a><a class="link" href="#shell-5">shell</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>mkdir new_directory
+cp source.txt destination.txt</p>
+</div>
+<hr>
+</div>
+<div class="sect2">
+<h3 id="latex-9"><a class="anchor" href="#latex-9"></a><a class="link" href="#latex-9">LaTeX</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+$
+M =
+\begin{bmatrix}
+\frac{5}{6} &amp; \frac{1}{6} &amp; 0 \\[0.3em]
+\frac{5}{6} &amp; 0 &amp; \frac{1}{6} \\[0.3em]
+0 &amp; \frac{5}{6} &amp; \frac{1}{6}
+\end{bmatrix}
+$
+'''</code></pre>
+</div>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+$
+f(x)=
+\begin{cases}
+1/d_{ij} &amp; \quad \text{when $d_{ij} \leq 160$}\\
+0 &amp; \quad \text{otherwise}
+\end{cases}
+$
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="abc-notation-5"><a class="anchor" href="#abc-notation-5"></a><a class="link" href="#abc-notation-5">ABC Notation</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+X:1
+T:Ohne Titel
+C:Aufgezeichnet 1784
+A:Seibis nahe Lichtenberg in Oberfranken
+S:Handschrift, bezeichnet und datiert: "Heinrich Nicol Philipp zu Seibis den 30 Junius 1784"
+M:4/4
+L:1/4
+K:D
+dd d2 | ee e2 | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+|:\
+fg ad | cB cA | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="plantuml-5"><a class="anchor" href="#plantuml-5"></a><a class="link" href="#plantuml-5">PlantUML</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+@startuml
+Alice -&gt; Bob: Authentication Request
+Bob --&gt; Alice: Authentication Response
+@enduml
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="bpmn-5"><a class="anchor" href="#bpmn-5"></a><a class="link" href="#bpmn-5">BPMN</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+@startbpmn
+start
+:Task 1;
+:Task 2;
+stop
+@endbpmn
+'''</code></pre>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="latex-10"><a class="anchor" href="#latex-10"></a><a class="link" href="#latex-10">LaTeX</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="latex-in-inline-code-5"><a class="anchor" href="#latex-in-inline-code-5"></a><a class="link" href="#latex-in-inline-code-5">LaTeX in inline-code</a></h3>
+<div class="paragraph">
+<p>`$[ x^n + y^n = z^n \]== Bullet list</p>
+</div>
+<div class="paragraph">
+<p>This is a test unordered list with mixed bullets:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>First item with a number 2. in it</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Another unordered list:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>1st item</p>
+</li>
+<li>
+<p>2nd item</p>
+</li>
+<li>
+<p>third item containing <em>italic</em> text</p>
+<div class="ulist">
+<ul>
+<li>
+<p>indented item</p>
+</li>
+<li>
+<p>second indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>fourth item</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>This is a test ordered list with indented items:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="olist loweralpha">
+<ol class="loweralpha" type="a">
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ol>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>Ordered list where everything has no number:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>This is a mixed list with indented items:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>This is another mixed list with indented items:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ol>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="headers-6"><a class="anchor" href="#headers-6"></a><a class="link" href="#headers-6">Headers</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="third-level-header-6"><a class="anchor" href="#third-level-header-6"></a><a class="link" href="#third-level-header-6">Third-level header</a></h3>
+<div class="sect3">
+<h4 id="fourth-level-header-6"><a class="anchor" href="#fourth-level-header-6"></a><a class="link" href="#fourth-level-header-6">Fourth-level header</a></h4>
+<div class="sect4">
+<h5 id="fifth-level-header-6"><a class="anchor" href="#fifth-level-header-6"></a><a class="link" href="#fifth-level-header-6">Fifth-level header</a></h5>
+<div class="sect5">
+<h6 id="sixth-level-header-6"><a class="anchor" href="#sixth-level-header-6"></a><a class="link" href="#sixth-level-header-6">Sixth-level header</a></h6>
+
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="media-and-links-6"><a class="anchor" href="#media-and-links-6"></a><a class="link" href="#media-and-links-6">Media and Links</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="nostr-address-6"><a class="anchor" href="#nostr-address-6"></a><a class="link" href="#nostr-address-6">Nostr address</a></h3>
+<div class="paragraph">
+<p>This should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l</p>
+</div>
+<div class="paragraph">
+<p>This is also plaintext:</p>
+</div>
+<div class="paragraph">
+<p>npub1gv069u6q7zkl393ad47xutpqmyfj0rrfrlnqnlfc2ld38k8nnl4st9wa6q</p>
+</div>
+<div class="paragraph">
+<p>These should be turned into links:</p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l">naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z">npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj">nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh">nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg">note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg</a></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="hashtag-6"><a class="anchor" href="#hashtag-6"></a><a class="link" href="#hashtag-6">Hashtag</a></h3>
+<div class="paragraph">
+<p><a class="hashtag-link text-primary-600 dark:text-primary-500 hover:underline" data-topic="testhashtag" data-url="/notes?t=testhashtag" href="/notes?t=testhashtag">#testhashtag</a> at the start of the line and <a class="hashtag-link text-primary-600 dark:text-primary-500 hover:underline" data-topic="inlinehashtag" data-url="/notes?t=inlinehashtag" href="/notes?t=inlinehashtag">#inlinehashtag</a> in the middle</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="wikilinks-6"><a class="anchor" href="#wikilinks-6"></a><a class="link" href="#wikilinks-6">Wikilinks</a></h3>
+<div class="paragraph">
+<p><a class="wikilink text-primary-600 dark:text-primary-500 hover:underline" data-dtag="nkbip-01" data-url="/events?d=nkbip-01" href="/events?d=nkbip-01">Specification</a> and <a class="wikilink text-primary-600 dark:text-primary-500 hover:underline" data-dtag="mirepoix" data-url="/events?d=mirepoix" href="/events?d=mirepoix">mirepoix</a></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="url-6"><a class="anchor" href="#url-6"></a><a class="link" href="#url-6">URL</a></h3>
+<div class="paragraph">
+<p><span class="opengraph-link-container" data-og-url="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html">
+      <a href="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span></p>
+</div>
+<div class="paragraph">
+<p><span class="opengraph-link-container" data-og-url="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html">
+      <a href="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>[Welt Online link]</p>
+</div>
+<div class="paragraph">
+<p>this should render as plaintext: <code>http://www.example.com</code></p>
+</div>
+<div class="paragraph">
+<p>this should be a hyperlink: www.example.com</p>
+</div>
+<div class="paragraph">
+<p>this should be a hyperlink to the http URL with the same address, so <a href="https://theforest.nostr1.com/" target="_blank" rel="noopener noreferrer">wss://theforest.nostr1.com</a> should render like <a href="https://theforest.nostr1.com"https://theforest.nostr1.com" target="_blank" rel="noopener noreferrer">wss://theforest.nostr1.com<a href="https://theforest.nostr1.com" target="_blank" rel="noreferrer noopener" class="break-words inline-flex items-baseline gap-1">width=100%&quot;&gt;
+&lt;/div&gt;
+&lt;/div&gt;
+&lt;div class=&quot;imageblock&quot;&gt;
+&lt;div class=&quot;content&quot;&gt;
+&lt;img src=&quot;image::https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png&quot; alt=&quot;https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>[width=100%][test image" width="100%">
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="media-6"><a class="anchor" href="#media-6"></a><a class="link" href="#media-6">Media</a></h3>
+<div class="sect3">
+<h4 id="youtube-6"><a class="anchor" href="#youtube-6"></a><a class="link" href="#youtube-6">YouTube</a></h4>
+<div class="paragraph">
+<p><a href="https://youtube.com/shorts/ZWfvChb-i0w" target="_blank" rel="noopener noreferrer">https://youtube.com/shorts/ZWfvChb-i0w</a></p>
+</div>
+<div class="paragraph">
+<p><a href="https://youtube.com/shorts/ZWfvChb-i0w" target="_blank" rel="noopener noreferrer">https://youtube.com/shorts/ZWfvChb-i0w</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Youtube link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="spotify-6"><a class="anchor" href="#spotify-6"></a><a class="link" href="#spotify-6">Spotify</a></h4>
+<div class="paragraph">
+<p><div class="media-embed spotify-embed" style="margin: 1rem 0;">
+      <iframe 
+        style="border-radius: 12px; width: 100%; max-width: 100%;" 
+        src="https://open.spotify.com/embed/episode/1GSZFA8vWltPyxYkArdRKx?utm_source=generator" 
+        width="100%" 
+        height="352" 
+        frameborder="0" 
+        allowfullscreen="" 
+        allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" 
+        loading="lazy">
+      </iframe>
+    </div></p>
+</div>
+<div class="paragraph">
+<p><a href="<div class="media-embed spotify-embed" style="margin: 1rem 0;">
+      <iframe 
+        style="border-radius: 12px; width: 100%; max-width: 100%;" 
+        src="https://open.spotify.com/embed/episode/1GSZFA8vWltPyxYkArdRKx?utm_source=generator" 
+        width="100%" 
+        height="352" 
+        frameborder="0" 
+        allowfullscreen="" 
+        allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" 
+        loading="lazy">
+      </iframe>
+    </div>"><span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Spotify link with pic&lt;/a&gt; <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span></p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="audio-6"><a class="anchor" href="#audio-6"></a><a class="link" href="#audio-6">Audio</a></h4>
+<div class="paragraph">
+<p>MEDIA:audio:<a href="https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3" target="_blank" rel="noopener noreferrer">https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3</a></p>
+</div>
+<div class="paragraph">
+<p>link:MEDIA:audio:<a href="https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3" target="_blank" rel="noopener noreferrer">https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Audio link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="video-6"><a class="anchor" href="#video-6"></a><a class="link" href="#video-6">Video</a></h4>
+<div class="paragraph">
+<p>MEDIA:video:<a href="https://v.nostr.build/MTjaYib4upQuf8zn.mp4" target="_blank" rel="noopener noreferrer">https://v.nostr.build/MTjaYib4upQuf8zn.mp4</a></p>
+</div>
+<div class="paragraph">
+<p>link:MEDIA:video:<a href="https://v.nostr.build/MTjaYib4upQuf8zn.mp4" target="_blank" rel="noopener noreferrer">https://v.nostr.build/MTjaYib4upQuf8zn.mp4</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Video link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="tables-6"><a class="anchor" href="#tables-6"></a><a class="link" href="#tables-6">Tables</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="orderly-6"><a class="anchor" href="#orderly-6"></a><a class="link" href="#orderly-6">Orderly</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 66.6667%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Description</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Title</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Text</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect2">
+<h3 id="unorderly-6"><a class="anchor" href="#unorderly-6"></a><a class="link" href="#unorderly-6">Unorderly</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 66.6667%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Description</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Title</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Text</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect2">
+<h3 id="with-alignment-6"><a class="anchor" href="#with-alignment-6"></a><a class="link" href="#with-alignment-6">With alignment</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Description</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">Test Text</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Title</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">Here_s this</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Text</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">And more</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="code-blocks-6"><a class="anchor" href="#code-blocks-6"></a><a class="link" href="#code-blocks-6">Code blocks</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="json-6"><a class="anchor" href="#json-6"></a><a class="link" href="#json-6">json</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+{
+    "id": "&lt;event_id&gt;",
+    "pubkey": "&lt;event_originator_pubkey&gt;",
+    "created_at": 1725087283,
+    "kind": 30040,
+    "tags": [
+        ["d", "aesop_s-fables-by-aesop"],
+        ["title", "Aesop_s Fables"],
+        ["author", "Aesop"],
+    ],
+    "sig": "&lt;event_signature&gt;"
+}
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="typescript-6"><a class="anchor" href="#typescript-6"></a><a class="link" href="#typescript-6">typescript</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+/**
+ * Get Nostr identifier type
+ */
+function getNostrType(id: string): _npub_ | _nprofile_ | _nevent_ | _naddr_ | _note_ | null {
+  if (id.startsWith(_npub_)) return _npub_;
+  if (id.startsWith(_nprofile_)) return _nprofile_;
+  if (id.startsWith(_nevent_)) return _nevent_;
+  if (id.startsWith(_naddr_)) return _naddr_;
+  if (id.startsWith(_note_)) return _note_;
+  return null;
+}
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="shell-6"><a class="anchor" href="#shell-6"></a><a class="link" href="#shell-6">shell</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>mkdir new_directory
+cp source.txt destination.txt</p>
+</div>
+<hr>
+</div>
+<div class="sect2">
+<h3 id="latex-11"><a class="anchor" href="#latex-11"></a><a class="link" href="#latex-11">LaTeX</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+$
+M =
+\begin{bmatrix}
+\frac{5}{6} &amp; \frac{1}{6} &amp; 0 \\[0.3em]
+\frac{5}{6} &amp; 0 &amp; \frac{1}{6} \\[0.3em]
+0 &amp; \frac{5}{6} &amp; \frac{1}{6}
+\end{bmatrix}
+$
+'''</code></pre>
+</div>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+$
+f(x)=
+\begin{cases}
+1/d_{ij} &amp; \quad \text{when $d_{ij} \leq 160$}\\
+0 &amp; \quad \text{otherwise}
+\end{cases}
+$
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="abc-notation-6"><a class="anchor" href="#abc-notation-6"></a><a class="link" href="#abc-notation-6">ABC Notation</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+X:1
+T:Ohne Titel
+C:Aufgezeichnet 1784
+A:Seibis nahe Lichtenberg in Oberfranken
+S:Handschrift, bezeichnet und datiert: "Heinrich Nicol Philipp zu Seibis den 30 Junius 1784"
+M:4/4
+L:1/4
+K:D
+dd d2 | ee e2 | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+|:\
+fg ad | cB cA | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="plantuml-6"><a class="anchor" href="#plantuml-6"></a><a class="link" href="#plantuml-6">PlantUML</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+@startuml
+Alice -&gt; Bob: Authentication Request
+Bob --&gt; Alice: Authentication Response
+@enduml
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="bpmn-6"><a class="anchor" href="#bpmn-6"></a><a class="link" href="#bpmn-6">BPMN</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+@startbpmn
+start
+:Task 1;
+:Task 2;
+stop
+@endbpmn
+'''</code></pre>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="latex-12"><a class="anchor" href="#latex-12"></a><a class="link" href="#latex-12">LaTeX</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="latex-in-inline-code-6"><a class="anchor" href="#latex-in-inline-code-6"></a><a class="link" href="#latex-in-inline-code-6">LaTeX in inline-code</a></h3>
+<div class="literalblock">
+<div class="content">
+<pre>and `$[\sqrt{x^2+1}\]== Bullet list</pre>
+</div>
+</div>
+<div class="paragraph">
+<p>This is a test unordered list with mixed bullets:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>First item with a number 2. in it</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Another unordered list:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>1st item</p>
+</li>
+<li>
+<p>2nd item</p>
+</li>
+<li>
+<p>third item containing <em>italic</em> text</p>
+<div class="ulist">
+<ul>
+<li>
+<p>indented item</p>
+</li>
+<li>
+<p>second indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>fourth item</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>This is a test ordered list with indented items:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="olist loweralpha">
+<ol class="loweralpha" type="a">
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ol>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>Ordered list where everything has no number:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>This is a mixed list with indented items:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>This is another mixed list with indented items:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ol>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="headers-7"><a class="anchor" href="#headers-7"></a><a class="link" href="#headers-7">Headers</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="third-level-header-7"><a class="anchor" href="#third-level-header-7"></a><a class="link" href="#third-level-header-7">Third-level header</a></h3>
+<div class="sect3">
+<h4 id="fourth-level-header-7"><a class="anchor" href="#fourth-level-header-7"></a><a class="link" href="#fourth-level-header-7">Fourth-level header</a></h4>
+<div class="sect4">
+<h5 id="fifth-level-header-7"><a class="anchor" href="#fifth-level-header-7"></a><a class="link" href="#fifth-level-header-7">Fifth-level header</a></h5>
+<div class="sect5">
+<h6 id="sixth-level-header-7"><a class="anchor" href="#sixth-level-header-7"></a><a class="link" href="#sixth-level-header-7">Sixth-level header</a></h6>
+
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="media-and-links-7"><a class="anchor" href="#media-and-links-7"></a><a class="link" href="#media-and-links-7">Media and Links</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="nostr-address-7"><a class="anchor" href="#nostr-address-7"></a><a class="link" href="#nostr-address-7">Nostr address</a></h3>
+<div class="paragraph">
+<p>This should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l</p>
+</div>
+<div class="paragraph">
+<p>This is also plaintext:</p>
+</div>
+<div class="paragraph">
+<p>npub1gv069u6q7zkl393ad47xutpqmyfj0rrfrlnqnlfc2ld38k8nnl4st9wa6q</p>
+</div>
+<div class="paragraph">
+<p>These should be turned into links:</p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l">naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z">npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj">nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh">nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg">note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg</a></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="hashtag-7"><a class="anchor" href="#hashtag-7"></a><a class="link" href="#hashtag-7">Hashtag</a></h3>
+<div class="paragraph">
+<p><a class="hashtag-link text-primary-600 dark:text-primary-500 hover:underline" data-topic="testhashtag" data-url="/notes?t=testhashtag" href="/notes?t=testhashtag">#testhashtag</a> at the start of the line and <a class="hashtag-link text-primary-600 dark:text-primary-500 hover:underline" data-topic="inlinehashtag" data-url="/notes?t=inlinehashtag" href="/notes?t=inlinehashtag">#inlinehashtag</a> in the middle</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="wikilinks-7"><a class="anchor" href="#wikilinks-7"></a><a class="link" href="#wikilinks-7">Wikilinks</a></h3>
+<div class="paragraph">
+<p><a class="wikilink text-primary-600 dark:text-primary-500 hover:underline" data-dtag="nkbip-01" data-url="/events?d=nkbip-01" href="/events?d=nkbip-01">Specification</a> and <a class="wikilink text-primary-600 dark:text-primary-500 hover:underline" data-dtag="mirepoix" data-url="/events?d=mirepoix" href="/events?d=mirepoix">mirepoix</a></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="url-7"><a class="anchor" href="#url-7"></a><a class="link" href="#url-7">URL</a></h3>
+<div class="paragraph">
+<p><span class="opengraph-link-container" data-og-url="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html">
+      <a href="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span></p>
+</div>
+<div class="paragraph">
+<p><span class="opengraph-link-container" data-og-url="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html">
+      <a href="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>[Welt Online link]</p>
+</div>
+<div class="paragraph">
+<p>this should render as plaintext: <code>http://www.example.com</code></p>
+</div>
+<div class="paragraph">
+<p>this should be a hyperlink: www.example.com</p>
+</div>
+<div class="paragraph">
+<p>this should be a hyperlink to the http URL with the same address, so <a href="https://theforest.nostr1.com/" target="_blank" rel="noopener noreferrer">wss://theforest.nostr1.com</a> should render like <a href="https://theforest.nostr1.com"https://theforest.nostr1.com" target="_blank" rel="noopener noreferrer">wss://theforest.nostr1.com<a href="https://theforest.nostr1.com" target="_blank" rel="noreferrer noopener" class="break-words inline-flex items-baseline gap-1">width=100%&quot;&gt;
+&lt;/div&gt;
+&lt;/div&gt;
+&lt;div class=&quot;imageblock&quot;&gt;
+&lt;div class=&quot;content&quot;&gt;
+&lt;img src=&quot;image::https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png&quot; alt=&quot;https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>[width=100%][test image" width="100%">
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="media-7"><a class="anchor" href="#media-7"></a><a class="link" href="#media-7">Media</a></h3>
+<div class="sect3">
+<h4 id="youtube-7"><a class="anchor" href="#youtube-7"></a><a class="link" href="#youtube-7">YouTube</a></h4>
+<div class="paragraph">
+<p><a href="https://youtube.com/shorts/ZWfvChb-i0w" target="_blank" rel="noopener noreferrer">https://youtube.com/shorts/ZWfvChb-i0w</a></p>
+</div>
+<div class="paragraph">
+<p><a href="https://youtube.com/shorts/ZWfvChb-i0w" target="_blank" rel="noopener noreferrer">https://youtube.com/shorts/ZWfvChb-i0w</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Youtube link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="spotify-7"><a class="anchor" href="#spotify-7"></a><a class="link" href="#spotify-7">Spotify</a></h4>
+<div class="paragraph">
+<p><div class="media-embed spotify-embed" style="margin: 1rem 0;">
+      <iframe 
+        style="border-radius: 12px; width: 100%; max-width: 100%;" 
+        src="https://open.spotify.com/embed/episode/1GSZFA8vWltPyxYkArdRKx?utm_source=generator" 
+        width="100%" 
+        height="352" 
+        frameborder="0" 
+        allowfullscreen="" 
+        allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" 
+        loading="lazy">
+      </iframe>
+    </div></p>
+</div>
+<div class="paragraph">
+<p><a href="<div class="media-embed spotify-embed" style="margin: 1rem 0;">
+      <iframe 
+        style="border-radius: 12px; width: 100%; max-width: 100%;" 
+        src="https://open.spotify.com/embed/episode/1GSZFA8vWltPyxYkArdRKx?utm_source=generator" 
+        width="100%" 
+        height="352" 
+        frameborder="0" 
+        allowfullscreen="" 
+        allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" 
+        loading="lazy">
+      </iframe>
+    </div>"><span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Spotify link with pic&lt;/a&gt; <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span></p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="audio-7"><a class="anchor" href="#audio-7"></a><a class="link" href="#audio-7">Audio</a></h4>
+<div class="paragraph">
+<p>MEDIA:audio:<a href="https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3" target="_blank" rel="noopener noreferrer">https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3</a></p>
+</div>
+<div class="paragraph">
+<p>link:MEDIA:audio:<a href="https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3" target="_blank" rel="noopener noreferrer">https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Audio link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="video-7"><a class="anchor" href="#video-7"></a><a class="link" href="#video-7">Video</a></h4>
+<div class="paragraph">
+<p>MEDIA:video:<a href="https://v.nostr.build/MTjaYib4upQuf8zn.mp4" target="_blank" rel="noopener noreferrer">https://v.nostr.build/MTjaYib4upQuf8zn.mp4</a></p>
+</div>
+<div class="paragraph">
+<p>link:MEDIA:video:<a href="https://v.nostr.build/MTjaYib4upQuf8zn.mp4" target="_blank" rel="noopener noreferrer">https://v.nostr.build/MTjaYib4upQuf8zn.mp4</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Video link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="tables-7"><a class="anchor" href="#tables-7"></a><a class="link" href="#tables-7">Tables</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="orderly-7"><a class="anchor" href="#orderly-7"></a><a class="link" href="#orderly-7">Orderly</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 66.6667%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Description</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Title</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Text</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect2">
+<h3 id="unorderly-7"><a class="anchor" href="#unorderly-7"></a><a class="link" href="#unorderly-7">Unorderly</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 66.6667%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Description</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Title</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Text</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect2">
+<h3 id="with-alignment-7"><a class="anchor" href="#with-alignment-7"></a><a class="link" href="#with-alignment-7">With alignment</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Description</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">Test Text</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Title</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">Here_s this</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Text</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">And more</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="code-blocks-7"><a class="anchor" href="#code-blocks-7"></a><a class="link" href="#code-blocks-7">Code blocks</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="json-7"><a class="anchor" href="#json-7"></a><a class="link" href="#json-7">json</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+{
+    "id": "&lt;event_id&gt;",
+    "pubkey": "&lt;event_originator_pubkey&gt;",
+    "created_at": 1725087283,
+    "kind": 30040,
+    "tags": [
+        ["d", "aesop_s-fables-by-aesop"],
+        ["title", "Aesop_s Fables"],
+        ["author", "Aesop"],
+    ],
+    "sig": "&lt;event_signature&gt;"
+}
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="typescript-7"><a class="anchor" href="#typescript-7"></a><a class="link" href="#typescript-7">typescript</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+/**
+ * Get Nostr identifier type
+ */
+function getNostrType(id: string): _npub_ | _nprofile_ | _nevent_ | _naddr_ | _note_ | null {
+  if (id.startsWith(_npub_)) return _npub_;
+  if (id.startsWith(_nprofile_)) return _nprofile_;
+  if (id.startsWith(_nevent_)) return _nevent_;
+  if (id.startsWith(_naddr_)) return _naddr_;
+  if (id.startsWith(_note_)) return _note_;
+  return null;
+}
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="shell-7"><a class="anchor" href="#shell-7"></a><a class="link" href="#shell-7">shell</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>mkdir new_directory
+cp source.txt destination.txt</p>
+</div>
+<hr>
+</div>
+<div class="sect2">
+<h3 id="latex-13"><a class="anchor" href="#latex-13"></a><a class="link" href="#latex-13">LaTeX</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+$
+M =
+\begin{bmatrix}
+\frac{5}{6} &amp; \frac{1}{6} &amp; 0 \\[0.3em]
+\frac{5}{6} &amp; 0 &amp; \frac{1}{6} \\[0.3em]
+0 &amp; \frac{5}{6} &amp; \frac{1}{6}
+\end{bmatrix}
+$
+'''</code></pre>
+</div>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+$
+f(x)=
+\begin{cases}
+1/d_{ij} &amp; \quad \text{when $d_{ij} \leq 160$}\\
+0 &amp; \quad \text{otherwise}
+\end{cases}
+$
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="abc-notation-7"><a class="anchor" href="#abc-notation-7"></a><a class="link" href="#abc-notation-7">ABC Notation</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+X:1
+T:Ohne Titel
+C:Aufgezeichnet 1784
+A:Seibis nahe Lichtenberg in Oberfranken
+S:Handschrift, bezeichnet und datiert: "Heinrich Nicol Philipp zu Seibis den 30 Junius 1784"
+M:4/4
+L:1/4
+K:D
+dd d2 | ee e2 | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+|:\
+fg ad | cB cA | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="plantuml-7"><a class="anchor" href="#plantuml-7"></a><a class="link" href="#plantuml-7">PlantUML</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+@startuml
+Alice -&gt; Bob: Authentication Request
+Bob --&gt; Alice: Authentication Response
+@enduml
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="bpmn-7"><a class="anchor" href="#bpmn-7"></a><a class="link" href="#bpmn-7">BPMN</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+@startbpmn
+start
+:Task 1;
+:Task 2;
+stop
+@endbpmn
+'''</code></pre>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="latex-14"><a class="anchor" href="#latex-14"></a><a class="link" href="#latex-14">LaTeX</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="latex-in-inline-code-7"><a class="anchor" href="#latex-in-inline-code-7"></a><a class="link" href="#latex-in-inline-code-7">LaTeX in inline-code</a></h3>
+<div class="paragraph">
+<p>`$[ x^n + y^n = z^n \]== Bullet list</p>
+</div>
+<div class="paragraph">
+<p>This is a test unordered list with mixed bullets:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>First item with a number 2. in it</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Another unordered list:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>1st item</p>
+</li>
+<li>
+<p>2nd item</p>
+</li>
+<li>
+<p>third item containing <em>italic</em> text</p>
+<div class="ulist">
+<ul>
+<li>
+<p>indented item</p>
+</li>
+<li>
+<p>second indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>fourth item</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>This is a test ordered list with indented items:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="olist loweralpha">
+<ol class="loweralpha" type="a">
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ol>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>Ordered list where everything has no number:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>This is a mixed list with indented items:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>This is another mixed list with indented items:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>First item</p>
+</li>
+<li>
+<p>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Indented item</p>
+</li>
+<li>
+<p>Indented item</p>
+</li>
+</ol>
+</div>
+</li>
+<li>
+<p>Fourth item</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="headers-8"><a class="anchor" href="#headers-8"></a><a class="link" href="#headers-8">Headers</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="third-level-header-8"><a class="anchor" href="#third-level-header-8"></a><a class="link" href="#third-level-header-8">Third-level header</a></h3>
+<div class="sect3">
+<h4 id="fourth-level-header-8"><a class="anchor" href="#fourth-level-header-8"></a><a class="link" href="#fourth-level-header-8">Fourth-level header</a></h4>
+<div class="sect4">
+<h5 id="fifth-level-header-8"><a class="anchor" href="#fifth-level-header-8"></a><a class="link" href="#fifth-level-header-8">Fifth-level header</a></h5>
+<div class="sect5">
+<h6 id="sixth-level-header-8"><a class="anchor" href="#sixth-level-header-8"></a><a class="link" href="#sixth-level-header-8">Sixth-level header</a></h6>
+
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="media-and-links-8"><a class="anchor" href="#media-and-links-8"></a><a class="link" href="#media-and-links-8">Media and Links</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="nostr-address-8"><a class="anchor" href="#nostr-address-8"></a><a class="link" href="#nostr-address-8">Nostr address</a></h3>
+<div class="paragraph">
+<p>This should be ignored and rendered as plaintext: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l</p>
+</div>
+<div class="paragraph">
+<p>This is also plaintext:</p>
+</div>
+<div class="paragraph">
+<p>npub1gv069u6q7zkl393ad47xutpqmyfj0rrfrlnqnlfc2ld38k8nnl4st9wa6q</p>
+</div>
+<div class="paragraph">
+<p>These should be turned into links:</p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l">naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z">npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj">nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh">nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh</a></p>
+</div>
+<div class="paragraph">
+<p><a href="nostr:note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg">note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg</a></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="hashtag-8"><a class="anchor" href="#hashtag-8"></a><a class="link" href="#hashtag-8">Hashtag</a></h3>
+<div class="paragraph">
+<p><a class="hashtag-link text-primary-600 dark:text-primary-500 hover:underline" data-topic="testhashtag" data-url="/notes?t=testhashtag" href="/notes?t=testhashtag">#testhashtag</a> at the start of the line and <a class="hashtag-link text-primary-600 dark:text-primary-500 hover:underline" data-topic="inlinehashtag" data-url="/notes?t=inlinehashtag" href="/notes?t=inlinehashtag">#inlinehashtag</a> in the middle</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="wikilinks-8"><a class="anchor" href="#wikilinks-8"></a><a class="link" href="#wikilinks-8">Wikilinks</a></h3>
+<div class="paragraph">
+<p><a class="wikilink text-primary-600 dark:text-primary-500 hover:underline" data-dtag="nkbip-01" data-url="/events?d=nkbip-01" href="/events?d=nkbip-01">Specification</a> and <a class="wikilink text-primary-600 dark:text-primary-500 hover:underline" data-dtag="mirepoix" data-url="/events?d=mirepoix" href="/events?d=mirepoix">mirepoix</a></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="url-8"><a class="anchor" href="#url-8"></a><a class="link" href="#url-8">URL</a></h3>
+<div class="paragraph">
+<p><span class="opengraph-link-container" data-og-url="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html">
+      <a href="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span></p>
+</div>
+<div class="paragraph">
+<p><span class="opengraph-link-container" data-og-url="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html">
+      <a href="https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">https://www.welt.de/politik/ausland/article69a7ca00ad41f3cd65a1bc63/iran-drohte-jedes-schiff-zu-verbrennen-trump-will-oel-tanker-durch-strasse-von-hormus-eskortieren.html <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>[Welt Online link]</p>
+</div>
+<div class="paragraph">
+<p>this should render as plaintext: <code>http://www.example.com</code></p>
+</div>
+<div class="paragraph">
+<p>this should be a hyperlink: www.example.com</p>
+</div>
+<div class="paragraph">
+<p>this should be a hyperlink to the http URL with the same address, so <a href="https://theforest.nostr1.com/" target="_blank" rel="noopener noreferrer">wss://theforest.nostr1.com</a> should render like <a href="https://theforest.nostr1.com"https://theforest.nostr1.com" target="_blank" rel="noopener noreferrer">wss://theforest.nostr1.com<a href="https://theforest.nostr1.com" target="_blank" rel="noreferrer noopener" class="break-words inline-flex items-baseline gap-1">width=100%&quot;&gt;
+&lt;/div&gt;
+&lt;/div&gt;
+&lt;div class=&quot;imageblock&quot;&gt;
+&lt;div class=&quot;content&quot;&gt;
+&lt;img src=&quot;image::https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png&quot; alt=&quot;https://blog.ronin.cloud/content/images/size/w2000/2022/02/markdown.png <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>[width=100%][test image" width="100%">
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="media-8"><a class="anchor" href="#media-8"></a><a class="link" href="#media-8">Media</a></h3>
+<div class="sect3">
+<h4 id="youtube-8"><a class="anchor" href="#youtube-8"></a><a class="link" href="#youtube-8">YouTube</a></h4>
+<div class="paragraph">
+<p><a href="https://youtube.com/shorts/ZWfvChb-i0w" target="_blank" rel="noopener noreferrer">https://youtube.com/shorts/ZWfvChb-i0w</a></p>
+</div>
+<div class="paragraph">
+<p><a href="https://youtube.com/shorts/ZWfvChb-i0w" target="_blank" rel="noopener noreferrer">https://youtube.com/shorts/ZWfvChb-i0w</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Youtube link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="spotify-8"><a class="anchor" href="#spotify-8"></a><a class="link" href="#spotify-8">Spotify</a></h4>
+<div class="paragraph">
+<p><div class="media-embed spotify-embed" style="margin: 1rem 0;">
+      <iframe 
+        style="border-radius: 12px; width: 100%; max-width: 100%;" 
+        src="https://open.spotify.com/embed/episode/1GSZFA8vWltPyxYkArdRKx?utm_source=generator" 
+        width="100%" 
+        height="352" 
+        frameborder="0" 
+        allowfullscreen="" 
+        allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" 
+        loading="lazy">
+      </iframe>
+    </div></p>
+</div>
+<div class="paragraph">
+<p><a href="<div class="media-embed spotify-embed" style="margin: 1rem 0;">
+      <iframe 
+        style="border-radius: 12px; width: 100%; max-width: 100%;" 
+        src="https://open.spotify.com/embed/episode/1GSZFA8vWltPyxYkArdRKx?utm_source=generator" 
+        width="100%" 
+        height="352" 
+        frameborder="0" 
+        allowfullscreen="" 
+        allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" 
+        loading="lazy">
+      </iframe>
+    </div>"><span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Spotify link with pic&lt;/a&gt; <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span></p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="audio-8"><a class="anchor" href="#audio-8"></a><a class="link" href="#audio-8">Audio</a></h4>
+<div class="paragraph">
+<p>MEDIA:audio:<a href="https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3" target="_blank" rel="noopener noreferrer">https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3</a></p>
+</div>
+<div class="paragraph">
+<p>link:MEDIA:audio:<a href="https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3" target="_blank" rel="noopener noreferrer">https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_rick_beato.mp3</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Audio link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="video-8"><a class="anchor" href="#video-8"></a><a class="link" href="#video-8">Video</a></h4>
+<div class="paragraph">
+<p>MEDIA:video:<a href="https://v.nostr.build/MTjaYib4upQuf8zn.mp4" target="_blank" rel="noopener noreferrer">https://v.nostr.build/MTjaYib4upQuf8zn.mp4</a></p>
+</div>
+<div class="paragraph">
+<p>link:MEDIA:video:<a href="https://v.nostr.build/MTjaYib4upQuf8zn.mp4" target="_blank" rel="noopener noreferrer">https://v.nostr.build/MTjaYib4upQuf8zn.mp4</a>[<span class="image"><img src="image::<span class="opengraph-link-container max-w-[400px] object-contain cursor-zoom-in" data-og-url="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" data-asciidoc-image="true" data-image-index="0" data-image-src="image::<span class=">
+      <a href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/YouTube_social_white_square_%282024%29.svg/960px-YouTube_social_white_square_%282024%29.svg.png&amp;quot;" target="_blank" rel="noreferrer noopener" class="opengraph-link break-words inline-flex items-baseline gap-1">Video link with pic <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg> <svg style="width: 0.75rem; height: 0.75rem; flex-shrink: 0;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg></a>
+      <div class="opengraph-preview" data-og-loading="true" style="display: none;">
+        <div class="opengraph-card">
+          <div class="opengraph-image-container">
+            <img class="opengraph-image" src="" alt="" style="display: none;" />
+          </div>
+          <div class="opengraph-content">
+            <div class="opengraph-site"></div>
+            <div class="opengraph-title"></div>
+            <div class="opengraph-description"></div>
+          </div>
+        </div>
+      </div>
+    </span>]</p>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="tables-8"><a class="anchor" href="#tables-8"></a><a class="link" href="#tables-8">Tables</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="orderly-8"><a class="anchor" href="#orderly-8"></a><a class="link" href="#orderly-8">Orderly</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 66.6667%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Description</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Title</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Text</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect2">
+<h3 id="unorderly-8"><a class="anchor" href="#unorderly-8"></a><a class="link" href="#unorderly-8">Unorderly</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 66.6667%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Description</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Title</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Text</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect2">
+<h3 id="with-alignment-8"><a class="anchor" href="#with-alignment-8"></a><a class="link" href="#with-alignment-8">With alignment</a></h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Syntax</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Description</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">Test Text</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Header</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Title</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">Here_s this</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Paragraph</p></td>
+<td class="tableblock halign-center valign-top"><p class="tableblock">Text</p></td>
+<td class="tableblock halign-right valign-top"><p class="tableblock">And more</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="code-blocks-8"><a class="anchor" href="#code-blocks-8"></a><a class="link" href="#code-blocks-8">Code blocks</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="json-8"><a class="anchor" href="#json-8"></a><a class="link" href="#json-8">json</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+{
+    "id": "&lt;event_id&gt;",
+    "pubkey": "&lt;event_originator_pubkey&gt;",
+    "created_at": 1725087283,
+    "kind": 30040,
+    "tags": [
+        ["d", "aesop_s-fables-by-aesop"],
+        ["title", "Aesop_s Fables"],
+        ["author", "Aesop"],
+    ],
+    "sig": "&lt;event_signature&gt;"
+}
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="typescript-8"><a class="anchor" href="#typescript-8"></a><a class="link" href="#typescript-8">typescript</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+/**
+ * Get Nostr identifier type
+ */
+function getNostrType(id: string): _npub_ | _nprofile_ | _nevent_ | _naddr_ | _note_ | null {
+  if (id.startsWith(_npub_)) return _npub_;
+  if (id.startsWith(_nprofile_)) return _nprofile_;
+  if (id.startsWith(_nevent_)) return _nevent_;
+  if (id.startsWith(_naddr_)) return _naddr_;
+  if (id.startsWith(_note_)) return _note_;
+  return null;
+}
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="shell-8"><a class="anchor" href="#shell-8"></a><a class="link" href="#shell-8">shell</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>mkdir new_directory
+cp source.txt destination.txt</p>
+</div>
+<hr>
+</div>
+<div class="sect2">
+<h3 id="latex-15"><a class="anchor" href="#latex-15"></a><a class="link" href="#latex-15">LaTeX</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+$
+M =
+\begin{bmatrix}
+\frac{5}{6} &amp; \frac{1}{6} &amp; 0 \\[0.3em]
+\frac{5}{6} &amp; 0 &amp; \frac{1}{6} \\[0.3em]
+0 &amp; \frac{5}{6} &amp; \frac{1}{6}
+\end{bmatrix}
+$
+'''</code></pre>
+</div>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+$
+f(x)=
+\begin{cases}
+1/d_{ij} &amp; \quad \text{when $d_{ij} \leq 160$}\\
+0 &amp; \quad \text{otherwise}
+\end{cases}
+$
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="abc-notation-8"><a class="anchor" href="#abc-notation-8"></a><a class="link" href="#abc-notation-8">ABC Notation</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+X:1
+T:Ohne Titel
+C:Aufgezeichnet 1784
+A:Seibis nahe Lichtenberg in Oberfranken
+S:Handschrift, bezeichnet und datiert: "Heinrich Nicol Philipp zu Seibis den 30 Junius 1784"
+M:4/4
+L:1/4
+K:D
+dd d2 | ee e2 | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+|:\
+fg ad | cB cA | fg ad | cB cA |\
+dd d2 | ee e2 | fg ad | ed/c/ d2 :|
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="plantuml-8"><a class="anchor" href="#plantuml-8"></a><a class="link" href="#plantuml-8">PlantUML</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+@startuml
+Alice -&gt; Bob: Authentication Request
+Bob --&gt; Alice: Authentication Response
+@enduml
+'''</code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="bpmn-8"><a class="anchor" href="#bpmn-8"></a><a class="link" href="#bpmn-8">BPMN</a></h3>
+<div class="listingblock">
+<div class="content">
+<pre><code>'''
+@startbpmn
+start
+:Task 1;
+:Task 2;
+stop
+@endbpmn
+'''</code></pre>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="latex-16"><a class="anchor" href="#latex-16"></a><a class="link" href="#latex-16">LaTeX</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="latex-in-inline-code-8"><a class="anchor" href="#latex-in-inline-code-8"></a><a class="link" href="#latex-in-inline-code-8">LaTeX in inline-code</a></h3>
+<div class="literalblock">
+<div class="content">
+<pre>and  and</pre>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="latex-outside-of-code"><a class="anchor" href="#latex-outside-of-code"></a><a class="link" href="#latex-outside-of-code">LaTeX outside of code</a></h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>This is a latex code block \mathbb{N} = \{ a \in \mathbb{Z} : a &gt; 0 \} and another that is an inline latex $\color{green}{X \sim Normal \; (\mu,\sigma^2)}$ and should be green</p>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="footnotes"><a class="anchor" href="#footnotes"></a><a class="link" href="#footnotes">Footnotes</a></h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>Here_s a simple footnote,<sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote.">1</a>]</sup> and here_s a longer one.<sup class="footnote">[<a id="_footnoteref_2" class="footnote" href="#_footnotedef_2" title="View footnote.">2</a>]</sup></p>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="anchor-links"><a class="anchor" href="#anchor-links"></a><a class="link" href="#anchor-links">Anchor links</a></h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p><a href="#bullet-list">Link to bullet list section</a></p>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="formatting"><a class="anchor" href="#formatting"></a><a class="link" href="#formatting">Formatting</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="strikethrough"><a class="anchor" href="#strikethrough"></a><a class="link" href="#strikethrough">Strikethrough</a></h3>
+<div class="paragraph">
+<p><span class="line-through line-through-2">The world is flat.</span> We now know that the world is round. This should not be <sub>struck</sub> through.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="bold"><a class="anchor" href="#bold"></a><a class="link" href="#bold">Bold</a></h3>
+<div class="paragraph">
+<p>This is <strong>bold</strong> text. So is this <strong>bold</strong> text.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="italic"><a class="anchor" href="#italic"></a><a class="link" href="#italic">Italic</a></h3>
+<div class="paragraph">
+<p>This is <em>italic</em> text. So is this <em>italic</em> text.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="task-list"><a class="anchor" href="#task-list"></a><a class="link" href="#task-list">Task List</a></h3>
+<div class="ulist checklist">
+<ul class="checklist">
+<li>
+<p>&#10003; Write the press release</p>
+</li>
+<li>
+<p>&#10063; Update the website</p>
+</li>
+<li>
+<p>&#10063; Contact the media</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect2">
+<h3 id="emoji-shortcodes"><a class="anchor" href="#emoji-shortcodes"></a><a class="link" href="#emoji-shortcodes">Emoji shortcodes</a></h3>
+<div class="paragraph">
+<p>Gone camping! :tent: Be back soon.</p>
+</div>
+<div class="paragraph">
+<p>That is so funny! :joy:</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="marking-and-highlighting-text"><a class="anchor" href="#marking-and-highlighting-text"></a><a class="link" href="#marking-and-highlighting-text">Marking and highlighting text</a></h3>
+<div class="paragraph">
+<p>I need to highlight these <span class="highlight">very important words</span>.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="subscript-and-superscript"><a class="anchor" href="#subscript-and-superscript"></a><a class="link" href="#subscript-and-superscript">Subscript and Superscript</a></h3>
+<div class="paragraph">
+<p>H<sub>2</sub>O</p>
+</div>
+<div class="paragraph">
+<p>X<sup>2</sup></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="delimiter"><a class="anchor" href="#delimiter"></a><a class="link" href="#delimiter">Delimiter</a></h3>
+<div class="paragraph">
+<p>based upon a -</p>
+</div>
+<div class="paragraph">
+<p>_*</p>
+</div>
+<div class="paragraph">
+<p>based upon a *</p>
+</div>
+<div class="paragraph">
+<p>*_</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="quotes"><a class="anchor" href="#quotes"></a><a class="link" href="#quotes">Quotes</a></h3>
+<div class="quoteblock">
+<blockquote>
+<div class="paragraph">
+<p>This is a single line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd  kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj</p>
+</div>
+</blockquote>
+</div>
+<div class="quoteblock">
+<blockquote>
+<div class="paragraph">
+<p>This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd  kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj
+This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd  kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj
+This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd  kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj
+This is a multi line blockequote sdfjsdlfkjasldkfjsdölfkjsdlfkjsadlöfkjsdlöfkjsadölfkjsdlf kjsldfkjsdalkjslkdfjlöskdfjlösdkjfsldkfjsöldkfjlösdkfjalsd  kfjlsdkfjlödkfjlaksdfjlkjdfslkjalsdkfjlasdkfj alsdkjflskdfj sdfklj</p>
+</div>
+</blockquote>
+</div>
+</div>
+</div>
+</div>
+<div id="footnotes">
+<hr>
+<div class="footnote" id="_footnotedef_1">
+<a href="#_footnoteref_1">1</a>. This is the first footnote.
+</div>
+<div class="footnote" id="_footnotedef_2">
+<a href="#_footnoteref_2">2</a>. Here_s one with multiple paragraphs and code.
+</div>
+</div>
+
+
+ + +
+

Extracted Metadata

+ + +

Nostr Links (5)

+ +
+ naddr: naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l + - nostr:naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqyghwumn8ghj7mn0wd68ytnvv9hxgtcqy4sj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwm3dvfuj6enyxgcrset98p3nsve2v5l +
+ +
+ npub: npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z + - nostr:npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z +
+ +
+ nevent: nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj + - nostr:nevent1qvzqqqqqqypzp382htsmu08k277ps40wqhnfm60st89h5pvjyutghq9cjasuh38qqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qqsysletg3lqnl4uy59xsj4rp9rgw67wg23l827f4uvn5ckn20fuxcq45d8pj +
+ +
+ nprofile: nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh + - nostr:nprofile1qqsxhedgkuneycxpcdjlg6tgtxdy8gurdz64nq2h0flc288a0jag98qguy3nh +
+ +
+ note: note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg + - nostr:note1txyefcha2xt3pgungx4k6j077dsteyef6hzpyuuku00s4h0eymzq4k33yg +
+ + + + +

Wikilinks (2)

+ +
+ [[NKBIP-01|Specification]] → dtag: nkbip-01 + (display: Specification) +
+ +
+ [[mirepoix]] → dtag: mirepoix + (display: mirepoix) +
+ + + + +

Hashtags (4)

+ +
+ #testhashtag +
+ +
+ #inlinehashtag +
+ +
+ #the +
+ +
+ #very +
+ + + + +

Links (15)

+ +
+ Welt Online link + External +
+ + + + + + + + + + + + + +
+ http://www.example.com` + External +
+ + + + + + + + + + + + + + + + + + +

Media URLs (2)

+ + + + + + + + +

Table of Contents

+
+ +
+ +
+ + + + + + \ No newline at end of file