@ascribed/review 0.2.0 → 0.2.1-next.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -32,11 +32,11 @@ Each changed block gets a label (Added, Changed, Removed, Moved) and a bar in th
32
32
 
33
33
  The blocks are found by their anchors: the element with exactly a change's anchor, or, when there's none (a paragraph in a tight list item has no element of its own), the smallest anchored element with the same file and includes whose lines contain it.
34
34
 
35
- The colors are the `--ascribe-review-*` custom properties at the top of `marks.css`, with light and dark values.
35
+ The colors are the `--ascribe-review-*` custom properties at the top of `marks.css`, with light and dark values. Their names don't change; their defaults are Ascribe's design tokens, shared with `@ascribed/elements`, and a release may change them, noting it in the changelog.
36
36
 
37
37
  ## The static report
38
38
 
39
- `src/report/` is the script of `ascribe diff --format html`'s report, which draws the list of changed pages and each page with its marks. It isn't an entry point: `pnpm --filter @ascribed/review embed` bundles it with the marks and `@ascribed/elements` into `crates/tessera-diff/src/html/`, where the binary embeds it, along with one stylesheet made of the element library's, `marks.css`, and `src/report/report.css`. It also writes the script's SHA-256 beside it, which the report's content security policy names as the one script allowed to run. Run it after changing any of them; `test/embedded.test.ts` fails until you do.
39
+ `src/report/` is the script of `ascribe diff --format html`'s report, which draws the list of changed pages and each page with its marks. It isn't an entry point: `pnpm --filter @ascribed/review embed` bundles it with the marks and `@ascribed/elements` into `crates/ascribe-diff/src/html/`, where the binary embeds it, along with one stylesheet made of the element library's, `marks.css`, and `src/report/report.css`. It also writes the script's SHA-256 beside it, which the report's content security policy names as the one script allowed to run. Run it after changing any of them; `test/embedded.test.ts` fails until you do.
40
40
 
41
41
  ## `@ascribed/review/overlay`
42
42
 
@@ -1,4 +1,5 @@
1
1
  import type { PageRef } from "../place/place.js";
2
+ import type { FormattedPiece } from "../shapes.js";
2
3
  import type { PullRequestInfo, ReviewSession } from "./session.js";
3
4
  /** The overlay's requests, one per `OverlayHost` method that reads or writes threads. */
4
5
  export type OverlayMethod = "load" | "commentTarget" | "comment" | "reply" | "allThreads" | "resolve" | "submit" | "discard";
@@ -9,6 +10,8 @@ export interface ChangedPageRef {
9
10
  status: "added" | "removed" | "changed";
10
11
  because: readonly string[];
11
12
  title?: string | null;
13
+ /** The title formatted, when its field sets `inline = "code"`. */
14
+ formatted_title?: readonly FormattedPiece[] | null;
12
15
  }
13
16
  /** What answering a request needs. */
