Browse Source

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
master
Silberengel 11 months ago
parent
commit
0f80ac585a
  1. 52
      src/app.css
  2. 152
      src/lib/utils/markdownParser.ts
  3. 182
      src/lib/utils/markdownTestfile.md
  4. 247
      src/routes/contact/+page.svelte

52
src/app.css

@ -223,58 +223,6 @@
.inline-code { .inline-code {
@apply font-mono text-sm bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded; @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 { @layer components {

152
src/lib/utils/markdownParser.ts

@ -100,48 +100,76 @@ async function getUserMetadata(identifier: string): Promise<{name?: string, disp
*/ */
function processLists(content: string): string { function processLists(content: string): string {
const lines = content.split('\n'); const lines = content.split('\n');
let inList = false;
let isOrdered = false;
let currentList: string[] = [];
const processed: 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 <li class="pl-1">${item}</li>`;
});
listHtml += `\n</${listType}>`;
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++) { for (let i = 0; i < lines.length; i++) {
const line = lines[i]; const line = lines[i];
const orderedMatch = line.match(/^(\d+)\.[ \t]+(.+)$/); // Count leading spaces to determine nesting level
const unorderedMatch = line.match(/^\*[ \t]+(.+)$/); 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 (orderedMatch || unorderedMatch) {
if (!inList) { const content = orderedMatch ? orderedMatch[2] : (unorderedMatch && unorderedMatch[1]) || '';
inList = true; const type = orderedMatch ? 'ol' : 'ul';
isOrdered = !!orderedMatch;
currentList = []; // 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 { } else {
if (inList) { // Not a list item - close all open lists and add the line
const listType = isOrdered ? 'ol' : 'ul'; while (listStack.length > 0) {
const listClass = isOrdered ? 'list-decimal' : 'list-disc'; closeList();
processed.push(`<${listType} class="${listClass} pl-6 my-4 space-y-1">`);
currentList.forEach(item => {
processed.push(` <li class="ml-4">${item}</li>`);
});
processed.push(`</${listType}>`);
inList = false;
currentList = [];
} }
processed.push(line); processed.push(line);
} }
} }
if (inList) { // Close any remaining open lists
const listType = isOrdered ? 'ol' : 'ul'; while (listStack.length > 0) {
const listClass = isOrdered ? 'list-decimal' : 'list-disc'; closeList();
processed.push(`<${listType} class="${listClass} pl-6 my-4 space-y-1">`);
currentList.forEach(item => {
processed.push(` <li class="ml-4">${item}</li>`);
});
processed.push(`</${listType}>`);
} }
return processed.join('\n'); return processed.join('\n');
@ -293,48 +321,18 @@ function restoreCodeBlocks(text: string, blocks: Map<string, string>): string {
const { code, language } = JSON.parse(blockContent); const { code, language } = JSON.parse(blockContent);
let processedCode = code; let processedCode = code;
// First escape HTML characters // Format JSON if the language is specified as json
processedCode = processedCode
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
// Format and highlight based on language
if (language === 'json') { if (language === 'json') {
try { try {
// Parse and format JSON const jsonObj = JSON.parse(code.trim());
const parsed = JSON.parse(code); processedCode = JSON.stringify(jsonObj, null, 2);
processedCode = JSON.stringify(parsed, null, 2)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
// Apply JSON syntax highlighting
processedCode = processedCode
// Match JSON keys (including colons)
.replace(/(&quot;[^&quot;]+&quot;):/g, '<span class="json-key">$1</span>:')
// Match string values (after colons and in arrays)
.replace(/: (&quot;[^&quot;]+&quot;)/g, ': <span class="json-string">$1</span>')
.replace(/\[(&quot;[^&quot;]+&quot;)/g, '[<span class="json-string">$1</span>')
.replace(/, (&quot;[^&quot;]+&quot;)/g, ', <span class="json-string">$1</span>')
// Match numbers
.replace(/: (-?\d+\.?\d*)/g, ': <span class="json-number">$1</span>')
.replace(/\[(-?\d+\.?\d*)/g, '[<span class="json-number">$1</span>')
.replace(/, (-?\d+\.?\d*)/g, ', <span class="json-number">$1</span>')
// Match booleans
.replace(/: (true|false)\b/g, ': <span class="json-boolean">$1</span>')
// Match null
.replace(/: (null)\b/g, ': <span class="json-null">$1</span>');
} catch (e) { } catch (e) {
// If JSON parsing fails, use the original escaped code
console.warn('Failed to parse JSON:', e); 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 { try {
if (hljs.getLanguage(language)) { if (hljs.getLanguage(language)) {
const highlighted = hljs.highlight(processedCode, { language }); const highlighted = hljs.highlight(processedCode, { language });
@ -346,7 +344,18 @@ function restoreCodeBlocks(text: string, blocks: Map<string, string>): string {
} }
const languageClass = language ? ` language-${language}` : ''; const languageClass = language ? ` language-${language}` : '';
const replacement = `<pre class="code-block" data-language="${language || ''}"><code class="hljs${languageClass}">${processedCode}</code></pre>`; const replacement = `<div class="relative group my-2">
<button
type="button"
class="absolute right-2 top-2 p-2 bg-gray-700/50 hover:bg-gray-600/50 rounded opacity-0 group-hover:opacity-100 transition-opacity"
onclick="event.preventDefault(); event.stopPropagation(); navigator.clipboard.writeText(this.parentElement.querySelector('code').textContent);"
>
<svg class="w-4 h-4 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"></path>
</svg>
</button>
<pre class="code-block whitespace-pre overflow-x-auto bg-gray-900 rounded-md p-3" data-language="${language || ''}"><code class="hljs${languageClass}">${processedCode}</code></pre>
</div>`;
result = result.replace(id, replacement); result = result.replace(id, replacement);
}); });
return result; return result;
@ -357,14 +366,7 @@ function restoreCodeBlocks(text: string, blocks: Map<string, string>): string {
*/ */
function processInlineCode(text: string): string { function processInlineCode(text: string): string {
return text.replace(INLINE_CODE_REGEX, (match, code) => { return text.replace(INLINE_CODE_REGEX, (match, code) => {
const escapedCode = code return `<code class="bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded text-sm font-mono">${code}</code>`;
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
return `<code class="bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded text-sm font-mono">${escapedCode}</code>`;
}); });
} }

182
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<<i)) != 0);
}
```
Asciidoc:
```adoc
= Header 1
preamble goes here
== Header 2
some more text
```
Gherkin:
```gherkin
Feature: Account Holder withdraws cash
Scenario: Account has sufficient funds
Given The account balance is $100
And the card is valid
And the machine contains enough money
When the Account Holder requests $20
Then the ATM should dispense $20
And the account balance should be $80
And the card should be returned
```
Go:
```go
package main
import (
"fmt"
"bufio"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
fmt.Print("Enter text: ")
scanner.Scan()
input := scanner.Text()
fmt.Println("You entered:", input)
}
```
or even Markdown:
```md
A H1 Header
============
Paragraphs are separated by a blank line.
2nd paragraph. *Italic*, **bold**, and `monospace`. Itemized lists
look like:
* this one
* that one
* the other one
Note that --- not considering the asterisk --- the actual text
content starts at 4-columns in.
> 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

247
src/routes/contact/+page.svelte

@ -1,5 +1,5 @@
<script lang='ts'> <script lang='ts'>
import { Heading, P, A, Button, Label, Textarea, Input } from "flowbite-svelte"; import { Heading, P, A, Button, Label, Textarea, Input, Tabs, TabItem, Modal } from 'flowbite-svelte';
import { ndkSignedIn, ndkInstance, activePubkey } from '$lib/ndk'; import { ndkSignedIn, ndkInstance, activePubkey } from '$lib/ndk';
import { standardRelays } from '$lib/consts'; import { standardRelays } from '$lib/consts';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
@ -15,6 +15,14 @@
submittedEvent = null; submittedEvent = null;
} }
function clearForm() {
subject = '';
content = '';
submissionError = '';
isExpanded = false;
activeTab = 'write';
}
let subject = ''; let subject = '';
let content = ''; let content = '';
let isSubmitting = false; let isSubmitting = false;
@ -25,6 +33,8 @@
let issueLink = ''; let issueLink = '';
let successfulRelays: string[] = []; let successfulRelays: string[] = [];
let isExpanded = false; let isExpanded = false;
let activeTab = 'write';
let showConfirmDialog = false;
// Store form data when user needs to login // Store form data when user needs to login
let savedFormData = { let savedFormData = {
@ -62,7 +72,10 @@
isExpanded = !isExpanded; isExpanded = !isExpanded;
} }
async function handleSubmit() { async function handleSubmit(e: Event) {
// Prevent form submission
e.preventDefault();
if (!subject || !content) { if (!subject || !content) {
submissionError = 'Please fill in all fields'; submissionError = 'Please fill in all fields';
return; return;
@ -81,9 +94,18 @@
return; return;
} }
// User is logged in, proceed with submission // Show confirmation dialog
showConfirmDialog = true;
}
async function confirmSubmit() {
showConfirmDialog = false;
await submitIssue(); await submitIssue();
} }
function cancelSubmit() {
showConfirmDialog = false;
}
async function submitIssue() { async function submitIssue() {
isSubmitting = true; isSubmitting = true;
@ -243,7 +265,7 @@
</script> </script>
<div class='w-full flex justify-center'> <div class='w-full flex justify-center'>
<main class='main-leather flex flex-col space-y-6 max-w-3xl w-full my-6 px-4'> <main class='main-leather flex flex-col space-y-6 max-w-3xl w-full my-6 px-6 sm:px-4'>
<Heading tag='h1' class='h-leather mb-2'>Contact GitCitadel</Heading> <Heading tag='h1' class='h-leather mb-2'>Contact GitCitadel</Heading>
<P class="mb-3"> <P class="mb-3">
@ -263,61 +285,107 @@
<form class="space-y-4 mt-6" on:submit|preventDefault={handleSubmit}> <form class="space-y-4 mt-6" on:submit|preventDefault={handleSubmit}>
<div> <div>
<Label for="subject" class="mb-2">Subject</Label> <Label for="subject" class="mb-2">Subject</Label>
<Input id="subject" class="w-full" placeholder="Issue subject" bind:value={subject} required /> <Input id="subject" class="w-full" placeholder="Issue subject" bind:value={subject} required autofocus />
</div> </div>
<div class="relative"> <div class="relative">
<Label for="content" class="mb-2">Description</Label> <Label for="content" class="mb-2">Description</Label>
<div class="relative {isExpanded ? 'h-[600px]' : 'h-[300px]'} overflow-y-scroll border border-gray-300 dark:border-gray-600 rounded-lg"> <div class="relative border border-gray-300 dark:border-gray-600 rounded-lg {isExpanded ? 'h-[1200px]' : 'h-[300px]'} transition-all duration-200">
<Textarea <div class="h-full flex flex-col">
id="content" <div class="border-b border-gray-300 dark:border-gray-600">
class="resize-none w-full h-auto min-h-[150%] border-0 focus:ring-0" <ul class="flex flex-wrap -mb-px text-sm font-medium text-center" role="tablist">
placeholder="Describe your issue in detail... <li class="mr-2" role="presentation">
<button
Markdown is supported, including code blocks with syntax highlighting for these languages: type="button"
class="inline-block p-4 rounded-t-lg {activeTab === 'write' ? 'border-b-2 border-primary-600 text-primary-600' : 'hover:text-gray-600 hover:border-gray-300'}"
JavaScript (js) on:click={() => activeTab = 'write'}
TypeScript (ts) role="tab"
Python (py) >
Java (java) Write
C++ (cpp) </button>
C (c) </li>
Rust (rust, rs) <li role="presentation">
Go (go) <button
Ruby (ruby, rb) type="button"
PHP (php) class="inline-block p-4 rounded-t-lg {activeTab === 'preview' ? 'border-b-2 border-primary-600 text-primary-600' : 'hover:text-gray-600 hover:border-gray-300'}"
Haskell (haskell, hs) on:click={() => activeTab = 'preview'}
Perl (perl, pl) role="tab"
R (r) >
SQL (sql) Preview
YAML (yaml, yml) </button>
HTML (html) </li>
CSS (css) </ul>
XML (xml) </div>
Shell/Bash (shell, bash, sh)
Markdown (markdown, md) <div class="flex-1 min-h-0 relative">
AsciiDoc (asciidoc, adoc) {#if activeTab === 'write'}
AsciiMath (asciimath) <div class="absolute inset-0">
LaTeX (latex, tex) <Textarea
Gherkin/Cucumber (gherkin, cucumber, feature) id="content"
class="w-full h-full resize-none border-0 focus:ring-0 bg-white dark:bg-gray-800 p-4 description-textarea"
Use ```language at the start of a code block to enable syntax highlighting." bind:value={content}
bind:value={content} required
required placeholder="Describe your issue in detail...
/>
Markdown formatting is supported:
# Headers (1-6 levels)
**Bold** or *Bold*
_Italic_ text
> Blockquotes
Lists, including nested:
* Bullets/unordered lists
1. Numbered/ordered lists
[Links](url)
![Images](url)
`Inline code`
```language
Code blocks with syntax highlighting for over 100 languages
```
| Tables | With |
|--------|------|
| Multiple | Rows |
Footnotes[^1] and [^1]: footnote content
Also renders nostr identifiers: npubs, nprofiles, nevents, notes, and naddrs. With or without the nostr: prefix."
/>
</div>
{:else}
<div class="absolute inset-0 p-4 prose dark:prose-invert max-w-none bg-white dark:bg-gray-800 prose-content">
{#await parseMarkdown(content)}
<p>Loading preview...</p>
{:then html}
{@html html}
{:catch error}
<p class="text-red-500">Error rendering markdown: {error.message}</p>
{/await}
</div>
{/if}
</div>
</div>
<Button
type="button"
size="xs"
class="absolute bottom-2 right-2 z-10 opacity-60 hover:opacity-100"
color="light"
on:click={toggleSize}
>
{isExpanded ? '⌃' : '⌄'}
</Button>
</div> </div>
<Button
size="xs"
class="absolute bottom-2 right-2 z-10 opacity-60 hover:opacity-100"
color="light"
on:click={toggleSize}
>
{isExpanded ? '⌃' : '⌄'}
</Button>
</div> </div>
<div class="flex justify-end"> <div class="flex justify-end space-x-4">
<Button type="submit" disabled={isSubmitting}> <Button type="button" color="alternative" on:click={clearForm}>
Clear Form
</Button>
<Button type="submit" tabindex={0}>
{#if isSubmitting} {#if isSubmitting}
Submitting... Submitting...
{:else} {:else}
@ -396,9 +464,31 @@ Use ```language at the start of a code block to enable syntax highlighting."
</main> </main>
</div> </div>
<!-- Confirmation Dialog -->
<Modal
bind:open={showConfirmDialog}
size="sm"
autoclose={false}
class="w-full"
>
<div class="text-center">
<h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-400">
Would you like to submit the issue?
</h3>
<div class="flex justify-center gap-4">
<Button color="alternative" on:click={cancelSubmit}>
Cancel
</Button>
<Button color="primary" on:click={confirmSubmit}>
Submit
</Button>
</div>
</div>
</Modal>
<!-- Login Modal --> <!-- Login Modal -->
<LoginModal <LoginModal
show={showLoginModal} show={showLoginModal}
onClose={() => showLoginModal = false} onClose={() => showLoginModal = false}
onLoginSuccess={() => { onLoginSuccess={() => {
// Restore saved form data // Restore saved form data
@ -446,23 +536,58 @@ Use ```language at the start of a code block to enable syntax highlighting."
color: var(--color-leather-primary); color: var(--color-leather-primary);
} }
/* Add custom scrollbar styling */
:global(.description-textarea) { :global(.description-textarea) {
overflow-y: scroll !important; overflow-y: scroll !important;
scrollbar-width: thin; scrollbar-width: thin !important;
scrollbar-color: rgba(156, 163, 175, 0.5) transparent; scrollbar-color: rgba(156, 163, 175, 0.5) transparent !important;
min-height: 100% !important;
} }
:global(.description-textarea::-webkit-scrollbar) { :global(.description-textarea::-webkit-scrollbar) {
width: 8px; width: 8px !important;
display: block !important;
} }
:global(.description-textarea::-webkit-scrollbar-track) { :global(.description-textarea::-webkit-scrollbar-track) {
background: transparent; background: transparent !important;
} }
:global(.description-textarea::-webkit-scrollbar-thumb) { :global(.description-textarea::-webkit-scrollbar-thumb) {
background-color: rgba(156, 163, 175, 0.5); background-color: rgba(156, 163, 175, 0.5) !important;
border-radius: 4px; border-radius: 4px !important;
}
:global(.description-textarea::-webkit-scrollbar-thumb:hover) {
background-color: rgba(156, 163, 175, 0.7) !important;
}
:global(.prose-content) {
overflow-y: scroll !important;
scrollbar-width: thin !important;
scrollbar-color: rgba(156, 163, 175, 0.5) transparent !important;
}
:global(.prose-content::-webkit-scrollbar) {
width: 8px !important;
display: block !important;
}
:global(.prose-content::-webkit-scrollbar-track) {
background: transparent !important;
}
:global(.prose-content::-webkit-scrollbar-thumb) {
background-color: rgba(156, 163, 175, 0.5) !important;
border-radius: 4px !important;
}
:global(.prose-content::-webkit-scrollbar-thumb:hover) {
background-color: rgba(156, 163, 175, 0.7) !important;
}
:global(.tab-content) {
position: relative;
display: flex;
flex-direction: column;
} }
</style> </style>

Loading…
Cancel
Save