prreviewbuddy 0.55.6 → 0.56.0
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/CHANGELOG.md +126 -0
- package/dist/{delete_review-DEvRanb6.js → delete_review-C5bz0Pts.js} +556 -38
- package/dist/main.js +9 -4
- package/dist/server.js +121 -20
- package/package.json +1 -1
- package/static/chunk-BeqgAIrQ.js +1 -1
- package/static/chunk-Cj8lEKkl.js +1 -0
- package/static/chunk-CndkgJPh.js +1 -0
- package/static/chunk-DxnpLefu.js +1 -0
- package/static/help.js +1 -1
- package/static/notifications.js +1 -1
- package/static/reviews.js +1 -1
- package/static/settings.js +1 -1
- package/static/shell.css +63 -2
- package/static/sidebar.css +91 -0
- package/static/workspace.js +75 -73
- package/static/chunk-B5CvMZtt.js +0 -1
- package/static/chunk-BpiqRF0Y.js +0 -1
- package/static/chunk-RriGSD6H.js +0 -1
|
@@ -6235,7 +6235,7 @@ function sanitiseThread(value, seen) {
|
|
|
6235
6235
|
const comments = rawComments.slice(0, 100).map(sanitiseComment).filter((c) => c !== null);
|
|
6236
6236
|
if (comments.length === 0) return null;
|
|
6237
6237
|
const dropped = Math.max(0, rawComments.length - comments.length);
|
|
6238
|
-
const declared = count(value.truncatedCommentCount);
|
|
6238
|
+
const declared = count$1(value.truncatedCommentCount);
|
|
6239
6239
|
return {
|
|
6240
6240
|
id,
|
|
6241
6241
|
type: value.type,
|
|
@@ -6260,7 +6260,7 @@ function sanitiseComment(value) {
|
|
|
6260
6260
|
body,
|
|
6261
6261
|
timestamp: timestamp(value.timestamp),
|
|
6262
6262
|
replyToId: label(value.replyToId) || null,
|
|
6263
|
-
url: href(value.url)
|
|
6263
|
+
url: href$1(value.url)
|
|
6264
6264
|
};
|
|
6265
6265
|
}
|
|
6266
6266
|
function sanitiseLocation(value) {
|
|
@@ -6269,7 +6269,7 @@ function sanitiseLocation(value) {
|
|
|
6269
6269
|
if (!file) return null;
|
|
6270
6270
|
return {
|
|
6271
6271
|
file,
|
|
6272
|
-
line: count(value.line) || null,
|
|
6272
|
+
line: count$1(value.line) || null,
|
|
6273
6273
|
side: value.side === "LEFT" ? "LEFT" : "RIGHT",
|
|
6274
6274
|
lineIsOriginal: value.lineIsOriginal === true,
|
|
6275
6275
|
commitSha: label(value.commitSha) || null
|
|
@@ -6289,8 +6289,8 @@ function sanitiseDecision(value) {
|
|
|
6289
6289
|
}
|
|
6290
6290
|
function sanitisePullRequest(value) {
|
|
6291
6291
|
if (!isRecord(value)) return null;
|
|
6292
|
-
const number = count(value.number);
|
|
6293
|
-
const url = href(value.url);
|
|
6292
|
+
const number = count$1(value.number);
|
|
6293
|
+
const url = href$1(value.url);
|
|
6294
6294
|
if (!number || !url) return null;
|
|
6295
6295
|
const pr = {
|
|
6296
6296
|
provider: label(value.provider) || "unknown",
|
|
@@ -6306,7 +6306,7 @@ function sanitisePullRequest(value) {
|
|
|
6306
6306
|
if (headRef) pr.headRef = headRef;
|
|
6307
6307
|
const baseRef = label(value.baseRef);
|
|
6308
6308
|
if (baseRef) pr.baseRef = baseRef;
|
|
6309
|
-
const filesUrl = href(value.filesUrl);
|
|
6309
|
+
const filesUrl = href$1(value.filesUrl);
|
|
6310
6310
|
if (filesUrl) pr.filesUrl = filesUrl;
|
|
6311
6311
|
const template = fileUrlTemplate$1(value.fileUrlTemplate);
|
|
6312
6312
|
if (template) pr.fileUrlTemplate = template;
|
|
@@ -6327,7 +6327,7 @@ function pullRequestAuthor(value) {
|
|
|
6327
6327
|
if (name) author.name = name;
|
|
6328
6328
|
const handle = label(value.handle).replace(/^@+/, "");
|
|
6329
6329
|
if (handle) author.handle = handle;
|
|
6330
|
-
const url = href(value.url);
|
|
6330
|
+
const url = href$1(value.url);
|
|
6331
6331
|
if (url) author.url = url;
|
|
6332
6332
|
return author.name || author.handle ? author : null;
|
|
6333
6333
|
}
|
|
@@ -6339,7 +6339,7 @@ function label(value) {
|
|
|
6339
6339
|
return typeof value === "string" ? value.trim().slice(0, 200) : "";
|
|
6340
6340
|
}
|
|
6341
6341
|
/** A non-negative integer, or 0. Strings are not numbers here, however numeric they look. */
|
|
6342
|
-
function count(value) {
|
|
6342
|
+
function count$1(value) {
|
|
6343
6343
|
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : 0;
|
|
6344
6344
|
}
|
|
6345
6345
|
/** Kept only if it parses. An unparseable date sorts and renders as nothing rather than as a lie. */
|
|
@@ -6364,7 +6364,7 @@ function fileUrlTemplate$1(value) {
|
|
|
6364
6364
|
if (typeof value !== "string") return "";
|
|
6365
6365
|
const trimmed = value.trim();
|
|
6366
6366
|
if (!trimmed.includes("{")) return "";
|
|
6367
|
-
return href(fillFileUrlTemplate(trimmed, "a")) ? trimmed : "";
|
|
6367
|
+
return href$1(fillFileUrlTemplate(trimmed, "a")) ? trimmed : "";
|
|
6368
6368
|
}
|
|
6369
6369
|
/**
|
|
6370
6370
|
* Substitute a path into a template.
|
|
@@ -6376,7 +6376,7 @@ function fileUrlTemplate$1(value) {
|
|
|
6376
6376
|
function fillFileUrlTemplate(template, path) {
|
|
6377
6377
|
return template.replace(/\{path\}/g, path).replace(/\{pathEncoded\}/g, encodeURIComponent(path)).replace(/\{pathSha1\}/g, () => createHash("sha1").update(path).digest("hex")).replace(/\{pathSha256\}/g, () => createHash("sha256").update(path).digest("hex"));
|
|
6378
6378
|
}
|
|
6379
|
-
function href(value) {
|
|
6379
|
+
function href$1(value) {
|
|
6380
6380
|
if (typeof value !== "string") return "";
|
|
6381
6381
|
try {
|
|
6382
6382
|
const url = new URL(value.trim());
|
|
@@ -9406,6 +9406,436 @@ function renderInlineText(value) {
|
|
|
9406
9406
|
//#region ../../packages/review-harness/src/domain/utils/code_block.ts
|
|
9407
9407
|
var CODE_BLOCK_CLASS = "pr-helper-code-block";
|
|
9408
9408
|
var CODE_COPY_CLASS = "pr-helper-code-copy";
|
|
9409
|
+
var LINE_START_TOKEN = /^\u0002(\d+)\u0002/;
|
|
9410
|
+
/** Anything outside this set (javascript:, data:, vbscript:, a relative path) is not a link. */
|
|
9411
|
+
var SAFE_HREF = /^(?:https?:\/\/|#)/i;
|
|
9412
|
+
/** Media is linked only over https: a link the reader opens in their own session. */
|
|
9413
|
+
var MEDIA_HREF = /^https:\/\//i;
|
|
9414
|
+
var GENERIC_ALT = /^(?:image|img|screenshot|screen ?shot|picture|photo|pic)$/i;
|
|
9415
|
+
var FILE_NAME = /\.(?:png|jpe?g|gif|webp|svg|bmp|heic|avif|tiff?)$/i;
|
|
9416
|
+
/** An image's link text, from its alt text when that says what it shows. Takes and gives plain text. */
|
|
9417
|
+
function imageLabel(alt) {
|
|
9418
|
+
const text = (alt ?? "").trim();
|
|
9419
|
+
return !text || GENERIC_ALT.test(text) || FILE_NAME.test(text) ? "View attached image" : `View image: ${text}`;
|
|
9420
|
+
}
|
|
9421
|
+
/**
|
|
9422
|
+
* A link to an image, audio or video, or null when its address is not https. Both arguments are
|
|
9423
|
+
* already escaped: the Markdown rules hand over escaped text, and liftHtml escapes before calling.
|
|
9424
|
+
*/
|
|
9425
|
+
function mediaAnchor(href, label) {
|
|
9426
|
+
return MEDIA_HREF.test(href) ? `<a href="${href}" target="_blank" rel="noopener noreferrer">${label}</a>` : null;
|
|
9427
|
+
}
|
|
9428
|
+
function mediaFromHtml(src, label) {
|
|
9429
|
+
return mediaAnchor(escapeHtml((src ?? "").trim()), escapeHtml(label));
|
|
9430
|
+
}
|
|
9431
|
+
var align = (value) => /^(?:left|right|center|justify)$/i.test(value) ? value.toLowerCase() : null;
|
|
9432
|
+
var span = (value) => /^[1-9]\d{0,2}$/.test(value) ? value : null;
|
|
9433
|
+
var count = (value) => /^\d{1,9}$/.test(value) ? value : null;
|
|
9434
|
+
var title = (value) => value.slice(0, 300);
|
|
9435
|
+
var href = (value) => {
|
|
9436
|
+
const url = value.trim();
|
|
9437
|
+
return SAFE_HREF.test(url) ? url : null;
|
|
9438
|
+
};
|
|
9439
|
+
/** Maps, not objects, so a name every object inherits (`constructor`, `__proto__`) is in neither. */
|
|
9440
|
+
var ALLOWED = new Map(Object.entries({
|
|
9441
|
+
a: {
|
|
9442
|
+
href,
|
|
9443
|
+
title
|
|
9444
|
+
},
|
|
9445
|
+
abbr: { title },
|
|
9446
|
+
b: {},
|
|
9447
|
+
blockquote: {},
|
|
9448
|
+
br: {},
|
|
9449
|
+
caption: { align },
|
|
9450
|
+
cite: {},
|
|
9451
|
+
code: {},
|
|
9452
|
+
dd: {},
|
|
9453
|
+
del: {},
|
|
9454
|
+
details: {},
|
|
9455
|
+
div: { align },
|
|
9456
|
+
dl: {},
|
|
9457
|
+
dt: {},
|
|
9458
|
+
em: {},
|
|
9459
|
+
hr: {},
|
|
9460
|
+
i: {},
|
|
9461
|
+
ins: {},
|
|
9462
|
+
kbd: {},
|
|
9463
|
+
li: {},
|
|
9464
|
+
mark: {},
|
|
9465
|
+
ol: { start: count },
|
|
9466
|
+
p: { align },
|
|
9467
|
+
q: {},
|
|
9468
|
+
s: {},
|
|
9469
|
+
samp: {},
|
|
9470
|
+
small: {},
|
|
9471
|
+
span: {},
|
|
9472
|
+
strike: {},
|
|
9473
|
+
strong: {},
|
|
9474
|
+
sub: {},
|
|
9475
|
+
summary: {},
|
|
9476
|
+
sup: {},
|
|
9477
|
+
table: { align },
|
|
9478
|
+
tbody: {},
|
|
9479
|
+
td: {
|
|
9480
|
+
align,
|
|
9481
|
+
colspan: span,
|
|
9482
|
+
rowspan: span
|
|
9483
|
+
},
|
|
9484
|
+
tfoot: {},
|
|
9485
|
+
th: {
|
|
9486
|
+
align,
|
|
9487
|
+
colspan: span,
|
|
9488
|
+
rowspan: span
|
|
9489
|
+
},
|
|
9490
|
+
thead: {},
|
|
9491
|
+
tr: { align },
|
|
9492
|
+
u: {},
|
|
9493
|
+
ul: {},
|
|
9494
|
+
var: {},
|
|
9495
|
+
h1: { align },
|
|
9496
|
+
h2: { align },
|
|
9497
|
+
h3: { align },
|
|
9498
|
+
h4: { align },
|
|
9499
|
+
h5: { align },
|
|
9500
|
+
h6: { align }
|
|
9501
|
+
}).map(([tag, checks]) => [tag, new Map(Object.entries({
|
|
9502
|
+
title,
|
|
9503
|
+
...checks
|
|
9504
|
+
}))]));
|
|
9505
|
+
var VOID = /* @__PURE__ */ new Set([
|
|
9506
|
+
"br",
|
|
9507
|
+
"hr",
|
|
9508
|
+
"img",
|
|
9509
|
+
"input",
|
|
9510
|
+
"source",
|
|
9511
|
+
"meta",
|
|
9512
|
+
"link",
|
|
9513
|
+
"base",
|
|
9514
|
+
"wbr",
|
|
9515
|
+
"col",
|
|
9516
|
+
"area",
|
|
9517
|
+
"track",
|
|
9518
|
+
"param",
|
|
9519
|
+
"embed"
|
|
9520
|
+
]);
|
|
9521
|
+
/** Gone, with everything inside them: what they hold is code, styling or someone else's page. */
|
|
9522
|
+
var DROP_WITH_CONTENT = /* @__PURE__ */ new Set([
|
|
9523
|
+
"script",
|
|
9524
|
+
"style",
|
|
9525
|
+
"iframe",
|
|
9526
|
+
"object",
|
|
9527
|
+
"embed",
|
|
9528
|
+
"svg",
|
|
9529
|
+
"math",
|
|
9530
|
+
"template",
|
|
9531
|
+
"noscript",
|
|
9532
|
+
"textarea",
|
|
9533
|
+
"frame",
|
|
9534
|
+
"frameset",
|
|
9535
|
+
"applet",
|
|
9536
|
+
"head",
|
|
9537
|
+
"title",
|
|
9538
|
+
"select"
|
|
9539
|
+
]);
|
|
9540
|
+
/** Gone, keeping what they hold: the words in a form or a figure are still the author's. */
|
|
9541
|
+
var DROP_KEEP_CONTENT = /* @__PURE__ */ new Set([
|
|
9542
|
+
"picture",
|
|
9543
|
+
"pre",
|
|
9544
|
+
"source",
|
|
9545
|
+
"track",
|
|
9546
|
+
"input",
|
|
9547
|
+
"form",
|
|
9548
|
+
"label",
|
|
9549
|
+
"button",
|
|
9550
|
+
"option",
|
|
9551
|
+
"optgroup",
|
|
9552
|
+
"font",
|
|
9553
|
+
"center",
|
|
9554
|
+
"section",
|
|
9555
|
+
"article",
|
|
9556
|
+
"header",
|
|
9557
|
+
"footer",
|
|
9558
|
+
"nav",
|
|
9559
|
+
"figure",
|
|
9560
|
+
"figcaption",
|
|
9561
|
+
"main",
|
|
9562
|
+
"aside",
|
|
9563
|
+
"html",
|
|
9564
|
+
"body",
|
|
9565
|
+
"meta",
|
|
9566
|
+
"link",
|
|
9567
|
+
"base",
|
|
9568
|
+
"wbr",
|
|
9569
|
+
"col",
|
|
9570
|
+
"colgroup",
|
|
9571
|
+
"area",
|
|
9572
|
+
"map",
|
|
9573
|
+
"param",
|
|
9574
|
+
"fieldset",
|
|
9575
|
+
"legend",
|
|
9576
|
+
"dialog"
|
|
9577
|
+
]);
|
|
9578
|
+
/** Tags that open an HTML block when a line begins with one (CommonMark's sixth kind). */
|
|
9579
|
+
var BLOCK = /* @__PURE__ */ new Set([
|
|
9580
|
+
"address",
|
|
9581
|
+
"article",
|
|
9582
|
+
"aside",
|
|
9583
|
+
"blockquote",
|
|
9584
|
+
"caption",
|
|
9585
|
+
"center",
|
|
9586
|
+
"dd",
|
|
9587
|
+
"details",
|
|
9588
|
+
"dialog",
|
|
9589
|
+
"div",
|
|
9590
|
+
"dl",
|
|
9591
|
+
"dt",
|
|
9592
|
+
"fieldset",
|
|
9593
|
+
"figcaption",
|
|
9594
|
+
"figure",
|
|
9595
|
+
"footer",
|
|
9596
|
+
"form",
|
|
9597
|
+
"h1",
|
|
9598
|
+
"h2",
|
|
9599
|
+
"h3",
|
|
9600
|
+
"h4",
|
|
9601
|
+
"h5",
|
|
9602
|
+
"h6",
|
|
9603
|
+
"header",
|
|
9604
|
+
"hr",
|
|
9605
|
+
"li",
|
|
9606
|
+
"main",
|
|
9607
|
+
"nav",
|
|
9608
|
+
"ol",
|
|
9609
|
+
"p",
|
|
9610
|
+
"pre",
|
|
9611
|
+
"section",
|
|
9612
|
+
"summary",
|
|
9613
|
+
"table",
|
|
9614
|
+
"tbody",
|
|
9615
|
+
"td",
|
|
9616
|
+
"tfoot",
|
|
9617
|
+
"th",
|
|
9618
|
+
"thead",
|
|
9619
|
+
"tr",
|
|
9620
|
+
"ul"
|
|
9621
|
+
]);
|
|
9622
|
+
/**
|
|
9623
|
+
* The tag a raw line opens an HTML block with, as GitHub reads one: the line begins, after at most
|
|
9624
|
+
* three spaces, with a block tag. Until the next blank line it is HTML, with no Markdown inside.
|
|
9625
|
+
* The preview splits a description with this, so it agrees with the renderer on where one ends.
|
|
9626
|
+
*/
|
|
9627
|
+
function opensHtmlBlock(line) {
|
|
9628
|
+
const match = line.match(/^ {0,3}<\/?([A-Za-z][A-Za-z0-9]*)(?=[\s/>]|$)/);
|
|
9629
|
+
return Boolean(match && BLOCK.has(match[1].toLowerCase()));
|
|
9630
|
+
}
|
|
9631
|
+
var SCAN = new RegExp([
|
|
9632
|
+
"`[^`\\n]+`",
|
|
9633
|
+
"<(\\/?)([A-Za-z][A-Za-z0-9-]*)((?:\\s+[^\\s\"'<>\\/=]+(?:\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s\"'=<>`]+))?)*)\\s*(\\/?)>",
|
|
9634
|
+
"&(?:#\\d{1,7}|#[xX][0-9a-fA-F]{1,6}|[A-Za-z][A-Za-z0-9]{1,31});"
|
|
9635
|
+
].join("|"), "g");
|
|
9636
|
+
var ATTRIBUTE = /([^\s"'<>/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
|
|
9637
|
+
/** An attribute's value as the author meant it: entities in it are read, so `&` is one `&`. */
|
|
9638
|
+
function decodeEntities(value) {
|
|
9639
|
+
return value.replace(/&(#\d{1,7}|#[xX][0-9a-fA-F]{1,6}|amp|lt|gt|quot|apos|nbsp);/g, (_match, entity) => {
|
|
9640
|
+
if (entity[0] === "#") {
|
|
9641
|
+
const code = entity[1] === "x" || entity[1] === "X" ? parseInt(entity.slice(2), 16) : parseInt(entity.slice(1), 10);
|
|
9642
|
+
return code > 0 && code <= 1114111 ? String.fromCodePoint(code) : "";
|
|
9643
|
+
}
|
|
9644
|
+
return {
|
|
9645
|
+
amp: "&",
|
|
9646
|
+
lt: "<",
|
|
9647
|
+
gt: ">",
|
|
9648
|
+
quot: "\"",
|
|
9649
|
+
apos: "'",
|
|
9650
|
+
nbsp: "\xA0"
|
|
9651
|
+
}[entity] ?? "";
|
|
9652
|
+
});
|
|
9653
|
+
}
|
|
9654
|
+
function attributesOf(source) {
|
|
9655
|
+
const result = /* @__PURE__ */ new Map();
|
|
9656
|
+
for (const match of source.matchAll(ATTRIBUTE)) {
|
|
9657
|
+
const name = match[1].toLowerCase();
|
|
9658
|
+
if (!result.has(name)) result.set(name, decodeEntities(match[2] ?? match[3] ?? match[4] ?? ""));
|
|
9659
|
+
}
|
|
9660
|
+
return result;
|
|
9661
|
+
}
|
|
9662
|
+
/** A heading is drawn three levels down, as Markdown headings are here: these sit inside a sidebar. */
|
|
9663
|
+
function emittedTag(name) {
|
|
9664
|
+
const heading = name.match(/^h([1-6])$/);
|
|
9665
|
+
return heading ? `h${Math.min(Number(heading[1]), 3) + 3}` : name;
|
|
9666
|
+
}
|
|
9667
|
+
function openingMarkup(name, attributeSource) {
|
|
9668
|
+
const tag = emittedTag(name);
|
|
9669
|
+
const allowed = ALLOWED.get(name);
|
|
9670
|
+
const kept = [];
|
|
9671
|
+
for (const [attribute, value] of attributesOf(attributeSource)) {
|
|
9672
|
+
const check = allowed.get(attribute);
|
|
9673
|
+
const safe = check ? check(value) : null;
|
|
9674
|
+
if (safe !== null) kept.push(` ${attribute}="${escapeHtml(safe)}"`);
|
|
9675
|
+
}
|
|
9676
|
+
if (name === "a" && kept.some((attribute) => attribute.startsWith(" href="))) kept.push(" target=\"_blank\" rel=\"noopener noreferrer\"");
|
|
9677
|
+
return `<${tag}${kept.join("")}>`;
|
|
9678
|
+
}
|
|
9679
|
+
/**
|
|
9680
|
+
* Every tag and entity the policy handles, replaced by a token; the rest left as text to be escaped.
|
|
9681
|
+
* Runs on Markdown with its code blocks already lifted out, so nothing in a code block is read here.
|
|
9682
|
+
*/
|
|
9683
|
+
function liftHtml(text) {
|
|
9684
|
+
const tokens = [];
|
|
9685
|
+
const token = (value) => `${tokens.push(value) - 1}`;
|
|
9686
|
+
let out = "";
|
|
9687
|
+
let last = 0;
|
|
9688
|
+
SCAN.lastIndex = 0;
|
|
9689
|
+
for (let match; match = SCAN.exec(text);) {
|
|
9690
|
+
const [whole, slash, rawName, attributeSource, selfClosing] = match;
|
|
9691
|
+
let replacement = null;
|
|
9692
|
+
if (whole[0] === "&") replacement = token({
|
|
9693
|
+
kind: "entity",
|
|
9694
|
+
name: "",
|
|
9695
|
+
tag: "",
|
|
9696
|
+
markup: whole
|
|
9697
|
+
});
|
|
9698
|
+
else if (rawName) {
|
|
9699
|
+
const name = rawName.toLowerCase();
|
|
9700
|
+
if (name === "img") {
|
|
9701
|
+
const attributes = attributesOf(attributeSource);
|
|
9702
|
+
const alt = attributes.get("alt");
|
|
9703
|
+
replacement = token({
|
|
9704
|
+
kind: "text",
|
|
9705
|
+
name,
|
|
9706
|
+
tag: "",
|
|
9707
|
+
markup: slash ? "" : mediaFromHtml(attributes.get("src"), imageLabel(alt)) ?? escapeHtml(alt ?? "")
|
|
9708
|
+
});
|
|
9709
|
+
} else if (name === "video" || name === "audio") {
|
|
9710
|
+
let src = slash ? void 0 : attributesOf(attributeSource).get("src");
|
|
9711
|
+
if (!slash && !selfClosing) {
|
|
9712
|
+
const rest = text.slice(SCAN.lastIndex);
|
|
9713
|
+
const close = rest.match(new RegExp(`</${name}\\s*>`, "i"));
|
|
9714
|
+
const inner = close ? rest.slice(0, close.index) : "";
|
|
9715
|
+
src ??= attributesOf(inner.match(/<source\b([^<>]*)>/i)?.[1] ?? "").get("src");
|
|
9716
|
+
if (close) SCAN.lastIndex += close.index + close[0].length;
|
|
9717
|
+
}
|
|
9718
|
+
replacement = token({
|
|
9719
|
+
kind: "text",
|
|
9720
|
+
name,
|
|
9721
|
+
tag: "",
|
|
9722
|
+
markup: (slash ? null : mediaFromHtml(src, name === "video" ? "Open attached video" : "Open attached audio")) ?? ""
|
|
9723
|
+
});
|
|
9724
|
+
} else if (DROP_WITH_CONTENT.has(name)) {
|
|
9725
|
+
if (!slash && !selfClosing && !VOID.has(name)) {
|
|
9726
|
+
const close = new RegExp(`</${name}\\s*>`, "i");
|
|
9727
|
+
const rest = text.slice(SCAN.lastIndex);
|
|
9728
|
+
const end = rest.search(close);
|
|
9729
|
+
if (end !== -1) SCAN.lastIndex += end + rest.slice(end).match(close)[0].length;
|
|
9730
|
+
}
|
|
9731
|
+
replacement = "";
|
|
9732
|
+
} else if (DROP_KEEP_CONTENT.has(name)) replacement = token({
|
|
9733
|
+
kind: "text",
|
|
9734
|
+
name,
|
|
9735
|
+
tag: "",
|
|
9736
|
+
markup: ""
|
|
9737
|
+
});
|
|
9738
|
+
else if (ALLOWED.has(name)) {
|
|
9739
|
+
const tag = emittedTag(name);
|
|
9740
|
+
if (slash) replacement = token({
|
|
9741
|
+
kind: "close",
|
|
9742
|
+
name,
|
|
9743
|
+
tag,
|
|
9744
|
+
markup: ""
|
|
9745
|
+
});
|
|
9746
|
+
else if (VOID.has(name)) replacement = token({
|
|
9747
|
+
kind: "void",
|
|
9748
|
+
name,
|
|
9749
|
+
tag,
|
|
9750
|
+
markup: openingMarkup(name, attributeSource)
|
|
9751
|
+
});
|
|
9752
|
+
else replacement = token({
|
|
9753
|
+
kind: "open",
|
|
9754
|
+
name,
|
|
9755
|
+
tag,
|
|
9756
|
+
markup: openingMarkup(name, attributeSource)
|
|
9757
|
+
});
|
|
9758
|
+
}
|
|
9759
|
+
}
|
|
9760
|
+
if (replacement === null) continue;
|
|
9761
|
+
out += text.slice(last, match.index) + replacement;
|
|
9762
|
+
last = SCAN.lastIndex;
|
|
9763
|
+
}
|
|
9764
|
+
return {
|
|
9765
|
+
text: out + text.slice(last),
|
|
9766
|
+
tokens
|
|
9767
|
+
};
|
|
9768
|
+
}
|
|
9769
|
+
/** Whether lifted text holds a token that is markup, which only text positions may take. */
|
|
9770
|
+
function holdsMarkup(text, lifted) {
|
|
9771
|
+
for (const match of text.matchAll(/\u0002(\d+)\u0002/g)) if (lifted.tokens[Number(match[1])].kind !== "entity") return true;
|
|
9772
|
+
return false;
|
|
9773
|
+
}
|
|
9774
|
+
/** Whether a lifted line begins with a block tag, so opens an HTML block. */
|
|
9775
|
+
function startsHtmlBlock(line, lifted) {
|
|
9776
|
+
const match = line.match(LINE_START_TOKEN);
|
|
9777
|
+
return Boolean(match && BLOCK.has(lifted.tokens[Number(match[1])].name));
|
|
9778
|
+
}
|
|
9779
|
+
/**
|
|
9780
|
+
* A link inside another unwrapped to its text, keeping the outer one: the destination the author
|
|
9781
|
+
* gave the whole thing. Media inside a link (a badge) is the usual case, written as Markdown or HTML
|
|
9782
|
+
* in any mix, and a browser handed nested links closes the outer one first, leaving the label linked
|
|
9783
|
+
* to the image. Run on finished output, where every `<a` and `</a>` is ours: the author's are
|
|
9784
|
+
* escaped or were rebuilt, so one rule covers every way the two were combined.
|
|
9785
|
+
*/
|
|
9786
|
+
function unnestLinks(html) {
|
|
9787
|
+
const unwrapped = [];
|
|
9788
|
+
return html.replace(/<a\b[^>]*>|<\/a>/g, (tag) => {
|
|
9789
|
+
if (tag === "</a>") return unwrapped.pop() ? "" : tag;
|
|
9790
|
+
const inner = unwrapped.length > 0;
|
|
9791
|
+
unwrapped.push(inner);
|
|
9792
|
+
return inner ? "" : tag;
|
|
9793
|
+
});
|
|
9794
|
+
}
|
|
9795
|
+
/**
|
|
9796
|
+
* The tokens put back, in document order, balanced, and only where text goes.
|
|
9797
|
+
*
|
|
9798
|
+
* Only where text goes: a token that a Markdown rule carried into one of our own tags (a link's
|
|
9799
|
+
* destination is an attribute) is dropped, since markup there would end the attribute and start
|
|
9800
|
+
* another. An entity is the exception, being safe in both. The test is cheap and exact because every raw `<` and `>` in this string is ours: the
|
|
9801
|
+
* author's are escaped, or are the tokens themselves.
|
|
9802
|
+
*
|
|
9803
|
+
* Balanced: a closer for a tag that is not open is dropped; one for a tag further out closes what
|
|
9804
|
+
* is inside it first; whatever is still open at the end is closed there, inside the description.
|
|
9805
|
+
*/
|
|
9806
|
+
function restoreHtml(html, lifted) {
|
|
9807
|
+
const open = [];
|
|
9808
|
+
const put = (index) => {
|
|
9809
|
+
const item = lifted.tokens[index];
|
|
9810
|
+
if (item.kind === "open") {
|
|
9811
|
+
open.push(item.tag);
|
|
9812
|
+
return item.markup;
|
|
9813
|
+
}
|
|
9814
|
+
if (item.kind === "close") {
|
|
9815
|
+
const at = open.lastIndexOf(item.tag);
|
|
9816
|
+
if (at === -1) return "";
|
|
9817
|
+
return open.splice(at).reverse().map((tag) => `</${tag}>`).join("");
|
|
9818
|
+
}
|
|
9819
|
+
return item.markup;
|
|
9820
|
+
};
|
|
9821
|
+
let out = "";
|
|
9822
|
+
let last = 0;
|
|
9823
|
+
let inTag = false;
|
|
9824
|
+
for (let i = 0; i < html.length; i++) {
|
|
9825
|
+
const char = html[i];
|
|
9826
|
+
if (char === "<") inTag = true;
|
|
9827
|
+
else if (char === ">") inTag = false;
|
|
9828
|
+
else if (char === "") {
|
|
9829
|
+
const end = html.indexOf("", i + 1);
|
|
9830
|
+
if (end === -1) break;
|
|
9831
|
+
const index = Number(html.slice(i + 1, end));
|
|
9832
|
+
out += html.slice(last, i) + (inTag && lifted.tokens[index].kind !== "entity" ? "" : put(index));
|
|
9833
|
+
i = end;
|
|
9834
|
+
last = end + 1;
|
|
9835
|
+
}
|
|
9836
|
+
}
|
|
9837
|
+
return out + html.slice(last) + open.reverse().map((tag) => `</${tag}>`).join("");
|
|
9838
|
+
}
|
|
9409
9839
|
//#endregion
|
|
9410
9840
|
//#region ../../packages/review-harness/src/domain/utils/markdown.ts
|
|
9411
9841
|
var BLOCK_MARKER = "\0";
|
|
@@ -9413,10 +9843,20 @@ var INLINE_MARKER = "";
|
|
|
9413
9843
|
var BLOCK_TOKEN = /\u0000(\d+)\u0000/g;
|
|
9414
9844
|
var BLOCK_TOKEN_LINE = /^\u0000\d+\u0000$/;
|
|
9415
9845
|
var INLINE_TOKEN = /\u0001(\d+)\u0001/g;
|
|
9416
|
-
|
|
9846
|
+
/**
|
|
9847
|
+
* Markdown a person wrote on GitHub: a PR description or comment. One policy for every place that
|
|
9848
|
+
* shows one, so the Author's context preview, its full view and a PR comment never disagree.
|
|
9849
|
+
*/
|
|
9850
|
+
var GITHUB_MARKDOWN = {
|
|
9851
|
+
hideHtmlComments: true,
|
|
9852
|
+
safeHtml: true,
|
|
9853
|
+
mermaid: true
|
|
9854
|
+
};
|
|
9417
9855
|
function renderMarkdown(value, options = {}) {
|
|
9418
|
-
const { text, blocks } = extractFencedBlocks(scanMarkdownLines(value.replace(/[\u0000\u0001]/g, ""), options));
|
|
9419
|
-
return restoreFencedBlocks(renderBlocks(text), blocks);
|
|
9856
|
+
const { text, blocks } = extractFencedBlocks(scanMarkdownLines(value.replace(/[\u0000\u0001\u0002]/g, ""), options), options);
|
|
9857
|
+
if (!options.safeHtml) return restoreFencedBlocks(renderBlocks(text), blocks);
|
|
9858
|
+
const lifted = liftHtml(text);
|
|
9859
|
+
return unnestLinks(restoreHtml(restoreFencedBlocks(renderBlocks(lifted.text, lifted), blocks), lifted));
|
|
9420
9860
|
}
|
|
9421
9861
|
/**
|
|
9422
9862
|
* The fence character a line opens a code block with, or null when it opens none. Shared with the
|
|
@@ -9519,51 +9959,118 @@ function scanMarkdownLines(value, options = {}) {
|
|
|
9519
9959
|
}
|
|
9520
9960
|
return result;
|
|
9521
9961
|
}
|
|
9522
|
-
function extractFencedBlocks(lines) {
|
|
9962
|
+
function extractFencedBlocks(lines, options = {}) {
|
|
9523
9963
|
const blocks = [];
|
|
9524
9964
|
const out = [];
|
|
9525
9965
|
let body = null;
|
|
9966
|
+
let diagramTitle;
|
|
9967
|
+
const close = () => {
|
|
9968
|
+
out.push(`${BLOCK_MARKER}${blocks.length}${BLOCK_MARKER}`);
|
|
9969
|
+
const code = body.join("\n");
|
|
9970
|
+
blocks.push(diagramTitle === void 0 ? { code } : {
|
|
9971
|
+
code,
|
|
9972
|
+
diagram: { label: `Mermaid diagram: ${diagramTitle ?? diagramType(code)}` }
|
|
9973
|
+
});
|
|
9974
|
+
body = null;
|
|
9975
|
+
};
|
|
9526
9976
|
for (const line of lines) {
|
|
9527
9977
|
if (line.absent) continue;
|
|
9528
|
-
if (line.code === "open")
|
|
9529
|
-
|
|
9530
|
-
|
|
9531
|
-
|
|
9532
|
-
|
|
9533
|
-
|
|
9534
|
-
|
|
9535
|
-
|
|
9536
|
-
if (body) {
|
|
9537
|
-
out.push(`${BLOCK_MARKER}${blocks.length}${BLOCK_MARKER}`);
|
|
9538
|
-
blocks.push(body.join("\n"));
|
|
9539
|
-
}
|
|
9978
|
+
if (line.code === "open") {
|
|
9979
|
+
body = [];
|
|
9980
|
+
diagramTitle = options.mermaid && fenceInfo(line.raw).toLowerCase() === "mermaid" ? titleAbove(out) : void 0;
|
|
9981
|
+
} else if (line.code === "body") body.push(line.raw);
|
|
9982
|
+
else if (line.code === "close") close();
|
|
9983
|
+
else out.push(line.visible);
|
|
9984
|
+
}
|
|
9985
|
+
if (body) close();
|
|
9540
9986
|
return {
|
|
9541
9987
|
text: out.join("\n"),
|
|
9542
9988
|
blocks
|
|
9543
9989
|
};
|
|
9544
9990
|
}
|
|
9991
|
+
/** The language a fence names, as written: `mermaid` in ```` ```mermaid ````. */
|
|
9992
|
+
function fenceInfo(line) {
|
|
9993
|
+
return line.match(/^\s*(?:`{3,}|~{3,})\s*([\w+#.-]*)/)?.[1] ?? "";
|
|
9994
|
+
}
|
|
9995
|
+
/**
|
|
9996
|
+
* What the author called a diagram: the heading, or the line that is all bold, just above it, as
|
|
9997
|
+
* in `**Diagram Walkthrough**`. Null when the nearest line above says something else.
|
|
9998
|
+
*/
|
|
9999
|
+
function titleAbove(out) {
|
|
10000
|
+
for (let i = out.length - 1; i >= 0; i--) {
|
|
10001
|
+
const line = out[i].trim();
|
|
10002
|
+
if (!line) continue;
|
|
10003
|
+
const title = line.match(/^#{1,6}\s+(.+?)\s*#*$/)?.[1] ?? line.match(/^(\*\*|__)(.+)\1$/)?.[2];
|
|
10004
|
+
return title ? title.replace(/[`*_]/g, "").trim() || null : null;
|
|
10005
|
+
}
|
|
10006
|
+
return null;
|
|
10007
|
+
}
|
|
10008
|
+
/**
|
|
10009
|
+
* A diagram's kind, from its first line that is not a directive or front matter: `flowchart`,
|
|
10010
|
+
* `sequenceDiagram`. Front matter is the `---` block a diagram may open with.
|
|
10011
|
+
*/
|
|
10012
|
+
function diagramType(code) {
|
|
10013
|
+
const lines = code.split("\n").map((line) => line.trim());
|
|
10014
|
+
let start = 0;
|
|
10015
|
+
if (lines.find((line) => line) === "---") {
|
|
10016
|
+
const open = lines.indexOf("---");
|
|
10017
|
+
const close = lines.indexOf("---", open + 1);
|
|
10018
|
+
start = close === -1 ? lines.length : close + 1;
|
|
10019
|
+
}
|
|
10020
|
+
return lines.slice(start).find((line) => line && !line.startsWith("%%"))?.split(/\s+/)[0] || "diagram";
|
|
10021
|
+
}
|
|
9545
10022
|
function restoreFencedBlocks(html, blocks) {
|
|
9546
|
-
return html.replace(BLOCK_TOKEN, (_match, index) =>
|
|
10023
|
+
return html.replace(BLOCK_TOKEN, (_match, index) => {
|
|
10024
|
+
const block = blocks[Number(index)];
|
|
10025
|
+
return block.diagram ? diagramFigure(block.code, block.diagram.label) : codeBlock(block.code, "Copy", "Copy code");
|
|
10026
|
+
});
|
|
10027
|
+
}
|
|
10028
|
+
function codeBlock(code, label, accessibleName, attributes = "") {
|
|
10029
|
+
return `<div class="${CODE_BLOCK_CLASS}"${attributes}><pre><code>${escapeHtml(code)}</code></pre><button type="button" class="${CODE_COPY_CLASS}" aria-label="${accessibleName}">${label}</button></div>`;
|
|
10030
|
+
}
|
|
10031
|
+
var diagramCount = 0;
|
|
10032
|
+
/**
|
|
10033
|
+
* A Mermaid block as the workspace's client finds it: the source, visible and copyable, and the
|
|
10034
|
+
* empty places the drawing, its toggle and its failure note go. The client draws it when it first
|
|
10035
|
+
* comes into view; until then, without JavaScript, or when drawing fails, the source is what shows.
|
|
10036
|
+
*/
|
|
10037
|
+
function diagramFigure(code, label) {
|
|
10038
|
+
const id = `prb-md-mermaid-source-${++diagramCount}`;
|
|
10039
|
+
return `<figure class="prb-md-mermaid" data-md-mermaid data-state="source"><div class="prb-md-mermaid-canvas" data-md-mermaid-canvas role="img" aria-label="${escapeHtml(label)}" hidden></div><p class="prb-md-mermaid-error" data-md-mermaid-error hidden>This diagram couldn\u2019t be displayed.</p><button type="button" class="prb-md-mermaid-toggle" data-md-mermaid-toggle aria-expanded="true" aria-controls="${id}" hidden>Hide source</button>` + codeBlock(code, "Copy source", "Copy source", ` id="${id}" data-md-mermaid-source`) + `</figure>`;
|
|
9547
10040
|
}
|
|
9548
|
-
function renderBlocks(text) {
|
|
10041
|
+
function renderBlocks(text, lifted) {
|
|
9549
10042
|
const out = [];
|
|
9550
10043
|
let paragraph = [];
|
|
9551
10044
|
let quote = [];
|
|
9552
10045
|
let list = null;
|
|
10046
|
+
let html = null;
|
|
9553
10047
|
const flush = () => {
|
|
9554
|
-
if (paragraph.length) out.push(`<p>${renderInline(paragraph.join(" "))}</p>`);
|
|
9555
|
-
if (quote.length) out.push(`<blockquote>${renderInline(quote.join(" "))}</blockquote>`);
|
|
9556
|
-
if (list) out.push(`<${list.tag}>${list.items.map((item) => `<li>${renderInline(item)}</li>`).join("")}</${list.tag}>`);
|
|
10048
|
+
if (paragraph.length) out.push(`<p>${renderInline(paragraph.join(" "), lifted)}</p>`);
|
|
10049
|
+
if (quote.length) out.push(`<blockquote>${renderInline(quote.join(" "), lifted)}</blockquote>`);
|
|
10050
|
+
if (list) out.push(`<${list.tag}>${list.items.map((item) => `<li>${renderInline(item, lifted)}</li>`).join("")}</${list.tag}>`);
|
|
9557
10051
|
paragraph = [];
|
|
9558
10052
|
quote = [];
|
|
9559
10053
|
list = null;
|
|
9560
10054
|
};
|
|
9561
10055
|
for (const raw of text.split("\n")) {
|
|
9562
10056
|
const line = raw.trim();
|
|
10057
|
+
if (html) {
|
|
10058
|
+
if (line) {
|
|
10059
|
+
html.push(escapeHtml(line));
|
|
10060
|
+
continue;
|
|
10061
|
+
}
|
|
10062
|
+
out.push(html.join("\n"));
|
|
10063
|
+
html = null;
|
|
10064
|
+
}
|
|
9563
10065
|
if (!line) {
|
|
9564
10066
|
flush();
|
|
9565
10067
|
continue;
|
|
9566
10068
|
}
|
|
10069
|
+
if (lifted && startsHtmlBlock(line, lifted)) {
|
|
10070
|
+
flush();
|
|
10071
|
+
html = [escapeHtml(line)];
|
|
10072
|
+
continue;
|
|
10073
|
+
}
|
|
9567
10074
|
if (BLOCK_TOKEN_LINE.test(line)) {
|
|
9568
10075
|
flush();
|
|
9569
10076
|
out.push(line);
|
|
@@ -9578,7 +10085,7 @@ function renderBlocks(text) {
|
|
|
9578
10085
|
if (heading) {
|
|
9579
10086
|
flush();
|
|
9580
10087
|
const level = Math.min(heading[1].length, 3) + 3;
|
|
9581
|
-
out.push(`<h${level}>${renderInline(heading[2])}</h${level}>`);
|
|
10088
|
+
out.push(`<h${level}>${renderInline(heading[2], lifted)}</h${level}>`);
|
|
9582
10089
|
continue;
|
|
9583
10090
|
}
|
|
9584
10091
|
const bullet = line.match(/^[-*+]\s+(.+)$/);
|
|
@@ -9622,14 +10129,15 @@ function renderBlocks(text) {
|
|
|
9622
10129
|
paragraph.push(line);
|
|
9623
10130
|
}
|
|
9624
10131
|
flush();
|
|
10132
|
+
if (html) out.push(html.join("\n"));
|
|
9625
10133
|
return out.join("");
|
|
9626
10134
|
}
|
|
9627
|
-
function renderInline(value) {
|
|
10135
|
+
function renderInline(value, lifted) {
|
|
9628
10136
|
const codeSpans = [];
|
|
9629
10137
|
return escapeHtml(value).replace(/`([^`\n]+)`/g, (_match, code) => {
|
|
9630
10138
|
codeSpans.push(code);
|
|
9631
10139
|
return `${INLINE_MARKER}${codeSpans.length - 1}${INLINE_MARKER}`;
|
|
9632
|
-
}).replace(/(?<!!)\[([^\]\n]+)\]\(([^)\s]+)\)/g, (match, label, href) => SAFE_HREF.test(href) ? `<a href="${href}" target="_blank" rel="noopener noreferrer">${label}</a>` : match).replace(/\*\*([^*\n]+)\*\*/g, "<strong>$1</strong>").replace(/__([^_\n]+)__/g, "<strong>$1</strong>").replace(/(^|[^*\w])\*([^*\n]+)\*/g, "$1<em>$2</em>").replace(/(^|[^_\w])_([^_\n]+)_(?![\w_])/g, "$1<em>$2</em>").replace(INLINE_TOKEN, (_match, index) => `<code>${codeSpans[Number(index)]}</code>`);
|
|
10140
|
+
}).replace(/!\[([^\]\n]*)\]\(([^)\s]+)\)/g, (match, alt, href) => lifted && !holdsMarkup(href, lifted) ? mediaAnchor(href, imageLabel(alt)) ?? match : match).replace(/(?<!!)\[([^\]\n]+)\]\(([^)\s]+)\)/g, (match, label, href) => SAFE_HREF.test(href) && !(lifted && holdsMarkup(href, lifted)) ? `<a href="${href}" target="_blank" rel="noopener noreferrer">${label}</a>` : match).replace(/\*\*([^*\n]+)\*\*/g, "<strong>$1</strong>").replace(/__([^_\n]+)__/g, "<strong>$1</strong>").replace(/(^|[^*\w])\*([^*\n]+)\*/g, "$1<em>$2</em>").replace(/(^|[^_\w])_([^_\n]+)_(?![\w_])/g, "$1<em>$2</em>").replace(INLINE_TOKEN, (_match, index) => `<code>${codeSpans[Number(index)]}</code>`);
|
|
9633
10141
|
}
|
|
9634
10142
|
//#endregion
|
|
9635
10143
|
//#region ../../packages/review-harness/src/domain/ai_review/author_description.ts
|
|
@@ -9713,7 +10221,7 @@ var ATX_HEADING = /^#{1,6}(?:\s|$)/;
|
|
|
9713
10221
|
* on what is code and what is a hidden comment.
|
|
9714
10222
|
*/
|
|
9715
10223
|
function visibleLines(description) {
|
|
9716
|
-
return scanMarkdownLines(description,
|
|
10224
|
+
return scanMarkdownLines(description, GITHUB_MARKDOWN).filter((line) => !line.absent);
|
|
9717
10225
|
}
|
|
9718
10226
|
/**
|
|
9719
10227
|
* The description's Markdown blocks, with a code block kept whole and always a block of its own.
|
|
@@ -9721,16 +10229,24 @@ function visibleLines(description) {
|
|
|
9721
10229
|
* Split where Markdown itself would end one: a blank line, a heading (always a block of its own)
|
|
9722
10230
|
* and a list that starts straight after a paragraph. Descriptions are often written with no blank
|
|
9723
10231
|
* lines at all, and on blank lines alone those would be one block. A line that is only a comment
|
|
9724
|
-
* ends a block like a blank one.
|
|
10232
|
+
* ends a block like a blank one. An HTML block runs from the line that opens it to the next blank
|
|
10233
|
+
* line whatever its lines look like, as the renderer reads it (`opensHtmlBlock`).
|
|
9725
10234
|
*/
|
|
9726
10235
|
function blocks(description) {
|
|
9727
10236
|
const result = [];
|
|
9728
10237
|
let current = [];
|
|
10238
|
+
let html = false;
|
|
9729
10239
|
const end = () => {
|
|
9730
10240
|
if (current.length) result.push(current);
|
|
9731
10241
|
current = [];
|
|
10242
|
+
html = false;
|
|
9732
10243
|
};
|
|
9733
|
-
for (const line of visibleLines(description)) if (line.
|
|
10244
|
+
for (const line of visibleLines(description)) if (html && line.visible.trim() !== "" && !line.code) current.push(line);
|
|
10245
|
+
else if (!line.code && opensHtmlBlock(line.visible)) {
|
|
10246
|
+
end();
|
|
10247
|
+
html = true;
|
|
10248
|
+
current.push(line);
|
|
10249
|
+
} else if (line.code === "open") {
|
|
9734
10250
|
end();
|
|
9735
10251
|
current.push(line);
|
|
9736
10252
|
} else if (line.code === "body") current.push(line);
|
|
@@ -9798,6 +10314,7 @@ function boundList(lines) {
|
|
|
9798
10314
|
* The first block the author wrote decides what to show; the bounds decide how much of it. A
|
|
9799
10315
|
* template's labels, comments and unticked items ahead of it are passed over, so "## Summary" opens
|
|
9800
10316
|
* on the summary itself. A heading of the author's own is kept, with the block it introduces.
|
|
10317
|
+
* Formatted HTML is passed over too, so the preview opens on words.
|
|
9801
10318
|
* A paragraph is cut after its first sentences or near a character limit, a list after its first
|
|
9802
10319
|
* items, and a long code block is left out; nothing is cut mid-word or left as open Markdown.
|
|
9803
10320
|
*
|
|
@@ -9814,6 +10331,7 @@ function authorPreview(description) {
|
|
|
9814
10331
|
const lines = all[i];
|
|
9815
10332
|
const first = lines.findIndex((line) => substance(line.visible) !== "");
|
|
9816
10333
|
if (first === -1) continue;
|
|
10334
|
+
if (opensHtmlBlock(lines[0].visible)) continue;
|
|
9817
10335
|
const raw = lines.slice(first).map((line) => line.visible);
|
|
9818
10336
|
if (raw.length === 1 && ATX_HEADING.test(lines[first].visible.trim())) {
|
|
9819
10337
|
shown.push(raw[0]);
|
|
@@ -13818,4 +14336,4 @@ function lineageIds(id) {
|
|
|
13818
14336
|
return [.../* @__PURE__ */ new Set([id, ...lineage.map((review) => review.id)])];
|
|
13819
14337
|
}
|
|
13820
14338
|
//#endregion
|
|
13821
|
-
export {
|
|
14339
|
+
export { asString as $, resolveAgent as $n, driverGone as $t, createPrState as A, offersLifecycle as An, statedWorkspacePort as At, toneOf as B, isQuestionOutstanding as Bn, reviewedShaOf as Bt, readEvents as C, workspaceStamps as Cn, PACKAGE_NAME as Ct, announceJob as D, lifecycleAction as Dn, MIN_WORKSPACE_PORT as Dt, telemetryUploadConsent as E, isLifecycle as En, MAX_WORKSPACE_PORT as Et, markAllRead as F, queueRemove as Fn, remoteHost as Ft, authorPreview as G, recommendationsOf as Gn, loadJob as Gt, withUsageRecorded as H, questionsOutstanding as Hn, endedAt as Ht, markRead as I, queueStamp as In, fillFileUrlTemplate as It, renderMarkdown as J, kissOutcome as Jn, isLiveJob as Jt, hasAuthorContent as K, describeAuthorship as Kn, saveJob as Kt, pruneNotifications as L, readQueue as Ln, checkCodeFreshness as Lt, eventName as M, sectionOf as Mn, claimRequestUrl as Mt, headline as N, queueAdd as Nn, forgeResolver as Nt, branchOf as O, lifecycleChip as On, parseWorkspacePort as Ot, listNotifications as P, queueAddMany as Pn, forgeFor as Pt, renderInlineText as Q, git as Qn, clearClaim as Qt, readNotification as R, doneVerb as Rn, checkReview as Rt, startJob as S, workspaceRevision as Sn, BUILD_VERSION as St, recordTelemetryUploadConsent as T, activityLabel as Tn, DEFAULT_WORKSPACE_PORT as Tt, EXPLAIN_SIMPLY_PROMPT as U, reviewerDispositions as Un, fail as Ut, unreadCount as V, issuesOutstanding as Vn, allJobs as Vt, UNESTABLISHED_MOTIVATION as W, processDiscussion as Wn, isTerminal as Wt, CODE_COPY_CLASS as X, displayRef as Xn, progressSteps as Xt, CODE_BLOCK_CLASS as Y, resolveTarget as Yn, phasesFor as Yt, escapeHtml as Z, isCommitReview as Zn, purposeOf as Zt, agentEnvOf as _, recentWorkspaces as _n, serverEntryExists as _t, wasBlocked as a, indexReviewGroups as an, STORE_ROOT as ar, modelHelpLines as at, reanalyseReview as b, summarise as bn, workspaceUrl as bt, RefreshUnavailableError as c, latestKissRun as cn, agentById as cr, compactQueue as ct, refreshPrContext as d, loadWorkspace as dn, AgentCancelledError as dr, readIndexToken as dt, isClaimed as en, resolveModel as er, extractJsonText as et, isUnchanged as f, matchingWorkspaceIds as fn, AgentUnavailableError as fr, bootstrapUrl as ft, runningJobs as g, previousKissResult as gn, reviewsUrl as gt, liveUpdateFor as h, presentedKissRunOf as hn, readServerRecord as ht, liveJobsFor as i, indexKeptIds as in, CONFIG_PATH as ir, removeWorktree as it, createPrUpdate as j, parseLifecycle as jn, writePortPreference as jt, subjectOf as k, lifecyclePill as kn, resolveWorkspacePort as kt, mergeUpdateInto as l, lineageKeyFor as ln, agentFor as lr, queueForReview as lt, UpdateAlreadyRunningError as m, positionInLineage as mn, ensureServer as mt, deleteReview as n, followedRefName as nn, readAgentPreference as nr, MANAGED_ROOT as nt, discardJob as o, kissDecisionRefusal as on, AGENT_IDS as or, relativeTime as ot, askCheckout as p, mutateWorkspace as pn, clearServerRecord as pt, GITHUB_MARKDOWN as q, WITHHELD_REASON as qn, PHASES as qt, lineageIds as r, groupByLineage as rn, writeAgentPreference as rr, readMarker as rt, FETCH_TIMEOUT_MS as s, kissDecisionsOf as sn, DEFAULT_AGENT_ID as sr, activateQueued as st, deleteLineage as t, decideKissRecommendation as tn, clearAgentPreference as tr, ReviewBeingDeletedError as tt, updateReview as u, lineagePosition as un, detectAgents as ur, settleQueue as ut, recordKissRun as v, reviewedCommit as vn, spawnServer as vt, record as w, LIFECYCLES as wn, feedbackUrl as wt, runJob as x, touchWorkspace as xn, writeServerRecord as xt, startKissJob as y, reviewedRepositories as yn, stopServer as yt, removeNotification as z, isIssueOutstanding as zn, followedBranch as zt };
|