diff --git a/src/lib/utils/markdownParser.ts b/src/lib/utils/markdownParser.ts
index e0e09ed..501edfe 100644
--- a/src/lib/utils/markdownParser.ts
+++ b/src/lib/utils/markdownParser.ts
@@ -7,17 +7,14 @@ import { ndkInstance } from '$lib/ndk';
import { nip19 } from 'nostr-tools';
// Regular expressions for nostr identifiers - process these first
-const NOSTR_NPUB_REGEX = /(?:nostr:)?(npub[a-zA-Z0-9]{59,60})/g;
+const NOSTR_PROFILE_REGEX = /(?:nostr:)?((?:npub|nprofile)[a-zA-Z0-9]{20,})/g;
+const NOSTR_NOTE_REGEX = /(?:nostr:)?((?:nevent|note|naddr)[a-zA-Z0-9]{20,})/g;
// Regular expressions for markdown elements
-const BLOCKQUOTE_REGEX = /^(?:>[ \t]*.+\n?(?:(?:>[ \t]*\n)*(?:>[ \t]*.+\n?))*)+/gm;
-const ORDERED_LIST_REGEX = /^(\d+)\.[ \t]+(.+)$/gm;
-const UNORDERED_LIST_REGEX = /^[-*][ \t]+(.+)$/gm;
const BOLD_REGEX = /\*\*([^*]+)\*\*|\*([^*]+)\*/g;
const ITALIC_REGEX = /_([^_]+)_/g;
const HEADING_REGEX = /^(#{1,6})\s+(.+)$/gm;
const HORIZONTAL_RULE_REGEX = /^(?:---|\*\*\*|___)$/gm;
-const CODE_BLOCK_REGEX = /```([^\n]*)\n([\s\S]*?)```/gm;
const INLINE_CODE_REGEX = /`([^`\n]+)`/g;
const LINK_REGEX = /\[([^\]]+)\]\(([^)]+)\)/g;
const IMAGE_REGEX = /!\[([^\]]*)\]\(([^)]+)\)/g;
@@ -29,38 +26,49 @@ const FOOTNOTE_DEFINITION_REGEX = /^\[(\^[^\]]+)\]:\s*(.+?)(?:\n(?!\[)|\n\n|$)/g
const npubCache = new Map ${quoteContent} ${quoteContent} ${quoteContent}
');
+ blockquotes.push({
+ id,
+ content: `
');
+ blockquotes.push({
+ id,
+ content: `
');
blockquotes.push({
id,
- content: `${cleanContent}
`
+ content: `
`
- });
- return id;
- });
+function formatCodeByLanguage(code: string, lang: string): string {
+ const language = lang.trim().toLowerCase();
+
+ // Remove any trailing whitespace or empty lines at start/end
+ let formattedCode = code.trim();
+
+ switch (language) {
+ case 'json':
+ try {
+ return JSON.stringify(JSON.parse(formattedCode), null, 2);
+ } catch (e) {
+ return formattedCode;
+ }
- // Then extract and save inline code
- processedText = processedText.replace(INLINE_CODE_REGEX, (match, code) => {
- const id = `INLINE_CODE_${inlineCodes.length}`;
- inlineCodes.push({
- id,
- content: `${escapeHtml(code)}${escapeHtml(code.trim())}`
- });
- return id;
- });
+ case 'javascript':
+ case 'js':
+ case 'typescript':
+ case 'ts':
+ try {
+ // Basic indentation for JS/TS
+ formattedCode = formattedCode
+ .split('\n')
+ .map(line => line.trim())
+ .join('\n');
+
+ // Add line breaks after certain characters
+ formattedCode = formattedCode
+ .replace(/([{([])\s*/g, '$1\n')
+ .replace(/\s*([\]})])/g, '\n$1')
+ .replace(/;\s*/g, ';\n')
+ .replace(/,\s*([^\s])/g, ',\n$1');
+
+ // Indent based on brackets
+ let indent = 0;
+ return formattedCode
+ .split('\n')
+ .map(line => {
+ line = line.trim();
+ if (line.match(/[}\])]$/)) indent--;
+ const formatted = ' '.repeat(Math.max(0, indent)) + line;
+ if (line.match(/[{([]\s*$/)) indent++;
+ return formatted;
+ })
+ .filter(line => line.trim())
+ .join('\n');
+ } catch (e) {
+ return formattedCode;
+ }
- // Now escape HTML in the remaining text
- processedText = escapeHtml(processedText);
+ case 'html':
+ case 'xml':
+ try {
+ // Basic indentation for HTML/XML
+ let indent = 0;
+ return formattedCode
+ .replace(/>\n<')
+ .split('\n')
+ .map(line => {
+ line = line.trim();
+ if (line.match(/<\/[^>]+>$/)) indent--;
+ const formatted = ' '.repeat(Math.max(0, indent)) + line;
+ if (line.match(/<[^/][^>]*>$/) && !line.match(/<[^>]+\/>/)) indent++;
+ return formatted;
+ })
+ .filter(line => line.trim())
+ .join('\n');
+ } catch (e) {
+ return formattedCode;
+ }
- // Restore code blocks
- blocks.forEach(({id, content}) => {
- processedText = processedText.replace(escapeHtml(id), content);
+ case 'css':
+ try {
+ // Basic indentation for CSS
+ return formattedCode
+ .replace(/\s*{\s*/g, ' {\n')
+ .replace(/;\s*/g, ';\n')
+ .replace(/\s*}\s*/g, '\n}\n')
+ .split('\n')
+ .map(line => line.trim())
+ .filter(line => line)
+ .map(line => line.startsWith('}') ? line : ' ' + line)
+ .join('\n');
+ } catch (e) {
+ return formattedCode;
+ }
+
+ case 'python':
+ case 'py':
+ try {
+ // Basic indentation for Python
+ let indent = 0;
+ return formattedCode
+ .split('\n')
+ .map(line => {
+ line = line.trim();
+ if (line.match(/^(return|break|continue|pass|else|elif|except|finally)\b/)) indent--;
+ const formatted = ' '.repeat(Math.max(0, indent)) + line;
+ if (line.match(/:\s*$/)) indent++;
+ return formatted;
+ })
+ .filter(line => line.trim())
+ .join('\n');
+ } catch (e) {
+ return formattedCode;
+ }
+
+ case 'cpp':
+ case 'c':
+ case 'rust':
+ try {
+ // Basic indentation for C/C++/Rust
+ let indent = 0;
+ return formattedCode
+ .split('\n')
+ .map(line => {
+ line = line.trim();
+ if (line.match(/^[}\])]/) || line.match(/^(public|private|protected):/)) indent--;
+ const formatted = ' '.repeat(Math.max(0, indent)) + line;
+ if (line.match(/[{[]$/)) indent++;
+ return formatted;
+ })
+ .filter(line => line.trim())
+ .join('\n');
+ } catch (e) {
+ return formattedCode;
+ }
+
+ case 'php':
+ try {
+ // Basic indentation for PHP
+ let indent = 0;
+ return formattedCode
+ .split('\n')
+ .map(line => {
+ line = line.trim();
+ if (line.match(/^[}\])]/) || line.match(/^(case|default):/)) indent--;
+ const formatted = ' '.repeat(Math.max(0, indent)) + line;
+ if (line.match(/[{[]$/) || line.match(/^(case|default):/)) indent++;
+ return formatted;
+ })
+ .filter(line => line.trim())
+ .join('\n');
+ } catch (e) {
+ return formattedCode;
+ }
+
+ case 'bash':
+ case 'shell':
+ case 'sh':
+ try {
+ // Basic formatting for shell scripts
+ return formattedCode
+ .split('\n')
+ .map(line => line.trim())
+ .filter(line => line)
+ .map(line => {
+ if (line.startsWith('#')) return line;
+ if (line.endsWith('\\')) return line + '\n';
+ if (line.match(/^(if|while|for|case)/)) return line;
+ if (line.match(/^(then|do|else|elif)/)) return ' ' + line;
+ if (line.match(/^(fi|done|esac)/)) return line;
+ return ' ' + line;
+ })
+ .join('\n');
+ } catch (e) {
+ return formattedCode;
+ }
+
+ default:
+ return formattedCode;
+ }
+}
+
+/**
+ * Process nostr identifiers
+ */
+async function processNostrIdentifiers(content: string): Promise
+ ${code}${escapedCode}`;
});
+}
+
+/**
+ * Process other markdown elements (excluding code)
+ */
+function processOtherElements(content: string): string {
+ // Process blockquotes first
+ content = processBlockquotes(content);
- // Restore inline code
- inlineCodes.forEach(({id, content}) => {
- processedText = processedText.replace(escapeHtml(id), content);
+ // Process basic markdown elements
+ content = content.replace(BOLD_REGEX, '$1$2');
+ content = content.replace(ITALIC_REGEX, '$1');
+ content = content.replace(HEADING_REGEX, (match, hashes, content) => {
+ const level = hashes.length;
+ const sizes = ['text-2xl', 'text-xl', 'text-lg', 'text-base', 'text-sm', 'text-xs'];
+ return `');
+ content = content.replace(LINK_REGEX, '$1');
+
+ // Process hashtags with standardized styling
+ content = content.replace(HASHTAG_REGEX, '#$1');
+
+ // Process horizontal rules
+ content = content.replace(HORIZONTAL_RULE_REGEX, '
');
+
+ return content;
}
/**
@@ -226,12 +597,12 @@ function processFootnotes(text: string): { text: string, footnotes: Map');
- html = html.replace(LINK_REGEX, '$1');
-
- // Process hashtags
- html = html.replace(HASHTAG_REGEX, '#$1');
-
- // Process horizontal rules
- html = html.replace(HORIZONTAL_RULE_REGEX, '
');
-
- // Handle paragraphs and line breaks
- html = html.replace(/\n{2,}/g, '
');
- html = html.replace(/\n/g, '
');
+ // Process footnotes
+ const { text: processedContent } = processFootnotes(content);
+ content = processedContent;
- // Wrap content in paragraph if needed
- if (!html.startsWith('<')) {
- html = `
${html}
`; - } + // Handle paragraphs and line breaks, preserving existing HTML + content = content + .split(/\n{2,}/) + .map(para => para.trim()) + .filter(para => para) + .map(para => para.startsWith('<') ? para : `${para}
`) + .join('\n\n'); - return html; -} + // Finally, restore code blocks + content = restoreCodeBlocks(content, blocks); -/** - * Escape HTML special characters to prevent XSS - */ -function escapeHtml(text: string): string { - return text - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); + return content; } /**