From 0f80ac585aecee657e7d83e43883d2edacce37a4 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Sat, 19 Apr 2025 21:25:38 +0200 Subject: [PATCH] Revert all the special json formatting. Use only the standard functions. Fixed the formatting. Added Cancel button, Preview tab, and Submission confirmation modal. Added copy buttons to each code block. Made sure the focus and the workflow are correct. Changed the text within the input box to Markdown instructions. Issue #215 --- src/app.css | 52 ------- src/lib/utils/markdownParser.ts | 152 +++++++++--------- src/lib/utils/markdownTestfile.md | 182 ++++++++++++++++++++++ src/routes/contact/+page.svelte | 247 ++++++++++++++++++++++-------- 4 files changed, 445 insertions(+), 188 deletions(-) create mode 100644 src/lib/utils/markdownTestfile.md 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 @@

    - +
    -
    -