diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index cc69b2e..f19b0d1 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -18,6 +18,10 @@ SPDX-License-Identifier: MIT let ghostEl = $state(); let viewportEl = $state(); + // The section marker is drawn outside the scrolling overlay, so it has to be + // offset by hand to stay level with the lines it spans. + let scrollTop = $state(0); + let sdpText = $state(exampleSDP); let caret = $state(0); let hover = $state(null); @@ -72,6 +76,27 @@ SPDX-License-Identifier: MIT ) ); + // The run of lines making up the media description the active line belongs to, + // marked in the gutter so the block a line is read against is visible without + // scanning upwards for the "m=" that opened it. Session-level lines are the + // preamble rather than a section, so they get no marker. + let sectionSpan = $derived.by(() => { + const section = active ? lines[active.lineIndex]?.section : null; + if (section == null) return null; + + let first = -1; + let last = -1; + lines.forEach((line, i) => { + if (line.section !== section) return; + if (first === -1) first = i; + // Blank lines trailing the section say nothing about it, so the marker + // stops at the last line that does. + if (line.content.trim()) last = i; + }); + + return first === -1 ? null : { first, count: Math.max(last, first) - first + 1 }; + }); + // Where this line sits, which is what decides whether "a=" lines are in scope. let placement = $derived( activeLine == null || activeLine.section === null @@ -206,6 +231,7 @@ SPDX-License-Identifier: MIT if (!ghostEl || !editorEl) return; ghostEl.scrollTop = editorEl.scrollTop; ghostEl.scrollLeft = editorEl.scrollLeft; + scrollTop = editorEl.scrollTop; // Scrolling moves the text under a stationary pointer. syncHover(); placeTooltip(); @@ -343,6 +369,22 @@ SPDX-License-Identifier: MIT class="relative overflow-hidden rounded-[5px] border border-hairline bg-surface transition-colors focus-within:border-subtle-outline" bind:this={viewportEl} > + + +