Skip to the content

Capsid menu

components/media/media-actions.ts

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

Functions

The functions of media-actions.ts
FunctionLinesComplexityCoveredCRAPMutation
applyBulkTag8 to 447No 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:37>37 to 371No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
copyText55 to 763No 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/media/media.react.tsx, components/media/media.ts.

Source

// The media library's bulk tags and the clipboard: plain functions the behaviour module and the React
// wrapper share.

// ---------------------------------------------------------------------------------------
// Bulk tags: add or remove one tag across a
// selection. An item already in the wanted state is skipped, so it is not rewritten for nothing,
// and one item's failure is recorded without stopping the rest.
export async function applyBulkTag<T>({
  ids,
  wanted,
  adding,
  missing,
  read,
  write,
}: {
  ids: readonly string[];
  wanted: string;
  adding: boolean;
  missing: string;
  read: (id: string) => Promise<{ item: T; tags: string[] } | null>;
  write: (id: string, item: T, tags: string[]) => Promise<unknown>;
}): Promise<{ done: string[]; skipped: string[]; failed: string[] }> {
  const failed: string[] = [];
  const done: string[] = [];
  const skipped: string[] = [];
  for (const id of ids) {
    try {
      const found = await read(id);
      if (!found) {
        failed.push(`${id}: ${missing}`);
        continue;
      }
      if (adding === found.tags.includes(wanted)) {
        skipped.push(id);
        continue;
      }
      await write(id, found.item, adding ? [...found.tags, wanted] : found.tags.filter((t) => t !== wanted));
      done.push(id);
    } catch (error) {
      failed.push(`${id}: ${error instanceof Error ? error.message : "failed"}`);
    }
  }
  return { done, skipped, failed };
}

// ---------------------------------------------------------------------------------------
// The clipboard. Copy through the Clipboard API; where the page may not, through a selected
// field and the document's own copy command; where neither works, say so and leave the text
// selected.

// How long the copy status stays: a success is short news, a failure leaves time to copy by hand.
export const COPIED_MS = 2500;
export const COPY_FAILED_MS = 8000;

export async function copyText(text: string): Promise<boolean> {
  try {
    await navigator.clipboard.writeText(text);
    return true;
  } catch {
    // fall through
  }
  try {
    const area = document.createElement("textarea");
    area.value = text;
    area.setAttribute("readonly", "");
    area.style.setProperty("position", "fixed");
    area.style.setProperty("opacity", "0");
    document.body.append(area);
    area.select();
    const ok = document.execCommand("copy");
    area.remove();
    return ok;
  } catch {
    return false;
  }
}