Skip to the content

Capsid menu

components/avatar/avatar.ts

37 lines and 8 functions in DrDustinEdwards/capsomer, at commit d3966cc on 2026-10-24. This file on GitHub.

Functions

The functions of avatar.ts
FunctionLinesComplexityCoveredCRAPMutation
watch7 to 205No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
<anonymous:9>9 to 91No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
fail10 to 101No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
ok11 to 111No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
<anonymous:16>16 to 191No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
enhance22 to 323No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
<anonymous:29>29 to 312No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
initials35 to 417No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet

Imports

It imports no other module of this repo. Imported by components/avatar/avatar.react.tsx, components/history-list/history-list-rows.ts, components/history-list/history-list.react.tsx.

Source

// The avatar's one behaviour: an image that fails to load gives way to the initials already
// in the HTML. Without script the picture simply covers them, so a page delivered as HTML
// still names the person in text.

const READY = "data-cap-ready";

function watch(root: HTMLElement): () => void {
  const img = root.querySelector<HTMLImageElement>(".cap-avatar-image");
  if (!img) return () => {};
  const fail = () => root.setAttribute("data-state", "error");
  const ok = () => root.removeAttribute("data-state");
  img.addEventListener("error", fail);
  img.addEventListener("load", ok);
  // An image that failed before this ran has already finished with no width.
  if (img.complete && img.naturalWidth === 0 && img.getAttribute("src")) fail();
  return () => {
    img.removeEventListener("error", fail);
    img.removeEventListener("load", ok);
  };
}

export function enhance(root: ParentNode = document): () => void {
  const undo: Array<() => void> = [];
  for (const el of root.querySelectorAll<HTMLElement>('[data-cap="avatar"]')) {
    if (el.hasAttribute(READY)) continue;
    el.setAttribute(READY, "");
    undo.push(watch(el));
  }
  return () => {
    for (const fn of undo) fn();
  };
}

// The initials for a name: the first letters of the first and last words.
export function initials(name: string): string {
  const words = name.trim().split(/\s+/).filter(Boolean);
  if (words.length === 0) return "";
  const first = words[0] ?? "";
  const last = words.length > 1 ? (words[words.length - 1] ?? "") : "";
  return `${[...first][0] ?? ""}${[...last][0] ?? ""}`;
}