14
17
  export interface RequestContext {
@@ -48,6 +51,7 @@ export declare function answerRequest(context: RequestContext, method: OverlayMe
48
51
  export declare function pagesShowing(file: string, pages: readonly ChangedPageRef[]): {
49
52
  path: string;
50
53
  title: string | null;
54
+ formatted_title: FormattedPiece[] | null;
51
55
  }[];
52
56
  /**
53
57
  * The git revision to compare with for a pull request's base: the remote
@@ -211,7 +211,11 @@ export function pagesShowing(file, pages) {
211
211
  .filter((page) => page.status !== "removed")
212
212
  .filter((page) => page.path === file || page.because.includes(file))
213
213
  .sort((a, b) => Number(b.path === file) - Number(a.path === file))
214
- .map((page) => ({ path: page.path, title: page.title ?? null }));
214
+ .map((page) => ({
215
+ path: page.path,
216
+ title: page.title ?? null,
217
+ formatted_title: page.formatted_title ? [...page.formatted_title] : null,
218
+ }));
215
219
  }
216
220
  /**
217
221
  * The git revision to compare with for a pull request's base: the remote
@@ -1 +1 @@
1
- {"version":3,"file":"requests.js","sourceRoot":"","sources":["../../src/github/requests.ts"],"names":[],"mappings":"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","sourcesContent":["// Answering the overlay's requests (`OverlayHost`) from a review session, for\n// a host that keeps GitHub to itself and passes the overlay's requests on as\n// messages: the page preview's extension, or the site preview's dev server.\n// The parameters arrive as untyped messages, so each is checked here.\nimport { anchorKey, parseSource, type Anchor } from \"../place/anchor.js\";\nimport { lineHunks, shiftLine, type Hunk, type Sides } from \"../place/lines.js\";\nimport type { PageRef, PlacedThreads } from \"../place/place.js\";\nimport { ReviewError } from \"../shared/errors.js\";\nimport { gitMaybe } from \"../shared/git.js\";\nimport { parseRemote } from \"./repository.js\";\nimport type { PullRequestInfo, ReviewEvent, ReviewSession } from \"./session.js\";\n\n/** The overlay's requests, one per `OverlayHost` method that reads or writes threads. */\nexport type OverlayMethod =\n \"load\" | \"commentTarget\" | \"comment\" | \"reply\" | \"allThreads\" | \"resolve\" | \"submit\" | \"discard\";\n\nexport const OVERLAY_METHODS: readonly OverlayMethod[] = [\n \"load\",\n \"commentTarget\",\n \"comment\",\n \"reply\",\n \"allThreads\",\n \"resolve\",\n \"submit\",\n \"discard\",\n];\n\n/** A changed page as `ascribe diff` lists it, with its title when the host knows it. */\nexport interface ChangedPageRef {\n path: string;\n status: \"added\" | \"removed\" | \"changed\";\n because: readonly string[];\n title?: string | null;\n}\n\n/** What answering a request needs. */\nexport interface RequestContext {\n session: ReviewSession;\n /** The page the overlay is on: its build and content path. */\n page: { build: string; path: string } | undefined;\n /** The page as the overlay last read it, with its anchors: comments are made on it. */\n lastPage: PageRef | undefined;\n /** The changed pages, for which pages show a thread. */\n changedPages(): Promise<readonly ChangedPageRef[]>;\n /**\n * For a host whose pages are rendered from an editor's unsaved text: a\n * file's text on disk (`saved`) and in the editor (`current`), by content\n * path, or `undefined` when they're the same. The session counts lines on\n * disk, so the page's blocks are moved to their lines there, and back.\n */\n unsaved?(path: string): Promise<{ saved: string; current: string } | undefined>;\n}\n\n/** An answer: the result, the page read (for `load`), and whether the request changed the threads. */\nexport interface Answer {\n result: unknown;\n page?: PageRef;\n changed: boolean;\n}\n\n/** Answers one of the overlay's requests. Rejects with a `ReviewError`. */\nexport async function answerRequest(\n context: RequestContext,\n method: OverlayMethod,\n params: Record<string, unknown>,\n): Promise<Answer> {\n const { session } = context;\n switch (method) {\n case \"load\": {\n const page = context.page;\n if (!page) throw new ReviewError(\"not-found\", \"There's no page to review here.\");\n const shown = anchors(params[\"anchors\"]);\n const edits = await unsavedEdits(context, shown);\n // The page's blocks at their lines on disk, and the way back.\n const back = new Map<string, Anchor>();\n for (const block of shown) {\n const saved = toSaved(edits, block, false);\n if (saved !== undefined && !back.has(anchorKey(saved))) back.set(anchorKey(saved), block);\n }\n const ref: PageRef = {\n build: page.build,\n path: page.path,\n anchors: edits.size === 0 ? shown : [...back.keys()].map((key) => fromKey(key)),\n removed: anchors(params[\"removed\"]),\n };\n const [placed, pending, viewer] = await Promise.all([\n session.threads(ref),\n session.pending(),\n session.viewer(),\n ]);\n const threads: PlacedThreads =\n edits.size === 0\n ? placed\n : {\n ...placed,\n blocks: placed.blocks.map((block) => ({\n ...block,\n anchor: back.get(anchorKey(block.anchor)) ?? block.anchor,\n })),\n };\n const pr = session.pullRequest;\n return {\n result: { pullRequest: { number: pr.number, url: pr.url }, threads, pending, viewer },\n page: ref,\n changed: false,\n };\n }\n case \"commentTarget\": {\n const shown = anchor(params[\"anchor\"]);\n const saved = toSaved(await unsavedEdits(context, [shown]), shown, true);\n if (saved === undefined)\n return { result: { kind: \"push-first\", message: UNSAVED }, changed: false };\n return { result: await session.commentTarget(saved), changed: false };\n }\n case \"allThreads\": {\n const [threads, pages] = await Promise.all([\n session.allThreads(),\n context.changedPages().catch(() => []),\n ]);\n return {\n result: threads.map((thread) => ({ thread, pages: pagesShowing(thread.path, pages) })),\n changed: false,\n };\n }\n case \"comment\": {\n const shown = anchor(params[\"anchor\"]);\n const target = toSaved(await unsavedEdits(context, [shown]), shown, true);\n if (target === undefined) throw new ReviewError(\"push-first\", UNSAVED);\n const page = context.lastPage ?? {\n build: context.page?.build ?? \"\",\n path: context.page?.path ?? \"\",\n anchors: [target],\n };\n const quote = typeof params[\"quote\"] === \"string\" ? params[\"quote\"] : undefined;\n return {\n result: await session.comment(target, text(params[\"body\"]), page, quote),\n changed: true,\n };\n }\n case \"reply\":\n return {\n result: await session.reply(\n text(params[\"threadId\"]),\n text(params[\"body\"]),\n params[\"when\"] === \"now\" ? \"now\" : \"withReview\",\n ),\n changed: true,\n };\n case \"resolve\":\n await session.resolve(text(params[\"threadId\"]), params[\"resolved\"] === true);\n return { result: null, changed: true };\n case \"submit\": {\n const body = typeof params[\"body\"] === \"string\" ? params[\"body\"] : undefined;\n await session.submit(event(params[\"event\"]), body);\n return { result: null, changed: true };\n }\n case \"discard\":\n await session.discard();\n return { result: null, changed: true };\n }\n}\n\n/** A file's unsaved edits: the line changes from the editor's text to the text on disk. */\ninterface Edit {\n hunks: Hunk[];\n sides: Sides;\n}\n\nconst UNSAVED = \"This block has changes that aren't saved. Save the file, then comment.\";\n\n/** The line changes from each shown file's text in the editor to its text on disk, where they differ. */\nasync function unsavedEdits(\n context: RequestContext,\n shown: readonly Anchor[],\n): Promise<Map<string, Edit>> {\n const edits = new Map<string, Edit>();\n if (!context.unsaved) return edits;\n const files = new Set<string>();\n for (const block of shown) {\n const range = parseSource(block.source);\n if (range) files.add(range.path);\n for (const include of block.via) {\n const at = parseInclude(include);\n if (at) files.add(at.path);\n }\n }\n for (const file of files) {\n const text = await context.unsaved(file);\n if (text === undefined) continue;\n const hunks = lineHunks(text.current, text.saved);\n if (hunks.length > 0) {\n edits.set(file, {\n hunks,\n sides: { old: text.current.split(/\\r?\\n/), new: text.saved.split(/\\r?\\n/) },\n });\n }\n }\n return edits;\n}\n\n/**\n * A block's anchor with its lines on disk. `strict` (for commenting) wants\n * every line of the block saved as it is; otherwise an edited block takes\n * the lines its text has on disk. `undefined` when nothing of it is saved.\n */\nfunction toSaved(\n edits: ReadonlyMap<string, Edit>,\n block: Anchor,\n strict: boolean,\n): Anchor | undefined {\n if (edits.size === 0) return block;\n const range = parseSource(block.source);\n if (range === undefined) return block;\n const edit = edits.get(range.path);\n let source = block.source;\n if (edit !== undefined) {\n const lines: number[] = [];\n for (let line = range.first; line <= range.last; line++) {\n const moved = shiftLine(edit.hunks, line, edit.sides);\n if (moved === undefined || moved.replaced) {\n if (strict) return undefined;\n if (moved === undefined) continue;\n }\n lines.push(moved.line);\n }\n if (lines.length === 0) return undefined;\n const colon = block.source.lastIndexOf(\":\");\n source = `${block.source.slice(0, colon)}:${Math.min(...lines)}-${Math.max(...lines)}`;\n }\n const via: string[] = [];\n for (const include of block.via) {\n const at = parseInclude(include);\n const includeEdit = at && edits.get(at.path);\n if (!at || !includeEdit) {\n via.push(include);\n continue;\n }\n const moved = shiftLine(includeEdit.hunks, at.line, includeEdit.sides);\n if (moved === undefined || (strict && moved.replaced)) return undefined;\n via.push(`${include.slice(0, include.lastIndexOf(\":\"))}:${moved.line}`);\n }\n return { source, via };\n}\n\n/** An include, `<path>:<line>`, parsed, with its path decoded. */\nfunction parseInclude(include: string): { path: string; line: number } | undefined {\n const colon = include.lastIndexOf(\":\");\n const line = Number(include.slice(colon + 1));\n if (colon <= 0 || !Number.isInteger(line) || line < 1) return undefined;\n const range = parseSource(`${include.slice(0, colon)}:1-1`);\n return range && { path: range.path, line };\n}\n\n/** The anchor `anchorKey` wrote. */\nfunction fromKey(key: string): Anchor {\n const [source = \"\", ...via] = key.split(\" \");\n return { source, via };\n}\n\n/**\n * The changed pages that show a file (a content path): the file's own page,\n * and the pages that changed through it (an include). `[]` when none does.\n */\nexport function pagesShowing(\n file: string,\n pages: readonly ChangedPageRef[],\n): { path: string; title: string | null }[] {\n return pages\n .filter((page) => page.status !== \"removed\")\n .filter((page) => page.path === file || page.because.includes(file))\n .sort((a, b) => Number(b.path === file) - Number(a.path === file))\n .map((page) => ({ path: page.path, title: page.title ?? null }));\n}\n\n/**\n * The git revision to compare with for a pull request's base: the remote\n * branch, from the remote whose URL is the pull request's repository, when\n * there is one; else the branch's name.\n */\nexport function pullRequestBase(\n remotes: readonly { name: string; repository: string | undefined }[],\n repository: string,\n baseRefName: string,\n): string {\n const remote =\n remotes.find((r) => r.repository === repository && r.name === \"upstream\") ??\n remotes.find((r) => r.repository === repository && r.name === \"origin\") ??\n remotes.find((r) => r.repository === repository);\n return remote ? `${remote.name}/${baseRefName}` : baseRefName;\n}\n\n/**\n * The git revision for the pull request's base in the checkout at `root`:\n * `origin/main` when that remote branch exists, else the branch's name.\n */\nexport async function baseRevision(root: string, pr: PullRequestInfo): Promise<string> {\n const out = (await gitMaybe(root, [\"remote\", \"-v\"]).catch(() => undefined)) ?? \"\";\n const remotes = out\n .split(\"\\n\")\n .map((line) => /^(\\S+)\\s+(\\S+)\\s+\\(fetch\\)$/.exec(line))\n .filter((match) => match !== null)\n .map((match) => {\n const repository = parseRemote(match[2] ?? \"\");\n return { name: match[1] ?? \"\", repository: repository && repoKey(repository) };\n });\n const revision = pullRequestBase(remotes, repoKey(pr.repository), pr.baseRefName);\n if (revision !== pr.baseRefName) {\n const exists = await gitMaybe(root, [\"rev-parse\", \"--verify\", \"--quiet\", revision]).catch(\n () => undefined,\n );\n if (exists !== undefined) return revision;\n }\n return pr.baseRefName;\n}\n\nfunction repoKey(repository: { host: string; owner: string; name: string }): string {\n return `${repository.host}/${repository.owner}/${repository.name}`.toLowerCase();\n}\n\nconst EVENTS: readonly ReviewEvent[] = [\"COMMENT\", \"APPROVE\", \"REQUEST_CHANGES\"];\n\nfunction event(value: unknown): ReviewEvent {\n if (EVENTS.includes(value as ReviewEvent)) return value as ReviewEvent;\n throw new ReviewError(\"refused\", `A review can't be submitted as ${String(value)}.`);\n}\n\nfunction text(value: unknown): string {\n if (typeof value !== \"string\") throw new ReviewError(\"refused\", \"The request is missing text.\");\n return value;\n}\n\nfunction anchor(value: unknown): Anchor {\n const record = value as Partial<Anchor> | null;\n if (typeof record?.source !== \"string\") {\n throw new ReviewError(\"refused\", \"The request is missing a block.\");\n }\n const via = Array.isArray(record.via) ? record.via.filter((v) => typeof v === \"string\") : [];\n return { source: record.source, via };\n}\n\nfunction anchors(value: unknown): Anchor[] {\n return Array.isArray(value) ? value.map(anchor) : [];\n}\n"]}
1
+ {"version":3,"file":"requests.js","sourceRoot":"","sources":["../../src/github/requests.ts"],"names":[],"mappings":"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","sourcesContent":["// Answering the overlay's requests (`OverlayHost`) from a review session, for\n// a host that keeps GitHub to itself and passes the overlay's requests on as\n// messages: the page preview's extension, or the site preview's dev server.\n// The parameters arrive as untyped messages, so each is checked here.\nimport { anchorKey, parseSource, type Anchor } from \"../place/anchor.js\";\nimport { lineHunks, shiftLine, type Hunk, type Sides } from \"../place/lines.js\";\nimport type { PageRef, PlacedThreads } from \"../place/place.js\";\nimport type { FormattedPiece } from \"../shapes.js\";\nimport { ReviewError } from \"../shared/errors.js\";\nimport { gitMaybe } from \"../shared/git.js\";\nimport { parseRemote } from \"./repository.js\";\nimport type { PullRequestInfo, ReviewEvent, ReviewSession } from \"./session.js\";\n\n/** The overlay's requests, one per `OverlayHost` method that reads or writes threads. */\nexport type OverlayMethod =\n \"load\" | \"commentTarget\" | \"comment\" | \"reply\" | \"allThreads\" | \"resolve\" | \"submit\" | \"discard\";\n\nexport const OVERLAY_METHODS: readonly OverlayMethod[] = [\n \"load\",\n \"commentTarget\",\n \"comment\",\n \"reply\",\n \"allThreads\",\n \"resolve\",\n \"submit\",\n \"discard\",\n];\n\n/** A changed page as `ascribe diff` lists it, with its title when the host knows it. */\nexport interface ChangedPageRef {\n path: string;\n status: \"added\" | \"removed\" | \"changed\";\n because: readonly string[];\n title?: string | null;\n /** The title formatted, when its field sets `inline = \"code\"`. */\n formatted_title?: readonly FormattedPiece[] | null;\n}\n\n/** What answering a request needs. */\nexport interface RequestContext {\n session: ReviewSession;\n /** The page the overlay is on: its build and content path. */\n page: { build: string; path: string } | undefined;\n /** The page as the overlay last read it, with its anchors: comments are made on it. */\n lastPage: PageRef | undefined;\n /** The changed pages, for which pages show a thread. */\n changedPages(): Promise<readonly ChangedPageRef[]>;\n /**\n * For a host whose pages are rendered from an editor's unsaved text: a\n * file's text on disk (`saved`) and in the editor (`current`), by content\n * path, or `undefined` when they're the same. The session counts lines on\n * disk, so the page's blocks are moved to their lines there, and back.\n */\n unsaved?(path: string): Promise<{ saved: string; current: string } | undefined>;\n}\n\n/** An answer: the result, the page read (for `load`), and whether the request changed the threads. */\nexport interface Answer {\n result: unknown;\n page?: PageRef;\n changed: boolean;\n}\n\n/** Answers one of the overlay's requests. Rejects with a `ReviewError`. */\nexport async function answerRequest(\n context: RequestContext,\n method: OverlayMethod,\n params: Record<string, unknown>,\n): Promise<Answer> {\n const { session } = context;\n switch (method) {\n case \"load\": {\n const page = context.page;\n if (!page) throw new ReviewError(\"not-found\", \"There's no page to review here.\");\n const shown = anchors(params[\"anchors\"]);\n const edits = await unsavedEdits(context, shown);\n // The page's blocks at their lines on disk, and the way back.\n const back = new Map<string, Anchor>();\n for (const block of shown) {\n const saved = toSaved(edits, block, false);\n if (saved !== undefined && !back.has(anchorKey(saved))) back.set(anchorKey(saved), block);\n }\n const ref: PageRef = {\n build: page.build,\n path: page.path,\n anchors: edits.size === 0 ? shown : [...back.keys()].map((key) => fromKey(key)),\n removed: anchors(params[\"removed\"]),\n };\n const [placed, pending, viewer] = await Promise.all([\n session.threads(ref),\n session.pending(),\n session.viewer(),\n ]);\n const threads: PlacedThreads =\n edits.size === 0\n ? placed\n : {\n ...placed,\n blocks: placed.blocks.map((block) => ({\n ...block,\n anchor: back.get(anchorKey(block.anchor)) ?? block.anchor,\n })),\n };\n const pr = session.pullRequest;\n return {\n result: { pullRequest: { number: pr.number, url: pr.url }, threads, pending, viewer },\n page: ref,\n changed: false,\n };\n }\n case \"commentTarget\": {\n const shown = anchor(params[\"anchor\"]);\n const saved = toSaved(await unsavedEdits(context, [shown]), shown, true);\n if (saved === undefined)\n return { result: { kind: \"push-first\", message: UNSAVED }, changed: false };\n return { result: await session.commentTarget(saved), changed: false };\n }\n case \"allThreads\": {\n const [threads, pages] = await Promise.all([\n session.allThreads(),\n context.changedPages().catch(() => []),\n ]);\n return {\n result: threads.map((thread) => ({ thread, pages: pagesShowing(thread.path, pages) })),\n changed: false,\n };\n }\n case \"comment\": {\n const shown = anchor(params[\"anchor\"]);\n const target = toSaved(await unsavedEdits(context, [shown]), shown, true);\n if (target === undefined) throw new ReviewError(\"push-first\", UNSAVED);\n const page = context.lastPage ?? {\n build: context.page?.build ?? \"\",\n path: context.page?.path ?? \"\",\n anchors: [target],\n };\n const quote = typeof params[\"quote\"] === \"string\" ? params[\"quote\"] : undefined;\n return {\n result: await session.comment(target, text(params[\"body\"]), page, quote),\n changed: true,\n };\n }\n case \"reply\":\n return {\n result: await session.reply(\n text(params[\"threadId\"]),\n text(params[\"body\"]),\n params[\"when\"] === \"now\" ? \"now\" : \"withReview\",\n ),\n changed: true,\n };\n case \"resolve\":\n await session.resolve(text(params[\"threadId\"]), params[\"resolved\"] === true);\n return { result: null, changed: true };\n case \"submit\": {\n const body = typeof params[\"body\"] === \"string\" ? params[\"body\"] : undefined;\n await session.submit(event(params[\"event\"]), body);\n return { result: null, changed: true };\n }\n case \"discard\":\n await session.discard();\n return { result: null, changed: true };\n }\n}\n\n/** A file's unsaved edits: the line changes from the editor's text to the text on disk. */\ninterface Edit {\n hunks: Hunk[];\n sides: Sides;\n}\n\nconst UNSAVED = \"This block has changes that aren't saved. Save the file, then comment.\";\n\n/** The line changes from each shown file's text in the editor to its text on disk, where they differ. */\nasync function unsavedEdits(\n context: RequestContext,\n shown: readonly Anchor[],\n): Promise<Map<string, Edit>> {\n const edits = new Map<string, Edit>();\n if (!context.unsaved) return edits;\n const files = new Set<string>();\n for (const block of shown) {\n const range = parseSource(block.source);\n if (range) files.add(range.path);\n for (const include of block.via) {\n const at = parseInclude(include);\n if (at) files.add(at.path);\n }\n }\n for (const file of files) {\n const text = await context.unsaved(file);\n if (text === undefined) continue;\n const hunks = lineHunks(text.current, text.saved);\n if (hunks.length > 0) {\n edits.set(file, {\n hunks,\n sides: { old: text.current.split(/\\r?\\n/), new: text.saved.split(/\\r?\\n/) },\n });\n }\n }\n return edits;\n}\n\n/**\n * A block's anchor with its lines on disk. `strict` (for commenting) wants\n * every line of the block saved as it is; otherwise an edited block takes\n * the lines its text has on disk. `undefined` when nothing of it is saved.\n */\nfunction toSaved(\n edits: ReadonlyMap<string, Edit>,\n block: Anchor,\n strict: boolean,\n): Anchor | undefined {\n if (edits.size === 0) return block;\n const range = parseSource(block.source);\n if (range === undefined) return block;\n const edit = edits.get(range.path);\n let source = block.source;\n if (edit !== undefined) {\n const lines: number[] = [];\n for (let line = range.first; line <= range.last; line++) {\n const moved = shiftLine(edit.hunks, line, edit.sides);\n if (moved === undefined || moved.replaced) {\n if (strict) return undefined;\n if (moved === undefined) continue;\n }\n lines.push(moved.line);\n }\n if (lines.length === 0) return undefined;\n const colon = block.source.lastIndexOf(\":\");\n source = `${block.source.slice(0, colon)}:${Math.min(...lines)}-${Math.max(...lines)}`;\n }\n const via: string[] = [];\n for (const include of block.via) {\n const at = parseInclude(include);\n const includeEdit = at && edits.get(at.path);\n if (!at || !includeEdit) {\n via.push(include);\n continue;\n }\n const moved = shiftLine(includeEdit.hunks, at.line, includeEdit.sides);\n if (moved === undefined || (strict && moved.replaced)) return undefined;\n via.push(`${include.slice(0, include.lastIndexOf(\":\"))}:${moved.line}`);\n }\n return { source, via };\n}\n\n/** An include, `<path>:<line>`, parsed, with its path decoded. */\nfunction parseInclude(include: string): { path: string; line: number } | undefined {\n const colon = include.lastIndexOf(\":\");\n const line = Number(include.slice(colon + 1));\n if (colon <= 0 || !Number.isInteger(line) || line < 1) return undefined;\n const range = parseSource(`${include.slice(0, colon)}:1-1`);\n return range && { path: range.path, line };\n}\n\n/** The anchor `anchorKey` wrote. */\nfunction fromKey(key: string): Anchor {\n const [source = \"\", ...via] = key.split(\" \");\n return { source, via };\n}\n\n/**\n * The changed pages that show a file (a content path): the file's own page,\n * and the pages that changed through it (an include). `[]` when none does.\n */\nexport function pagesShowing(\n file: string,\n pages: readonly ChangedPageRef[],\n): { path: string; title: string | null; formatted_title: FormattedPiece[] | null }[] {\n return pages\n .filter((page) => page.status !== \"removed\")\n .filter((page) => page.path === file || page.because.includes(file))\n .sort((a, b) => Number(b.path === file) - Number(a.path === file))\n .map((page) => ({\n path: page.path,\n title: page.title ?? null,\n formatted_title: page.formatted_title ? [...page.formatted_title] : null,\n }));\n}\n\n/**\n * The git revision to compare with for a pull request's base: the remote\n * branch, from the remote whose URL is the pull request's repository, when\n * there is one; else the branch's name.\n */\nexport function pullRequestBase(\n remotes: readonly { name: string; repository: string | undefined }[],\n repository: string,\n baseRefName: string,\n): string {\n const remote =\n remotes.find((r) => r.repository === repository && r.name === \"upstream\") ??\n remotes.find((r) => r.repository === repository && r.name === \"origin\") ??\n remotes.find((r) => r.repository === repository);\n return remote ? `${remote.name}/${baseRefName}` : baseRefName;\n}\n\n/**\n * The git revision for the pull request's base in the checkout at `root`:\n * `origin/main` when that remote branch exists, else the branch's name.\n */\nexport async function baseRevision(root: string, pr: PullRequestInfo): Promise<string> {\n const out = (await gitMaybe(root, [\"remote\", \"-v\"]).catch(() => undefined)) ?? \"\";\n const remotes = out\n .split(\"\\n\")\n .map((line) => /^(\\S+)\\s+(\\S+)\\s+\\(fetch\\)$/.exec(line))\n .filter((match) => match !== null)\n .map((match) => {\n const repository = parseRemote(match[2] ?? \"\");\n return { name: match[1] ?? \"\", repository: repository && repoKey(repository) };\n });\n const revision = pullRequestBase(remotes, repoKey(pr.repository), pr.baseRefName);\n if (revision !== pr.baseRefName) {\n const exists = await gitMaybe(root, [\"rev-parse\", \"--verify\", \"--quiet\", revision]).catch(\n () => undefined,\n );\n if (exists !== undefined) return revision;\n }\n return pr.baseRefName;\n}\n\nfunction repoKey(repository: { host: string; owner: string; name: string }): string {\n return `${repository.host}/${repository.owner}/${repository.name}`.toLowerCase();\n}\n\nconst EVENTS: readonly ReviewEvent[] = [\"COMMENT\", \"APPROVE\", \"REQUEST_CHANGES\"];\n\nfunction event(value: unknown): ReviewEvent {\n if (EVENTS.includes(value as ReviewEvent)) return value as ReviewEvent;\n throw new ReviewError(\"refused\", `A review can't be submitted as ${String(value)}.`);\n}\n\nfunction text(value: unknown): string {\n if (typeof value !== \"string\") throw new ReviewError(\"refused\", \"The request is missing text.\");\n return value;\n}\n\nfunction anchor(value: unknown): Anchor {\n const record = value as Partial<Anchor> | null;\n if (typeof record?.source !== \"string\") {\n throw new ReviewError(\"refused\", \"The request is missing a block.\");\n }\n const via = Array.isArray(record.via) ? record.via.filter((v) => typeof v === \"string\") : [];\n return { source: record.source, via };\n}\n\nfunction anchors(value: unknown): Anchor[] {\n return Array.isArray(value) ? value.map(anchor) : [];\n}\n"]}
@@ -1,27 +1,7 @@
1
- /** Where a block is written: the anchor grammar's `source` and `via`. */
2
- export interface Anchor {
3
- source: string;
4
- via: string[];
5
- }
6
- /** The words that differ inside a changed block of prose. */
7
- export interface Words {
8
- /** `[start, end)` ranges, in characters, of `now_text`. */
9
- now: [number, number][];
10
- /** `[start, end)` ranges, in characters, of `was_text`. */
11
- was: [number, number][];
12
- now_text: string;
13
- was_text: string;
14
- }
15
- /** One block's change, as `ascribe diff --format json` writes it. */
16
- export interface Change {
17
- kind: "changed" | "added" | "removed" | "moved";
18
- now?: Anchor;
19
- was?: Anchor;
20
- words?: Words;
21
- after?: Anchor;
22
- parent?: Anchor;
23
- text?: string;
24
- }
1
+ import type { Anchor, Change } from "../shapes.js";
2
+ /** A block's change, where it's written, and its words, as `ascribe diff --format json` writes them. */
3
+ export type { Anchor, Change, Words } from "../shapes.js";
4
+ export { titleNodes, type FormattedPiece, type PieceKind } from "./title.js";
25
5
  /** What the page shows: its changes, the page as it will be, or as it was. */
