|
4 | 4 | import { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from "$lib/components/ui/tooltip"; |
5 | 5 | import * as ToggleGroup from "$lib/components/ui/toggle-group"; |
6 | 6 | import snarkdown from "snarkdown"; |
7 | | - |
| 7 | +
|
8 | 8 | import Bold from "@lucide/svelte/icons/bold"; |
9 | 9 | import Italic from "@lucide/svelte/icons/italic"; |
10 | 10 | import Heading from "@lucide/svelte/icons/heading"; |
|
15 | 15 | import Eye from "@lucide/svelte/icons/eye"; |
16 | 16 | import FileEdit from "@lucide/svelte/icons/file-edit"; |
17 | 17 |
|
18 | | - let { value = $bindable(), id = undefined, placeholder = undefined } = $props<{ |
| 18 | + let { |
| 19 | + value = $bindable(), |
| 20 | + id, |
| 21 | + placeholder, |
| 22 | + } = $props<{ |
19 | 23 | value?: string; |
20 | 24 | id?: string; |
21 | 25 | placeholder?: string; |
|
30 | 34 | const start = textarea.selectionStart; |
31 | 35 | const end = textarea.selectionEnd; |
32 | 36 | const text = textarea.value; |
33 | | - const selection = text.substring(start, end); |
34 | | - const beforeText = text.substring(0, start); |
35 | | - const afterText = text.substring(end); |
| 37 | + const selection = text.slice(start, end); |
| 38 | + const beforeText = text.slice(0, Math.max(0, start)); |
| 39 | + const afterText = text.slice(Math.max(0, end)); |
36 | 40 |
|
37 | 41 | value = beforeText + before + selection + after + afterText; |
38 | 42 |
|
|
56 | 60 | const lineStart = text.lastIndexOf("\n", start - 1) + 1; |
57 | 61 | const lineEnd = text.indexOf("\n", end); |
58 | 62 | const lineEndPos = lineEnd === -1 ? text.length : lineEnd; |
59 | | - const line = text.substring(lineStart, lineEndPos); |
| 63 | + const line = text.slice(lineStart, lineEndPos); |
60 | 64 |
|
61 | 65 | // Regex to match existing header prefix (up to 5 # followed by a space) |
62 | 66 | const headerMatch = line.match(/^(#{1,5})\s/); |
63 | | - let newLine = line; |
64 | | -
|
65 | | - if (headerMatch && headerMatch[1]) { |
66 | | - const hashes = headerMatch[1]; |
67 | | - const currentLevel = hashes.length; |
68 | | - if (currentLevel < 5) { |
69 | | - // Increment level: e.g. ## -> ### |
70 | | - newLine = "#".repeat(currentLevel + 1) + " " + line.substring(hashes.length + 1); |
71 | | - } else { |
72 | | - // Level 5 reached, back to normal paragraph |
73 | | - newLine = line.substring(hashes.length + 1); |
74 | | - } |
75 | | - } else { |
76 | | - // No header, start with H1 |
77 | | - newLine = "# " + line; |
78 | | - } |
79 | | -
|
80 | | - const beforeText = text.substring(0, lineStart); |
81 | | - const afterText = text.substring(lineEndPos); |
| 67 | + const hashes = headerMatch?.[1]; |
| 68 | +
|
| 69 | + const newLine = hashes |
| 70 | + ? hashes.length < 5 |
| 71 | + ? "#".repeat(hashes.length + 1) + " " + line.slice(hashes.length + 1) |
| 72 | + : line.slice(hashes.length + 1) |
| 73 | + : "# " + line; |
| 74 | +
|
| 75 | + const beforeText = text.slice(0, Math.max(0, lineStart)); |
| 76 | + const afterText = text.slice(Math.max(0, lineEndPos)); |
82 | 77 |
|
83 | 78 | value = beforeText + newLine + afterText; |
84 | 79 |
|
|
108 | 103 | <div class="flex flex-wrap items-center justify-between border-b p-1"> |
109 | 104 | <div class="flex flex-wrap gap-1"> |
110 | 105 | <TooltipProvider> |
111 | | - {#each actions as item} |
| 106 | + {#each actions as item (item.label)} |
112 | 107 | <Tooltip> |
113 | 108 | <TooltipTrigger> |
114 | 109 | <Button |
|
142 | 137 | </ToggleGroup.Item> |
143 | 138 | </ToggleGroup.Root> |
144 | 139 | </div> |
145 | | - |
| 140 | + |
146 | 141 | {#if view === "edit"} |
147 | 142 | <Textarea |
148 | 143 | bind:ref={textarea} |
149 | 144 | bind:value |
150 | | - id={id} |
| 145 | + {id} |
151 | 146 | {placeholder} |
152 | | - class="min-h-[300px] max-h-[400px] overflow-y-auto border-none focus-visible:ring-0" |
| 147 | + class="max-h-[400px] min-h-[300px] overflow-y-auto border-none focus-visible:ring-0" |
153 | 148 | /> |
154 | 149 | {:else} |
155 | | - <div class="min-h-[300px] max-h-[400px] overflow-y-auto p-4"> |
156 | | - <div class="prose prose-sm dark:prose-invert max-w-none"> |
| 150 | + <div class="max-h-[400px] min-h-[300px] overflow-y-auto p-4"> |
| 151 | + <div class="prose prose-sm max-w-none dark:prose-invert"> |
| 152 | + <!-- Using {@html} to render markdown as HTML. --> |
| 153 | + <!-- eslint-disable-next-line svelte/no-at-html-tags --> |
157 | 154 | {@html html} |
158 | 155 | </div> |
159 | 156 | </div> |
|
168 | 165 | box-shadow: none !important; |
169 | 166 | } |
170 | 167 | </style> |
171 | | - |
0 commit comments