Files
windmill/frontend/src/lib/components/graph/GroupNoteArea.svelte
T
Diego ImbertandClaude Opus 5 fe36aa34f0 fix(frontend): render ordered lists in markdown descriptions
`GfmMarkdown` defaulted to `prose-xs`, which Tailwind Typography does not
define — the class only ever matched four hand-rolled rules in app.css, all
scoped to `ul`. Every surface on that default (script and flow descriptions,
flow-graph notes, markdown job results) therefore rendered `<ol>` with
Preflight's `list-style: none` and no typography at all: no numbers, no
heading or paragraph rhythm.

Route the default through the shared `markdownProse` stacks instead, and cut
the app.css list rules down to the dash glyph so ordered and unordered lists
share Tailwind Typography's indentation and rhythm.

Fixes #10971

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S6G5gDXJnm6uqch4uCPkPE
2026-09-04 15:38:34 +02:00

152 lines
4.4 KiB
Svelte

<script lang="ts">
import GfmMarkdown from '$lib/components/GfmMarkdown.svelte'
import { Check, X } from 'lucide-svelte'
import { NOTE_COLORS, NOTE_TEXT_COLOR_OVERRIDE, NoteColor } from './noteColors'
import { stopPropagation, preventDefault } from 'svelte/legacy'
interface Props {
note: string
color?: string
collapsed?: boolean
editMode: boolean
onHeightChange: (height: number) => void
onNoteUpdate: (text: string) => void
}
let { note, color, collapsed = false, editMode, onHeightChange, onNoteUpdate }: Props = $props()
let editing = $state(false)
let editHeight = $state(0)
let textContent = $state('')
let textareaElement: HTMLTextAreaElement | undefined = $state(undefined)
let containerElement: HTMLDivElement | undefined = $state(undefined)
function autoResize(el: HTMLTextAreaElement) {
el.style.height = 'auto'
el.style.height = el.scrollHeight + 'px'
}
let noteColorConfig = $derived(
color
? (NOTE_COLORS[color as NoteColor] ?? NOTE_COLORS[NoteColor.BLUE])
: NOTE_COLORS[NoteColor.BLUE]
)
// Measure height and report to parent (skip while editing to avoid full graph rebuilds)
$effect(() => {
if (containerElement && !editing) {
const height = containerElement.clientHeight
onHeightChange(height)
}
})
// Also observe resize for dynamic content
$effect(() => {
if (!containerElement) return
const observer = new ResizeObserver((entries) => {
if (editing) return
for (const entry of entries) {
onHeightChange(entry.contentRect.height)
}
})
observer.observe(containerElement)
return () => {
observer.disconnect()
onHeightChange(0)
}
})
function handleDoubleClick() {
if (!editMode) return
editHeight = containerElement?.clientHeight ?? 0
editing = true
textContent = note
requestAnimationFrame(() => {
if (textareaElement) {
autoResize(textareaElement)
textareaElement.focus()
}
})
}
function handleSave() {
editing = false
if (textContent !== note) {
onNoteUpdate(textContent)
}
}
function handleCancel() {
editing = false
textContent = note
}
function handleKeydown(event: KeyboardEvent) {
event.stopPropagation()
if (event.key === 'Escape') {
handleSave()
}
}
</script>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
bind:this={containerElement}
class="nodrag nopan {collapsed ? 'mx-px' : 'w-full rounded-b-md'}"
>
<div class={collapsed ? 'relative' : 'w-full rounded-b-md relative'}>
{#if editing}
<div class="absolute top-0 right-1 flex gap-0.5 z-10">
<button
class="p-0.5 {noteColorConfig.text} opacity-60 hover:opacity-100 cursor-pointer"
onpointerdown={stopPropagation(preventDefault(handleSave))}
title="Save (Esc)"
>
<Check size={11} />
</button>
<button
class="p-0.5 {noteColorConfig.text} opacity-60 hover:opacity-100 cursor-pointer"
onpointerdown={stopPropagation(preventDefault(handleCancel))}
title="Cancel"
>
<X size={11} />
</button>
</div>
<textarea
bind:this={textareaElement}
bind:value={textContent}
class="w-full shadow-none resize-none !text-2xs overflow-y-auto border-none bg-transparent p-1 nodrag nopan nowheel focus:outline-none select-text {noteColorConfig.text}"
style:max-height="max({editHeight}px, 4lh)"
oninput={() => textareaElement && autoResize(textareaElement)}
placeholder="Write a note (markdown supported)"
onblur={handleSave}
onkeydown={handleKeydown}
onpointerdown={stopPropagation(() => {})}
spellcheck="false"
></textarea>
{:else if note}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="w-full text-2xs break-words overflow-hidden p-2 select-text {noteColorConfig.text} {NOTE_TEXT_COLOR_OVERRIDE} {editMode
? 'cursor-pointer'
: ''}"
ondblclick={editMode ? stopPropagation(preventDefault(handleDoubleClick)) : undefined}
onpointerdown={editMode ? stopPropagation(() => {}) : undefined}
>
<GfmMarkdown md={note} noPadding />
</div>
{:else}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="text-2xs italic opacity-60 p-2 {noteColorConfig.text} {editMode
? 'cursor-pointer'
: ''}"
ondblclick={editMode ? stopPropagation(preventDefault(handleDoubleClick)) : undefined}
onpointerdown={editMode ? stopPropagation(() => {}) : undefined}
>
Double click to edit the note
</div>
{/if}
</div>
</div>