johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
<script lang="ts">
import { page } from '$app/state';
import { goto } from '$app/navigation';
import { highlight } from '#lib/highlight.ts';
import { onMount, type Snippet } from 'svelte';
/** A text file, with a gutter of line numbers to link to, and before it `gutter`, a column as tall as the text, when given. A line's number marks it, shift and a second marks the lines between. */
let {
text,
lines,
ranges,
gutter
}: { text: string; lines: number; ranges?: string | null; gutter?: Snippet } = $props();
/** The lines `#L5` or `#L5-L9` marks, first to last, or null. */
const marked = $derived.by(() => {
const m = /^#L(\d+)(?:-L(\d+))?$/.exec(page.url.hash);
if (!m) return null;
const a = +m[1];
const b = m[2] ? +m[2] : a;
const first = Math.max(1, Math.min(a, b));
const last = Math.min(lines, Math.max(a, b));
return first <= last ? { first, last } : null;
});
let ln: HTMLElement | undefined = $state();
onMount(() => {
if (marked) ln?.querySelector(`#L${marked.first}`)?.scrollIntoView({ block: 'center' });
});
function mark(e: MouseEvent) {
const a = (e.target as Element).closest('a');
const n = a ? +a.id.slice(1) : 0;
if (!n || e.metaKey || e.ctrlKey || e.altKey) return;
e.preventDefault();
const hash =
e.shiftKey && marked ? (n === marked.first ? `#L${n}` : `#L${marked.first}-L${n}`) : `#L${n}`;
goto(hash, { replace: true, reset: false });
}
</script>
<div class="code" class:gutted={gutter}>
{#if marked}<div
class="marked"
style:--first={marked.first}
style:--count={marked.last - marked.first + 1}
></div>{/if}{#if gutter}{@render gutter()}{/if}<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_noninteractive_element_interactions -->
<pre
class="ln"
aria-hidden="true"
bind:this={ln}
onclick={mark}>{#each { length: lines }, i (i)}<a id="L{i + 1}" href="#L{i + 1}" tabindex="-1"
>{i + 1}</a
>{'\n'}{/each}</pre>
<pre class="src" use:highlight={{ ranges, text }}><code>{text}</code></pre>
</div>