26
6
  export type Show = "changes" | "will" | "was";
27
7
  export interface MarkOptions {
@@ -1,17 +1,5 @@
1
- // @ascribed/review/marks: marks what changed on a rendered page, in the
2
- // browser.
3
- //
4
- // The page is Ascribe's site output rendered with source anchors (the
5
- // site-render contract, §7): every block's element carries
6
- // `data-ascribe-source`, and `data-ascribe-via` when it came through
7
- // includes. The changes are `ascribe diff --format json`'s, for that page.
8
- // `markChanges` applies them to the page's elements; `marks.css` (exported
9
- // as `@ascribed/review/marks.css`) draws them, and `setShow` switches between
10
- // the changes, the page as it will be, and the page as it was.
11
- //
12
- // Everything the marks add is an element with `data-ascribe-ui`, and every
13
- // element they change gets `data-ascribe-change`, so `clearMarks` can put the
14
- // page back as it was rendered.
1
+ import { CLASS_DEL, CLASS_FLASH, CLASS_HINT, CLASS_INS, CLASS_LABEL, CLASS_LABEL_BEFORE, CLASS_MARKS, CLASS_MOVED_FROM, CLASS_MOVE_AFTER, CLASS_MOVE_LINK, CLASS_MOVE_NOTE, CLASS_OPEN, CLASS_REMOVED, CLASS_REMOVED_BODY, CLASS_TOGGLE, CLASS_WAS_COPY, CLASS_WHERE, DATA_CHANGE, DATA_HAS_WAS, DATA_HINT, DATA_LABEL, DATA_MOVE, DATA_SHOW, DATA_SOURCE, DATA_UI, DATA_VIA, DATA_WAS_ONLY, DATA_WAS_SOURCE, ELEMENT_TAB, } from "../names.js";
2
+ export { titleNodes } from "./title.js";
15
3
  const LABELS = {
16
4
  added: "Added",
17
5
  changed: "Changed",
@@ -34,7 +22,7 @@ const LABEL_INSIDE = new Set([
34
22
  "td",
35
23
  "th",
36
24
  // A tab's panel: before it would put the label among the tab list's panels.
37
- "ascribe-tab",
25
+ ELEMENT_TAB,
38
26
  ]);
39
27
  /** Parses `<path>:<first>-<last>`, splitting at the last `:`. */
40
28
  export function parseSource(source) {
@@ -78,7 +66,7 @@ export function describeSource(source, via) {
78
66
  return text;
79
67
  }
80
68
  function viaOf(element) {
81
- return element.getAttribute("data-ascribe-via") ?? "";
69
+ return element.getAttribute(DATA_VIA) ?? "";
82
70
  }
83
71
  /**
84
72
  * The element of a block: the one with exactly its anchor, or, when there's
@@ -88,8 +76,8 @@ function viaOf(element) {
88
76
  */
89
77
  export function findBlock(root, anchor) {
90
78
  const via = anchor.via.join(" ");
91
- const anchored = Array.from(root.querySelectorAll("[data-ascribe-source]"));
92
- const exact = anchored.find((el) => el.getAttribute("data-ascribe-source") === anchor.source && viaOf(el) === via);
79
+ const anchored = Array.from(root.querySelectorAll(`[${DATA_SOURCE}]`));
80
+ const exact = anchored.find((el) => el.getAttribute(DATA_SOURCE) === anchor.source && viaOf(el) === via);
93
81
  if (exact)
94
82
  return exact;
95
83
  const want = parseSource(anchor.source);
@@ -100,7 +88,7 @@ export function findBlock(root, anchor) {
100
88
  for (const el of anchored) {
101
89
  if (viaOf(el) !== via)
102
90
  continue;
103
- const has = parseSource(el.getAttribute("data-ascribe-source") ?? "");
91
+ const has = parseSource(el.getAttribute(DATA_SOURCE) ?? "");
104
92
  if (!has || has.path !== want.path)
105
93
  continue;
106
94
  if (has.first > want.first || has.last < want.last)
@@ -116,7 +104,7 @@ export function findBlock(root, anchor) {
116
104
  function ui(doc, tag, className, text) {
117
105
  const el = doc.createElement(tag);
118
106
  el.className = className;
119
- el.setAttribute("data-ascribe-ui", "");
107
+ el.setAttribute(DATA_UI, "");
120
108
  if (text !== undefined)
121
109
  el.textContent = text;
122
110
  return el;
@@ -124,13 +112,13 @@ function ui(doc, tag, className, text) {
124
112
  /** A label, inside the block at its start, or just before it. */
125
113
  function addLabel(element, kind) {
126
114
  const doc = element.ownerDocument;
127
- const label = ui(doc, "span", "ascribe-label", LABELS[kind]);
128
- label.setAttribute("data-ascribe-label", kind);
115
+ const label = ui(doc, "span", CLASS_LABEL, LABELS[kind]);
116
+ label.setAttribute(DATA_LABEL, kind);
129
117
  if (LABEL_INSIDE.has(element.localName)) {
130
118
  element.prepend(label);
131
119
  }
132
120
  else {
133
- label.classList.add("ascribe-label-before");
121
+ label.classList.add(CLASS_LABEL_BEFORE);
134
122
  element.before(label);
135
123
  }
136
124
  }
@@ -148,8 +136,8 @@ function contentOf(holder, before) {
148
136
  function copyOf(element) {
149
137
  const copy = element.cloneNode(true);
150
138
  for (const el of [copy, ...Array.from(copy.querySelectorAll("*"))]) {
151
- el.removeAttribute("data-ascribe-source");
152
- el.removeAttribute("data-ascribe-via");
139
+ el.removeAttribute(DATA_SOURCE);
140
+ el.removeAttribute(DATA_VIA);
153
141
  el.removeAttribute("id");
154
142
  }
155
143
  return copy;
@@ -181,7 +169,7 @@ function ownText(element) {
181
169
  for (const child of Array.from(node.childNodes)) {
182
170
  if (child.nodeType === Node.TEXT_NODE)
183
171
  text += child.nodeValue ?? "";
184
- else if (child instanceof Element && !child.hasAttribute("data-ascribe-ui"))
172
+ else if (child instanceof Element && !child.hasAttribute(DATA_UI))
185
173
  walk(child);
186
174
  }
187
175
  };
@@ -205,9 +193,7 @@ class Placer {
205
193
  let previous = this.#last.get(after) ?? after;
206
194
  // Past what the marks put after the block itself: its copy as it
207
195
  // was, or its "moved from" link.
208
- for (let next = previous.nextElementSibling; next &&
209
- (next.hasAttribute("data-ascribe-was-only") ||
210
- next.classList.contains("ascribe-move-after")); next = next.nextElementSibling) {
196
+ for (let next = previous.nextElementSibling; next && (next.hasAttribute(DATA_WAS_ONLY) || next.classList.contains(CLASS_MOVE_AFTER)); next = next.nextElementSibling) {
211
197
  previous = next;
212
198
  }
213
199
  const el = make(after.parentElement);
@@ -226,7 +212,7 @@ class Placer {
226
212
  else {
227
213
  // First among the container's blocks: before its first anchored
228
214
  // child, or its first label, or at its end.
229
- const first = Array.from(container.children).find((c) => c.hasAttribute("data-ascribe-source") || c.hasAttribute("data-ascribe-ui"));
215
+ const first = Array.from(container.children).find((c) => c.hasAttribute(DATA_SOURCE) || c.hasAttribute(DATA_UI));
230
216
  if (first)
231
217
  first.before(el);
232
218
  else
@@ -270,8 +256,8 @@ function textMap(element) {
270
256
  }
271
257
  }
272
258
  else if (child instanceof Element &&
273
- !child.hasAttribute("data-ascribe-ui") &&
274
- !child.hasAttribute("data-ascribe-source")) {
259
+ !child.hasAttribute(DATA_UI) &&
260
+ !child.hasAttribute(DATA_SOURCE)) {
275
261
  walk(child);
276
262
  }
277
263
  }
@@ -342,10 +328,10 @@ function markWords(element, words) {
342
328
  edits.sort((a, b) => b.at - a.at || (a.kind === "del" ? -1 : 1));
343
329
  for (const edit of edits) {
344
330
  if (edit.kind === "ins") {
345
- wrapRange(map, offset + edit.at, offset + edit.end, () => ui(doc, "ins", "ascribe-ins"));
331
+ wrapRange(map, offset + edit.at, offset + edit.end, () => ui(doc, "ins", CLASS_INS));
346
332
  }
347
333
  else {
348
- const del = ui(doc, "del", "ascribe-del", edit.text);
334
+ const del = ui(doc, "del", CLASS_DEL, edit.text);
349
335
  insertAt(map, offset + edit.at, del, element);
350
336
  }
351
337
  }
@@ -407,33 +393,33 @@ export function markChanges(root, changes, options = {}) {
407
393
  const was = options.was ?? null;
408
394
  const placer = new Placer(root);
409
395
  const marks = [];
410
- root.classList.add("ascribe-marks");
411
- if (!root.hasAttribute("data-ascribe-show"))
412
- root.setAttribute("data-ascribe-show", "changes");
396
+ root.classList.add(CLASS_MARKS);
397
+ if (!root.hasAttribute(DATA_SHOW))
398
+ root.setAttribute(DATA_SHOW, "changes");
413
399
  let moveCount = 0;
414
400
  for (const change of changes) {
415
401
  switch (change.kind) {
416
402
  case "added":
417
403
  case "changed": {
418
404
  const element = change.now ? findBlock(root, change.now) : null;
419
- if (!element || element.hasAttribute("data-ascribe-change"))
405
+ if (!element || element.hasAttribute(DATA_CHANGE))
420
406
  break;
421
- element.setAttribute("data-ascribe-change", change.kind);
407
+ element.setAttribute(DATA_CHANGE, change.kind);
422
408
  addLabel(element, change.kind);
423
409
  if (change.kind === "changed") {
424
410
  // Where it was, for threads on its old text.
425
411
  if (change.was)
426
- element.setAttribute("data-ascribe-was-source", change.was.source);
412
+ element.setAttribute(DATA_WAS_SOURCE, change.was.source);
427
413
  const before = was && change.was ? findBlock(was, change.was) : null;
428
414
  if (change.words)
429
415
  markWords(element, change.words);
430
416
  if (before) {
431
417
  // As it was, the block as the old page rendered it.
432
418
  const copy = copyOf(before);
433
- copy.setAttribute("data-ascribe-ui", "");
434
- copy.setAttribute("data-ascribe-was-only", "");
419
+ copy.setAttribute(DATA_UI, "");
420
+ copy.setAttribute(DATA_WAS_ONLY, "");
435
421
  element.after(copy);
436
- element.setAttribute("data-ascribe-has-was", "");
422
+ element.setAttribute(DATA_HAS_WAS, "");
437
423
  }
438
424
  }
439
425
  marks.push({ change, element });
@@ -442,23 +428,23 @@ export function markChanges(root, changes, options = {}) {
442
428
  case "removed": {
443
429
  const before = was && change.was ? findBlock(was, change.was) : null;
444
430
  const element = placer.place(change, (container) => {
445
- const holder = ui(doc, holderFor(container), "ascribe-removed");
446
- holder.setAttribute("data-ascribe-change", "removed");
431
+ const holder = ui(doc, holderFor(container), CLASS_REMOVED);
432
+ holder.setAttribute(DATA_CHANGE, "removed");
447
433
  if (change.was)
448
- holder.setAttribute("data-ascribe-was-source", change.was.source);
449
- const label = ui(doc, "span", "ascribe-label", LABELS.removed);
450
- label.setAttribute("data-ascribe-label", "removed");
451
- const body = ui(doc, "div", "ascribe-removed-body");
434
+ holder.setAttribute(DATA_WAS_SOURCE, change.was.source);
435
+ const label = ui(doc, "span", CLASS_LABEL, LABELS.removed);
436
+ label.setAttribute(DATA_LABEL, "removed");
437
+ const body = ui(doc, "div", CLASS_REMOVED_BODY);
452
438
  if (before)
453
439
  body.append(...contentOf(holder, before));
454
440
  else
455
441
  body.textContent = change.text ?? "";
456
- const toggle = ui(doc, "button", "ascribe-toggle", "Show");
442
+ const toggle = ui(doc, "button", CLASS_TOGGLE, "Show");
457
443
  toggle.type = "button";
458
444
  toggle.setAttribute("aria-expanded", "false");
459
445
  toggle.addEventListener("click", (event) => {
460
446
  event.stopPropagation();
461
- const open = holder.classList.toggle("ascribe-open");
447
+ const open = holder.classList.toggle(CLASS_OPEN);
462
448
  toggle.textContent = open ? "Collapse" : "Show";
463
449
  toggle.setAttribute("aria-expanded", String(open));
464
450
  });
@@ -470,25 +456,25 @@ export function markChanges(root, changes, options = {}) {
470
456
  }
471
457
  case "moved": {
472
458
  const element = change.now ? findBlock(root, change.now) : null;
473
- if (!element || element.hasAttribute("data-ascribe-change"))
459
+ if (!element || element.hasAttribute(DATA_CHANGE))
474
460
  break;
475
461
  const id = `ascribe-move-${++moveCount}`;
476
462
  const before = was && change.was ? findBlock(was, change.was) : null;
477
463
  const stub = placer.place(change, (container) => {
478
- const holder = ui(doc, holderFor(container), "ascribe-moved-from");
464
+ const holder = ui(doc, holderFor(container), CLASS_MOVED_FROM);
479
465
  holder.id = `${id}-from`;
480
- holder.setAttribute("data-ascribe-change", "moved-from");
466
+ holder.setAttribute(DATA_CHANGE, "moved-from");
481
467
  if (change.was)
482
- holder.setAttribute("data-ascribe-was-source", change.was.source);
483
- const note = ui(doc, "span", "ascribe-move-note");
468
+ holder.setAttribute(DATA_WAS_SOURCE, change.was.source);
469
+ const note = ui(doc, "span", CLASS_MOVE_NOTE);
484
470
  note.append(doc.createTextNode(`A ${blockWord(element)} moved from here `));
485
- const link = ui(doc, "a", "ascribe-move-link");
471
+ const link = ui(doc, "a", CLASS_MOVE_LINK);
486
472
  link.href = `#${id}`;
487
473
  note.append(link);
488
474
  holder.append(note);
489
475
  // As it was, the block itself.
490
- const copy = ui(doc, "div", "ascribe-was-copy");
491
- copy.setAttribute("data-ascribe-was-only", "");
476
+ const copy = ui(doc, "div", CLASS_WAS_COPY);
477
+ copy.setAttribute(DATA_WAS_ONLY, "");
492
478
  if (before)
493
479
  copy.append(...contentOf(holder, before));
494
480
  else
@@ -496,26 +482,26 @@ export function markChanges(root, changes, options = {}) {
496
482
  holder.append(copy);
497
483
  return holder;
498
484
  });
499
- element.setAttribute("data-ascribe-change", "moved");
500
- element.setAttribute("data-ascribe-move", id);
485
+ element.setAttribute(DATA_CHANGE, "moved");
486
+ element.setAttribute(DATA_MOVE, id);
501
487
  if (!element.id)
502
488
  element.id = id;
503
489
  addLabel(element, "moved");
504
- const back = ui(doc, "a", "ascribe-move-link");
490
+ const back = ui(doc, "a", CLASS_MOVE_LINK);
505
491
  back.href = `#${stub.id}`;
506
492
  back.textContent = `from “${headingBefore(root, stub) ?? "the top of the page"}” ↑`;
507
- const forward = stub.querySelector(".ascribe-move-link");
493
+ const forward = stub.querySelector(`.${CLASS_MOVE_LINK}`);
508
494
  if (forward) {
509
495
  forward.href = `#${element.id}`;
510
496
  forward.textContent = `to “${headingBefore(root, element) ?? "the top of the page"}” ↓`;
511
497
  }
512
498
  if (LABEL_INSIDE.has(element.localName)) {
513
- const note = ui(doc, "span", "ascribe-move-note");
499
+ const note = ui(doc, "span", CLASS_MOVE_NOTE);
514
500
  note.append(" ", back);
515
501
  element.append(note);
516
502
  }
517
503
  else {
518
- const holder = ui(doc, "div", "ascribe-move-note ascribe-move-after");
504
+ const holder = ui(doc, "div", `${CLASS_MOVE_NOTE} ${CLASS_MOVE_AFTER}`);
519
505
  holder.append(back);
520
506
  element.after(holder);
521
507
  }
@@ -552,7 +538,7 @@ function hintHidden(root, marks) {
552
538
  const summaries = new Map();
553
539
  for (const { change, element } of marks) {
554
540
  for (let el = element; el && el !== root; el = el.parentElement) {
555
- if (el.localName === "ascribe-tab") {
541
+ if (el.localName === ELEMENT_TAB) {
556
542
  const tab = tabs.get(el) ?? { added: false, count: 0 };
557
543
  if (el === element && change.kind === "added")
558
544
  tab.added = true;
@@ -570,8 +556,8 @@ function hintHidden(root, marks) {
570
556
  const hint = (kind, text) => {
571
557
  // A space before it, not only a margin, so the label reads "Linux 1
572
558
  // change" to a screen reader, not "Linux1 change".
573
- const el = ui(doc, "span", "ascribe-hint", ` ${text}`);
574
- el.setAttribute("data-ascribe-hint", kind);
559
+ const el = ui(doc, "span", CLASS_HINT, ` ${text}`);
560
+ el.setAttribute(DATA_HINT, kind);
575
561
  return el;
576
562
  };
577
563
  const watching = [];
@@ -615,9 +601,9 @@ function summaryOf(element) {
615
601
  */
616
602
  export function tabButton(tab) {
617
603
  const group = tab.parentElement;
618
- if (tab.localName !== "ascribe-tab" || !group)
604
+ if (tab.localName !== ELEMENT_TAB || !group)
619
605
  return null;
620
- const tabs = Array.from(group.children).filter((c) => c.localName === "ascribe-tab");
606
+ const tabs = Array.from(group.children).filter((c) => c.localName === ELEMENT_TAB);
621
607
  const buttons = group.querySelectorAll(':scope > [role="tablist"] > [role="tab"]');
622
608
  return buttons[tabs.indexOf(tab)] ?? null;
623
609
  }
@@ -643,27 +629,27 @@ export function clearMarks(root) {
643
629
  for (const observer of tabWatchers.get(root) ?? [])
644
630
  observer.disconnect();
645
631
  tabWatchers.delete(root);
646
- for (const el of Array.from(root.querySelectorAll("[data-ascribe-ui]"))) {
632
+ for (const el of Array.from(root.querySelectorAll(`[${DATA_UI}]`))) {
647
633
  if (el.localName === "ins")
648
634
  el.replaceWith(...Array.from(el.childNodes));
649
635
  else
650
636
  el.remove();
651
637
  }
652
- for (const el of Array.from(root.querySelectorAll("[data-ascribe-change]"))) {
653
- el.removeAttribute("data-ascribe-change");
654
- el.removeAttribute("data-ascribe-has-was");
655
- el.removeAttribute("data-ascribe-was-source");
656
- if (el.hasAttribute("data-ascribe-move")) {
657
- if (el.id === el.getAttribute("data-ascribe-move"))
638
+ for (const el of Array.from(root.querySelectorAll(`[${DATA_CHANGE}]`))) {
639
+ el.removeAttribute(DATA_CHANGE);
640
+ el.removeAttribute(DATA_HAS_WAS);
641
+ el.removeAttribute(DATA_WAS_SOURCE);
642
+ if (el.hasAttribute(DATA_MOVE)) {
643
+ if (el.id === el.getAttribute(DATA_MOVE))
658
644
  el.removeAttribute("id");
659
- el.removeAttribute("data-ascribe-move");
645
+ el.removeAttribute(DATA_MOVE);
660
646
  }
661
647
  }
662
648
  root.normalize();
663
649
  }
664
650
  /** Shows the changes, the page as it will be, or the page as it was. */
665
651
  export function setShow(root, show) {
666
- root.setAttribute("data-ascribe-show", show);
652
+ root.setAttribute(DATA_SHOW, show);
667
653
  }
668
654
  /**
669
655
  * Makes `element` visible: opens the `<details>` and selects the tabs it's
@@ -673,7 +659,7 @@ export function reveal(element) {
673
659
  for (let el = element; el; el = el.parentElement) {
674
660
  if (el instanceof HTMLDetailsElement && !el.open)
675
661
  el.open = true;
676
- if (el.localName === "ascribe-tab" && el.hidden)
662
+ if (el.localName === ELEMENT_TAB && el.hidden)
677
663
  tabButton(el)?.click();
678
664
  }
679
665
  }
@@ -684,10 +670,10 @@ export function goTo(element) {
684
670
  if (typeof element.scrollIntoView === "function") {
685
671
  element.scrollIntoView({ block: "center", behavior: "smooth" });
686
672
  }
687
- element.classList.remove("ascribe-flash");
673
+ element.classList.remove(CLASS_FLASH);
688
674
  // Restart the animation.
689
675
  void element.offsetWidth;
690
- element.classList.add("ascribe-flash");
676
+ element.classList.add(CLASS_FLASH);
691
677
  if (!element.hasAttribute("tabindex"))
692
678
  element.setAttribute("tabindex", "-1");
693
679
  element.focus({ preventScroll: true });
@@ -698,13 +684,11 @@ export function goTo(element) {
698
684
  */
699
685
  export function showSources(root) {
700
686
  const doc = root.ownerDocument;
701
- const tip = ui(doc, "span", "ascribe-where");
687
+ const tip = ui(doc, "span", CLASS_WHERE);
702
688
  tip.setAttribute("role", "status");
703
689
  let current = null;
704
690
  const show = (target) => {
705
- const el = target instanceof Element
706
- ? target.closest("[data-ascribe-source], [data-ascribe-was-source]")
707
- : null;
691
+ const el = target instanceof Element ? target.closest(`[${DATA_SOURCE}], [${DATA_WAS_SOURCE}]`) : null;
708
692
  if (el === current)
709
693
  return;
710
694
  current = el;
@@ -713,10 +697,10 @@ export function showSources(root) {
713
697
  current = null;
714
698
  return;
715
699
  }
716
- const source = el.getAttribute("data-ascribe-source");
700
+ const source = el.getAttribute(DATA_SOURCE);
717
701
  tip.textContent = source
718
- ? describeSource(source, el.getAttribute("data-ascribe-via"))
719
- : `was ${describeSource(el.getAttribute("data-ascribe-was-source") ?? "")}`;
702
+ ? describeSource(source, el.getAttribute(DATA_VIA))
703
+ : `was ${describeSource(el.getAttribute(DATA_WAS_SOURCE) ?? "")}`;
720
704
  const box = el.getBoundingClientRect();
721
705
  const host = root.getBoundingClientRect();
722
706
  tip.style.top = `${box.top - host.top}px`;