Browse Source

final bug fix

master
Silberengel 10 months ago
parent
commit
f32fd7cfd5
  1. 6
      README.md
  2. 5
      src/app.css
  3. 54
      src/lib/utils/markup/MarkupInfo.md
  4. 27
      src/lib/utils/markup/advancedMarkupParser.ts
  5. 91
      src/lib/utils/markup/basicMarkupParser.ts
  6. 16
      src/lib/utils/markup/markupTestfile.md
  7. 6
      src/lib/utils/mime.ts
  8. 2
      src/routes/about/+page.svelte
  9. 12
      src/routes/contact/+page.svelte

6
README.md

@ -114,4 +114,8 @@ npm run test
For the Playwright end-to-end (e2e) tests: For the Playwright end-to-end (e2e) tests:
```bash ```bash
npx playwright test npx playwright test
``` ```
## Markup Support
Alexandria supports both Markdown and AsciiDoc markup for different content types. For a detailed list of supported tags and features in the basic and advanced markdown parsers, as well as information about AsciiDoc usage for publications and wikis, see [MarkupInfo.md](src/lib/utils/markup/MarkupInfo.md).

5
src/app.css

@ -251,11 +251,6 @@
@apply dark:text-white; @apply dark:text-white;
} }
/* Footnotes */
:global(.footnotes-ol) {
list-style-type: decimal !important;
}
/* Rendered publication content */ /* Rendered publication content */
.publication-leather { .publication-leather {
@apply flex flex-col space-y-4; @apply flex flex-col space-y-4;

54
src/lib/utils/markup/MarkupInfo.md

@ -0,0 +1,54 @@
# Markup Support in Alexandria
Alexandria supports multiple markup formats for different use cases. Below is a summary of the supported tags and features for each parser, as well as the formats used for publications and wikis.
## Basic Markdown Parser
The **basic markdown parser** supports:
- **Headers:**
- ATX-style: `# H1` through `###### H6`
- Setext-style: `H1\n=====`
- **Bold:** `*bold*` or `**bold**`
- **Italic:** `_italic_` or `__italic__`
- **Strikethrough:** `~strikethrough~` or `~~strikethrough~~`
- **Blockquotes:** `> quoted text`
- **Unordered lists:** `* item`
- **Ordered lists:** `1. item`
- **Links:** `[text](url)`
- **Images:** `![alt](url)`
- **Hashtags:** `#hashtag`
- **Nostr identifiers:** npub, nprofile, nevent, naddr, note, with or without `nostr:` prefix
- **Emoji shortcodes:** `:smile:`
## Advanced Markdown Parser
The **advanced markdown parser** includes all features of the basic parser, plus:
- **Inline code:** `` `code` ``
- **Syntax highlighting:** for code blocks in over 100 languages
- **Tables:** Pipe-delimited tables with or without headers
- **Footnotes:** `[^1]` and `[ ^1 ]: footnote text`
- **Wikilinks:** `[[Page Name]]` (NIP-54)
- **Better footnote rendering:** with backreferences and unique numbering
## Publications and Wikis
**Publications** and **wikis** in Alexandria use **AsciiDoc** as their primary markup language, not Markdown.
AsciiDoc supports a much broader set of formatting, semantic, and structural features, including:
- Section and document structure
- Advanced tables, callouts, admonitions
- Cross-references, footnotes, and bibliography
- Custom attributes and macros
- And much more
For more information on AsciiDoc, see the [AsciiDoc documentation](https://asciidoc.org/).
---
**Note:**
- The markdown parsers are primarily used for comments, issues, and other user-generated content.
- Publications and wikis are rendered using AsciiDoc for maximum expressiveness and compatibility.
- All URLs are sanitized to remove tracking parameters, and YouTube links are presented in a clean, privacy-friendly format.

27
src/lib/utils/markdown/advancedMarkdownParser.ts → src/lib/utils/markup/advancedMarkupParser.ts

@ -1,4 +1,4 @@
import { parseBasicMarkdown } from './basicMarkdownParser'; import { parseBasicmarkup } from './basicMarkupParser';
import hljs from 'highlight.js'; import hljs from 'highlight.js';
import 'highlight.js/lib/common'; // Import common languages import 'highlight.js/lib/common'; // Import common languages
import 'highlight.js/styles/github-dark.css'; // Dark theme only import 'highlight.js/styles/github-dark.css'; // Dark theme only
@ -8,7 +8,7 @@ hljs.configure({
ignoreUnescapedHTML: true ignoreUnescapedHTML: true
}); });
// Regular expressions for advanced markdown elements // Regular expressions for advanced markup elements
const HEADING_REGEX = /^(#{1,6})\s+(.+)$/gm; const HEADING_REGEX = /^(#{1,6})\s+(.+)$/gm;
const ALTERNATE_HEADING_REGEX = /^([^\n]+)\n(=+|-+)\n/gm; const ALTERNATE_HEADING_REGEX = /^([^\n]+)\n(=+|-+)\n/gm;
const INLINE_CODE_REGEX = /`([^`\n]+)`/g; const INLINE_CODE_REGEX = /`([^`\n]+)`/g;
@ -138,13 +138,16 @@ function processFootnotes(content: string): string {
try { try {
if (!content) return ''; if (!content) return '';
// Collect all footnote definitions // Collect all footnote definitions (but do not remove them from the text yet)
const footnotes = new Map<string, string>(); const footnotes = new Map<string, string>();
let processedContent = content.replace(FOOTNOTE_DEFINITION_REGEX, (match, id, text) => { content.replace(FOOTNOTE_DEFINITION_REGEX, (match, id, text) => {
footnotes.set(id, text.trim()); footnotes.set(id, text.trim());
return ''; return match;
}); });
// Remove all footnote definition lines from the main content
let processedContent = content.replace(FOOTNOTE_DEFINITION_REGEX, '');
// Track all references to each footnote // Track all references to each footnote
const referenceOrder: { id: string, refNum: number, label: string }[] = []; const referenceOrder: { id: string, refNum: number, label: string }[] = [];
const referenceMap = new Map<string, number[]>(); // id -> [refNum, ...] const referenceMap = new Map<string, number[]>(); // id -> [refNum, ...]
@ -341,9 +344,9 @@ function restoreCodeBlocks(text: string, blocks: Map<string, string>): string {
} }
/** /**
* Parse markdown text with advanced formatting * Parse markup text with advanced formatting
*/ */
export async function parseAdvancedMarkdown(text: string): Promise<string> { export async function parseAdvancedmarkup(text: string): Promise<string> {
if (!text) return ''; if (!text) return '';
try { try {
@ -369,18 +372,18 @@ export async function parseAdvancedMarkdown(text: string): Promise<string> {
return `<code class="px-1.5 py-0.5 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded text-sm font-mono">${escapedCode}</code>`; return `<code class="px-1.5 py-0.5 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded text-sm font-mono">${escapedCode}</code>`;
}); });
// Process footnotes // Process footnotes (only references, not definitions)
processedText = processFootnotes(processedText); processedText = processFootnotes(processedText);
// Process basic markdown (which will also handle Nostr identifiers) // Process basic markup (which will also handle Nostr identifiers)
processedText = await parseBasicMarkdown(processedText); processedText = await parseBasicmarkup(processedText);
// Step 3: Restore code blocks // Step 3: Restore code blocks
processedText = restoreCodeBlocks(processedText, blocks); processedText = restoreCodeBlocks(processedText, blocks);
return processedText; return processedText;
} catch (e: unknown) { } catch (e: unknown) {
console.error('Error in parseAdvancedMarkdown:', e); console.error('Error in parseAdvancedmarkup:', e);
return `<div class=\"text-red-500\">Error processing markdown: ${(e as Error)?.message ?? 'Unknown error'}</div>`; return `<div class=\"text-red-500\">Error processing markup: ${(e as Error)?.message ?? 'Unknown error'}</div>`;
} }
} }

91
src/lib/utils/markdown/basicMarkdownParser.ts → src/lib/utils/markup/basicMarkupParser.ts

@ -2,20 +2,16 @@ import { processNostrIdentifiers } from '../nostrUtils';
import * as emoji from 'node-emoji'; import * as emoji from 'node-emoji';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
// Regular expressions for basic markdown elements // Regular expressions for basic markup elements
const BOLD_REGEX = /(\*\*|[*])((?:[^*\n]|\*(?!\*))+)\1/g; const BOLD_REGEX = /(\*\*|[*])((?:[^*\n]|\*(?!\*))+)\1/g;
const ITALIC_REGEX = /\b(_[^_\n]+_|\b__[^_\n]+__)\b/g; const ITALIC_REGEX = /\b(_[^_\n]+_|\b__[^_\n]+__)\b/g;
const STRIKETHROUGH_REGEX = /~~([^~\n]+)~~|~([^~\n]+)~/g; const STRIKETHROUGH_REGEX = /~~([^~\n]+)~~|~([^~\n]+)~/g;
const HASHTAG_REGEX = /(?<![^\s])#([a-zA-Z0-9_]+)(?!\w)/g; const HASHTAG_REGEX = /(?<![^\s])#([a-zA-Z0-9_]+)(?!\w)/g;
const BLOCKQUOTE_REGEX = /^([ \t]*>[ \t]?.*)(?:\n\1[ \t]*(?!>).*)*$/gm; const BLOCKQUOTE_REGEX = /^([ \t]*>[ \t]?.*)(?:\n\1[ \t]*(?!>).*)*$/gm;
// List regex patterns // markup patterns
const UNORDERED_LIST_REGEX = /^(\s*[-*+]\s+)(.*?)$/gm; const markup_LINK = /\[([^\]]+)\]\(([^)]+)\)/g;
const ORDERED_LIST_REGEX = /^(\s*\d+\.\s+)(.*?)$/gm; const markup_IMAGE = /!\[([^\]]*)\]\(([^)]+)\)/g;
// Markdown patterns
const MARKDOWN_LINK = /\[([^\]]+)\]\(([^)]+)\)/g;
const MARKDOWN_IMAGE = /!\[([^\]]*)\]\(([^)]+)\)/g;
// URL patterns // URL patterns
const WSS_URL = /wss:\/\/[^\s<>"]+/g; const WSS_URL = /wss:\/\/[^\s<>"]+/g;
@ -36,7 +32,7 @@ function replaceAlexandriaNostrLinks(text: string): string {
// Regex for 64-char hex // Regex for 64-char hex
const hexPattern = /\b[a-fA-F0-9]{64}\b/; const hexPattern = /\b[a-fA-F0-9]{64}\b/;
// 1. Alexandria/localhost Markdown links // 1. Alexandria/localhost markup links
text = text.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, (match, _label, url) => { text = text.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, (match, _label, url) => {
if (alexandriaPattern.test(url)) { if (alexandriaPattern.test(url)) {
if (/[?&]d=/.test(url)) return match; if (/[?&]d=/.test(url)) return match;
@ -150,39 +146,37 @@ function replaceWikilinks(text: string): string {
}); });
} }
function renderListGroup(lines: string[]): string { function renderListGroup(lines: string[], typeHint?: 'ol' | 'ul'): string {
// Recursive function to render a list group with proper nesting function parseList(start: number, indent: number, type: 'ol' | 'ul'): [string, number] {
function render(lines: string[], start = 0, indent = 0): [string, number] {
let html = ''; let html = '';
let i = start; let i = start;
let currentTag = ''; html += `<${type} class="${type === 'ol' ? 'list-decimal' : 'list-disc'} ml-6 mb-2">`;
while (i < lines.length) { while (i < lines.length) {
const line = lines[i]; const line = lines[i];
const match = line.match(/^([ \t]*)([*+-]|\d+\.)[ \t]+(.*)$/); const match = line.match(/^([ \t]*)([*+-]|\d+\.)[ \t]+(.*)$/);
if (!match) break; if (!match) break;
const lineIndent = match[1].replace(/\t/g, ' ').length; const lineIndent = match[1].replace(/\t/g, ' ').length;
const isOrdered = /\d+\./.test(match[2]); const isOrdered = /\d+\./.test(match[2]);
const tag = isOrdered ? 'ol' : 'ul'; const itemType = isOrdered ? 'ol' : 'ul';
if (!currentTag) { if (lineIndent > indent) {
html += `<${tag} class="ml-6 mb-2">`;
currentTag = tag;
} else if (lineIndent > indent) {
// Nested list // Nested list
const [nestedHtml, consumed] = render(lines, i, lineIndent); const [nestedHtml, consumed] = parseList(i, lineIndent, itemType);
html += nestedHtml; html = html.replace(/<\/li>$/, '') + nestedHtml + '</li>';
i = consumed; i = consumed;
continue; continue;
} else if (lineIndent < indent) { }
if (lineIndent < indent || itemType !== type) {
break; break;
} }
html += `<li class="mb-1">${match[3]}`; html += `<li class="mb-1">${match[3]}`;
// Check if next line is more indented (nested) // Check for next line being a nested list
if (i + 1 < lines.length) { if (i + 1 < lines.length) {
const nextMatch = lines[i + 1].match(/^([ \t]*)([*+-]|\d+\.)[ \t]+/); const nextMatch = lines[i + 1].match(/^([ \t]*)([*+-]|\d+\.)[ \t]+/);
if (nextMatch) { if (nextMatch) {
const nextIndent = nextMatch[1].replace(/\t/g, ' ').length; const nextIndent = nextMatch[1].replace(/\t/g, ' ').length;
const nextType = /\d+\./.test(nextMatch[2]) ? 'ol' : 'ul';
if (nextIndent > lineIndent) { if (nextIndent > lineIndent) {
const [nestedHtml, consumed] = render(lines, i + 1, nextIndent); const [nestedHtml, consumed] = parseList(i + 1, nextIndent, nextType);
html += nestedHtml; html += nestedHtml;
i = consumed - 1; i = consumed - 1;
} }
@ -191,10 +185,15 @@ function renderListGroup(lines: string[]): string {
html += '</li>'; html += '</li>';
i++; i++;
} }
html += `</${currentTag}>`; html += `</${type}>`;
return [html, i]; return [html, i];
} }
const [html] = render(lines); if (!lines.length) return '';
const firstLine = lines[0];
const match = firstLine.match(/^([ \t]*)([*+-]|\d+\.)[ \t]+/);
const indent = match ? match[1].replace(/\t/g, ' ').length : 0;
const type = typeHint || (match && /\d+\./.test(match[2]) ? 'ol' : 'ul');
const [html] = parseList(0, indent, type);
return html; return html;
} }
@ -207,8 +206,8 @@ function processBasicFormatting(content: string): string {
// Sanitize Alexandria Nostr links before further processing // Sanitize Alexandria Nostr links before further processing
processedText = replaceAlexandriaNostrLinks(processedText); processedText = replaceAlexandriaNostrLinks(processedText);
// Process Markdown images first // Process markup images first
processedText = processedText.replace(MARKDOWN_IMAGE, (match, alt, url) => { processedText = processedText.replace(markup_IMAGE, (match, alt, url) => {
url = stripTrackingParams(url); url = stripTrackingParams(url);
if (YOUTUBE_URL_REGEX.test(url)) { if (YOUTUBE_URL_REGEX.test(url)) {
const videoId = extractYouTubeVideoId(url); const videoId = extractYouTubeVideoId(url);
@ -230,8 +229,8 @@ function processBasicFormatting(content: string): string {
return `<a href="${url}" class="text-primary-600 dark:text-primary-500 hover:underline" target="_blank" rel="noopener noreferrer">${alt || url}</a>`; return `<a href="${url}" class="text-primary-600 dark:text-primary-500 hover:underline" target="_blank" rel="noopener noreferrer">${alt || url}</a>`;
}); });
// Process Markdown links // Process markup links
processedText = processedText.replace(MARKDOWN_LINK, (match, text, url) => processedText = processedText.replace(markup_LINK, (match, text, url) =>
`<a href="${stripTrackingParams(url)}" class="text-primary-600 dark:text-primary-500 hover:underline" target="_blank" rel="noopener noreferrer">${text}</a>` `<a href="${stripTrackingParams(url)}" class="text-primary-600 dark:text-primary-500 hover:underline" target="_blank" rel="noopener noreferrer">${text}</a>`
); );
@ -291,7 +290,9 @@ function processBasicFormatting(content: string): string {
inList = true; inList = true;
} else { } else {
if (inList) { if (inList) {
output += renderListGroup(buffer); const firstLine = buffer[0];
const isOrdered = /^\s*\d+\.\s+/.test(firstLine);
output += renderListGroup(buffer, isOrdered ? 'ol' : 'ul');
buffer = []; buffer = [];
inList = false; inList = false;
} }
@ -299,7 +300,9 @@ function processBasicFormatting(content: string): string {
} }
} }
if (buffer.length) { if (buffer.length) {
output += renderListGroup(buffer); const firstLine = buffer[0];
const isOrdered = /^\s*\d+\.\s+/.test(firstLine);
output += renderListGroup(buffer, isOrdered ? 'ol' : 'ul');
} }
processedText = output; processedText = output;
// --- End Improved List Grouping and Parsing --- // --- End Improved List Grouping and Parsing ---
@ -348,7 +351,7 @@ function processEmojiShortcuts(content: string): string {
} }
} }
export async function parseBasicMarkdown(text: string): Promise<string> { export async function parseBasicmarkup(text: string): Promise<string> {
if (!text) return ''; if (!text) return '';
try { try {
@ -357,26 +360,6 @@ export async function parseBasicMarkdown(text: string): Promise<string> {
// Process emoji shortcuts // Process emoji shortcuts
processedText = processEmojiShortcuts(processedText); processedText = processEmojiShortcuts(processedText);
// Process lists - handle ordered lists first
processedText = processedText
// Process ordered lists
.replace(ORDERED_LIST_REGEX, (match, marker, content) => {
// Count leading spaces to determine nesting level
const indent = marker.match(/^\s*/)[0].length;
const extraIndent = indent > 0 ? ` ml-${indent * 4}` : '';
return `<li class="py-2${extraIndent}">${content}</li>`;
})
.replace(/<li.*?>.*?<\/li>\n?/gs, '<ol class="list-decimal my-4 ml-8">$&</ol>')
// Process unordered lists
.replace(UNORDERED_LIST_REGEX, (match, marker, content) => {
// Count leading spaces to determine nesting level
const indent = marker.match(/^\s*/)[0].length;
const extraIndent = indent > 0 ? ` ml-${indent * 4}` : '';
return `<li class="py-2${extraIndent}">${content}</li>`;
})
.replace(/<li.*?>.*?<\/li>\n?/gs, '<ul class="list-disc my-4 ml-8">$&</ul>');
// Process blockquotes // Process blockquotes
processedText = processBlockquotes(processedText); processedText = processBlockquotes(processedText);
@ -397,7 +380,7 @@ export async function parseBasicMarkdown(text: string): Promise<string> {
return processedText; return processedText;
} catch (e: unknown) { } catch (e: unknown) {
console.error('Error in parseBasicMarkdown:', e); console.error('Error in parseBasicmarkup:', e);
return `<div class="text-red-500">Error processing markdown: ${(e as Error)?.message ?? 'Unknown error'}</div>`; return `<div class="text-red-500">Error processing markup: ${(e as Error)?.message ?? 'Unknown error'}</div>`;
} }
} }

16
src/lib/utils/markdown/markdownTestfile.md → src/lib/utils/markup/markupTestfile.md

@ -3,7 +3,7 @@ This is a test
### Disclaimer ### Disclaimer
It is _only_ a test, for __sure__. I just wanted to see if the markdown renders correctly on the page, even if I use **two asterisks** for bold text, instead of *one asterisk*.[^1] It is _only_ a test, for __sure__. I just wanted to see if the markup renders correctly on the page, even if I use **two asterisks** for bold text, instead of *one asterisk*.[^1]
# H1 # H1
## H2 ## H2
@ -12,7 +12,7 @@ It is _only_ a test, for __sure__. I just wanted to see if the markdown renders
##### H5 ##### H5
###### H6 ###### H6
This file is full of ~errors~ opportunities to ~~mess up the formatting~~ check your markdown parser. This file is full of ~errors~ opportunities to ~~mess up the formatting~~ check your markup parser.
You can even learn about [[mirepoix]], [[nkbip-03]], or [[roman catholic church|catholics]] You can even learn about [[mirepoix]], [[nkbip-03]], or [[roman catholic church|catholics]]
@ -95,13 +95,13 @@ https://primal.net/e/nevent1qqsqum7j25p9z8vcyn93dsd7edx34w07eqav50qnde3vrfs466q5
https://primal.net/p/nprofile1qqs06gywary09qmcp2249ztwfq3ue8wxhl2yyp3c39thzp55plvj0sgjn9mdk https://primal.net/p/nprofile1qqs06gywary09qmcp2249ztwfq3ue8wxhl2yyp3c39thzp55plvj0sgjn9mdk
URL with a tracking parameter, no Markdown: URL with a tracking parameter, no markup:
https://example.com?utm_source=newsletter1&utm_medium=email&utm_campaign=sale https://example.com?utm_source=newsletter1&utm_medium=email&utm_campaign=sale
Image without Markdown: Image without markup:
https://upload.wikimedia.org/wikipedia/commons/f/f1/Heart_coraz%C3%B3n.svg https://upload.wikimedia.org/wikipedia/commons/f/f1/Heart_coraz%C3%B3n.svg
This is an implementation of [Nostr-flavored Markdown](https://github.com/nostrability/nostrability/issues/146) for #gitstuff issue notes. This is an implementation of [Nostr-flavored markup](https://github.com/nostrability/nostrability/issues/146) for #gitstuff issue notes.
You can even turn Alexandria URLs into embedded events, if they have hexids or bech32 addresses: You can even turn Alexandria URLs into embedded events, if they have hexids or bech32 addresses:
http://localhost:4173/publication?id=nevent1qqstjcyerjx4laxlxc70cwzuxf3u9kkzuhdhgtu8pwrzvh7k5d5zdngpzemhxue69uhhyetvv9ujumn0wd68ytnzv9hxgq3qm3xdppkd0njmrqe2ma8a6ys39zvgp5k8u22mev8xsnqp4nh80srq0ylvuw http://localhost:4173/publication?id=nevent1qqstjcyerjx4laxlxc70cwzuxf3u9kkzuhdhgtu8pwrzvh7k5d5zdngpzemhxue69uhhyetvv9ujumn0wd68ytnzv9hxgq3qm3xdppkd0njmrqe2ma8a6ys39zvgp5k8u22mev8xsnqp4nh80srq0ylvuw
@ -109,7 +109,7 @@ http://localhost:4173/publication?id=nevent1qqstjcyerjx4laxlxc70cwzuxf3u9kkzuhdh
But not if they have d-tags: But not if they have d-tags:
http://next-alexandria.gitcitadel.eu/publication?d=relay-test-thecitadel-by-unknown-v-1 http://next-alexandria.gitcitadel.eu/publication?d=relay-test-thecitadel-by-unknown-v-1
And within a Markdown tag: [Markdown link title](http://alexandria.gitcitadel.com/publication?id=84ad65f7a321404f55d97c2208dd3686c41724e6c347d3ee53cfe16f67cdfb7c). And within a markup tag: [markup link title](http://alexandria.gitcitadel.com/publication?id=84ad65f7a321404f55d97c2208dd3686c41724e6c347d3ee53cfe16f67cdfb7c).
And to localhost: http://localhost:4173/publication?id=c36b54991e459221f444612d88ea94ef5bb4a1b93863ef89b1328996746f6d25 And to localhost: http://localhost:4173/publication?id=c36b54991e459221f444612d88ea94ef5bb4a1b93863ef89b1328996746f6d25
@ -125,7 +125,7 @@ You can even use a multi-line code block, with a json tag.
```json ```json
{ {
"created_at":1745038670,"content":"# This is a test\n\nIt is _only_ a test. I just wanted to see if the *markdown* renders correctly on the page, even if I use **two asterisks** for bold text.[^1]\n\nnpub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z wrote this. That's the same person as nostr:npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z. That is a different person from npub1s3ht77dq4zqnya8vjun5jp3p44pr794ru36d0ltxu65chljw8xjqd975wz.\n\n> This is important information\n\n> This is multiple\n> lines of\n> important information\n> with a second[^2] footnote.\n\n* but\n* not\n* really\n\n## More testing\n\n1. first\n2. second\n3. third\n\nHere is a horizontal rule:\n\n---\n\nThis is an implementation of [Nostr-flavored Markdown](github.com/nostrability/nostrability/issues/146 ) for #gitstuff issue notes.\n\nYou can even include `code inline` or\n\n```\nin a code block\n```\n\nYou can even use a \n\n```json\nmultiline of json block\n```\n\n\n![Nostr logo](https://user-images.githubusercontent.com/99301796/219900773-d6d02038-e2a0-4334-9f28-c14d40ab6fe7.png)\n\n[^1]: this is a footnote\n[^2]: so is this","tags":[["subject","test"],["alt","git repository issue: test"],["a","30617:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:Alexandria","","root"],["p","fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1"],["t","gitstuff"]],"kind":1621,"pubkey":"dd664d5e4016433a8cd69f005ae1480804351789b59de5af06276de65633d319","id":"e78a689369511fdb3c36b990380c2d8db2b5e62f13f6b836e93ef5a09611afe8","sig":"7a2b3a6f6f61b6ea04de1fe873e46d40f2a220f02cdae004342430aa1df67647a9589459382f22576c651b3d09811546bbd79564cf472deaff032f137e94a865" "created_at":1745038670,"content":"# This is a test\n\nIt is _only_ a test. I just wanted to see if the *markup* renders correctly on the page, even if I use **two asterisks** for bold text.[^1]\n\nnpub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z wrote this. That's the same person as nostr:npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z. That is a different person from npub1s3ht77dq4zqnya8vjun5jp3p44pr794ru36d0ltxu65chljw8xjqd975wz.\n\n> This is important information\n\n> This is multiple\n> lines of\n> important information\n> with a second[^2] footnote.\n\n* but\n* not\n* really\n\n## More testing\n\n1. first\n2. second\n3. third\n\nHere is a horizontal rule:\n\n---\n\nThis is an implementation of [Nostr-flavored markup](github.com/nostrability/nostrability/issues/146 ) for #gitstuff issue notes.\n\nYou can even include `code inline` or\n\n```\nin a code block\n```\n\nYou can even use a \n\n```json\nmultiline of json block\n```\n\n\n![Nostr logo](https://user-images.githubusercontent.com/99301796/219900773-d6d02038-e2a0-4334-9f28-c14d40ab6fe7.png)\n\n[^1]: this is a footnote\n[^2]: so is this","tags":[["subject","test"],["alt","git repository issue: test"],["a","30617:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:Alexandria","","root"],["p","fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1"],["t","gitstuff"]],"kind":1621,"pubkey":"dd664d5e4016433a8cd69f005ae1480804351789b59de5af06276de65633d319","id":"e78a689369511fdb3c36b990380c2d8db2b5e62f13f6b836e93ef5a09611afe8","sig":"7a2b3a6f6f61b6ea04de1fe873e46d40f2a220f02cdae004342430aa1df67647a9589459382f22576c651b3d09811546bbd79564cf472deaff032f137e94a865"
} }
``` ```
@ -180,7 +180,7 @@ package main
} }
``` ```
or even Markdown: or even markup:
```md ```md
A H1 Header A H1 Header

6
src/lib/utils/mime.ts

@ -55,13 +55,13 @@ export function getMimeTags(kind: number): [string, string][] {
// Issue // Issue
case 1621: case 1621:
mTag = ["m", "text/markdown"]; mTag = ["m", "text/markup"];
MTag = ["M", `git/issue/${replaceability}`]; MTag = ["M", `git/issue/${replaceability}`];
break; break;
// Issue comment // Issue comment
case 1622: case 1622:
mTag = ["m", "text/markdown"]; mTag = ["m", "text/markup"];
MTag = ["M", `git/comment/${replaceability}`]; MTag = ["M", `git/comment/${replaceability}`];
break; break;
@ -85,7 +85,7 @@ export function getMimeTags(kind: number): [string, string][] {
// Long-form note // Long-form note
case 30023: case 30023:
mTag = ["m", "text/markdown"]; mTag = ["m", "text/markup"];
MTag = ["M", `article/long-form/${replaceability}`]; MTag = ["M", `article/long-form/${replaceability}`];
break; break;

2
src/routes/about/+page.svelte

@ -26,7 +26,7 @@
href="/publication?d=gitcitadel-project-documentation-curated-publications-specification-7-by-stella-v-1" href="/publication?d=gitcitadel-project-documentation-curated-publications-specification-7-by-stella-v-1"
>curated publications</A >curated publications</A
> (in Asciidoc), wiki pages (Asciidoc), and will eventually also support long-form > (in Asciidoc), wiki pages (Asciidoc), and will eventually also support long-form
articles (Markdown). It is produced by the <A articles (markup). It is produced by the <A
href="/publication?d=gitcitadel-project-documentation-gitcitadel-project-1-by-stella-v-1" href="/publication?d=gitcitadel-project-documentation-gitcitadel-project-1-by-stella-v-1"
>GitCitadel project team</A >GitCitadel project team</A
>. >.

12
src/routes/contact/+page.svelte

@ -6,7 +6,7 @@
import { NDKEvent, NDKRelaySet } from '@nostr-dev-kit/ndk'; import { NDKEvent, NDKRelaySet } from '@nostr-dev-kit/ndk';
// @ts-ignore - Workaround for Svelte component import issue // @ts-ignore - Workaround for Svelte component import issue
import LoginModal from '$lib/components/LoginModal.svelte'; import LoginModal from '$lib/components/LoginModal.svelte';
import { parseAdvancedMarkdown } from '$lib/utils/markdown/advancedMarkdownParser'; import { parseAdvancedmarkup } from '$lib/utils/markup/advancedMarkupParser';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import { getMimeTags } from '$lib/utils/mime'; import { getMimeTags } from '$lib/utils/mime';
@ -334,7 +334,7 @@
required required
placeholder="Describe your issue in detail... placeholder="Describe your issue in detail...
The following Markdown is supported: The following markup is supported:
# Headers (1-6 levels) # Headers (1-6 levels)
@ -369,9 +369,9 @@ Also renders nostr identifiers: npubs, nprofiles, nevents, notes, and naddrs. Wi
/> />
</div> </div>
{:else} {:else}
<div class="absolute inset-0 p-4 max-w-none bg-white dark:bg-gray-800 prose-content markdown-content"> <div class="absolute inset-0 p-4 max-w-none bg-white dark:bg-gray-800 prose-content markup-content">
{#key content} {#key content}
{#await parseAdvancedMarkdown(content)} {#await parseAdvancedmarkup(content)}
<p>Loading preview...</p> <p>Loading preview...</p>
{:then html} {:then html}
{@html html || '<p class="text-gray-500">Nothing to preview</p>'} {@html html || '<p class="text-gray-500">Nothing to preview</p>'}
@ -436,12 +436,12 @@ Also renders nostr identifiers: npubs, nprofiles, nevents, notes, and naddrs. Wi
<div> <div>
<span class="font-semibold">Description:</span> <span class="font-semibold">Description:</span>
<div class="mt-1 note-leather max-h-[400px] overflow-y-auto"> <div class="mt-1 note-leather max-h-[400px] overflow-y-auto">
{#await parseAdvancedMarkdown(submittedEvent.content)} {#await parseAdvancedmarkup(submittedEvent.content)}
<p>Loading...</p> <p>Loading...</p>
{:then html} {:then html}
{@html html} {@html html}
{:catch error} {:catch error}
<p class="text-red-500">Error rendering markdown: {error.message}</p> <p class="text-red-500">Error rendering markup: {error.message}</p>
{/await} {/await}
</div> </div>
</div> </div>

Loading…
Cancel
Save