diff --git a/src/app.css b/src/app.css index 1a7c399..ba7dfdb 100644 --- a/src/app.css +++ b/src/app.css @@ -223,58 +223,6 @@ .inline-code { @apply font-mono text-sm bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded; } - - /* JSON syntax highlighting */ - .code-block[data-language="json"] { - color: #24292e; /* Default text color */ - } - - .code-block[data-language="json"] .json-key { - color: #005cc5; - } - - .code-block[data-language="json"] .json-string { - color: #032f62; - } - - .code-block[data-language="json"] .json-number { - color: #005cc5; - } - - .code-block[data-language="json"] .json-boolean { - color: #d73a49; - } - - .code-block[data-language="json"] .json-null { - color: #d73a49; - } - - /* Dark mode */ - @media (prefers-color-scheme: dark) { - .code-block[data-language="json"] { - color: #e1e4e8; - } - - .code-block[data-language="json"] .json-key { - color: #79b8ff; - } - - .code-block[data-language="json"] .json-string { - color: #9ecbff; - } - - .code-block[data-language="json"] .json-number { - color: #79b8ff; - } - - .code-block[data-language="json"] .json-boolean { - color: #f97583; - } - - .code-block[data-language="json"] .json-null { - color: #f97583; - } - } } @layer components { diff --git a/src/lib/utils/markdownParser.ts b/src/lib/utils/markdownParser.ts index 935bdc3..5456b49 100644 --- a/src/lib/utils/markdownParser.ts +++ b/src/lib/utils/markdownParser.ts @@ -100,48 +100,76 @@ async function getUserMetadata(identifier: string): Promise<{name?: string, disp */ function processLists(content: string): string { const lines = content.split('\n'); - let inList = false; - let isOrdered = false; - let currentList: string[] = []; const processed: string[] = []; + const listStack: { type: 'ol' | 'ul', items: string[], level: number }[] = []; + + function closeList() { + if (listStack.length > 0) { + const list = listStack.pop()!; + const listType = list.type; + const listClass = listType === 'ol' ? 'list-decimal' : 'list-disc'; + const indentClass = list.level > 0 ? 'ml-6' : 'ml-4'; + let listHtml = `<${listType} class="${listClass} ${indentClass} my-2 space-y-2">`; + list.items.forEach(item => { + listHtml += `\n
  • ${item}
  • `; + }); + listHtml += `\n`; + + if (listStack.length > 0) { + // If we're in a nested list, add this as an item to the parent + const parentList = listStack[listStack.length - 1]; + const lastItem = parentList.items.pop()!; + parentList.items.push(lastItem + '\n' + listHtml); + } else { + processed.push(listHtml); + } + } + } for (let i = 0; i < lines.length; i++) { const line = lines[i]; - const orderedMatch = line.match(/^(\d+)\.[ \t]+(.+)$/); - const unorderedMatch = line.match(/^\*[ \t]+(.+)$/); + // Count leading spaces to determine nesting level + const leadingSpaces = line.match(/^(\s*)/)?.[0]?.length ?? 0; + const effectiveLevel = Math.floor(leadingSpaces / 2); // 2 spaces per level + // Trim the line and check for list markers + const trimmedLine = line.trim(); + const orderedMatch = trimmedLine.match(/^(\d+)\.[ \t]+(.+)$/); + const unorderedMatch = trimmedLine.match(/^[-*][ \t]+(.+)$/); + if (orderedMatch || unorderedMatch) { - if (!inList) { - inList = true; - isOrdered = !!orderedMatch; - currentList = []; + const content = orderedMatch ? orderedMatch[2] : (unorderedMatch && unorderedMatch[1]) || ''; + const type = orderedMatch ? 'ol' : 'ul'; + + // Close any lists that are at a deeper level + while (listStack.length > 0 && listStack[listStack.length - 1].level > effectiveLevel) { + closeList(); + } + + // If we're at a new level, start a new list + if (listStack.length === 0 || listStack[listStack.length - 1].level < effectiveLevel) { + listStack.push({ type, items: [], level: effectiveLevel }); + } + // If we're at the same level but different type, close the current list and start a new one + else if (listStack[listStack.length - 1].type !== type && listStack[listStack.length - 1].level === effectiveLevel) { + closeList(); + listStack.push({ type, items: [], level: effectiveLevel }); } - const content = orderedMatch ? orderedMatch[2] : unorderedMatch![1]; - currentList.push(content); + + // Add the item to the current list + listStack[listStack.length - 1].items.push(content); } else { - if (inList) { - const listType = isOrdered ? 'ol' : 'ul'; - const listClass = isOrdered ? 'list-decimal' : 'list-disc'; - processed.push(`<${listType} class="${listClass} pl-6 my-4 space-y-1">`); - currentList.forEach(item => { - processed.push(`
  • ${item}
  • `); - }); - processed.push(``); - inList = false; - currentList = []; + // Not a list item - close all open lists and add the line + while (listStack.length > 0) { + closeList(); } processed.push(line); } } - if (inList) { - const listType = isOrdered ? 'ol' : 'ul'; - const listClass = isOrdered ? 'list-decimal' : 'list-disc'; - processed.push(`<${listType} class="${listClass} pl-6 my-4 space-y-1">`); - currentList.forEach(item => { - processed.push(`
  • ${item}
  • `); - }); - processed.push(``); + // Close any remaining open lists + while (listStack.length > 0) { + closeList(); } return processed.join('\n'); @@ -293,48 +321,18 @@ function restoreCodeBlocks(text: string, blocks: Map): string { const { code, language } = JSON.parse(blockContent); let processedCode = code; - // First escape HTML characters - processedCode = processedCode - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); - - // Format and highlight based on language + // Format JSON if the language is specified as json if (language === 'json') { try { - // Parse and format JSON - const parsed = JSON.parse(code); - processedCode = JSON.stringify(parsed, null, 2) - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); - - // Apply JSON syntax highlighting - processedCode = processedCode - // Match JSON keys (including colons) - .replace(/("[^"]+"):/g, '$1:') - // Match string values (after colons and in arrays) - .replace(/: ("[^"]+")/g, ': $1') - .replace(/\[("[^"]+")/g, '[$1') - .replace(/, ("[^"]+")/g, ', $1') - // Match numbers - .replace(/: (-?\d+\.?\d*)/g, ': $1') - .replace(/\[(-?\d+\.?\d*)/g, '[$1') - .replace(/, (-?\d+\.?\d*)/g, ', $1') - // Match booleans - .replace(/: (true|false)\b/g, ': $1') - // Match null - .replace(/: (null)\b/g, ': $1'); + const jsonObj = JSON.parse(code.trim()); + processedCode = JSON.stringify(jsonObj, null, 2); } catch (e) { - // If JSON parsing fails, use the original escaped code console.warn('Failed to parse JSON:', e); } - } else if (language) { - // Use highlight.js for other languages + } + + // Apply syntax highlighting if language is specified + if (language) { try { if (hljs.getLanguage(language)) { const highlighted = hljs.highlight(processedCode, { language }); @@ -346,7 +344,18 @@ function restoreCodeBlocks(text: string, blocks: Map): string { } const languageClass = language ? ` language-${language}` : ''; - const replacement = `
    ${processedCode}
    `; + const replacement = `
    + +
    ${processedCode}
    +
    `; result = result.replace(id, replacement); }); return result; @@ -357,14 +366,7 @@ function restoreCodeBlocks(text: string, blocks: Map): string { */ function processInlineCode(text: string): string { return text.replace(INLINE_CODE_REGEX, (match, code) => { - const escapedCode = code - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); - - return `${escapedCode}`; + return `${code}`; }); } diff --git a/src/lib/utils/markdownTestfile.md b/src/lib/utils/markdownTestfile.md new file mode 100644 index 0000000..c73a4dd --- /dev/null +++ b/src/lib/utils/markdownTestfile.md @@ -0,0 +1,182 @@ +This is a test +============ + +### Disclaimer + +It 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, instead of *one asterisk*.[^1] + +npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z wrote this. That's the same person as nostr:npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z and nprofile1qydhwumn8ghj7argv4nx7un9wd6zumn0wd68yvfwvdhk6tcpr3mhxue69uhkx6rjd9ehgurfd3kzumn0wd68yvfwvdhk6tcqyr7jprhgeregx7q2j4fgjmjgy0xfm34l63pqvwyf2acsd9q0mynuzp4qva3. That is a different person from npub1s3ht77dq4zqnya8vjun5jp3p44pr794ru36d0ltxu65chljw8xjqd975wz. + +> This is important information + +> This is multiple +> lines of +> important information +> with a second[^2] footnote. + +This is an unordered list: +* but +* not +* really + +This is an unordered list with nesting: +* but + * not + * really +* but + * yes, + * really + +## More testing + +An ordered list: +1. first +2. second +3. third + +Let's nest that: +1. first + 2. second indented +3. third + 4. fourth indented + 5. fifth indented even more + +This is ordered and unordered mixed: +1. first + 2. second indented +3. third + * make this a bullet point + 4. fourth indented even more + * second bullet point + +Here is a horizontal rule: + +--- + +Try embedded a nostr note with nevent: + +nostr:nevent1qvzqqqqqqypzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqydhwumn8ghj7argv4nx7un9wd6zumn0wd68yvfwvdhk6tcpr3mhxue69uhkx6rjd9ehgurfd3kzumn0wd68yvfwvdhk6tcqyrzdyycehfwyekef75z5wnnygqeps6a4qvc8dunvumzr08g06svgcptkske + +Here with note: + +note1cnfpxxd6t3xdk204q4r5uezqxgvxhdgrxpm0ym8xcsme6r75rzxqcj9lmz + +Here with a naddr: + +nostr:naddr1qvzqqqr4gupzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqydhwumn8ghj7argv4nx7un9wd6zumn0wd68yvfwvdhk6tcpr3mhxue69uhkx6rjd9ehgurfd3kzumn0wd68yvfwvdhk6tcqzasj6ar9wd6xv6tvv5kkvmmj94kkzuntv3hhwmsu0ktnz + +This is an implementation of [Nostr-flavored Markdown](https://github.com/nostrability/nostrability/issues/146) for #gitstuff issue notes. + +You can even include `code inline` or + +``` +in a code block +``` + +You can even use a multi-line code block, with a json tag. + +```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" +} +``` + +C or C++: +```cpp +bool getBit(int num, int i) { + return ((num & (1< Block quotes are +> written like so. +> +> They can span multiple paragraphs, +> if you like. +``` + +#### Here is an image! + +![Nostr logo](https://user-images.githubusercontent.com/99301796/219900773-d6d02038-e2a0-4334-9f28-c14d40ab6fe7.png) + +### I went ahead and implemented tables, too. + +A neat table: + +| Syntax | Description | +| ----------- | ----------- | +| Header | Title | +| Paragraph | Text | + +A messy table (should render the same as above): + +| Syntax | Description | +| --- | ----------- | +| Header | Title | +| Paragraph | Text | + +[^1]: this is a footnote +[^2]: so is this \ No newline at end of file diff --git a/src/routes/contact/+page.svelte b/src/routes/contact/+page.svelte index 2116261..895dff9 100644 --- a/src/routes/contact/+page.svelte +++ b/src/routes/contact/+page.svelte @@ -1,5 +1,5 @@
    -
    +
    Contact GitCitadel

    @@ -263,61 +285,107 @@

    - +
    -
    -