@ascribed/review 0.2.0 → 0.2.1-next.10
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 +1 -1
- package/dist/github/requests.d.ts +4 -0
- package/dist/github/requests.js +5 -1
- package/dist/github/requests.js.map +1 -1
- package/dist/marks/index.d.ts +4 -24
- package/dist/marks/index.js +73 -89
- package/dist/marks/index.js.map +1 -1
- package/dist/marks/title.d.ts +8 -0
- package/dist/marks/title.js +17 -0
- package/dist/marks/title.js.map +1 -0
- package/dist/names.d.ts +95 -0
- package/dist/names.js +99 -0
- package/dist/names.js.map +1 -0
- package/dist/overlay/index.d.ts +1 -1
- package/dist/overlay/index.js.map +1 -1
- package/dist/overlay/overlay.js +32 -22
- package/dist/overlay/overlay.js.map +1 -1
- package/dist/overlay/style.d.ts +1 -1
- package/dist/overlay/style.js +2 -1
- package/dist/overlay/style.js.map +1 -1
- package/dist/overlay/text.js +3 -2
- package/dist/overlay/text.js.map +1 -1
- package/dist/overlay/types.d.ts +8 -2
- package/dist/overlay/types.js.map +1 -1
- package/dist/place/anchor.d.ts +3 -7
- package/dist/place/anchor.js.map +1 -1
- package/dist/shapes.d.ts +271 -0
- package/dist/shapes.js +9 -0
- package/dist/shapes.js.map +1 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ The colors are the `--ascribe-review-*` custom properties at the top of `marks.c
|
|
|
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/
|
|
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
|
package/dist/github/requests.js
CHANGED
|
@@ -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) => ({
|
|
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"]}
|
package/dist/marks/index.d.ts
CHANGED
|
@@ -1,27 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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 {
|
package/dist/marks/index.js
CHANGED
|
@@ -1,17 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
92
|
-
const exact = anchored.find((el) => el.getAttribute(
|
|
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(
|
|
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(
|
|
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",
|
|
128
|
-
label.setAttribute(
|
|
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(
|
|
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(
|
|
152
|
-
el.removeAttribute(
|
|
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(
|
|
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(
|
|
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(
|
|
274
|
-
!child.hasAttribute(
|
|
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",
|
|
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",
|
|
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(
|
|
411
|
-
if (!root.hasAttribute(
|
|
412
|
-
root.setAttribute(
|
|
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(
|
|
405
|
+
if (!element || element.hasAttribute(DATA_CHANGE))
|
|
420
406
|
break;
|
|
421
|
-
element.setAttribute(
|
|
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(
|
|
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(
|
|
434
|
-
copy.setAttribute(
|
|
419
|
+
copy.setAttribute(DATA_UI, "");
|
|
420
|
+
copy.setAttribute(DATA_WAS_ONLY, "");
|
|
435
421
|
element.after(copy);
|
|
436
|
-
element.setAttribute(
|
|
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),
|
|
446
|
-
holder.setAttribute(
|
|
431
|
+
const holder = ui(doc, holderFor(container), CLASS_REMOVED);
|
|
432
|
+
holder.setAttribute(DATA_CHANGE, "removed");
|
|
447
433
|
if (change.was)
|
|
448
|
-
holder.setAttribute(
|
|
449
|
-
const label = ui(doc, "span",
|
|
450
|
-
label.setAttribute(
|
|
451
|
-
const body = ui(doc, "div",
|
|
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",
|
|
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(
|
|
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(
|
|
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),
|
|
464
|
+
const holder = ui(doc, holderFor(container), CLASS_MOVED_FROM);
|
|
479
465
|
holder.id = `${id}-from`;
|
|
480
|
-
holder.setAttribute(
|
|
466
|
+
holder.setAttribute(DATA_CHANGE, "moved-from");
|
|
481
467
|
if (change.was)
|
|
482
|
-
holder.setAttribute(
|
|
483
|
-
const note = ui(doc, "span",
|
|
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",
|
|
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",
|
|
491
|
-
copy.setAttribute(
|
|
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(
|
|
500
|
-
element.setAttribute(
|
|
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",
|
|
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(
|
|
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",
|
|
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",
|
|
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 ===
|
|
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",
|
|
574
|
-
el.setAttribute(
|
|
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 !==
|
|
604
|
+
if (tab.localName !== ELEMENT_TAB || !group)
|
|
619
605
|
return null;
|
|
620
|
-
const tabs = Array.from(group.children).filter((c) => c.localName ===
|
|
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(
|
|
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(
|
|
653
|
-
el.removeAttribute(
|
|
654
|
-
el.removeAttribute(
|
|
655
|
-
el.removeAttribute(
|
|
656
|
-
if (el.hasAttribute(
|
|
657
|
-
if (el.id === el.getAttribute(
|
|
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(
|
|
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(
|
|
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 ===
|
|
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(
|
|
673
|
+
element.classList.remove(CLASS_FLASH);
|
|
688
674
|
// Restart the animation.
|
|
689
675
|
void element.offsetWidth;
|
|
690
|
-
element.classList.add(
|
|
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",
|
|
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(
|
|
700
|
+
const source = el.getAttribute(DATA_SOURCE);
|
|
717
701
|
tip.textContent = source
|
|
718
|
-
? describeSource(source, el.getAttribute(
|
|
719
|
-
: `was ${describeSource(el.getAttribute(
|
|
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`;
|