CodeLines

tcmts.share.CodeLines · #xqmc6wg6 · term · runs in the browser

uses window

Code as numbered lines. A line's number links to it (#L12); shift-clicking another makes a range (#L12-L20). The lines the address names are marked and scrolled to.

import { useEffect, useRef, useState } from "react";
import type { CSSProperties, MouseEvent } from "react";

export function CodeLines({ lines }: { lines: string[] }) {
  const [sel, setSel] = useState<[number, number] | undefined>(undefined);
  const from = useRef<number | undefined>(undefined);
  useEffect(() => {
    const read = () => {
      const m = /^#L(\d+)(?:-L?(\d+))?$/.exec(window.location.hash);
      if (!m) return setSel(undefined);
      const a = Number(m[1]);
      const b = Number(m[2] ?? m[1]);
      const r: [number, number] = [Math.min(a, b), Math.max(a, b)];
      setSel(r);
      from.current = r[0];
      document
        .getElementById(`L${r[0]}`)
        ?.scrollIntoView({ block: r[0] === r[1] ? "center" : "start" });
    };
    read();
    window.addEventListener("hashchange", read);
    return () => window.removeEventListener("hashchange", read);
  }, []);
  const pick = (e: MouseEvent, n: number) => {
    e.preventDefault();
    const r: [number, number] =
      e.shiftKey && from.current !== undefined
        ? [Math.min(from.current, n), Math.max(from.current, n)]
        : [n, n];
    if (!e.shiftKey) from.current = n;
    setSel(r);
    const { pathname, search } = window.location;
    window.history.replaceState(
      window.history.state,
      "",
      `${pathname}${search}#L${r[0]}${r[1] > r[0] ? `-L${r[1]}` : ""}`,
    );
  };
  return (
    <div className="lines" style={{ "--nw": `${String(lines.length).length}ch` } as CSSProperties}>
      {lines.map((l, i) => {
        const n = i + 1;
        return (
          <div key={n} id={`L${n}`} className={sel && n >= sel[0] && n <= sel[1] ? "ln on" : "ln"}>
            <a
              className="num"
              href={`#L${n}`}
              data-n={n}
              aria-label={`Line ${n}`}
              onClick={(e) => pick(e, n)}
            />
            <span className="lc" dangerouslySetInnerHTML={{ __html: l }} />
          </div>
        );
      })}
    </div>
  );
}

used by