@kitlangton/knobs 0.4.0-next.8 → 0.4.0-next.9
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 +8 -4
- package/dist/review-react.js +18579 -0
- package/dist/review-solid.js +18481 -0
- package/dist/review.js +18487 -0
- package/dist/themes.js +121 -23
- package/dist/types/review/font.d.ts +13 -0
- package/dist/types/review/foundations.d.ts +175 -0
- package/dist/types/review/index.d.ts +13 -0
- package/dist/types/review/react.d.ts +1 -0
- package/dist/types/review/solid.d.ts +1 -0
- package/dist/types/themes/index.d.ts +7 -2
- package/dist/types/themes/review.d.ts +8 -0
- package/dist/types/ui/history-css.d.ts +1 -1
- package/dist/types/ui/index.d.ts +2 -2
- package/dist/types/ui/review-look.d.ts +6 -0
- package/dist/types/ui/review-options.d.ts +28 -0
- package/dist/types/ui/selection-pill.d.ts +3 -0
- package/dist/types/ui/walkthrough-media.d.ts +24 -1
- package/dist/types/ui/walkthrough-model.d.ts +87 -2
- package/dist/types/ui/walkthrough.d.ts +2 -2
- package/dist/ui-react.js +1924 -1568
- package/dist/ui-solid.js +1924 -1568
- package/dist/ui.js +590 -211
- package/package.json +13 -1
package/dist/ui-solid.js
CHANGED
|
@@ -4205,7 +4205,7 @@ function planPill(state, next, flags = {}) {
|
|
|
4205
4205
|
if (t && t.x === next.x && t.y === next.y && t.w === next.w && t.h === next.h)
|
|
4206
4206
|
return { kind: "stay" };
|
|
4207
4207
|
const d = Math.hypot(next.x - state.at.x, next.y - state.at.y);
|
|
4208
|
-
return { kind: "travel", box: next, spring: pillSpring(d) };
|
|
4208
|
+
return { kind: "travel", box: next, spring: pillSpring(d, flags.spring) };
|
|
4209
4209
|
}
|
|
4210
4210
|
function selectionPill(container, options) {
|
|
4211
4211
|
const doc = container.ownerDocument;
|
|
@@ -4229,7 +4229,7 @@ function selectionPill(container, options) {
|
|
|
4229
4229
|
container.prepend(pill);
|
|
4230
4230
|
if (copyOn)
|
|
4231
4231
|
container.append(copy);
|
|
4232
|
-
const { omega, zeta } = springMotion(SPRINGS.default);
|
|
4232
|
+
const { omega, zeta } = springMotion(options.spring ?? SPRINGS.default);
|
|
4233
4233
|
const springs = [0, 0, 0, 0].map(() => new Spring(0, omega, zeta, 0.01));
|
|
4234
4234
|
const [sx, sy, sw, sh] = springs;
|
|
4235
4235
|
let shown = false;
|
|
@@ -4330,7 +4330,8 @@ function selectionPill(container, options) {
|
|
|
4330
4330
|
const plan = planPill({ shown, at: shown ? at() : null, target }, next, {
|
|
4331
4331
|
first,
|
|
4332
4332
|
reduced: reducedMotion(win),
|
|
4333
|
-
...o?.snap ? { snap: true } : {}
|
|
4333
|
+
...o?.snap ? { snap: true } : {},
|
|
4334
|
+
...options.spring ? { spring: options.spring } : {}
|
|
4334
4335
|
});
|
|
4335
4336
|
first = false;
|
|
4336
4337
|
for (const n of clones.values())
|
|
@@ -11380,6 +11381,7 @@ function compare(container, options) {
|
|
|
11380
11381
|
let loupeHeld = false;
|
|
11381
11382
|
let pointer = null;
|
|
11382
11383
|
let drag = null;
|
|
11384
|
+
let tap = null;
|
|
11383
11385
|
const touches = new Map;
|
|
11384
11386
|
let pinch = null;
|
|
11385
11387
|
const zu = new Spring(0, ZOOM.omega, ZOOM.zeta, 0.00001);
|
|
@@ -12211,15 +12213,18 @@ function compare(container, options) {
|
|
|
12211
12213
|
touches.set(e.pointerId, p);
|
|
12212
12214
|
stage.setPointerCapture(e.pointerId);
|
|
12213
12215
|
if (touches.size >= 2) {
|
|
12216
|
+
tap = null;
|
|
12214
12217
|
e.preventDefault();
|
|
12215
12218
|
return startPinch();
|
|
12216
12219
|
}
|
|
12220
|
+
tap = e.target === handle ? null : { id: e.pointerId, x: p.x, y: p.y };
|
|
12217
12221
|
if (mode === "wipe") {
|
|
12218
12222
|
const wr = wipeRect(layoutNow().L.overlay);
|
|
12219
12223
|
const inside = p.x >= wr.x - SLOP && p.x <= wr.x + wr.w + SLOP && p.y >= wr.y && p.y <= wr.y + wr.h;
|
|
12220
12224
|
const grab = wipeGrab(p.x, wr, wipeLine.x, SLOP);
|
|
12221
12225
|
if (inside && (grab.onLine || !zoomed())) {
|
|
12222
12226
|
drag = { kind: "wipe", offset: grab.offset };
|
|
12227
|
+
tap = null;
|
|
12223
12228
|
root.dataset.dragging = "";
|
|
12224
12229
|
gripAt.snap(wr.h > 0 ? (p.y - wr.y) / wr.h : 0.5);
|
|
12225
12230
|
e.preventDefault();
|
|
@@ -12239,6 +12244,8 @@ function compare(container, options) {
|
|
|
12239
12244
|
const onMove = (e) => {
|
|
12240
12245
|
const p = local(e);
|
|
12241
12246
|
pointer = p;
|
|
12247
|
+
if (tap && Math.hypot(p.x - tap.x, p.y - tap.y) > TAP_SLOP)
|
|
12248
|
+
tap = null;
|
|
12242
12249
|
if (touches.has(e.pointerId))
|
|
12243
12250
|
touches.set(e.pointerId, p);
|
|
12244
12251
|
if (pinch && touches.size >= 2) {
|
|
@@ -12271,6 +12278,11 @@ function compare(container, options) {
|
|
|
12271
12278
|
touches.delete(e.pointerId);
|
|
12272
12279
|
if (touches.size < 2)
|
|
12273
12280
|
pinch = null;
|
|
12281
|
+
if (tap?.id === e.pointerId) {
|
|
12282
|
+
tap = null;
|
|
12283
|
+
if (e.type === "pointerup" && sides.some((s) => s.kind === "video"))
|
|
12284
|
+
api.toggle();
|
|
12285
|
+
}
|
|
12274
12286
|
if (!drag)
|
|
12275
12287
|
return;
|
|
12276
12288
|
if (drag.kind === "wipe") {
|
|
@@ -12672,7 +12684,7 @@ function compare(container, options) {
|
|
|
12672
12684
|
wake();
|
|
12673
12685
|
return api;
|
|
12674
12686
|
}
|
|
12675
|
-
var LAYOUT2, FADE6, FADE_REDUCED, ZOOM, WIPE_CATCH, SLOP = 12, INSET = 8, GRIP, MARK, LOUPE, FAST_SEEK_FRAMES = 15, RELEASE_MS = 3000, MODE_LABEL;
|
|
12687
|
+
var LAYOUT2, FADE6, FADE_REDUCED, ZOOM, WIPE_CATCH, SLOP = 12, TAP_SLOP = 4, INSET = 8, GRIP, MARK, LOUPE, FAST_SEEK_FRAMES = 15, RELEASE_MS = 3000, MODE_LABEL;
|
|
12676
12688
|
var init_compare = __esm(() => {
|
|
12677
12689
|
init_motion();
|
|
12678
12690
|
init_page_hints();
|
|
@@ -12823,7 +12835,10 @@ function player(container, options) {
|
|
|
12823
12835
|
return tl.time;
|
|
12824
12836
|
return v.ended ? Number.POSITIVE_INFINITY : v.currentTime;
|
|
12825
12837
|
});
|
|
12826
|
-
const onClick = () =>
|
|
12838
|
+
const onClick = () => {
|
|
12839
|
+
tl.focus();
|
|
12840
|
+
tl.toggle();
|
|
12841
|
+
};
|
|
12827
12842
|
frame.addEventListener("click", onClick);
|
|
12828
12843
|
v.src = options.src;
|
|
12829
12844
|
const api = {
|
|
@@ -17298,1002 +17313,480 @@ function confirm(options) {
|
|
|
17298
17313
|
// src/ui/walkthrough.ts
|
|
17299
17314
|
init_icons();
|
|
17300
17315
|
init_look();
|
|
17301
|
-
init_selection_pill();
|
|
17302
17316
|
|
|
17303
|
-
// src/
|
|
17304
|
-
|
|
17305
|
-
|
|
17306
|
-
var
|
|
17307
|
-
|
|
17308
|
-
|
|
17309
|
-
|
|
17310
|
-
|
|
17311
|
-
|
|
17312
|
-
|
|
17313
|
-
|
|
17314
|
-
|
|
17315
|
-
|
|
17316
|
-
|
|
17317
|
+
// src/review/foundations.ts
|
|
17318
|
+
init_motion();
|
|
17319
|
+
var TRACKING = 0.03;
|
|
17320
|
+
var MEASURE = "66ch";
|
|
17321
|
+
var FONT = {
|
|
17322
|
+
sans: 'system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI Variable Text","Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif',
|
|
17323
|
+
mono: 'ui-monospace,"SF Mono",SFMono-Regular,"Cascadia Mono","JetBrains Mono",Menlo,Consolas,monospace'
|
|
17324
|
+
};
|
|
17325
|
+
var FEATURES = {
|
|
17326
|
+
figures: "tabular-nums",
|
|
17327
|
+
data: '"tnum" 1,"ss01" 1',
|
|
17328
|
+
code: "none"
|
|
17329
|
+
};
|
|
17330
|
+
var MOTION2 = [
|
|
17331
|
+
{
|
|
17332
|
+
name: "hover",
|
|
17333
|
+
spring: null,
|
|
17334
|
+
ms: "0 in, 120 out",
|
|
17335
|
+
use: "A hover's fill: on at once, off softly"
|
|
17336
|
+
},
|
|
17337
|
+
{
|
|
17338
|
+
name: "quick",
|
|
17339
|
+
spring: FADE,
|
|
17340
|
+
use: "Replacing a whole thing in place: fade, scale .98, blur 3 px"
|
|
17341
|
+
},
|
|
17342
|
+
{ name: "close", spring: CLOSE, use: "A popover closing" },
|
|
17343
|
+
{ name: "snappy", spring: SPRINGS.snappy, use: "Controls: a toggle, a pick, a chapter row" },
|
|
17344
|
+
{ name: "state", spring: SPRINGS.default, use: "The selection pill, a disclosure opening" },
|
|
17345
|
+
{
|
|
17346
|
+
name: "calm",
|
|
17347
|
+
spring: SPRINGS.calm,
|
|
17348
|
+
use: "Layout: a compare's height, the page scrolling to a step"
|
|
17349
|
+
},
|
|
17350
|
+
{ name: "smooth", spring: SPRINGS.smooth, use: "A step opening to the full window" }
|
|
17351
|
+
];
|
|
17352
|
+
var foundationCss = `
|
|
17353
|
+
:where(.knobs-walkthrough,.knobs-wt-slot,.knobs-review){
|
|
17354
|
+
--_kr-track:calc((var(--_k-type-body) - 1em) * ${TRACKING});
|
|
17355
|
+
--_kr-measure:var(--k-walkthrough-measure,${MEASURE});
|
|
17356
|
+
--_kr-block:calc(var(--_k-space-title) * 2);
|
|
17357
|
+
--_kr-data:${FEATURES.data};
|
|
17358
|
+
letter-spacing:var(--_kr-track);font-variant-numeric:${FEATURES.figures};font-optical-sizing:auto;font-kerning:normal}
|
|
17359
|
+
:where(.knobs-walkthrough,.knobs-wt-slot,.knobs-review) :where(*){letter-spacing:var(--_kr-track)}
|
|
17360
|
+
:where(.knobs-walkthrough,.knobs-wt-slot,.knobs-review) :where(code,pre,kbd,samp,.knobs-wt-mono){letter-spacing:0;font-variant-ligatures:${FEATURES.code}}
|
|
17361
|
+
`;
|
|
17362
|
+
|
|
17363
|
+
// src/themes/review.ts
|
|
17364
|
+
init_define();
|
|
17365
|
+
|
|
17366
|
+
// src/themes/void.ts
|
|
17367
|
+
init_define();
|
|
17368
|
+
init_knobs();
|
|
17369
|
+
var voidTheme = defineTheme({
|
|
17370
|
+
name: "void",
|
|
17371
|
+
extends: knobs,
|
|
17372
|
+
gray: { hue: 0, chroma: 0 },
|
|
17373
|
+
accent: { dark: "#ffffff", light: "#1c1c1c" },
|
|
17374
|
+
material: "flat",
|
|
17375
|
+
tokens: {
|
|
17376
|
+
dark: {
|
|
17377
|
+
"bg-page": "#000000",
|
|
17378
|
+
"bg-inset": "#0a0a0a",
|
|
17379
|
+
bg: "#000000",
|
|
17380
|
+
"bg-raised": "#1c1c1e",
|
|
17381
|
+
"bg-overlay": "#2c2c2e",
|
|
17382
|
+
fg: "#ededed",
|
|
17383
|
+
"fg-2": "#cecece",
|
|
17384
|
+
"fg-3": "#989898",
|
|
17385
|
+
"fg-4": "#7a7a7a",
|
|
17386
|
+
"compare-stage": "#000000"
|
|
17317
17387
|
}
|
|
17318
|
-
el.setAttribute("role", "status");
|
|
17319
|
-
el.setAttribute("aria-live", "polite");
|
|
17320
|
-
const text = doc.createElement("span");
|
|
17321
|
-
text.className = "knobs-toast-text";
|
|
17322
|
-
const button = doc.createElement("button");
|
|
17323
|
-
button.type = "button";
|
|
17324
|
-
button.className = "knobs-toast-action";
|
|
17325
|
-
el.append(text, button);
|
|
17326
|
-
const region = {
|
|
17327
|
-
el,
|
|
17328
|
-
text,
|
|
17329
|
-
button,
|
|
17330
|
-
motion: presence(el, { scale: 0.96, blur: 4, dy: 10, duration: 0.36 }),
|
|
17331
|
-
timer: 0
|
|
17332
|
-
};
|
|
17333
|
-
r = region;
|
|
17334
|
-
regions.set(key, region);
|
|
17335
|
-
el.addEventListener("pointerenter", () => win.clearTimeout(region.timer));
|
|
17336
|
-
el.addEventListener("pointerleave", () => {
|
|
17337
|
-
if (region.motion.shown)
|
|
17338
|
-
arm(region, 1600);
|
|
17339
|
-
});
|
|
17340
17388
|
}
|
|
17341
|
-
|
|
17342
|
-
|
|
17343
|
-
|
|
17344
|
-
|
|
17345
|
-
|
|
17346
|
-
|
|
17347
|
-
|
|
17348
|
-
|
|
17349
|
-
|
|
17350
|
-
|
|
17351
|
-
|
|
17352
|
-
|
|
17353
|
-
|
|
17354
|
-
|
|
17355
|
-
|
|
17356
|
-
|
|
17357
|
-
|
|
17358
|
-
|
|
17359
|
-
|
|
17360
|
-
|
|
17361
|
-
|
|
17362
|
-
|
|
17389
|
+
});
|
|
17390
|
+
|
|
17391
|
+
// src/themes/review.ts
|
|
17392
|
+
var reviewTheme = defineTheme({
|
|
17393
|
+
name: "review",
|
|
17394
|
+
extends: voidTheme,
|
|
17395
|
+
font: FONT,
|
|
17396
|
+
tokens: {
|
|
17397
|
+
dark: {
|
|
17398
|
+
fill: "#ededed0f",
|
|
17399
|
+
"fill-hover": "#ededed1a",
|
|
17400
|
+
"fill-selected": "#ededed24",
|
|
17401
|
+
"border-subtle": "#ededed47",
|
|
17402
|
+
border: "#ededed4f",
|
|
17403
|
+
"border-strong": "#585858"
|
|
17404
|
+
},
|
|
17405
|
+
light: {
|
|
17406
|
+
"bg-page": "#ffffff",
|
|
17407
|
+
"bg-inset": "#f7f7f7",
|
|
17408
|
+
bg: "#ffffff",
|
|
17409
|
+
"bg-raised": "#ffffff",
|
|
17410
|
+
"bg-overlay": "#ffffff",
|
|
17411
|
+
fill: "#1919190a",
|
|
17412
|
+
"fill-hover": "#19191912",
|
|
17413
|
+
"fill-selected": "#1919191c",
|
|
17414
|
+
"border-subtle": "#19191917",
|
|
17415
|
+
border: "#1919191d",
|
|
17416
|
+
"border-strong": "#dcdcdc"
|
|
17363
17417
|
}
|
|
17364
|
-
}
|
|
17418
|
+
}
|
|
17419
|
+
});
|
|
17420
|
+
|
|
17421
|
+
// src/ui/review-look.ts
|
|
17422
|
+
var MODES2 = { dark: "dark", light: "light", auto: "system" };
|
|
17423
|
+
var THEMED = '[data-knobs-theme]:not([data-knobs-theme="dark"],[data-knobs-theme="light"])';
|
|
17424
|
+
function reviewLook(options, container) {
|
|
17425
|
+
const t = options.theme;
|
|
17426
|
+
if (typeof t === "object")
|
|
17427
|
+
return options;
|
|
17428
|
+
if (!t && container?.closest(THEMED))
|
|
17429
|
+
return options;
|
|
17430
|
+
const appearance = options.appearance ?? (t ? MODES2[t] : undefined);
|
|
17431
|
+
return { ...options, theme: reviewTheme, ...appearance ? { appearance } : {} };
|
|
17365
17432
|
}
|
|
17366
17433
|
|
|
17367
|
-
// src/ui/walkthrough-
|
|
17368
|
-
var
|
|
17369
|
-
|
|
17370
|
-
|
|
17371
|
-
var ARROW = '<svg class="knobs-wt-anno-arrow" viewBox="0 0 28 28" width="28" height="28" aria-hidden="true"><path d="M3 3 L24 24 M24 24 L24 13 M24 24 L13 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
17372
|
-
var shapeOf = (a) => a.shape ?? (a.w !== undefined && a.h !== undefined ? "box" : "dot");
|
|
17373
|
-
var showsAt = (a, time) => !a.at || time === undefined || time >= a.at[0] && time <= a.at[1];
|
|
17374
|
-
function placeMark(a, rect, view = ALL) {
|
|
17375
|
-
const w = a.w ?? 0;
|
|
17376
|
-
const h = a.h ?? 0;
|
|
17377
|
-
const x = (a.x - view.u) / view.w;
|
|
17378
|
-
const y = (a.y - view.v) / view.h;
|
|
17379
|
-
const ww = w / view.w;
|
|
17380
|
-
const hh = h / view.h;
|
|
17381
|
-
if (x + ww < 0 || y + hh < 0 || x > 1 || y > 1)
|
|
17382
|
-
return null;
|
|
17383
|
-
return { x: rect.x + x * rect.w, y: rect.y + y * rect.h, w: ww * rect.w, h: hh * rect.h };
|
|
17434
|
+
// src/ui/walkthrough-model.ts
|
|
17435
|
+
var DEFAULT_VERDICTS = ["ship", "change", "unsure"];
|
|
17436
|
+
function slug(text) {
|
|
17437
|
+
return text.toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/^(.{0,48})-.*$/, (m, head) => m.length > 48 && head ? head : m).slice(0, 48) || "item";
|
|
17384
17438
|
}
|
|
17385
|
-
|
|
17386
|
-
|
|
17387
|
-
|
|
17388
|
-
|
|
17389
|
-
|
|
17390
|
-
|
|
17391
|
-
|
|
17392
|
-
scope.dataset.annotations = "off";
|
|
17393
|
-
for (const b of Array.from(scope.querySelectorAll(".knobs-wt-anno-toggle")))
|
|
17394
|
-
syncToggle(b, on);
|
|
17439
|
+
function uniqueIds() {
|
|
17440
|
+
const seen = new Map;
|
|
17441
|
+
return (id) => {
|
|
17442
|
+
const n = (seen.get(id) ?? 0) + 1;
|
|
17443
|
+
seen.set(id, n);
|
|
17444
|
+
return n === 1 ? id : `${id}-${n}`;
|
|
17445
|
+
};
|
|
17395
17446
|
}
|
|
17396
|
-
|
|
17397
|
-
|
|
17398
|
-
|
|
17399
|
-
|
|
17447
|
+
var label = (id) => id.charAt(0).toUpperCase() + id.slice(1).replace(/[-_]+/g, " ");
|
|
17448
|
+
function resolveSrc(src, base) {
|
|
17449
|
+
if (!base || /^([a-z][a-z0-9+.-]*:|\/\/|\/)/i.test(src))
|
|
17450
|
+
return src;
|
|
17451
|
+
return base.replace(/\/?$/, "/") + src.replace(/^\.\//, "");
|
|
17400
17452
|
}
|
|
17401
|
-
|
|
17402
|
-
|
|
17403
|
-
|
|
17404
|
-
|
|
17405
|
-
|
|
17406
|
-
|
|
17407
|
-
|
|
17408
|
-
|
|
17409
|
-
|
|
17410
|
-
|
|
17411
|
-
|
|
17412
|
-
|
|
17413
|
-
|
|
17414
|
-
|
|
17415
|
-
|
|
17416
|
-
}
|
|
17417
|
-
|
|
17418
|
-
|
|
17419
|
-
|
|
17420
|
-
|
|
17421
|
-
|
|
17422
|
-
|
|
17423
|
-
|
|
17424
|
-
|
|
17425
|
-
|
|
17426
|
-
|
|
17427
|
-
|
|
17428
|
-
|
|
17429
|
-
|
|
17430
|
-
|
|
17431
|
-
m.append(l);
|
|
17432
|
-
}
|
|
17433
|
-
layer.append(m);
|
|
17434
|
-
return m;
|
|
17435
|
-
};
|
|
17436
|
-
let settled = false;
|
|
17437
|
-
const update = () => {
|
|
17438
|
-
const p = place();
|
|
17439
|
-
if (marks.length !== p.rects.length) {
|
|
17440
|
-
for (const row of marks)
|
|
17441
|
-
for (const m of row)
|
|
17442
|
-
m.remove();
|
|
17443
|
-
marks = p.rects.map(() => list.map(make));
|
|
17444
|
-
}
|
|
17445
|
-
p.rects.forEach((rect, i) => {
|
|
17446
|
-
list.forEach((a, j) => {
|
|
17447
|
-
const m = marks[i][j];
|
|
17448
|
-
const box = placeMark(a, rect, p.view);
|
|
17449
|
-
const show = box !== null && showsAt(a, p.time);
|
|
17450
|
-
if (box) {
|
|
17451
|
-
m.style.transform = `translate(${box.x.toFixed(2)}px,${box.y.toFixed(2)}px)`;
|
|
17452
|
-
m.style.width = `${box.w.toFixed(2)}px`;
|
|
17453
|
-
m.style.height = `${box.h.toFixed(2)}px`;
|
|
17454
|
-
}
|
|
17455
|
-
m.toggleAttribute("data-hidden", !show);
|
|
17456
|
-
});
|
|
17457
|
-
});
|
|
17458
|
-
if (!settled) {
|
|
17459
|
-
settled = true;
|
|
17460
|
-
requestAnimationFrame(() => requestAnimationFrame(() => layer.dataset.settled = ""));
|
|
17453
|
+
var VIDEO2 = /\.(mp4|m4v|webm|mov|ogv)(\?|#|$)/i;
|
|
17454
|
+
var mediaOf = (src) => VIDEO2.test(src) ? { kind: "video", src } : { kind: "image", src };
|
|
17455
|
+
function parseAspect(aspect) {
|
|
17456
|
+
if (aspect === undefined)
|
|
17457
|
+
return;
|
|
17458
|
+
if (typeof aspect === "number")
|
|
17459
|
+
return aspect > 0 && Number.isFinite(aspect) ? aspect : undefined;
|
|
17460
|
+
const m = /^\s*([\d.]+)\s*(?:[/x:×]\s*([\d.]+))?\s*$/.exec(aspect);
|
|
17461
|
+
if (!m)
|
|
17462
|
+
return;
|
|
17463
|
+
const w = Number(m[1]);
|
|
17464
|
+
const h = m[2] === undefined ? 1 : Number(m[2]);
|
|
17465
|
+
return w > 0 && h > 0 ? w / h : undefined;
|
|
17466
|
+
}
|
|
17467
|
+
function source2(s, base) {
|
|
17468
|
+
const v = typeof s === "string" ? { src: s } : s;
|
|
17469
|
+
return { ...v, src: resolveSrc(v.src, base) };
|
|
17470
|
+
}
|
|
17471
|
+
function fillMedia(m, base) {
|
|
17472
|
+
if (typeof m === "string")
|
|
17473
|
+
return mediaOf(resolveSrc(m, base));
|
|
17474
|
+
switch (m.kind) {
|
|
17475
|
+
case "compare": {
|
|
17476
|
+
const { chapters, ...rest } = m;
|
|
17477
|
+
return {
|
|
17478
|
+
...rest,
|
|
17479
|
+
before: source2(m.before, base),
|
|
17480
|
+
after: source2(m.after, base),
|
|
17481
|
+
...chapters?.length ? { chapters: fillChapters(chapters) } : {}
|
|
17482
|
+
};
|
|
17461
17483
|
}
|
|
17462
|
-
|
|
17463
|
-
|
|
17464
|
-
|
|
17465
|
-
|
|
17466
|
-
|
|
17467
|
-
|
|
17468
|
-
|
|
17469
|
-
|
|
17470
|
-
|
|
17471
|
-
|
|
17472
|
-
toggle.remove();
|
|
17484
|
+
case "video": {
|
|
17485
|
+
const { chapters, captionsSrc, captions, ...rest } = m;
|
|
17486
|
+
const vtt = captions ?? captionsSrc;
|
|
17487
|
+
return {
|
|
17488
|
+
...rest,
|
|
17489
|
+
src: resolveSrc(m.src, base),
|
|
17490
|
+
...m.poster ? { poster: resolveSrc(m.poster, base) } : {},
|
|
17491
|
+
...vtt ? { captions: resolveSrc(vtt, base) } : {},
|
|
17492
|
+
...chapters?.length ? { chapters: fillChapters(chapters) } : {}
|
|
17493
|
+
};
|
|
17473
17494
|
}
|
|
17474
|
-
|
|
17475
|
-
}
|
|
17476
|
-
|
|
17477
|
-
|
|
17478
|
-
|
|
17479
|
-
|
|
17480
|
-
|
|
17481
|
-
|
|
17482
|
-
|
|
17483
|
-
|
|
17484
|
-
|
|
17485
|
-
|
|
17486
|
-
|
|
17487
|
-
|
|
17488
|
-
|
|
17489
|
-
|
|
17490
|
-
|
|
17491
|
-
|
|
17492
|
-
|
|
17493
|
-
|
|
17494
|
-
|
|
17495
|
-
:
|
|
17496
|
-
|
|
17497
|
-
|
|
17498
|
-
.
|
|
17499
|
-
.
|
|
17500
|
-
.
|
|
17501
|
-
|
|
17502
|
-
|
|
17503
|
-
|
|
17504
|
-
|
|
17505
|
-
|
|
17506
|
-
|
|
17507
|
-
|
|
17508
|
-
|
|
17509
|
-
.knobs-hy-blabel{top:0;padding-left:4px;overflow:hidden;color:var(--_k-fg-2)}
|
|
17510
|
-
.knobs-hy-label{grid-column:1;align-self:center;min-width:0;margin-right:var(--_k-space-title);overflow:hidden;white-space:nowrap;
|
|
17511
|
-
font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
17512
|
-
.knobs-history[data-narrow] .knobs-hy-label{grid-column:2;align-self:end;margin:0}
|
|
17513
|
-
:is(.knobs-hy-label,.knobs-hy-blabel)[data-overflow]{-webkit-mask-image:var(--hy-fade);mask-image:var(--hy-fade)}
|
|
17514
|
-
.knobs-hy-track{position:relative;grid-column:2;height:var(--hy-lane)}
|
|
17515
|
-
.knobs-hy-layer{position:absolute;inset:0;grid-column:2;grid-row:1/-1;pointer-events:none}
|
|
17516
|
-
|
|
17517
|
-
/* Bands and month lines behind the lanes; today, the selection and the pointer's line over them. */
|
|
17518
|
-
.knobs-hy-band{position:absolute;top:0;bottom:0;background:color-mix(in oklch,var(--_k-fg) 3.5%,transparent);box-shadow:inset 1px 0 0 var(--_k-border-subtle)}
|
|
17519
|
-
:is(.knobs-hy-grid,.knobs-hy-today,.knobs-hy-hover,.knobs-hy-sel){position:absolute;left:0;top:calc(var(--hy-bandrow) + var(--hy-axis));bottom:0;width:1px;margin-left:-.5px}
|
|
17520
|
-
.knobs-hy-grid{background:var(--_k-border-subtle)}
|
|
17521
|
-
.knobs-hy-today{top:calc(var(--hy-bandrow) + var(--hy-axis) - 4px);background:var(--_k-accent)}
|
|
17522
|
-
.knobs-hy-hover{background:color-mix(in oklch,var(--_k-fg) 32%,transparent);opacity:0;transition:opacity calc(var(--_k-hover-out) * 1ms)}
|
|
17523
|
-
.knobs-hy-hover[data-on]{opacity:1;transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
17524
|
-
.knobs-hy-sel{background:var(--_k-fg-2);opacity:0}
|
|
17525
|
-
.knobs-hy-sel[data-on]{opacity:.6}
|
|
17526
|
-
|
|
17527
|
-
/* Spans: a soft bar in their state's tone, inferred ones hatched; the tag at the end. */
|
|
17528
|
-
.knobs-hy-span{position:absolute;top:calc((var(--hy-lane) - var(--hy-bar)) / 2);height:var(--hy-bar);border-radius:var(--_k-radius-sm);cursor:pointer;
|
|
17529
|
-
background:color-mix(in oklch,var(--hy-c) 24%,transparent);transition:background calc(var(--_k-hover-out) * 1ms),box-shadow calc(var(--_k-hover-out) * 1ms)}
|
|
17530
|
-
.knobs-hy-span[data-inferred]{background:repeating-linear-gradient(135deg,color-mix(in oklch,var(--hy-c) 34%,transparent) 0 2px,color-mix(in oklch,var(--hy-c) 9%,transparent) 2px 6px)}
|
|
17531
|
-
.knobs-hy-span[data-hover]{background-color:color-mix(in oklch,var(--hy-c) 34%,transparent);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
17532
|
-
.knobs-hy-span[data-selected]{box-shadow:inset 0 0 0 1px var(--hy-c)}
|
|
17533
|
-
.knobs-hy-span[data-clip-start]{border-top-left-radius:0;border-bottom-left-radius:0;-webkit-mask-image:linear-gradient(to right,transparent,#000 16px);mask-image:linear-gradient(to right,transparent,#000 16px)}
|
|
17534
|
-
.knobs-hy-span[data-clip-end]{border-top-right-radius:0;border-bottom-right-radius:0;-webkit-mask-image:linear-gradient(to left,transparent,#000 16px);mask-image:linear-gradient(to left,transparent,#000 16px)}
|
|
17535
|
-
.knobs-hy-tag{position:absolute;top:0;right:6px;font-size:var(--_k-type-small);line-height:var(--hy-bar);white-space:nowrap;pointer-events:none;
|
|
17536
|
-
color:color-mix(in oklch,var(--hy-c) 55%,var(--_k-fg))}
|
|
17537
|
-
.knobs-hy-tag[data-place="after"]{right:auto;left:calc(100% + 6px)}
|
|
17538
|
-
.knobs-hy-tag[data-place="none"]{visibility:hidden}
|
|
17539
|
-
|
|
17540
|
-
/* Event marks (their kind named in the tooltip): a dot for a commit, a diamond for a release, a cross for a deletion, a square for a
|
|
17541
|
-
channel's switch, a triangle for a finding; inferred ones hollow. */
|
|
17542
|
-
.knobs-hy-ev{position:absolute;top:0;width:16px;height:var(--hy-lane);margin-left:-8px;cursor:pointer;--hy-c:var(--_k-fg-2);--hy-s:9px}
|
|
17543
|
-
.knobs-hy-ev::before{content:"";position:absolute;left:50%;top:50%;width:var(--hy-s);height:var(--hy-s);margin:calc(var(--hy-s) / -2) 0 0 calc(var(--hy-s) / -2);
|
|
17544
|
-
border-radius:50%;background:var(--hy-c);transition:scale calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy)}
|
|
17545
|
-
.knobs-hy-ev[data-range]{width:calc(var(--hy-w) + 16px)}
|
|
17546
|
-
.knobs-hy-ev[data-range]::before{left:calc(8px - var(--hy-s) / 2);right:calc(8px - var(--hy-s) / 2);width:auto;margin-left:0;border-radius:var(--_k-round)}
|
|
17547
|
-
.knobs-hy-ev[data-type="release"]{--hy-s:8px}
|
|
17548
|
-
.knobs-hy-ev[data-type="release"]::before{border-radius:1.5px;rotate:45deg}
|
|
17549
|
-
.knobs-hy-ev[data-type="channel"]{--hy-s:8px}
|
|
17550
|
-
.knobs-hy-ev[data-type="channel"]::before{border-radius:2px}
|
|
17551
|
-
.knobs-hy-ev[data-type="audit"]{--hy-s:10px}
|
|
17552
|
-
.knobs-hy-ev[data-type="audit"]::before{border-radius:0;clip-path:polygon(50% 4%,100% 92%,0 92%)}
|
|
17553
|
-
.knobs-hy-ev[data-type="deletion"]::before{border-radius:0;rotate:45deg;
|
|
17554
|
-
background:linear-gradient(var(--hy-c),var(--hy-c)) center/100% 1.75px no-repeat,linear-gradient(var(--hy-c),var(--hy-c)) center/1.75px 100% no-repeat}
|
|
17555
|
-
.knobs-hy-ev[data-inferred]:not([data-type="audit"],[data-type="deletion"])::before{background:transparent;box-shadow:inset 0 0 0 1.5px var(--hy-c)}
|
|
17556
|
-
.knobs-hy-ev[data-inferred]:is([data-type="audit"],[data-type="deletion"])::before{opacity:.5}
|
|
17557
|
-
/* Held-back marks (commits, by default) come up while the pointer is on their lane, and stay with
|
|
17558
|
-
"Show commits" or while selected. */
|
|
17559
|
-
.knobs-hy-ev[data-quiet]{opacity:0;transition:opacity calc(var(--_k-hover-out) * 1ms)}
|
|
17560
|
-
.knobs-hy-track:hover .knobs-hy-ev[data-quiet]{opacity:.6;transition-duration:calc(var(--_k-motion-snappy-ms) * 1ms)}
|
|
17561
|
-
:is(.knobs-history[data-all] .knobs-hy-ev[data-quiet],.knobs-hy-ev[data-quiet]:is([data-selected],[data-hover])){opacity:1}
|
|
17562
|
-
.knobs-hy-ev[data-hover]::before{scale:1.25}
|
|
17563
|
-
.knobs-hy-ev[data-selected]::before{scale:1.3}
|
|
17564
|
-
.knobs-hy-ev[data-selected]::after{content:"";position:absolute;left:50%;top:50%;width:calc(var(--hy-s) + 9px);height:calc(var(--hy-s) + 9px);
|
|
17565
|
-
margin:calc((var(--hy-s) + 9px) / -2) 0 0 calc((var(--hy-s) + 9px) / -2);border-radius:50%;box-shadow:0 0 0 1.5px var(--hy-c)}
|
|
17566
|
-
.knobs-hy-ev[data-range][data-selected]::after{left:3px;right:3px;width:auto;margin-left:0;border-radius:var(--_k-round)}
|
|
17567
|
-
.knobs-history:focus-visible .knobs-hy-ev[data-selected]::after{box-shadow:0 0 0 2px var(--_k-focus)}
|
|
17568
|
-
.knobs-history:focus-visible .knobs-hy-span[data-selected]{box-shadow:inset 0 0 0 2px var(--_k-focus)}
|
|
17569
|
-
|
|
17570
|
-
/* ——— under the chart: "Show commits", and a legend only when inferred and verified both appear ——— */
|
|
17571
|
-
.knobs-hy-foot{display:flex;flex-wrap:wrap;align-items:center;gap:var(--_k-space-inside) var(--_k-space-title);margin-top:var(--_k-space-sibling);padding-left:var(--hy-gutter);
|
|
17572
|
-
font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
17573
|
-
.knobs-hy-legend{display:flex;flex-wrap:wrap;gap:var(--_k-space-inside) var(--_k-space-title)}
|
|
17574
|
-
.knobs-ui .knobs-hy-more{height:20px;margin-left:-6px;padding:0 6px;border-radius:var(--_k-radius-sm);color:var(--_k-fg-3);cursor:pointer;
|
|
17575
|
-
transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
17576
|
-
.knobs-ui .knobs-hy-more:is(:hover,:focus-visible){background:var(--_k-fill);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
17577
|
-
.knobs-hy-key{display:inline-flex;align-items:center;gap:6px;white-space:nowrap}
|
|
17578
|
-
.knobs-hy-swatch{flex:none;width:14px;height:8px;border-radius:2px;background:color-mix(in oklch,var(--hy-c) 34%,transparent)}
|
|
17579
|
-
.knobs-hy-swatch[data-inferred]{background:repeating-linear-gradient(135deg,color-mix(in oklch,var(--hy-c) 60%,transparent) 0 1.5px,transparent 1.5px 4px)}
|
|
17580
|
-
|
|
17581
|
-
/* ——— the detail: one area under the chart, its height on a spring ——— */
|
|
17582
|
-
.knobs-hy-detail{position:relative;overflow:hidden;user-select:text;-webkit-user-select:text}
|
|
17583
|
-
.knobs-hy-detail[data-moving]{-webkit-mask-image:linear-gradient(to bottom,#000 calc(100% - 16px),transparent);mask-image:linear-gradient(to bottom,#000 calc(100% - 16px),transparent)}
|
|
17584
|
-
.knobs-hy-dc{padding:var(--_k-space-title) 0 0 var(--hy-gutter);transition:opacity calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy)}
|
|
17585
|
-
.knobs-hy-dc:is([data-enter],[data-leave]){opacity:0}
|
|
17586
|
-
.knobs-hy-dc[data-leave]{position:absolute;left:0;right:0;top:0;transition-duration:calc(var(--_k-motion-snappy-ms) * .8ms)}
|
|
17587
|
-
.knobs-hy-dmeta{display:flex;flex-wrap:wrap;gap:0 var(--_k-space-sibling);font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
17588
|
-
.knobs-hy-dmeta>[data-tone]{color:var(--hy-c)}
|
|
17589
|
-
.knobs-hy-dtitle{margin-top:var(--_k-space-inside);font-size:var(--_k-type-body);line-height:var(--_k-leading-body);font-weight:var(--_k-weight-title);color:var(--_k-fg);text-wrap:balance}
|
|
17590
|
-
.knobs-hy-dtext{margin:var(--_k-space-inside) 0 0;max-width:68ch;font-size:var(--_k-type-body);line-height:var(--_k-leading-body);color:var(--_k-fg-2);text-wrap:pretty}
|
|
17591
|
-
.knobs-hy-dtext code{font:calc(1em - 1px) var(--_k-mono);padding:1px 4px;border-radius:var(--_k-radius-sm);background:var(--_k-fill)}
|
|
17592
|
-
.knobs-hy-dfoot{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:var(--_k-space-sibling)}
|
|
17593
|
-
.knobs-hy-dfoot:empty{display:none}
|
|
17594
|
-
.knobs-hy-evidence{display:inline-flex;align-items:center;height:20px;padding:0 6px;border-radius:var(--_k-radius-sm);background:var(--_k-fill);color:var(--_k-fg-2);
|
|
17595
|
-
font:var(--_k-type-small)/1 var(--_k-mono);text-decoration:none;transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
17596
|
-
a.knobs-hy-evidence:hover{background:var(--_k-fill-hover);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
17597
|
-
.knobs-ui .knobs-hy-jump{display:inline-flex;align-items:center;gap:4px;height:20px;padding:0 6px 0 8px;border-radius:var(--_k-radius-sm);color:var(--_k-fg-2);cursor:pointer;
|
|
17598
|
-
font-size:var(--_k-type-small);transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
17599
|
-
.knobs-ui .knobs-hy-jump:is(:hover,:focus-visible){background:var(--_k-fill);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
17600
|
-
.knobs-hy-jump svg{width:12px;height:12px}
|
|
17601
|
-
|
|
17602
|
-
/* ——— the pointer's tooltip: title, date, one line ——— */
|
|
17603
|
-
.knobs-hy-tip{position:absolute;left:0;top:0;max-width:280px;padding:6px 8px;border-radius:var(--_k-radius-md);background:var(--_k-bg-overlay);
|
|
17604
|
-
box-shadow:var(--_k-shadow-overlay),0 0 0 1px var(--_k-border);pointer-events:none;opacity:0;transition:opacity calc(var(--_k-hover-out) * 1ms)}
|
|
17605
|
-
.knobs-hy-tip[data-on]{opacity:1;transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
17606
|
-
.knobs-hy-tt{font-size:var(--_k-type-small);line-height:var(--_k-leading-small);font-weight:var(--_k-weight-label);color:var(--_k-fg);text-wrap:balance}
|
|
17607
|
-
:is(.knobs-hy-td,.knobs-hy-tl){font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
17608
|
-
.knobs-hy-tl{margin-top:2px;color:var(--_k-fg-2);text-wrap:pretty}
|
|
17609
|
-
.knobs-hy-tl:empty{display:none}
|
|
17610
|
-
|
|
17611
|
-
@media(prefers-reduced-motion:reduce){
|
|
17612
|
-
.knobs-history :where(*,::before,::after){transition-property:opacity,background,color,box-shadow!important}
|
|
17613
|
-
.knobs-hy-dc{transition-duration:calc(var(--_k-hover-out) * 1ms)!important}
|
|
17614
|
-
}
|
|
17615
|
-
`;
|
|
17616
|
-
|
|
17617
|
-
// src/ui/history-model.ts
|
|
17618
|
-
var MS_DAY = 86400000;
|
|
17619
|
-
var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
17620
|
-
var dayOf = (y, m, d) => Math.floor(Date.UTC(y, m, d) / MS_DAY);
|
|
17621
|
-
var parts = (day) => {
|
|
17622
|
-
const t = new Date(day * MS_DAY);
|
|
17623
|
-
return { y: t.getUTCFullYear(), m: t.getUTCMonth(), d: t.getUTCDate() };
|
|
17624
|
-
};
|
|
17625
|
-
var todayDay = (now = Date.now()) => {
|
|
17626
|
-
const t = new Date(now);
|
|
17627
|
-
return dayOf(t.getFullYear(), t.getMonth(), t.getDate());
|
|
17628
|
-
};
|
|
17629
|
-
var monthIndex = (name) => MONTHS.findIndex((m) => m.toLowerCase() === name.slice(0, 3).toLowerCase());
|
|
17630
|
-
function parseDay(text, year) {
|
|
17631
|
-
const s = text.trim();
|
|
17632
|
-
const iso = /^(\d{4})-(\d{2})-(\d{2})/.exec(s);
|
|
17633
|
-
if (iso) {
|
|
17634
|
-
const y = Number(iso[1]);
|
|
17635
|
-
const day = dayOf(y, Number(iso[2]) - 1, Number(iso[3]));
|
|
17636
|
-
return { from: day, to: day, year: y };
|
|
17495
|
+
case "image":
|
|
17496
|
+
return { ...m, src: resolveSrc(m.src, base) };
|
|
17497
|
+
case "history":
|
|
17498
|
+
if ("data" in m)
|
|
17499
|
+
return m;
|
|
17500
|
+
return {
|
|
17501
|
+
kind: "history",
|
|
17502
|
+
data: historyData(m),
|
|
17503
|
+
...m.label ? { label: m.label } : {},
|
|
17504
|
+
...m.marks ? { marks: m.marks } : {}
|
|
17505
|
+
};
|
|
17506
|
+
case "stack":
|
|
17507
|
+
return {
|
|
17508
|
+
kind: "stack",
|
|
17509
|
+
...m.base ? { base: m.base } : {},
|
|
17510
|
+
items: m.items.map(fillStackItem)
|
|
17511
|
+
};
|
|
17512
|
+
case "table":
|
|
17513
|
+
return fillTable(m);
|
|
17514
|
+
case "facts":
|
|
17515
|
+
return {
|
|
17516
|
+
kind: "facts",
|
|
17517
|
+
...m.title ? { title: m.title } : {},
|
|
17518
|
+
groups: [
|
|
17519
|
+
...m.facts ? [{ facts: fillFacts(m.facts) }] : [],
|
|
17520
|
+
...(m.groups ?? []).map((g) => ({
|
|
17521
|
+
...g.title ? { title: g.title } : {},
|
|
17522
|
+
facts: fillFacts(g.facts)
|
|
17523
|
+
}))
|
|
17524
|
+
]
|
|
17525
|
+
};
|
|
17526
|
+
case "decision":
|
|
17527
|
+
return { kind: "decision", options: fillDecision(m.options, m.noun, base) };
|
|
17528
|
+
default:
|
|
17529
|
+
return m;
|
|
17637
17530
|
}
|
|
17638
|
-
const m = /^([A-Za-z]{3,9})\.?\s+(\d{1,2})(?:\s*[–—-]\s*(?:([A-Za-z]{3,9})\.?\s+)?(\d{1,2}))?(?:,?\s+(\d{4}))?$/.exec(s);
|
|
17639
|
-
if (!m)
|
|
17640
|
-
return null;
|
|
17641
|
-
const m1 = monthIndex(m[1]);
|
|
17642
|
-
const m2 = m[3] ? monthIndex(m[3]) : m1;
|
|
17643
|
-
if (m1 < 0 || m2 < 0)
|
|
17644
|
-
return null;
|
|
17645
|
-
const written = m[5] ? Number(m[5]) : undefined;
|
|
17646
|
-
const y = written ?? year;
|
|
17647
|
-
if (y === undefined)
|
|
17648
|
-
return null;
|
|
17649
|
-
const from = dayOf(y, m1, Number(m[2]));
|
|
17650
|
-
let to = m[4] ? dayOf(y, m2, Number(m[4])) : from;
|
|
17651
|
-
if (to < from)
|
|
17652
|
-
to = dayOf(y + 1, m2, Number(m[4]));
|
|
17653
|
-
return { from, to, ...written !== undefined ? { year: written } : {} };
|
|
17654
17531
|
}
|
|
17655
|
-
function
|
|
17656
|
-
const
|
|
17657
|
-
return
|
|
17532
|
+
function fillFacts(facts) {
|
|
17533
|
+
const list = Array.isArray(facts) ? facts : Object.entries(facts).map(([term, value]) => ({ term, value }));
|
|
17534
|
+
return list.map((f) => {
|
|
17535
|
+
const code = f.code === true;
|
|
17536
|
+
const raw = typeof f.value === "string" ? [f.value] : [...f.value];
|
|
17537
|
+
const value = (code ? raw.flatMap((v) => v.split(/\s*,\s*/)) : raw).map((v) => v.trim()).filter(Boolean);
|
|
17538
|
+
return { term: f.term, value, code };
|
|
17539
|
+
});
|
|
17658
17540
|
}
|
|
17659
|
-
function
|
|
17660
|
-
|
|
17661
|
-
|
|
17662
|
-
|
|
17663
|
-
|
|
17664
|
-
|
|
17665
|
-
|
|
17666
|
-
|
|
17667
|
-
|
|
17668
|
-
|
|
17669
|
-
|
|
17670
|
-
|
|
17541
|
+
function optionColumns(count, width, min = 280, gap = 48) {
|
|
17542
|
+
for (let c = Math.min(count, 4);c > 1; c--)
|
|
17543
|
+
if (count % c === 0 && (width - gap * (c - 1)) / c >= min)
|
|
17544
|
+
return c;
|
|
17545
|
+
return 1;
|
|
17546
|
+
}
|
|
17547
|
+
function fillDecision(options, noun = "Option", base) {
|
|
17548
|
+
const unique = uniqueIds();
|
|
17549
|
+
return options.map((o, i) => {
|
|
17550
|
+
if ("number" in o)
|
|
17551
|
+
return o;
|
|
17552
|
+
const extra = [
|
|
17553
|
+
...o.pros?.length ? [{ term: "For", value: o.pros }] : [],
|
|
17554
|
+
...o.cons?.length ? [{ term: "Against", value: o.cons }] : []
|
|
17555
|
+
];
|
|
17556
|
+
return {
|
|
17557
|
+
id: unique(o.id ?? slug(o.title)),
|
|
17558
|
+
number: `${noun} ${i + 1}`,
|
|
17559
|
+
title: o.title,
|
|
17560
|
+
...o.summary ? { summary: o.summary } : {},
|
|
17561
|
+
...o.media !== undefined ? { media: fillMedia(o.media, base) } : {},
|
|
17562
|
+
facts: [...o.facts ? fillFacts(o.facts) : [], ...fillFacts(extra)],
|
|
17563
|
+
recommended: o.recommended === true
|
|
17564
|
+
};
|
|
17565
|
+
});
|
|
17671
17566
|
}
|
|
17672
|
-
|
|
17673
|
-
|
|
17674
|
-
|
|
17675
|
-
|
|
17676
|
-
|
|
17677
|
-
|
|
17678
|
-
|
|
17679
|
-
|
|
17680
|
-
|
|
17681
|
-
|
|
17567
|
+
function historyData(m) {
|
|
17568
|
+
const events = m.events ?? [];
|
|
17569
|
+
if (m.lanes || m.bands || m.states || m.axis)
|
|
17570
|
+
return {
|
|
17571
|
+
...m.axis ? { axis: m.axis } : {},
|
|
17572
|
+
...m.lanes ? { lanes: m.lanes } : {},
|
|
17573
|
+
...events.length ? { events } : {},
|
|
17574
|
+
...m.bands ? { bands: m.bands } : {},
|
|
17575
|
+
...m.states ? { states: m.states } : {}
|
|
17576
|
+
};
|
|
17577
|
+
const groups = [];
|
|
17578
|
+
if (events.length)
|
|
17579
|
+
groups.push({ events });
|
|
17580
|
+
for (const g of m.groups ?? [])
|
|
17581
|
+
groups.push({
|
|
17582
|
+
...g.heading ? { heading: g.heading } : {},
|
|
17583
|
+
...g.summary ? { summary: g.summary } : {},
|
|
17584
|
+
events: g.events ?? g.rows ?? []
|
|
17585
|
+
});
|
|
17586
|
+
return { groups };
|
|
17682
17587
|
}
|
|
17683
|
-
|
|
17684
|
-
|
|
17685
|
-
|
|
17686
|
-
const
|
|
17687
|
-
|
|
17688
|
-
let year = all.map((e) => parseDay(e.date, 0)?.year).find((y) => y !== undefined) ?? parts(today).y;
|
|
17689
|
-
for (const g of groups) {
|
|
17690
|
-
let lo = Infinity;
|
|
17691
|
-
let hi = -Infinity;
|
|
17692
|
-
for (const e of g.events ?? g.rows ?? []) {
|
|
17693
|
-
const d = parseDay(e.date, year);
|
|
17694
|
-
if (!d)
|
|
17695
|
-
continue;
|
|
17696
|
-
if (d.year !== undefined)
|
|
17697
|
-
year = d.year;
|
|
17698
|
-
lo = Math.min(lo, d.from);
|
|
17699
|
-
hi = Math.max(hi, d.to);
|
|
17700
|
-
events.push({
|
|
17701
|
-
...e,
|
|
17702
|
-
date: iso(d.from),
|
|
17703
|
-
...d.to > d.from ? { to: iso(d.to) } : {},
|
|
17704
|
-
lane: e.lane ?? "code"
|
|
17705
|
-
});
|
|
17706
|
-
}
|
|
17707
|
-
const label = g.heading ? bandLabel(g.heading) : "";
|
|
17708
|
-
if (label && lo <= hi)
|
|
17709
|
-
bands.push({ from: iso(lo), to: iso(hi), label });
|
|
17710
|
-
}
|
|
17711
|
-
return { events, bands };
|
|
17588
|
+
function parseBranch(branch) {
|
|
17589
|
+
if (!branch?.trim())
|
|
17590
|
+
return {};
|
|
17591
|
+
const [head, base] = branch.split(/\s*(?:→|->|=>|⇒)\s*/, 2).map((s) => s.trim());
|
|
17592
|
+
return { ...head ? { head } : {}, ...base ? { base } : {} };
|
|
17712
17593
|
}
|
|
17713
|
-
function
|
|
17714
|
-
|
|
17715
|
-
|
|
17594
|
+
function parseDelta(delta) {
|
|
17595
|
+
if (delta === undefined || typeof delta === "object")
|
|
17596
|
+
return delta;
|
|
17597
|
+
const m = /\+\s*([\d,_]+)[^\d+]*?[-−–]\s*([\d,_]+)/.exec(delta);
|
|
17598
|
+
if (!m)
|
|
17599
|
+
return delta.trim() || undefined;
|
|
17600
|
+
const n = (s) => Number(s.replace(/[,_]/g, ""));
|
|
17601
|
+
return { add: n(m[1]), del: n(m[2]) };
|
|
17716
17602
|
}
|
|
17717
|
-
|
|
17718
|
-
const
|
|
17719
|
-
|
|
17720
|
-
|
|
17721
|
-
|
|
17722
|
-
|
|
17723
|
-
const
|
|
17724
|
-
|
|
17725
|
-
|
|
17726
|
-
|
|
17727
|
-
|
|
17728
|
-
|
|
17729
|
-
|
|
17730
|
-
|
|
17731
|
-
|
|
17732
|
-
|
|
17733
|
-
|
|
17734
|
-
|
|
17735
|
-
|
|
17736
|
-
states.set(key, s);
|
|
17737
|
-
return s;
|
|
17603
|
+
function fillStackItem(it) {
|
|
17604
|
+
const parsed = parseBranch(it.branch);
|
|
17605
|
+
const head = parsed.head ?? it.head;
|
|
17606
|
+
const base = parsed.base ?? it.base;
|
|
17607
|
+
const delta = parseDelta(it.delta);
|
|
17608
|
+
const step = it.step ?? it.stepId;
|
|
17609
|
+
const pr = String(it.pr).trim();
|
|
17610
|
+
return {
|
|
17611
|
+
pr: /^\d+$/.test(pr) ? `#${pr}` : pr,
|
|
17612
|
+
...it.href ? { href: it.href } : {},
|
|
17613
|
+
title: it.title,
|
|
17614
|
+
...head ? { head } : {},
|
|
17615
|
+
...base ? { base } : {},
|
|
17616
|
+
...delta !== undefined ? { delta } : {},
|
|
17617
|
+
...it.status ? { status: it.status } : {},
|
|
17618
|
+
...it.tone ? { tone: it.tone } : {},
|
|
17619
|
+
...it.summary ? { summary: it.summary } : {},
|
|
17620
|
+
...step ? { step } : {},
|
|
17621
|
+
...it.order !== undefined && it.order !== "" ? { order: String(it.order) } : {}
|
|
17738
17622
|
};
|
|
17739
|
-
|
|
17740
|
-
|
|
17741
|
-
const
|
|
17742
|
-
const
|
|
17743
|
-
|
|
17744
|
-
|
|
17745
|
-
|
|
17746
|
-
|
|
17747
|
-
|
|
17623
|
+
}
|
|
17624
|
+
function stackLayout(m) {
|
|
17625
|
+
const items = m.items;
|
|
17626
|
+
const byHead = new Map;
|
|
17627
|
+
items.forEach((it, i) => it.head && !byHead.has(it.head) && byHead.set(it.head, i));
|
|
17628
|
+
let trunk = m.base ?? null;
|
|
17629
|
+
if (!trunk) {
|
|
17630
|
+
const tally = new Map;
|
|
17631
|
+
for (const it of items)
|
|
17632
|
+
if (it.base && !byHead.has(it.base))
|
|
17633
|
+
tally.set(it.base, (tally.get(it.base) ?? 0) + 1);
|
|
17634
|
+
let best = 0;
|
|
17635
|
+
for (const [b, n] of tally)
|
|
17636
|
+
if (n > best)
|
|
17637
|
+
[trunk, best] = [b, n];
|
|
17638
|
+
}
|
|
17639
|
+
const parentOf = items.map((it, i) => {
|
|
17640
|
+
const p = it.base === undefined ? undefined : byHead.get(it.base);
|
|
17641
|
+
return p === undefined || p === i ? null : p;
|
|
17642
|
+
});
|
|
17643
|
+
items.forEach((_, i) => {
|
|
17644
|
+
const seen = new Set;
|
|
17645
|
+
for (let n = i;n !== null; n = parentOf[n]) {
|
|
17646
|
+
if (seen.has(n)) {
|
|
17647
|
+
const loop = [n];
|
|
17648
|
+
for (let k = parentOf[n];k !== n && k !== null; k = parentOf[k])
|
|
17649
|
+
loop.push(k);
|
|
17650
|
+
parentOf[Math.min(...loop)] = null;
|
|
17651
|
+
break;
|
|
17652
|
+
}
|
|
17653
|
+
seen.add(n);
|
|
17748
17654
|
}
|
|
17749
|
-
|
|
17750
|
-
|
|
17751
|
-
|
|
17752
|
-
|
|
17753
|
-
|
|
17754
|
-
|
|
17755
|
-
const
|
|
17756
|
-
|
|
17757
|
-
|
|
17758
|
-
|
|
17759
|
-
|
|
17760
|
-
|
|
17761
|
-
|
|
17762
|
-
|
|
17763
|
-
|
|
17764
|
-
|
|
17765
|
-
|
|
17766
|
-
|
|
17767
|
-
const id = s.id ?? `${l.id}/${i}`;
|
|
17768
|
-
const item = {
|
|
17769
|
-
id,
|
|
17770
|
-
kind: "span",
|
|
17771
|
-
lane: l.id,
|
|
17772
|
-
from: a.from,
|
|
17773
|
-
to,
|
|
17774
|
-
open,
|
|
17775
|
-
title: s.title ?? `${l.label} · ${st.label}`,
|
|
17776
|
-
...s.detail ? { detail: s.detail } : {},
|
|
17777
|
-
tone: st.tone,
|
|
17778
|
-
inferred: s.provenance === "inferred",
|
|
17779
|
-
...s.provenance ? { provenance: s.provenance } : {},
|
|
17780
|
-
...s.source ? { source: s.source } : {},
|
|
17781
|
-
evidence: s.evidence ?? [],
|
|
17782
|
-
...s.step ? { step: s.step } : {},
|
|
17783
|
-
state: s.state,
|
|
17784
|
-
tags
|
|
17785
|
-
};
|
|
17786
|
-
if (item.inferred)
|
|
17787
|
-
inferred = true;
|
|
17788
|
-
into.spans.push(item);
|
|
17789
|
-
items.set(id, item);
|
|
17655
|
+
});
|
|
17656
|
+
const kids = items.map(() => []);
|
|
17657
|
+
const roots = [];
|
|
17658
|
+
parentOf.forEach((p, i) => (p === null ? roots : kids[p]).push(i));
|
|
17659
|
+
const rows = [];
|
|
17660
|
+
const walk = (list, depth, rails) => list.forEach((i, n) => {
|
|
17661
|
+
const it = items[i];
|
|
17662
|
+
const last = n === list.length - 1;
|
|
17663
|
+
const off = parentOf[i] === null && it.base && it.base !== trunk ? it.base : undefined;
|
|
17664
|
+
rows.push({
|
|
17665
|
+
item: it,
|
|
17666
|
+
index: i,
|
|
17667
|
+
depth,
|
|
17668
|
+
parent: parentOf[i],
|
|
17669
|
+
last,
|
|
17670
|
+
rails,
|
|
17671
|
+
children: kids[i].length,
|
|
17672
|
+
...off ? { offBase: off } : {}
|
|
17790
17673
|
});
|
|
17791
|
-
|
|
17792
|
-
const types = [];
|
|
17793
|
-
const events = [];
|
|
17794
|
-
rawEvents.forEach((e, index) => {
|
|
17795
|
-
const a = day(e.date);
|
|
17796
|
-
if (!a)
|
|
17797
|
-
return;
|
|
17798
|
-
const to = Math.max(a.to, day(e.to)?.to ?? a.to);
|
|
17799
|
-
const into = lane(e.lane ?? "code");
|
|
17800
|
-
const id = e.id ?? `e${index}`;
|
|
17801
|
-
const item = {
|
|
17802
|
-
id,
|
|
17803
|
-
kind: "event",
|
|
17804
|
-
lane: into.id,
|
|
17805
|
-
from: a.from,
|
|
17806
|
-
to,
|
|
17807
|
-
open: false,
|
|
17808
|
-
title: e.title,
|
|
17809
|
-
...e.detail ? { detail: e.detail } : {},
|
|
17810
|
-
tone: e.tone ?? TYPE_TONE[e.type ?? ""] ?? "dim",
|
|
17811
|
-
...e.type ? { type: e.type } : {},
|
|
17812
|
-
inferred: e.provenance === "inferred",
|
|
17813
|
-
...e.provenance ? { provenance: e.provenance } : {},
|
|
17814
|
-
...e.source ? { source: e.source } : {},
|
|
17815
|
-
evidence: e.evidence ?? [],
|
|
17816
|
-
...e.step ? { step: e.step } : {},
|
|
17817
|
-
tags: []
|
|
17818
|
-
};
|
|
17819
|
-
if (item.inferred)
|
|
17820
|
-
inferred = true;
|
|
17821
|
-
if (e.type && !types.includes(e.type))
|
|
17822
|
-
types.push(e.type);
|
|
17823
|
-
into.events.push(item);
|
|
17824
|
-
items.set(id, item);
|
|
17825
|
-
events.push({ item, index });
|
|
17674
|
+
walk(kids[i], depth + 1, [...rails, !last]);
|
|
17826
17675
|
});
|
|
17827
|
-
|
|
17828
|
-
|
|
17829
|
-
const a = day(b.from);
|
|
17830
|
-
if (!a)
|
|
17831
|
-
continue;
|
|
17832
|
-
const z = b.to ? day(b.to) : null;
|
|
17833
|
-
bands.push({ from: a.from, to: z ? z.to : today, label: b.label });
|
|
17834
|
-
}
|
|
17835
|
-
bands.sort((x, y) => x.from - y.from);
|
|
17836
|
-
const all = [...items.values()].flatMap((i) => [i.from, i.to]);
|
|
17837
|
-
for (const b of bands)
|
|
17838
|
-
all.push(b.from, b.to);
|
|
17839
|
-
const hasOpen = [...items.values()].some((i) => i.open);
|
|
17840
|
-
const lo = all.length ? Math.min(...all) : today - 90;
|
|
17841
|
-
let hi = all.length ? Math.max(...all) : today;
|
|
17842
|
-
if (hasOpen || data.axis?.today)
|
|
17843
|
-
hi = Math.max(hi, today);
|
|
17844
|
-
const from = day(data.axis?.from)?.from ?? startOfMonth(lo);
|
|
17845
|
-
const to = day(data.axis?.to)?.to ?? hi + Math.max(6, Math.round((hi - from) * 0.05));
|
|
17846
|
-
const laneIndex = (id) => laneOrder.indexOf(id);
|
|
17847
|
-
const order = eventOrder(events.map((e) => ({ ...e, laneIndex: laneIndex(e.item.lane) })));
|
|
17848
|
-
for (const l of lanes.values())
|
|
17849
|
-
l.events.sort((a, b) => a.from - b.from);
|
|
17850
|
-
return {
|
|
17851
|
-
from,
|
|
17852
|
-
to: Math.max(to, from + 1),
|
|
17853
|
-
today,
|
|
17854
|
-
lanes: laneOrder.map((id) => lanes.get(id)),
|
|
17855
|
-
events: order,
|
|
17856
|
-
bands,
|
|
17857
|
-
states: [...states.values()],
|
|
17858
|
-
types,
|
|
17859
|
-
inferred,
|
|
17860
|
-
verified: [...items.values()].some((i) => i.provenance === "verified"),
|
|
17861
|
-
items
|
|
17862
|
-
};
|
|
17676
|
+
walk(roots, 0, []);
|
|
17677
|
+
return { trunk, rows, flat: !trunk && rows.every((r) => r.depth === 0 && !r.children) };
|
|
17863
17678
|
}
|
|
17864
|
-
var
|
|
17865
|
-
function
|
|
17866
|
-
if (
|
|
17867
|
-
return
|
|
17868
|
-
|
|
17869
|
-
return quiet.length === events.length ? new Set : new Set(quiet.map((e) => e.id));
|
|
17679
|
+
var isNum = (c) => typeof c === "number" || typeof c === "object" && typeof c.text === "number";
|
|
17680
|
+
function fillCell(c) {
|
|
17681
|
+
if (typeof c !== "object")
|
|
17682
|
+
return { text: String(c) };
|
|
17683
|
+
return { ...c, text: String(c.text) };
|
|
17870
17684
|
}
|
|
17871
|
-
|
|
17872
|
-
|
|
17873
|
-
|
|
17874
|
-
|
|
17875
|
-
|
|
17876
|
-
|
|
17877
|
-
};
|
|
17878
|
-
function quietLabel(events, quiet) {
|
|
17879
|
-
const types = [];
|
|
17880
|
-
for (const e of events)
|
|
17881
|
-
if (quiet.has(e.id) && e.type && !types.includes(e.type))
|
|
17882
|
-
types.push(e.type);
|
|
17883
|
-
const names = types.map((t) => PLURALS[t] ?? `${t}s`);
|
|
17884
|
-
return names.length > 1 ? `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}` : names[0] ?? "";
|
|
17885
|
-
}
|
|
17886
|
-
function eventOrder(events) {
|
|
17887
|
-
return [...events].sort((a, b) => a.item.from - b.item.from || a.laneIndex - b.laneIndex || a.index - b.index).map((e) => e.item);
|
|
17888
|
-
}
|
|
17889
|
-
function stepEvent(order, current, dir, at) {
|
|
17890
|
-
if (!order.length)
|
|
17891
|
-
return null;
|
|
17892
|
-
const i = current === null ? -1 : order.findIndex((e) => e.id === current);
|
|
17893
|
-
if (i < 0 && at !== undefined) {
|
|
17894
|
-
const next = dir > 0 ? order.find((e) => e.from >= at) : [...order].reverse().find((e) => e.from <= at);
|
|
17895
|
-
return next ?? (dir > 0 ? order[order.length - 1] : order[0]);
|
|
17896
|
-
}
|
|
17897
|
-
if (i < 0)
|
|
17898
|
-
return dir > 0 ? order[0] : order[order.length - 1];
|
|
17899
|
-
return order[Math.max(0, Math.min(order.length - 1, i + dir))];
|
|
17900
|
-
}
|
|
17901
|
-
function dayScale(from, to, width) {
|
|
17902
|
-
const span = Math.max(1, to - from);
|
|
17903
|
-
return {
|
|
17904
|
-
x: (day) => (day - from) / span * width,
|
|
17905
|
-
day: (x) => from + x / Math.max(1, width) * span,
|
|
17906
|
-
f: (day) => (day - from) / span
|
|
17907
|
-
};
|
|
17908
|
-
}
|
|
17909
|
-
var STEPS = [1, 2, 3, 6, 12];
|
|
17910
|
-
function monthTicks(from, to, width, minGap = 44) {
|
|
17911
|
-
const perMonth = width / Math.max(1, to - from) * 30.44;
|
|
17912
|
-
const step = STEPS.find((s) => s * perMonth >= minGap) ?? 12;
|
|
17913
|
-
const ticks = [];
|
|
17914
|
-
const { y } = parts(from);
|
|
17915
|
-
let { m } = parts(from);
|
|
17916
|
-
let d = dayOf(y, m, 1);
|
|
17917
|
-
if (d < from) {
|
|
17918
|
-
m++;
|
|
17919
|
-
d = dayOf(y, m, 1);
|
|
17920
|
-
}
|
|
17921
|
-
while (d <= to) {
|
|
17922
|
-
const p = parts(d);
|
|
17923
|
-
if (p.m % step === 0)
|
|
17924
|
-
ticks.push({ day: d, label: p.m === 0 ? String(p.y) : MONTHS[p.m] });
|
|
17925
|
-
m++;
|
|
17926
|
-
d = dayOf(y, m, 1);
|
|
17927
|
-
}
|
|
17928
|
-
return ticks;
|
|
17929
|
-
}
|
|
17930
|
-
function fitLabels(labels, width, reserved = [], gap = 8) {
|
|
17931
|
-
let end = -Infinity;
|
|
17932
|
-
return labels.map(({ x, w }) => {
|
|
17933
|
-
const x1 = x + w;
|
|
17934
|
-
if (x < end + gap || x1 > width)
|
|
17935
|
-
return false;
|
|
17936
|
-
if (reserved.some((r) => x < r.x1 + gap && x1 > r.x0 - gap))
|
|
17937
|
-
return false;
|
|
17938
|
-
end = x1;
|
|
17939
|
-
return true;
|
|
17685
|
+
function fillTable(m) {
|
|
17686
|
+
const wrapped = !m.columns.some((c) => c.key === "cells");
|
|
17687
|
+
const raw = m.rows.map((r) => {
|
|
17688
|
+
const keys = Object.keys(r);
|
|
17689
|
+
const cells = r.cells;
|
|
17690
|
+
return wrapped && keys.length === 1 && keys[0] === "cells" && cells && typeof cells === "object" ? cells : r;
|
|
17940
17691
|
});
|
|
17941
|
-
|
|
17942
|
-
|
|
17943
|
-
|
|
17944
|
-
return
|
|
17692
|
+
const columns = m.columns.map((c) => {
|
|
17693
|
+
const present = raw.map((r) => r[c.key]).filter((v) => v !== undefined && v !== "");
|
|
17694
|
+
const numeric = present.length > 0 && present.every(isNum);
|
|
17695
|
+
return { ...c, align: c.align ?? (numeric ? "right" : "left") };
|
|
17696
|
+
});
|
|
17697
|
+
const rows = raw.map((r) => {
|
|
17698
|
+
const out = {};
|
|
17699
|
+
for (const [k, v] of Object.entries(r))
|
|
17700
|
+
if (v !== undefined && v !== null)
|
|
17701
|
+
out[k] = fillCell(v);
|
|
17702
|
+
return out;
|
|
17703
|
+
});
|
|
17704
|
+
const primary = columns.find((c) => c.key === m.primary)?.key ?? columns[0]?.key ?? "";
|
|
17945
17705
|
return {
|
|
17946
|
-
|
|
17947
|
-
|
|
17948
|
-
|
|
17949
|
-
|
|
17706
|
+
kind: "table",
|
|
17707
|
+
columns,
|
|
17708
|
+
rows,
|
|
17709
|
+
...m.caption ? { caption: m.caption } : {},
|
|
17710
|
+
stack: m.stack ?? "auto",
|
|
17711
|
+
primary
|
|
17950
17712
|
};
|
|
17951
17713
|
}
|
|
17952
|
-
function
|
|
17953
|
-
|
|
17954
|
-
|
|
17955
|
-
|
|
17956
|
-
|
|
17957
|
-
|
|
17958
|
-
|
|
17959
|
-
|
|
17960
|
-
|
|
17961
|
-
|
|
17962
|
-
|
|
17963
|
-
|
|
17964
|
-
const end = Math.min(b.to, next ? next.from : Infinity);
|
|
17965
|
-
return Math.max(0, x(end) - x(b.from));
|
|
17714
|
+
function fillChapters(chapters) {
|
|
17715
|
+
const id = uniqueIds();
|
|
17716
|
+
const starts = chapters.map((c) => c.start).sort((a, b) => a - b);
|
|
17717
|
+
return chapters.map((c) => {
|
|
17718
|
+
const next = starts.find((s) => s > c.start);
|
|
17719
|
+
const end = c.end ?? (c.duration !== undefined ? Math.round((c.start + c.duration) * 1000) / 1000 : next);
|
|
17720
|
+
return {
|
|
17721
|
+
id: id(c.id ?? slug(c.label)),
|
|
17722
|
+
label: c.label,
|
|
17723
|
+
start: c.start,
|
|
17724
|
+
...end !== undefined && end > c.start ? { end } : {}
|
|
17725
|
+
};
|
|
17966
17726
|
});
|
|
17967
17727
|
}
|
|
17968
|
-
function
|
|
17969
|
-
let
|
|
17970
|
-
|
|
17971
|
-
|
|
17972
|
-
|
|
17973
|
-
|
|
17974
|
-
|
|
17975
|
-
gap = d;
|
|
17976
|
-
}
|
|
17977
|
-
}
|
|
17978
|
-
return best;
|
|
17979
|
-
}
|
|
17980
|
-
function firstSentence(text) {
|
|
17981
|
-
if (!text)
|
|
17982
|
-
return "";
|
|
17983
|
-
const plain = text.replace(/`([^`]*)`/g, "$1").replace(/\*\*?([^*]+)\*\*?/g, "$1").trim();
|
|
17984
|
-
const m = /^.*?[.!?](?=\s|$)/.exec(plain);
|
|
17985
|
-
return (m ? m[0] : plain).trim();
|
|
17986
|
-
}
|
|
17987
|
-
|
|
17988
|
-
// src/ui/history.ts
|
|
17989
|
-
init_look();
|
|
17990
|
-
|
|
17991
|
-
// src/ui/walkthrough-model.ts
|
|
17992
|
-
var DEFAULT_VERDICTS = ["ship", "change", "unsure"];
|
|
17993
|
-
function slug(text) {
|
|
17994
|
-
return text.toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/^(.{0,48})-.*$/, (m, head) => m.length > 48 && head ? head : m).slice(0, 48) || "item";
|
|
17995
|
-
}
|
|
17996
|
-
function uniqueIds() {
|
|
17997
|
-
const seen = new Map;
|
|
17998
|
-
return (id) => {
|
|
17999
|
-
const n = (seen.get(id) ?? 0) + 1;
|
|
18000
|
-
seen.set(id, n);
|
|
18001
|
-
return n === 1 ? id : `${id}-${n}`;
|
|
18002
|
-
};
|
|
17728
|
+
function chapterAt(chapters, time) {
|
|
17729
|
+
let at = -1;
|
|
17730
|
+
chapters.forEach((c, i) => {
|
|
17731
|
+
if (time >= c.start && time < (c.end ?? Infinity) && (at < 0 || c.start >= chapters[at].start))
|
|
17732
|
+
at = i;
|
|
17733
|
+
});
|
|
17734
|
+
return at;
|
|
18003
17735
|
}
|
|
18004
|
-
|
|
18005
|
-
|
|
18006
|
-
|
|
18007
|
-
|
|
18008
|
-
|
|
17736
|
+
function clock(seconds) {
|
|
17737
|
+
const t = Math.max(0, Math.floor(seconds));
|
|
17738
|
+
const h = Math.floor(t / 3600);
|
|
17739
|
+
const m = Math.floor(t % 3600 / 60);
|
|
17740
|
+
const s = String(t % 60).padStart(2, "0");
|
|
17741
|
+
return h ? `${h}:${String(m).padStart(2, "0")}:${s}` : `${m}:${s}`;
|
|
18009
17742
|
}
|
|
18010
|
-
|
|
18011
|
-
|
|
18012
|
-
function parseAspect(aspect) {
|
|
18013
|
-
if (aspect === undefined)
|
|
18014
|
-
return;
|
|
18015
|
-
if (typeof aspect === "number")
|
|
18016
|
-
return aspect > 0 && Number.isFinite(aspect) ? aspect : undefined;
|
|
18017
|
-
const m = /^\s*([\d.]+)\s*(?:[/x:×]\s*([\d.]+))?\s*$/.exec(aspect);
|
|
18018
|
-
if (!m)
|
|
17743
|
+
function findStep(steps, ref) {
|
|
17744
|
+
if (ref === undefined)
|
|
18019
17745
|
return;
|
|
18020
|
-
const
|
|
18021
|
-
|
|
18022
|
-
return w > 0 && h > 0 ? w / h : undefined;
|
|
17746
|
+
const r = ref.trim().replace(/^#/, "");
|
|
17747
|
+
return steps.find((s) => s.key === r || s.id === r || s.number === r);
|
|
18023
17748
|
}
|
|
18024
|
-
function
|
|
18025
|
-
const
|
|
18026
|
-
|
|
17749
|
+
function linkLabel(href) {
|
|
17750
|
+
const pr = /\/(?:pull|issues)\/(\d+)/.exec(href);
|
|
17751
|
+
if (pr)
|
|
17752
|
+
return `#${pr[1]}`;
|
|
17753
|
+
const tail = href.replace(/[?#].*$/, "").replace(/\/+$/, "").split("/").pop();
|
|
17754
|
+
return tail || href;
|
|
18027
17755
|
}
|
|
18028
|
-
function
|
|
18029
|
-
|
|
18030
|
-
|
|
18031
|
-
|
|
18032
|
-
|
|
18033
|
-
|
|
18034
|
-
|
|
18035
|
-
|
|
18036
|
-
|
|
18037
|
-
|
|
18038
|
-
|
|
18039
|
-
|
|
18040
|
-
|
|
18041
|
-
|
|
18042
|
-
|
|
18043
|
-
|
|
18044
|
-
|
|
18045
|
-
|
|
18046
|
-
|
|
18047
|
-
|
|
18048
|
-
|
|
18049
|
-
|
|
18050
|
-
|
|
18051
|
-
|
|
18052
|
-
|
|
18053
|
-
|
|
18054
|
-
|
|
18055
|
-
|
|
18056
|
-
|
|
18057
|
-
|
|
18058
|
-
|
|
18059
|
-
|
|
18060
|
-
|
|
18061
|
-
|
|
18062
|
-
};
|
|
18063
|
-
case "stack":
|
|
18064
|
-
return {
|
|
18065
|
-
kind: "stack",
|
|
18066
|
-
...m.base ? { base: m.base } : {},
|
|
18067
|
-
items: m.items.map(fillStackItem)
|
|
18068
|
-
};
|
|
18069
|
-
case "table":
|
|
18070
|
-
return fillTable(m);
|
|
18071
|
-
default:
|
|
18072
|
-
return m;
|
|
18073
|
-
}
|
|
18074
|
-
}
|
|
18075
|
-
function historyData(m) {
|
|
18076
|
-
const events = m.events ?? [];
|
|
18077
|
-
if (m.lanes || m.bands || m.states || m.axis)
|
|
18078
|
-
return {
|
|
18079
|
-
...m.axis ? { axis: m.axis } : {},
|
|
18080
|
-
...m.lanes ? { lanes: m.lanes } : {},
|
|
18081
|
-
...events.length ? { events } : {},
|
|
18082
|
-
...m.bands ? { bands: m.bands } : {},
|
|
18083
|
-
...m.states ? { states: m.states } : {}
|
|
18084
|
-
};
|
|
18085
|
-
const groups = [];
|
|
18086
|
-
if (events.length)
|
|
18087
|
-
groups.push({ events });
|
|
18088
|
-
for (const g of m.groups ?? [])
|
|
18089
|
-
groups.push({
|
|
18090
|
-
...g.heading ? { heading: g.heading } : {},
|
|
18091
|
-
...g.summary ? { summary: g.summary } : {},
|
|
18092
|
-
events: g.events ?? g.rows ?? []
|
|
18093
|
-
});
|
|
18094
|
-
return { groups };
|
|
18095
|
-
}
|
|
18096
|
-
function parseBranch(branch) {
|
|
18097
|
-
if (!branch?.trim())
|
|
18098
|
-
return {};
|
|
18099
|
-
const [head, base] = branch.split(/\s*(?:→|->|=>|⇒)\s*/, 2).map((s) => s.trim());
|
|
18100
|
-
return { ...head ? { head } : {}, ...base ? { base } : {} };
|
|
18101
|
-
}
|
|
18102
|
-
function parseDelta(delta) {
|
|
18103
|
-
if (delta === undefined || typeof delta === "object")
|
|
18104
|
-
return delta;
|
|
18105
|
-
const m = /\+\s*([\d,_]+)[^\d+]*?[-−–]\s*([\d,_]+)/.exec(delta);
|
|
18106
|
-
if (!m)
|
|
18107
|
-
return delta.trim() || undefined;
|
|
18108
|
-
const n = (s) => Number(s.replace(/[,_]/g, ""));
|
|
18109
|
-
return { add: n(m[1]), del: n(m[2]) };
|
|
18110
|
-
}
|
|
18111
|
-
function fillStackItem(it) {
|
|
18112
|
-
const parsed = parseBranch(it.branch);
|
|
18113
|
-
const head = parsed.head ?? it.head;
|
|
18114
|
-
const base = parsed.base ?? it.base;
|
|
18115
|
-
const delta = parseDelta(it.delta);
|
|
18116
|
-
const step = it.step ?? it.stepId;
|
|
18117
|
-
const pr = String(it.pr).trim();
|
|
18118
|
-
return {
|
|
18119
|
-
pr: /^\d+$/.test(pr) ? `#${pr}` : pr,
|
|
18120
|
-
...it.href ? { href: it.href } : {},
|
|
18121
|
-
title: it.title,
|
|
18122
|
-
...head ? { head } : {},
|
|
18123
|
-
...base ? { base } : {},
|
|
18124
|
-
...delta !== undefined ? { delta } : {},
|
|
18125
|
-
...it.status ? { status: it.status } : {},
|
|
18126
|
-
...it.tone ? { tone: it.tone } : {},
|
|
18127
|
-
...it.summary ? { summary: it.summary } : {},
|
|
18128
|
-
...step ? { step } : {},
|
|
18129
|
-
...it.order !== undefined && it.order !== "" ? { order: String(it.order) } : {}
|
|
18130
|
-
};
|
|
18131
|
-
}
|
|
18132
|
-
function stackLayout(m) {
|
|
18133
|
-
const items = m.items;
|
|
18134
|
-
const byHead = new Map;
|
|
18135
|
-
items.forEach((it, i) => it.head && !byHead.has(it.head) && byHead.set(it.head, i));
|
|
18136
|
-
let trunk = m.base ?? null;
|
|
18137
|
-
if (!trunk) {
|
|
18138
|
-
const tally = new Map;
|
|
18139
|
-
for (const it of items)
|
|
18140
|
-
if (it.base && !byHead.has(it.base))
|
|
18141
|
-
tally.set(it.base, (tally.get(it.base) ?? 0) + 1);
|
|
18142
|
-
let best = 0;
|
|
18143
|
-
for (const [b, n] of tally)
|
|
18144
|
-
if (n > best)
|
|
18145
|
-
[trunk, best] = [b, n];
|
|
18146
|
-
}
|
|
18147
|
-
const parentOf = items.map((it, i) => {
|
|
18148
|
-
const p = it.base === undefined ? undefined : byHead.get(it.base);
|
|
18149
|
-
return p === undefined || p === i ? null : p;
|
|
18150
|
-
});
|
|
18151
|
-
items.forEach((_, i) => {
|
|
18152
|
-
const seen = new Set;
|
|
18153
|
-
for (let n = i;n !== null; n = parentOf[n]) {
|
|
18154
|
-
if (seen.has(n)) {
|
|
18155
|
-
const loop = [n];
|
|
18156
|
-
for (let k = parentOf[n];k !== n && k !== null; k = parentOf[k])
|
|
18157
|
-
loop.push(k);
|
|
18158
|
-
parentOf[Math.min(...loop)] = null;
|
|
18159
|
-
break;
|
|
18160
|
-
}
|
|
18161
|
-
seen.add(n);
|
|
18162
|
-
}
|
|
18163
|
-
});
|
|
18164
|
-
const kids = items.map(() => []);
|
|
18165
|
-
const roots = [];
|
|
18166
|
-
parentOf.forEach((p, i) => (p === null ? roots : kids[p]).push(i));
|
|
18167
|
-
const rows = [];
|
|
18168
|
-
const walk = (list, depth, rails) => list.forEach((i, n) => {
|
|
18169
|
-
const it = items[i];
|
|
18170
|
-
const last = n === list.length - 1;
|
|
18171
|
-
const off = parentOf[i] === null && it.base && it.base !== trunk ? it.base : undefined;
|
|
18172
|
-
rows.push({
|
|
18173
|
-
item: it,
|
|
18174
|
-
index: i,
|
|
18175
|
-
depth,
|
|
18176
|
-
parent: parentOf[i],
|
|
18177
|
-
last,
|
|
18178
|
-
rails,
|
|
18179
|
-
children: kids[i].length,
|
|
18180
|
-
...off ? { offBase: off } : {}
|
|
18181
|
-
});
|
|
18182
|
-
walk(kids[i], depth + 1, [...rails, !last]);
|
|
18183
|
-
});
|
|
18184
|
-
walk(roots, 0, []);
|
|
18185
|
-
return { trunk, rows, flat: !trunk && rows.every((r) => r.depth === 0 && !r.children) };
|
|
18186
|
-
}
|
|
18187
|
-
var isNum = (c) => typeof c === "number" || typeof c === "object" && typeof c.text === "number";
|
|
18188
|
-
function fillCell(c) {
|
|
18189
|
-
if (typeof c !== "object")
|
|
18190
|
-
return { text: String(c) };
|
|
18191
|
-
return { ...c, text: String(c.text) };
|
|
18192
|
-
}
|
|
18193
|
-
function fillTable(m) {
|
|
18194
|
-
const wrapped = !m.columns.some((c) => c.key === "cells");
|
|
18195
|
-
const raw = m.rows.map((r) => {
|
|
18196
|
-
const keys = Object.keys(r);
|
|
18197
|
-
const cells = r.cells;
|
|
18198
|
-
return wrapped && keys.length === 1 && keys[0] === "cells" && cells && typeof cells === "object" ? cells : r;
|
|
18199
|
-
});
|
|
18200
|
-
const columns = m.columns.map((c) => {
|
|
18201
|
-
const present = raw.map((r) => r[c.key]).filter((v) => v !== undefined && v !== "");
|
|
18202
|
-
const numeric = present.length > 0 && present.every(isNum);
|
|
18203
|
-
return { ...c, align: c.align ?? (numeric ? "right" : "left") };
|
|
18204
|
-
});
|
|
18205
|
-
const rows = raw.map((r) => {
|
|
18206
|
-
const out = {};
|
|
18207
|
-
for (const [k, v] of Object.entries(r))
|
|
18208
|
-
if (v !== undefined && v !== null)
|
|
18209
|
-
out[k] = fillCell(v);
|
|
18210
|
-
return out;
|
|
18211
|
-
});
|
|
18212
|
-
const primary = columns.find((c) => c.key === m.primary)?.key ?? columns[0]?.key ?? "";
|
|
18213
|
-
return {
|
|
18214
|
-
kind: "table",
|
|
18215
|
-
columns,
|
|
18216
|
-
rows,
|
|
18217
|
-
...m.caption ? { caption: m.caption } : {},
|
|
18218
|
-
stack: m.stack ?? "auto",
|
|
18219
|
-
primary
|
|
18220
|
-
};
|
|
18221
|
-
}
|
|
18222
|
-
function fillChapters(chapters) {
|
|
18223
|
-
const id = uniqueIds();
|
|
18224
|
-
const starts = chapters.map((c) => c.start).sort((a, b) => a - b);
|
|
18225
|
-
return chapters.map((c) => {
|
|
18226
|
-
const next = starts.find((s) => s > c.start);
|
|
18227
|
-
const end = c.end ?? (c.duration !== undefined ? Math.round((c.start + c.duration) * 1000) / 1000 : next);
|
|
18228
|
-
return {
|
|
18229
|
-
id: id(c.id ?? slug(c.label)),
|
|
18230
|
-
label: c.label,
|
|
18231
|
-
start: c.start,
|
|
18232
|
-
...end !== undefined && end > c.start ? { end } : {}
|
|
18233
|
-
};
|
|
18234
|
-
});
|
|
18235
|
-
}
|
|
18236
|
-
function chapterAt(chapters, time) {
|
|
18237
|
-
let at = -1;
|
|
18238
|
-
chapters.forEach((c, i) => {
|
|
18239
|
-
if (time >= c.start && time < (c.end ?? Infinity) && (at < 0 || c.start >= chapters[at].start))
|
|
18240
|
-
at = i;
|
|
18241
|
-
});
|
|
18242
|
-
return at;
|
|
18243
|
-
}
|
|
18244
|
-
function clock(seconds) {
|
|
18245
|
-
const t = Math.max(0, Math.floor(seconds));
|
|
18246
|
-
const h = Math.floor(t / 3600);
|
|
18247
|
-
const m = Math.floor(t % 3600 / 60);
|
|
18248
|
-
const s = String(t % 60).padStart(2, "0");
|
|
18249
|
-
return h ? `${h}:${String(m).padStart(2, "0")}:${s}` : `${m}:${s}`;
|
|
18250
|
-
}
|
|
18251
|
-
function findStep(steps, ref) {
|
|
18252
|
-
if (ref === undefined)
|
|
18253
|
-
return;
|
|
18254
|
-
const r = ref.trim().replace(/^#/, "");
|
|
18255
|
-
return steps.find((s) => s.key === r || s.id === r || s.number === r);
|
|
18256
|
-
}
|
|
18257
|
-
function linkLabel(href) {
|
|
18258
|
-
const pr = /\/(?:pull|issues)\/(\d+)/.exec(href);
|
|
18259
|
-
if (pr)
|
|
18260
|
-
return `#${pr[1]}`;
|
|
18261
|
-
const tail = href.replace(/[?#].*$/, "").replace(/\/+$/, "").split("/").pop();
|
|
18262
|
-
return tail || href;
|
|
18263
|
-
}
|
|
18264
|
-
function normalizeWalkthrough(doc) {
|
|
18265
|
-
const sectionId = uniqueIds();
|
|
18266
|
-
const steps = [];
|
|
18267
|
-
const sections = doc.sections.map((s, si) => {
|
|
18268
|
-
const id = sectionId(s.id ?? slug(s.title));
|
|
18269
|
-
const stepId = uniqueIds();
|
|
18270
|
-
const own = s.steps.map((step, i) => {
|
|
18271
|
-
const sid = stepId(step.id ?? slug(step.title));
|
|
18272
|
-
const optionId = uniqueIds();
|
|
18273
|
-
const options = (step.options ?? []).map((o, oi) => ({
|
|
18274
|
-
id: optionId(o.id ?? String.fromCharCode(97 + oi % 26)),
|
|
18275
|
-
label: o.label,
|
|
18276
|
-
letter: String.fromCharCode(65 + oi % 26),
|
|
18277
|
-
...o.caption ? { caption: o.caption } : {},
|
|
18278
|
-
...o.media !== undefined ? { media: fillMedia(o.media, doc.base) } : {},
|
|
18279
|
-
suggested: o.suggested === true
|
|
18280
|
-
}));
|
|
18281
|
-
const list = step.media === undefined ? [] : Array.isArray(step.media) ? step.media : [step.media];
|
|
18282
|
-
const asked = step.ask === "verdict" ? "note" : step.ask;
|
|
18283
|
-
const ask = asked === "pick" && !options.length ? "note" : asked ?? (options.length ? "pick" : "note");
|
|
18284
|
-
const walk = {
|
|
18285
|
-
key: `${id}/${sid}`,
|
|
18286
|
-
id: sid,
|
|
18287
|
-
section: id,
|
|
18288
|
-
number: `${si + 1}.${i + 1}`,
|
|
18289
|
-
title: step.title,
|
|
18290
|
-
...step.caption ? { caption: step.caption } : {},
|
|
18291
|
-
...step.question ? { question: step.question } : {},
|
|
18292
|
-
media: list.map((m) => fillMedia(m, doc.base)),
|
|
18293
|
-
columns: Math.max(1, Math.min(3, Math.round(step.columns ?? 1))),
|
|
18294
|
-
...step.more?.trim() ? { more: step.more.trim() } : {},
|
|
18295
|
-
options,
|
|
18296
|
-
ask
|
|
17756
|
+
function normalizeWalkthrough(doc) {
|
|
17757
|
+
const sectionId = uniqueIds();
|
|
17758
|
+
const steps = [];
|
|
17759
|
+
const sections = doc.sections.map((s, si) => {
|
|
17760
|
+
const id = sectionId(s.id ?? slug(s.title));
|
|
17761
|
+
const stepId = uniqueIds();
|
|
17762
|
+
const own = s.steps.map((step, i) => {
|
|
17763
|
+
const sid = stepId(step.id ?? slug(step.title));
|
|
17764
|
+
const optionId = uniqueIds();
|
|
17765
|
+
const options = (step.options ?? []).map((o, oi) => ({
|
|
17766
|
+
id: optionId(o.id ?? String.fromCharCode(97 + oi % 26)),
|
|
17767
|
+
label: o.label,
|
|
17768
|
+
letter: String.fromCharCode(65 + oi % 26),
|
|
17769
|
+
...o.caption ? { caption: o.caption } : {},
|
|
17770
|
+
...o.media !== undefined ? { media: fillMedia(o.media, doc.base) } : {},
|
|
17771
|
+
...o.facts ? { facts: fillFacts(o.facts) } : {},
|
|
17772
|
+
suggested: o.suggested === true || o.recommended === true
|
|
17773
|
+
}));
|
|
17774
|
+
const list = step.media === undefined ? [] : Array.isArray(step.media) ? step.media : [step.media];
|
|
17775
|
+
const asked = step.ask === "verdict" ? "note" : step.ask;
|
|
17776
|
+
const ask = asked === "pick" && !options.length ? "note" : asked ?? (options.length ? "pick" : "note");
|
|
17777
|
+
const walk = {
|
|
17778
|
+
key: `${id}/${sid}`,
|
|
17779
|
+
id: sid,
|
|
17780
|
+
section: id,
|
|
17781
|
+
number: `${si + 1}.${i + 1}`,
|
|
17782
|
+
title: step.title,
|
|
17783
|
+
...step.caption ? { caption: step.caption } : {},
|
|
17784
|
+
...step.question ? { question: step.question } : {},
|
|
17785
|
+
media: list.map((m) => fillMedia(m, doc.base)),
|
|
17786
|
+
columns: Math.max(1, Math.min(3, Math.round(step.columns ?? 1))),
|
|
17787
|
+
...step.more?.trim() ? { more: step.more.trim() } : {},
|
|
17788
|
+
options,
|
|
17789
|
+
ask
|
|
18297
17790
|
};
|
|
18298
17791
|
steps.push(walk);
|
|
18299
17792
|
return walk;
|
|
@@ -18362,398 +17855,1192 @@ function prStatus(status) {
|
|
|
18362
17855
|
const state = PR_STATES.includes(first) ? first : first === "shipped" || first === "done" ? "merged" : null;
|
|
18363
17856
|
return { state, checks: /\b(green|passing|passed|ci|checks? pass)/.test(words) };
|
|
18364
17857
|
}
|
|
18365
|
-
function knownPRs(doc, sections, steps = []) {
|
|
18366
|
-
const out = {};
|
|
18367
|
-
const repo = repoOf(doc);
|
|
18368
|
-
const add = (n, pr) => {
|
|
18369
|
-
const known = out[n] ?? {};
|
|
18370
|
-
const next = { ...known };
|
|
18371
|
-
for (const [k, v] of Object.entries(pr))
|
|
18372
|
-
if (v !== undefined && next[k] === undefined)
|
|
18373
|
-
next[k] = v;
|
|
18374
|
-
out[n] = next;
|
|
18375
|
-
};
|
|
18376
|
-
for (const [k, pr] of Object.entries(doc.prs ?? {})) {
|
|
18377
|
-
const n = k.replace(/^#/, "");
|
|
18378
|
-
if (!/^\d+$/.test(n))
|
|
18379
|
-
continue;
|
|
18380
|
-
add(n, pr);
|
|
18381
|
-
}
|
|
18382
|
-
for (const step of steps)
|
|
18383
|
-
for (const m of step.media) {
|
|
18384
|
-
if (m.kind !== "stack")
|
|
18385
|
-
continue;
|
|
18386
|
-
for (const it of m.items) {
|
|
18387
|
-
const n = it.pr.replace(/^#/, "");
|
|
18388
|
-
if (!/^\d+$/.test(n))
|
|
18389
|
-
continue;
|
|
18390
|
-
const said = prStatus(it.status);
|
|
18391
|
-
add(n, {
|
|
18392
|
-
...said.state ? { state: said.state } : {},
|
|
18393
|
-
...said.state && said.checks ? { checks: true } : {},
|
|
18394
|
-
...it.title ? { title: plainMarkdown(it.title) } : {},
|
|
18395
|
-
...it.href ? { href: it.href } : {}
|
|
18396
|
-
});
|
|
18397
|
-
}
|
|
18398
|
-
}
|
|
18399
|
-
for (const s of sections) {
|
|
18400
|
-
const n = s.link && prNumber(s.link.href);
|
|
18401
|
-
if (!n)
|
|
18402
|
-
continue;
|
|
18403
|
-
const said = prStatus(s.status);
|
|
18404
|
-
add(n, {
|
|
18405
|
-
href: s.link.href.replace(/(\/pull\/\d+).*$/, "$1"),
|
|
18406
|
-
...said.state ? { state: said.state } : {},
|
|
18407
|
-
title: plainMarkdown(s.title)
|
|
18408
|
-
});
|
|
18409
|
-
}
|
|
18410
|
-
if (repo) {
|
|
18411
|
-
for (const n of Object.keys(out))
|
|
18412
|
-
if (!out[n].href)
|
|
18413
|
-
out[n] = { ...out[n], href: `https://github.com/${repo}/pull/${n}` };
|
|
17858
|
+
function knownPRs(doc, sections, steps = []) {
|
|
17859
|
+
const out = {};
|
|
17860
|
+
const repo = repoOf(doc);
|
|
17861
|
+
const add = (n, pr) => {
|
|
17862
|
+
const known = out[n] ?? {};
|
|
17863
|
+
const next = { ...known };
|
|
17864
|
+
for (const [k, v] of Object.entries(pr))
|
|
17865
|
+
if (v !== undefined && next[k] === undefined)
|
|
17866
|
+
next[k] = v;
|
|
17867
|
+
out[n] = next;
|
|
17868
|
+
};
|
|
17869
|
+
for (const [k, pr] of Object.entries(doc.prs ?? {})) {
|
|
17870
|
+
const n = k.replace(/^#/, "");
|
|
17871
|
+
if (!/^\d+$/.test(n))
|
|
17872
|
+
continue;
|
|
17873
|
+
add(n, pr);
|
|
17874
|
+
}
|
|
17875
|
+
for (const step of steps)
|
|
17876
|
+
for (const m of step.media) {
|
|
17877
|
+
if (m.kind !== "stack")
|
|
17878
|
+
continue;
|
|
17879
|
+
for (const it of m.items) {
|
|
17880
|
+
const n = it.pr.replace(/^#/, "");
|
|
17881
|
+
if (!/^\d+$/.test(n))
|
|
17882
|
+
continue;
|
|
17883
|
+
const said = prStatus(it.status);
|
|
17884
|
+
add(n, {
|
|
17885
|
+
...said.state ? { state: said.state } : {},
|
|
17886
|
+
...said.state && said.checks ? { checks: true } : {},
|
|
17887
|
+
...it.title ? { title: plainMarkdown(it.title) } : {},
|
|
17888
|
+
...it.href ? { href: it.href } : {}
|
|
17889
|
+
});
|
|
17890
|
+
}
|
|
17891
|
+
}
|
|
17892
|
+
for (const s of sections) {
|
|
17893
|
+
const n = s.link && prNumber(s.link.href);
|
|
17894
|
+
if (!n)
|
|
17895
|
+
continue;
|
|
17896
|
+
const said = prStatus(s.status);
|
|
17897
|
+
add(n, {
|
|
17898
|
+
href: s.link.href.replace(/(\/pull\/\d+).*$/, "$1"),
|
|
17899
|
+
...said.state ? { state: said.state } : {},
|
|
17900
|
+
title: plainMarkdown(s.title)
|
|
17901
|
+
});
|
|
17902
|
+
}
|
|
17903
|
+
if (repo) {
|
|
17904
|
+
for (const n of Object.keys(out))
|
|
17905
|
+
if (!out[n].href)
|
|
17906
|
+
out[n] = { ...out[n], href: `https://github.com/${repo}/pull/${n}` };
|
|
17907
|
+
}
|
|
17908
|
+
return out;
|
|
17909
|
+
}
|
|
17910
|
+
function findPR(prs, n, repo, kind = "pr") {
|
|
17911
|
+
const entry = prs[n];
|
|
17912
|
+
const known = entry && (entry.kind ?? "pr") === kind ? entry : undefined;
|
|
17913
|
+
const path = kind === "issue" ? "issues" : "pull";
|
|
17914
|
+
const href = repo ? `https://github.com/${repo}/${path}/${n}` : undefined;
|
|
17915
|
+
const base = kind === "issue" ? { kind } : {};
|
|
17916
|
+
if (known)
|
|
17917
|
+
return known.href || !href ? { ...base, ...known } : { ...base, ...known, href };
|
|
17918
|
+
return href ? { ...base, href } : kind === "issue" ? { kind } : null;
|
|
17919
|
+
}
|
|
17920
|
+
var issueNumber = (href) => /^https?:\/\/[^/]+\/.+\/issues\/(\d+)\/?(?:[?#].*)?$/.exec(href)?.[1] ?? null;
|
|
17921
|
+
var isPullRequest = (href) => /\/(?:pull|merge_requests)\/\d+(?:[/?#]|$)/.test(href);
|
|
17922
|
+
function withKickers(sections, source, kickers = "pr") {
|
|
17923
|
+
if (kickers === false)
|
|
17924
|
+
return [...sections];
|
|
17925
|
+
const noun = typeof kickers === "object" ? kickers.noun.trim() : "PR";
|
|
17926
|
+
const counted = typeof kickers === "object" ? sections : sections.filter((s) => s.link && isPullRequest(s.link.href));
|
|
17927
|
+
return sections.map((s, i) => {
|
|
17928
|
+
const own = source[i]?.kicker;
|
|
17929
|
+
if (own !== undefined || s.kicker)
|
|
17930
|
+
return s;
|
|
17931
|
+
const at = counted.indexOf(s);
|
|
17932
|
+
return at < 0 ? s : { ...s, kicker: `${noun} ${at + 1} of ${counted.length}`.trim() };
|
|
17933
|
+
});
|
|
17934
|
+
}
|
|
17935
|
+
function tableStacks(stack, room, natural) {
|
|
17936
|
+
if (stack !== "auto")
|
|
17937
|
+
return stack === "always";
|
|
17938
|
+
return room > 0 && natural > room + 1;
|
|
17939
|
+
}
|
|
17940
|
+
function noteBarReserve(bar, inset, gap) {
|
|
17941
|
+
return Math.max(0, Math.ceil(bar)) + inset + gap;
|
|
17942
|
+
}
|
|
17943
|
+
function parseAt(at) {
|
|
17944
|
+
if (!at)
|
|
17945
|
+
return null;
|
|
17946
|
+
const t = Date.parse(at);
|
|
17947
|
+
return Number.isFinite(t) ? t : null;
|
|
17948
|
+
}
|
|
17949
|
+
function sortFeed(items) {
|
|
17950
|
+
return items.map((item, i) => ({ item, i })).sort((a, b) => {
|
|
17951
|
+
const x = a.item.at ?? -Infinity;
|
|
17952
|
+
const y = b.item.at ?? -Infinity;
|
|
17953
|
+
return x !== y ? y - x : b.i - a.i;
|
|
17954
|
+
}).map((e) => e.item);
|
|
17955
|
+
}
|
|
17956
|
+
function ago(at, now) {
|
|
17957
|
+
const s = Math.max(0, (now - at) / 1000);
|
|
17958
|
+
if (s < 45)
|
|
17959
|
+
return "just now";
|
|
17960
|
+
if (s < 3600)
|
|
17961
|
+
return `${Math.round(s / 60)}m ago`;
|
|
17962
|
+
if (s < 86400)
|
|
17963
|
+
return `${Math.round(s / 3600)}h ago`;
|
|
17964
|
+
if (s < 7 * 86400)
|
|
17965
|
+
return `${Math.round(s / 86400)}d ago`;
|
|
17966
|
+
return new Date(at).toISOString().slice(0, 10);
|
|
17967
|
+
}
|
|
17968
|
+
function statusLook(status) {
|
|
17969
|
+
const id = slug(status);
|
|
17970
|
+
const tone = id === "ready" || id === "merged" || id === "shipped" ? "good" : id === "needs-taste" || id === "needs-review" || id === "review" ? "warn" : id === "blocked" || id === "broken" || id === "failing" ? "bad" : id === "draft" || id === "wip" ? "dim" : "info";
|
|
17971
|
+
return { label: label(status), tone };
|
|
17972
|
+
}
|
|
17973
|
+
function verdictTone(id, index) {
|
|
17974
|
+
if (/^(ship|yes|good|approve|keep|lgtm)/.test(id))
|
|
17975
|
+
return "good";
|
|
17976
|
+
if (/^(change|fix|revise|tweak|redo)/.test(id))
|
|
17977
|
+
return "warn";
|
|
17978
|
+
if (/^(no|reject|drop|revert|bad)/.test(id))
|
|
17979
|
+
return "bad";
|
|
17980
|
+
if (/^(unsure|maybe|skip|later)/.test(id))
|
|
17981
|
+
return "dim";
|
|
17982
|
+
return index === 0 ? "good" : "dim";
|
|
17983
|
+
}
|
|
17984
|
+
var EMPTY_STATE = { steps: {} };
|
|
17985
|
+
function setFeedback(state, key, patch) {
|
|
17986
|
+
const next = { ...state.steps[key] };
|
|
17987
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
17988
|
+
if (v === undefined)
|
|
17989
|
+
continue;
|
|
17990
|
+
if (v === null || v.trim() === "")
|
|
17991
|
+
delete next[k];
|
|
17992
|
+
else
|
|
17993
|
+
next[k] = v;
|
|
17994
|
+
}
|
|
17995
|
+
const steps = { ...state.steps };
|
|
17996
|
+
if (Object.keys(next).length)
|
|
17997
|
+
steps[key] = next;
|
|
17998
|
+
else
|
|
17999
|
+
delete steps[key];
|
|
18000
|
+
return { ...state, steps };
|
|
18001
|
+
}
|
|
18002
|
+
var hasContent = (n) => Boolean(n.text?.trim() || n.voice || n.ink?.paths.length);
|
|
18003
|
+
function addNote(state, note) {
|
|
18004
|
+
if (!hasContent(note))
|
|
18005
|
+
return state;
|
|
18006
|
+
return { ...state, notes: [...state.notes ?? [], note] };
|
|
18007
|
+
}
|
|
18008
|
+
function removeNote(state, id) {
|
|
18009
|
+
return { ...state, notes: (state.notes ?? []).filter((n) => n.id !== id) };
|
|
18010
|
+
}
|
|
18011
|
+
var notesOn = (state, key) => (state.notes ?? []).filter((n) => n.step === key);
|
|
18012
|
+
function isAnswered(step, state) {
|
|
18013
|
+
if (step.ask === "none")
|
|
18014
|
+
return false;
|
|
18015
|
+
const fb = state.steps[step.key];
|
|
18016
|
+
if (step.ask === "pick")
|
|
18017
|
+
return fb?.pick !== undefined;
|
|
18018
|
+
return Boolean(fb?.verdict || fb?.note || notesOn(state, step.key).length);
|
|
18019
|
+
}
|
|
18020
|
+
function digitChoice(step, fb, key) {
|
|
18021
|
+
if (step.ask !== "pick" || !/^[1-9]$/.test(key))
|
|
18022
|
+
return;
|
|
18023
|
+
const id = step.options[Number(key) - 1]?.id;
|
|
18024
|
+
if (id === undefined)
|
|
18025
|
+
return;
|
|
18026
|
+
return fb?.pick === id ? null : id;
|
|
18027
|
+
}
|
|
18028
|
+
function walkthroughProgress(w, state) {
|
|
18029
|
+
const asked = w.steps.filter((s) => s.ask !== "none");
|
|
18030
|
+
const done = asked.filter((s) => isAnswered(s, state));
|
|
18031
|
+
const next = asked.find((s) => !isAnswered(s, state));
|
|
18032
|
+
return { done: done.length, total: asked.length, next: next?.key ?? null };
|
|
18033
|
+
}
|
|
18034
|
+
function adjacentStep(keys, current, dir) {
|
|
18035
|
+
if (!keys.length)
|
|
18036
|
+
return;
|
|
18037
|
+
const i = current === null ? -1 : keys.indexOf(current);
|
|
18038
|
+
if (i < 0)
|
|
18039
|
+
return dir > 0 ? keys[0] : keys[keys.length - 1];
|
|
18040
|
+
return keys[Math.max(0, Math.min(keys.length - 1, i + dir))];
|
|
18041
|
+
}
|
|
18042
|
+
var LOG_MAX = 40;
|
|
18043
|
+
function pushContext(log, event, max = LOG_MAX) {
|
|
18044
|
+
const last = log[log.length - 1];
|
|
18045
|
+
const merge = last && last.what === event.what && last.step === event.step && (event.what === "seek" || event.what === "step") && event.at - last.at < 1500;
|
|
18046
|
+
const next = merge ? [...log.slice(0, -1), event] : [...log, event];
|
|
18047
|
+
return next.slice(-max);
|
|
18048
|
+
}
|
|
18049
|
+
function recentContext(log, now, windowMs = 90000, min = 4) {
|
|
18050
|
+
const inWindow = log.filter((e) => now - e.at <= windowMs);
|
|
18051
|
+
return inWindow.length >= min ? inWindow : log.slice(-min);
|
|
18052
|
+
}
|
|
18053
|
+
function formatTime(seconds) {
|
|
18054
|
+
const t = Math.max(0, Math.round(seconds * 10) / 10);
|
|
18055
|
+
const m = Math.floor(t / 60);
|
|
18056
|
+
const s = (t - m * 60).toFixed(1).padStart(4, "0");
|
|
18057
|
+
return `${m}:${s}`;
|
|
18058
|
+
}
|
|
18059
|
+
function inkPath(points, w, h) {
|
|
18060
|
+
if (!points.length || w <= 0 || h <= 0)
|
|
18061
|
+
return "";
|
|
18062
|
+
const n = (v) => String(Math.round(Math.max(0, Math.min(1, v)) * 1000) / 1000);
|
|
18063
|
+
const kept = [points[0]];
|
|
18064
|
+
for (const p of points.slice(1)) {
|
|
18065
|
+
const l = kept[kept.length - 1];
|
|
18066
|
+
if (Math.hypot(p.x - l.x, p.y - l.y) >= 1)
|
|
18067
|
+
kept.push(p);
|
|
18068
|
+
}
|
|
18069
|
+
return kept.map((p, i) => `${i ? "L" : "M"}${n(p.x / w)} ${n(p.y / h)}`).join("");
|
|
18070
|
+
}
|
|
18071
|
+
function zoomWords(z) {
|
|
18072
|
+
const times = Math.round(1 / Math.max(0.001, Math.max(z.w, z.h)) * 10) / 10;
|
|
18073
|
+
return `${times}× at ${Math.round(z.u * 100)}%, ${Math.round(z.v * 100)}%`;
|
|
18074
|
+
}
|
|
18075
|
+
function describeMedia(m) {
|
|
18076
|
+
if (!m)
|
|
18077
|
+
return "";
|
|
18078
|
+
const parts = [];
|
|
18079
|
+
if (m.file)
|
|
18080
|
+
parts.push(`${m.kind} ${m.file}`);
|
|
18081
|
+
else if (m.time !== undefined)
|
|
18082
|
+
parts.push(`${m.kind} at ${formatTime(m.time)}`);
|
|
18083
|
+
else
|
|
18084
|
+
parts.push(m.kind);
|
|
18085
|
+
if (m.chapter)
|
|
18086
|
+
parts[0] += ` in “${m.chapter}”`;
|
|
18087
|
+
if (m.loop)
|
|
18088
|
+
parts.push(`loop ${formatTime(m.loop.start)}–${formatTime(m.loop.end)}`);
|
|
18089
|
+
if (m.zoom)
|
|
18090
|
+
parts.push(`zoomed ${zoomWords(m.zoom)}`);
|
|
18091
|
+
if (m.mode)
|
|
18092
|
+
parts.push(m.mode);
|
|
18093
|
+
if (m.full)
|
|
18094
|
+
parts.push("full window");
|
|
18095
|
+
return parts.join(", ");
|
|
18096
|
+
}
|
|
18097
|
+
function readState(raw) {
|
|
18098
|
+
if (!raw)
|
|
18099
|
+
return EMPTY_STATE;
|
|
18100
|
+
try {
|
|
18101
|
+
const v = JSON.parse(raw);
|
|
18102
|
+
if (!v || typeof v !== "object")
|
|
18103
|
+
return EMPTY_STATE;
|
|
18104
|
+
const o = v;
|
|
18105
|
+
const steps = {};
|
|
18106
|
+
if (o.steps && typeof o.steps === "object")
|
|
18107
|
+
for (const [key, fb] of Object.entries(o.steps)) {
|
|
18108
|
+
if (!fb || typeof fb !== "object")
|
|
18109
|
+
continue;
|
|
18110
|
+
const f = fb;
|
|
18111
|
+
const clean = {};
|
|
18112
|
+
for (const k of ["verdict", "pick", "note"])
|
|
18113
|
+
if (typeof f[k] === "string" && f[k].trim())
|
|
18114
|
+
clean[k] = f[k];
|
|
18115
|
+
if (Object.keys(clean).length)
|
|
18116
|
+
steps[key] = clean;
|
|
18117
|
+
}
|
|
18118
|
+
const notes = Array.isArray(o.notes) ? o.notes.filter(isNote) : [];
|
|
18119
|
+
return {
|
|
18120
|
+
steps,
|
|
18121
|
+
...notes.length ? { notes } : {},
|
|
18122
|
+
...typeof o.note === "string" && o.note.trim() ? { note: o.note } : {}
|
|
18123
|
+
};
|
|
18124
|
+
} catch {
|
|
18125
|
+
return EMPTY_STATE;
|
|
18126
|
+
}
|
|
18127
|
+
}
|
|
18128
|
+
function isNote(v) {
|
|
18129
|
+
if (!v || typeof v !== "object")
|
|
18130
|
+
return false;
|
|
18131
|
+
const n = v;
|
|
18132
|
+
const ctx = n.context;
|
|
18133
|
+
return typeof n.id === "string" && typeof n.at === "number" && (n.step === null || typeof n.step === "string") && (n.text === undefined || typeof n.text === "string") && !!ctx && Array.isArray(ctx.log) && hasContent(n);
|
|
18134
|
+
}
|
|
18135
|
+
var noteOut = (n) => ({
|
|
18136
|
+
at: new Date(n.at).toISOString(),
|
|
18137
|
+
...n.text?.trim() ? { text: n.text.trim() } : {},
|
|
18138
|
+
...n.voice ? { voice: n.voice } : {},
|
|
18139
|
+
...n.ink?.paths.length ? { ink: n.ink } : {},
|
|
18140
|
+
...n.context.media ? { media: n.context.media } : {},
|
|
18141
|
+
log: n.context.log.map(({ at, ...e }) => ({ ...e, ago: Math.round((n.at - at) / 100) / 10 }))
|
|
18142
|
+
});
|
|
18143
|
+
function collectFeedback(w, state) {
|
|
18144
|
+
const progress = walkthroughProgress(w, state);
|
|
18145
|
+
const sectionTitle = new Map(w.sections.map((s) => [s.id, s.link ? `${s.link.label} ${s.title}` : s.title]));
|
|
18146
|
+
const keys = new Set(w.steps.map((s) => s.key));
|
|
18147
|
+
const steps = [];
|
|
18148
|
+
for (const s of w.steps) {
|
|
18149
|
+
const fb = state.steps[s.key];
|
|
18150
|
+
const notes = notesOn(state, s.key).map(noteOut);
|
|
18151
|
+
if (s.ask === "none" && !notes.length)
|
|
18152
|
+
continue;
|
|
18153
|
+
const option = s.ask === "pick" ? s.options.find((o) => o.id === fb?.pick) : undefined;
|
|
18154
|
+
const suggested = s.options.find((o) => o.suggested);
|
|
18155
|
+
steps.push({
|
|
18156
|
+
key: s.key,
|
|
18157
|
+
number: s.number,
|
|
18158
|
+
section: sectionTitle.get(s.section) ?? s.section,
|
|
18159
|
+
title: s.title,
|
|
18160
|
+
ask: s.ask,
|
|
18161
|
+
...option ? { pick: option.id, pickLabel: `${option.letter}: ${option.label}` } : {},
|
|
18162
|
+
...option && suggested && suggested.id !== option.id ? { suggested: `${suggested.letter}: ${suggested.label}` } : {},
|
|
18163
|
+
notes,
|
|
18164
|
+
...fb?.verdict ? { verdict: fb.verdict } : {},
|
|
18165
|
+
...fb?.note ? { note: fb.note } : {}
|
|
18166
|
+
});
|
|
18167
|
+
}
|
|
18168
|
+
return {
|
|
18169
|
+
id: w.id,
|
|
18170
|
+
title: w.title,
|
|
18171
|
+
reviewed: progress.done,
|
|
18172
|
+
total: progress.total,
|
|
18173
|
+
...state.note ? { note: state.note } : {},
|
|
18174
|
+
steps,
|
|
18175
|
+
general: (state.notes ?? []).filter((n) => !n.step || !keys.has(n.step)).map(noteOut)
|
|
18176
|
+
};
|
|
18177
|
+
}
|
|
18178
|
+
var quote = (text) => text.trim().replace(/\s*\n\s*/g, " ");
|
|
18179
|
+
function noteLine(n) {
|
|
18180
|
+
const bits = [];
|
|
18181
|
+
if (n.text)
|
|
18182
|
+
bits.push(`“${quote(n.text)}”`);
|
|
18183
|
+
if (n.voice)
|
|
18184
|
+
bits.push(`voice note ${formatTime(n.voice.duration)} (${n.voice.url})`);
|
|
18185
|
+
if (n.ink)
|
|
18186
|
+
bits.push(`ink, ${n.ink.paths.length} stroke${n.ink.paths.length === 1 ? "" : "s"}`);
|
|
18187
|
+
const seen = describeMedia(n.media);
|
|
18188
|
+
return `${bits.join(" + ")}${seen ? ` · ${seen}` : ""}`;
|
|
18189
|
+
}
|
|
18190
|
+
function feedbackMarkdown(fb, verdicts) {
|
|
18191
|
+
const name = (id) => verdicts?.find((v) => v.id === id)?.label ?? label(id);
|
|
18192
|
+
const lines = [
|
|
18193
|
+
`## Feedback: ${fb.title}`,
|
|
18194
|
+
"",
|
|
18195
|
+
`${fb.reviewed} of ${fb.total} reviewed · \`${fb.id}\``
|
|
18196
|
+
];
|
|
18197
|
+
if (fb.note)
|
|
18198
|
+
lines.push("", quote(fb.note));
|
|
18199
|
+
if (fb.general.length) {
|
|
18200
|
+
lines.push("", "### General");
|
|
18201
|
+
for (const n of fb.general)
|
|
18202
|
+
lines.push(`- ${noteLine(n)}`);
|
|
18203
|
+
}
|
|
18204
|
+
let section = "";
|
|
18205
|
+
for (const s of fb.steps) {
|
|
18206
|
+
if (s.section !== section) {
|
|
18207
|
+
section = s.section;
|
|
18208
|
+
lines.push("", `### ${section}`);
|
|
18209
|
+
}
|
|
18210
|
+
const head = [];
|
|
18211
|
+
if (s.pickLabel)
|
|
18212
|
+
head.push(`**Picked ${s.pickLabel}**${s.suggested ? ` (suggested ${s.suggested})` : ""}`);
|
|
18213
|
+
if (s.verdict)
|
|
18214
|
+
head.push(`**${name(s.verdict)}**`);
|
|
18215
|
+
if (s.note)
|
|
18216
|
+
head.push(`“${quote(s.note)}”`);
|
|
18217
|
+
const open = !head.length && !s.notes.length;
|
|
18218
|
+
lines.push(`- ${s.number} ${s.title}: ${open ? "_open_" : head.join(" · ") || `${s.notes.length} note${s.notes.length === 1 ? "" : "s"}`}`);
|
|
18219
|
+
for (const n of s.notes)
|
|
18220
|
+
lines.push(` - ${noteLine(n)}`);
|
|
18221
|
+
}
|
|
18222
|
+
return lines.join(`
|
|
18223
|
+
`) + `
|
|
18224
|
+
`;
|
|
18225
|
+
}
|
|
18226
|
+
var escapeHtml = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
18227
|
+
function plainMarkdown(text) {
|
|
18228
|
+
return text.replace(/\[([^\]\n]+)\]\([^)\s]+\)/g, "$1").replace(/`([^`\n]+)`/g, "$1").replace(/\*\*([^*\n]+)\*\*/g, "$1").replace(/(^|[^*\w])\*([^*\n]+)\*(?!\w)/g, "$1$2").replace(/\s+/g, " ").trim();
|
|
18229
|
+
}
|
|
18230
|
+
function inlineMarkdown(text) {
|
|
18231
|
+
const codes = [];
|
|
18232
|
+
let s = text.replace(/`([^`\n]+)`/g, (_, c) => {
|
|
18233
|
+
codes.push(c);
|
|
18234
|
+
return `\x00${codes.length - 1}\x00`;
|
|
18235
|
+
});
|
|
18236
|
+
s = escapeHtml(s);
|
|
18237
|
+
s = s.replace(/\[([^\]\n]+)\]\(([^)\s]+)\)/g, (m, t, href) => {
|
|
18238
|
+
const raw = href.replace(/&/g, "&");
|
|
18239
|
+
if (!/^(https?:\/\/|\/|\.{0,2}\/|#)/i.test(raw) && /^[a-z][a-z0-9+.-]*:/i.test(raw))
|
|
18240
|
+
return m;
|
|
18241
|
+
return `<a href="${href}" target="_blank" rel="noreferrer">${t}</a>`;
|
|
18242
|
+
});
|
|
18243
|
+
s = s.replace(/\*\*([^*\n]+)\*\*/g, "<b>$1</b>");
|
|
18244
|
+
s = s.replace(/(^|[^*\w])\*([^*\n]+)\*(?!\w)/g, "$1<i>$2</i>");
|
|
18245
|
+
s = s.replace(/\n{2,}/g, "<br><br>").replace(/\n/g, " ");
|
|
18246
|
+
return s.replace(/\u0000(\d+)\u0000/g, (_, i) => `<code>${escapeHtml(codes[Number(i)])}</code>`);
|
|
18247
|
+
}
|
|
18248
|
+
|
|
18249
|
+
// src/ui/review-options.ts
|
|
18250
|
+
var el = (doc, tag, cls) => {
|
|
18251
|
+
const n = doc.createElement(tag);
|
|
18252
|
+
if (cls)
|
|
18253
|
+
n.className = cls;
|
|
18254
|
+
return n;
|
|
18255
|
+
};
|
|
18256
|
+
function renderFacts(doc, groups) {
|
|
18257
|
+
const box = el(doc, "div", "knobs-wt-facts");
|
|
18258
|
+
for (const g of groups) {
|
|
18259
|
+
if (!g.facts.length && !g.title)
|
|
18260
|
+
continue;
|
|
18261
|
+
const group = el(doc, "section", "knobs-wt-fgroup");
|
|
18262
|
+
if (g.title) {
|
|
18263
|
+
const head = el(doc, "h3", "knobs-wt-fhead");
|
|
18264
|
+
head.innerHTML = inlineMarkdown(g.title);
|
|
18265
|
+
group.append(head);
|
|
18266
|
+
}
|
|
18267
|
+
if (g.facts.length)
|
|
18268
|
+
group.append(factList(doc, g.facts));
|
|
18269
|
+
box.append(group);
|
|
18270
|
+
}
|
|
18271
|
+
return box;
|
|
18272
|
+
}
|
|
18273
|
+
function factList(doc, facts) {
|
|
18274
|
+
const dl = el(doc, "dl", "knobs-wt-fterms");
|
|
18275
|
+
for (const f of facts) {
|
|
18276
|
+
const row = el(doc, "div", "knobs-wt-fact");
|
|
18277
|
+
const dt = el(doc, "dt");
|
|
18278
|
+
dt.innerHTML = inlineMarkdown(f.term);
|
|
18279
|
+
const dd = el(doc, "dd");
|
|
18280
|
+
if (f.code) {
|
|
18281
|
+
dd.dataset.code = "";
|
|
18282
|
+
for (const v of f.value) {
|
|
18283
|
+
const chip = el(doc, "code");
|
|
18284
|
+
chip.textContent = v;
|
|
18285
|
+
dd.append(chip);
|
|
18286
|
+
}
|
|
18287
|
+
} else
|
|
18288
|
+
for (const v of f.value) {
|
|
18289
|
+
const line = el(doc, "div");
|
|
18290
|
+
line.innerHTML = inlineMarkdown(v);
|
|
18291
|
+
dd.append(line);
|
|
18292
|
+
}
|
|
18293
|
+
row.append(dt, dd);
|
|
18294
|
+
dl.append(row);
|
|
18295
|
+
}
|
|
18296
|
+
return dl;
|
|
18297
|
+
}
|
|
18298
|
+
function optionTile(doc, o, options = {}) {
|
|
18299
|
+
const tile = el(doc, "div", "knobs-wt-option");
|
|
18300
|
+
const head = options.head ?? el(doc, "div");
|
|
18301
|
+
head.classList.add("knobs-wt-ohead");
|
|
18302
|
+
const kicker = el(doc, "span", "knobs-wt-okicker");
|
|
18303
|
+
const n = el(doc, "span");
|
|
18304
|
+
n.textContent = o.number;
|
|
18305
|
+
kicker.append(n);
|
|
18306
|
+
if (o.recommended) {
|
|
18307
|
+
const rec = el(doc, "span", "knobs-wt-orec");
|
|
18308
|
+
rec.textContent = "Recommended";
|
|
18309
|
+
kicker.append(rec);
|
|
18310
|
+
}
|
|
18311
|
+
const title = el(doc, "span", "knobs-wt-otitle");
|
|
18312
|
+
title.innerHTML = inlineMarkdown(o.title);
|
|
18313
|
+
head.append(kicker, title);
|
|
18314
|
+
tile.append(head);
|
|
18315
|
+
if (o.summary) {
|
|
18316
|
+
const p = el(doc, "p", "knobs-wt-osum");
|
|
18317
|
+
p.innerHTML = inlineMarkdown(o.summary);
|
|
18318
|
+
tile.append(p);
|
|
18319
|
+
}
|
|
18320
|
+
options.media?.(tile);
|
|
18321
|
+
if (o.facts?.length)
|
|
18322
|
+
tile.append(renderFacts(doc, [{ facts: o.facts }]));
|
|
18323
|
+
return { tile, head, kicker };
|
|
18324
|
+
}
|
|
18325
|
+
function layoutOptions(root, tiles) {
|
|
18326
|
+
let cols = 0;
|
|
18327
|
+
const layout = () => {
|
|
18328
|
+
const width = root.clientWidth;
|
|
18329
|
+
if (!width)
|
|
18330
|
+
return;
|
|
18331
|
+
const next = optionColumns(tiles.length, width);
|
|
18332
|
+
if (next === cols)
|
|
18333
|
+
return;
|
|
18334
|
+
cols = next;
|
|
18335
|
+
root.style.setProperty("--kw-cols", String(cols));
|
|
18336
|
+
const rows = Math.ceil(tiles.length / cols);
|
|
18337
|
+
tiles.forEach((t, i) => {
|
|
18338
|
+
const row = Math.floor(i / cols);
|
|
18339
|
+
t.dataset.col = String(i % cols);
|
|
18340
|
+
t.dataset.row = String(row);
|
|
18341
|
+
t.toggleAttribute("data-end", i % cols === cols - 1);
|
|
18342
|
+
t.toggleAttribute("data-last-row", row === rows - 1);
|
|
18343
|
+
});
|
|
18344
|
+
};
|
|
18345
|
+
layout();
|
|
18346
|
+
const win = root.ownerDocument.defaultView;
|
|
18347
|
+
if (!win || typeof win.ResizeObserver !== "function")
|
|
18348
|
+
return () => {};
|
|
18349
|
+
const ro = new win.ResizeObserver(layout);
|
|
18350
|
+
ro.observe(root);
|
|
18351
|
+
return () => ro.disconnect();
|
|
18352
|
+
}
|
|
18353
|
+
|
|
18354
|
+
// src/ui/walkthrough.ts
|
|
18355
|
+
init_selection_pill();
|
|
18356
|
+
|
|
18357
|
+
// src/ui/toast.ts
|
|
18358
|
+
init_look();
|
|
18359
|
+
init_layer();
|
|
18360
|
+
var regions = new WeakMap;
|
|
18361
|
+
function toast(message, options = {}) {
|
|
18362
|
+
const doc = options.within?.ownerDocument ?? options.doc ?? document;
|
|
18363
|
+
const win = doc.defaultView;
|
|
18364
|
+
const key = options.within ?? doc;
|
|
18365
|
+
let r = regions.get(key);
|
|
18366
|
+
if (!r || !r.el.isConnected) {
|
|
18367
|
+
const el = options.within ? Object.assign(doc.createElement("div"), { className: "knobs-root knobs-ui knobs-toast" }) : layerElement(doc, "knobs-layer knobs-toast", options.look);
|
|
18368
|
+
if (options.within) {
|
|
18369
|
+
injectUiStyles(doc);
|
|
18370
|
+
options.within.append(el);
|
|
18371
|
+
}
|
|
18372
|
+
el.setAttribute("role", "status");
|
|
18373
|
+
el.setAttribute("aria-live", "polite");
|
|
18374
|
+
const text = doc.createElement("span");
|
|
18375
|
+
text.className = "knobs-toast-text";
|
|
18376
|
+
const button = doc.createElement("button");
|
|
18377
|
+
button.type = "button";
|
|
18378
|
+
button.className = "knobs-toast-action";
|
|
18379
|
+
el.append(text, button);
|
|
18380
|
+
const region = {
|
|
18381
|
+
el,
|
|
18382
|
+
text,
|
|
18383
|
+
button,
|
|
18384
|
+
motion: presence(el, { scale: 0.96, blur: 4, dy: 10, duration: 0.36 }),
|
|
18385
|
+
timer: 0
|
|
18386
|
+
};
|
|
18387
|
+
r = region;
|
|
18388
|
+
regions.set(key, region);
|
|
18389
|
+
el.addEventListener("pointerenter", () => win.clearTimeout(region.timer));
|
|
18390
|
+
el.addEventListener("pointerleave", () => {
|
|
18391
|
+
if (region.motion.shown)
|
|
18392
|
+
arm(region, 1600);
|
|
18393
|
+
});
|
|
18394
|
+
}
|
|
18395
|
+
const region = r;
|
|
18396
|
+
const arm = (reg, ms) => {
|
|
18397
|
+
win.clearTimeout(reg.timer);
|
|
18398
|
+
reg.timer = win.setTimeout(() => void reg.motion.hide(), ms);
|
|
18399
|
+
};
|
|
18400
|
+
region.text.textContent = message;
|
|
18401
|
+
region.el.dataset.tone = options.tone ?? "info";
|
|
18402
|
+
const action = options.action;
|
|
18403
|
+
region.button.hidden = !action;
|
|
18404
|
+
region.button.textContent = action?.label ?? "";
|
|
18405
|
+
region.button.onclick = action ? () => {
|
|
18406
|
+
action.run();
|
|
18407
|
+
region.motion.hide();
|
|
18408
|
+
} : null;
|
|
18409
|
+
region.motion.show();
|
|
18410
|
+
arm(region, options.linger ?? (options.tone === "error" ? 6000 : 3200));
|
|
18411
|
+
return {
|
|
18412
|
+
dismiss() {
|
|
18413
|
+
if (region.text.textContent !== message)
|
|
18414
|
+
return;
|
|
18415
|
+
win.clearTimeout(region.timer);
|
|
18416
|
+
region.motion.hide();
|
|
18417
|
+
}
|
|
18418
|
+
};
|
|
18419
|
+
}
|
|
18420
|
+
|
|
18421
|
+
// src/ui/walkthrough-annotations.ts
|
|
18422
|
+
var EYE = "M247.31,124.76c-.35-.79-8.82-19.58-27.65-38.41C194.57,61.26,162.88,48,128,48S61.43,61.26,36.34,86.35C17.51,105.18,9,124,8.69,124.76a8,8,0,0,0,0,6.5c.35.79,8.82,19.57,27.65,38.4C61.43,194.74,93.12,208,128,208s66.57-13.26,91.66-38.34c18.83-18.83,27.3-37.61,27.65-38.4A8,8,0,0,0,247.31,124.76ZM128,192c-30.78,0-57.67-11.19-79.93-33.25A133.47,133.47,0,0,1,25,128,133.33,133.33,0,0,1,48.07,97.25C70.33,75.19,97.22,64,128,64s57.67,11.19,79.93,33.25A133.46,133.46,0,0,1,231.05,128C223.84,141.46,192.43,192,128,192Zm0-112a48,48,0,1,0,48,48A48.05,48.05,0,0,0,128,80Zm0,80a32,32,0,1,1,32-32A32,32,0,0,1,128,160Z";
|
|
18423
|
+
var EYE_OFF = "M53.92,34.62A8,8,0,1,0,42.08,45.38L61.32,66.55C25,88.84,9.38,123.2,8.69,124.76a8,8,0,0,0,0,6.5c.35.79,8.82,19.57,27.65,38.4C61.43,194.74,93.12,208,128,208a127.11,127.11,0,0,0,52.07-10.83l22,24.21a8,8,0,1,0,11.84-10.76Zm47.33,75.84,41.67,45.85a32,32,0,0,1-41.67-45.85ZM128,192c-30.78,0-57.67-11.19-79.93-33.25A133.16,133.16,0,0,1,25,128c4.69-8.79,19.66-33.39,47.35-49.38l18,19.75a48,48,0,0,0,63.66,70l14.73,16.2A112,112,0,0,1,128,192Zm6-95.43a8,8,0,0,1,3-15.72,48.16,48.16,0,0,1,38.77,42.64,8,8,0,0,1-7.22,8.71,6.39,6.39,0,0,1-.75,0,8,8,0,0,1-8-7.26A32.09,32.09,0,0,0,134,96.57Zm113.28,34.69c-.42.94-10.55,23.37-33.36,43.8a8,8,0,1,1-10.67-11.92A132.77,132.77,0,0,0,231.05,128a133.15,133.15,0,0,0-23.12-30.77C185.67,75.19,158.78,64,128,64a118.37,118.37,0,0,0-19.36,1.57A8,8,0,1,1,106,49.79,134,134,0,0,1,128,48c34.88,0,66.57,13.26,91.66,38.35,18.83,18.83,27.3,37.62,27.65,38.41A8,8,0,0,1,247.31,131.26Z";
|
|
18424
|
+
var glyph = (d) => `<svg class="knobs-icon" viewBox="0 0 256 256" width="16" height="16" fill="currentColor" aria-hidden="true" focusable="false"><path d="${d}"/></svg>`;
|
|
18425
|
+
var ARROW = '<svg class="knobs-wt-anno-arrow" viewBox="0 0 28 28" width="28" height="28" aria-hidden="true"><path d="M3 3 L24 24 M24 24 L24 13 M24 24 L13 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
18426
|
+
var shapeOf = (a) => a.shape ?? (a.w !== undefined && a.h !== undefined ? "box" : "dot");
|
|
18427
|
+
var showsAt = (a, time) => !a.at || time === undefined || time >= a.at[0] && time <= a.at[1];
|
|
18428
|
+
function placeMark(a, rect, view = ALL) {
|
|
18429
|
+
const w = a.w ?? 0;
|
|
18430
|
+
const h = a.h ?? 0;
|
|
18431
|
+
const x = (a.x - view.u) / view.w;
|
|
18432
|
+
const y = (a.y - view.v) / view.h;
|
|
18433
|
+
const ww = w / view.w;
|
|
18434
|
+
const hh = h / view.h;
|
|
18435
|
+
if (x + ww < 0 || y + hh < 0 || x > 1 || y > 1)
|
|
18436
|
+
return null;
|
|
18437
|
+
return { x: rect.x + x * rect.w, y: rect.y + y * rect.h, w: ww * rect.w, h: hh * rect.h };
|
|
18438
|
+
}
|
|
18439
|
+
var ALL = { u: 0, v: 0, w: 1, h: 1 };
|
|
18440
|
+
var scopeOf = (el) => el.closest(".knobs-walkthrough") ?? el.closest(".knobs-wt-slot") ?? el;
|
|
18441
|
+
var annotationsOn = (scope) => scope.dataset.annotations !== "off";
|
|
18442
|
+
function setAnnotations(scope, on) {
|
|
18443
|
+
if (on)
|
|
18444
|
+
delete scope.dataset.annotations;
|
|
18445
|
+
else
|
|
18446
|
+
scope.dataset.annotations = "off";
|
|
18447
|
+
for (const b of Array.from(scope.querySelectorAll(".knobs-wt-anno-toggle")))
|
|
18448
|
+
syncToggle(b, on);
|
|
18449
|
+
}
|
|
18450
|
+
function syncToggle(b, on) {
|
|
18451
|
+
b.setAttribute("aria-pressed", String(on));
|
|
18452
|
+
b.title = on ? "Hide annotations (a)" : "Show annotations (a)";
|
|
18453
|
+
b.innerHTML = glyph(on ? EYE : EYE_OFF);
|
|
18454
|
+
}
|
|
18455
|
+
function annotationLayer(host, list, place, corner = host) {
|
|
18456
|
+
const doc = host.ownerDocument;
|
|
18457
|
+
const layer = doc.createElement("div");
|
|
18458
|
+
layer.className = "knobs-wt-anno";
|
|
18459
|
+
layer.setAttribute("aria-hidden", "true");
|
|
18460
|
+
host.append(layer);
|
|
18461
|
+
const toggle = doc.createElement("button");
|
|
18462
|
+
toggle.type = "button";
|
|
18463
|
+
toggle.className = "knobs-wt-anno-toggle";
|
|
18464
|
+
toggle.setAttribute("aria-label", "Annotations");
|
|
18465
|
+
syncToggle(toggle, annotationsOn(scopeOf(host)));
|
|
18466
|
+
toggle.addEventListener("click", (e) => {
|
|
18467
|
+
e.stopPropagation();
|
|
18468
|
+
const scope = scopeOf(host);
|
|
18469
|
+
setAnnotations(scope, !annotationsOn(scope));
|
|
18470
|
+
});
|
|
18471
|
+
corner.append(toggle);
|
|
18472
|
+
let marks = [];
|
|
18473
|
+
const make = (a) => {
|
|
18474
|
+
const m = doc.createElement("div");
|
|
18475
|
+
const shape = shapeOf(a);
|
|
18476
|
+
m.className = "knobs-wt-mark";
|
|
18477
|
+
m.dataset.shape = shape;
|
|
18478
|
+
m.dataset.hidden = "";
|
|
18479
|
+
if (shape === "arrow")
|
|
18480
|
+
m.innerHTML = ARROW;
|
|
18481
|
+
if (a.label) {
|
|
18482
|
+
const l = doc.createElement("span");
|
|
18483
|
+
l.className = "knobs-wt-mark-label";
|
|
18484
|
+
l.textContent = a.label;
|
|
18485
|
+
m.append(l);
|
|
18486
|
+
}
|
|
18487
|
+
layer.append(m);
|
|
18488
|
+
return m;
|
|
18489
|
+
};
|
|
18490
|
+
let settled = false;
|
|
18491
|
+
const update = () => {
|
|
18492
|
+
const p = place();
|
|
18493
|
+
if (marks.length !== p.rects.length) {
|
|
18494
|
+
for (const row of marks)
|
|
18495
|
+
for (const m of row)
|
|
18496
|
+
m.remove();
|
|
18497
|
+
marks = p.rects.map(() => list.map(make));
|
|
18498
|
+
}
|
|
18499
|
+
p.rects.forEach((rect, i) => {
|
|
18500
|
+
list.forEach((a, j) => {
|
|
18501
|
+
const m = marks[i][j];
|
|
18502
|
+
const box = placeMark(a, rect, p.view);
|
|
18503
|
+
const show = box !== null && showsAt(a, p.time);
|
|
18504
|
+
if (box) {
|
|
18505
|
+
m.style.transform = `translate(${box.x.toFixed(2)}px,${box.y.toFixed(2)}px)`;
|
|
18506
|
+
m.style.width = `${box.w.toFixed(2)}px`;
|
|
18507
|
+
m.style.height = `${box.h.toFixed(2)}px`;
|
|
18508
|
+
}
|
|
18509
|
+
m.toggleAttribute("data-hidden", !show);
|
|
18510
|
+
});
|
|
18511
|
+
});
|
|
18512
|
+
if (!settled) {
|
|
18513
|
+
settled = true;
|
|
18514
|
+
requestAnimationFrame(() => requestAnimationFrame(() => layer.dataset.settled = ""));
|
|
18515
|
+
}
|
|
18516
|
+
};
|
|
18517
|
+
const ro = new ResizeObserver(() => update());
|
|
18518
|
+
ro.observe(host);
|
|
18519
|
+
update();
|
|
18520
|
+
return {
|
|
18521
|
+
element: layer,
|
|
18522
|
+
update,
|
|
18523
|
+
dispose() {
|
|
18524
|
+
ro.disconnect();
|
|
18525
|
+
layer.remove();
|
|
18526
|
+
toggle.remove();
|
|
18527
|
+
}
|
|
18528
|
+
};
|
|
18529
|
+
}
|
|
18530
|
+
|
|
18531
|
+
// src/ui/walkthrough-blocks.ts
|
|
18532
|
+
init_motion();
|
|
18533
|
+
|
|
18534
|
+
// src/ui/history.ts
|
|
18535
|
+
init_motion();
|
|
18536
|
+
init_glide();
|
|
18537
|
+
|
|
18538
|
+
// src/ui/history-css.ts
|
|
18539
|
+
var historyCss = `
|
|
18540
|
+
:where(.knobs-history,.knobs-history *){box-sizing:border-box}
|
|
18541
|
+
.knobs-ui.knobs-history{--hy-gutter:128px;--hy-lane:24px;--hy-bar:16px;--hy-axis:20px;--hy-bandrow:0px;--hy-c:var(--_k-fg-3);
|
|
18542
|
+
--hy-fade:linear-gradient(to right,#000 calc(100% - 1.5em),transparent);
|
|
18543
|
+
position:relative;display:flex;flex-direction:column;width:100%;min-width:0;outline:none;color:var(--_k-fg);
|
|
18544
|
+
font:var(--_k-weight) var(--_k-type-body)/var(--_k-leading-body) var(--_k-font);font-variant-numeric:tabular-nums;font-feature-settings:"tnum";
|
|
18545
|
+
text-align:left;letter-spacing:normal;-webkit-font-smoothing:antialiased;user-select:none;-webkit-user-select:none}
|
|
18546
|
+
.knobs-history[data-bands]{--hy-bandrow:var(--_k-leading-small)}
|
|
18547
|
+
.knobs-history[data-narrow]{--hy-gutter:0px}
|
|
18548
|
+
.knobs-history [hidden]{display:none!important}
|
|
18549
|
+
:where(.knobs-ui) :is(.knobs-hy-ev,.knobs-hy-span,.knobs-hy-jump,.knobs-hy-more){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;
|
|
18550
|
+
background:transparent;border:0;border-radius:0;margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
|
|
18551
|
+
.knobs-history [data-tone="good"]{--hy-c:var(--_k-ok)}
|
|
18552
|
+
.knobs-history [data-tone="warn"]{--hy-c:var(--_k-warn)}
|
|
18553
|
+
.knobs-history [data-tone="bad"]{--hy-c:var(--_k-danger)}
|
|
18554
|
+
.knobs-history [data-tone="info"]{--hy-c:var(--_k-info)}
|
|
18555
|
+
.knobs-history [data-tone="dim"]{--hy-c:var(--_k-fg-3)}
|
|
18556
|
+
|
|
18557
|
+
/* ——— the chart: lane labels beside the plot (above each lane where it's narrow) ——— */
|
|
18558
|
+
.knobs-hy-chart{position:relative;display:grid;grid-template-columns:var(--hy-gutter) minmax(0,1fr)}
|
|
18559
|
+
.knobs-hy-axis{position:relative;grid-column:2;grid-row:1;height:calc(var(--hy-bandrow) + var(--hy-axis))}
|
|
18560
|
+
:is(.knobs-hy-tick,.knobs-hy-now,.knobs-hy-blabel){position:absolute;white-space:nowrap;font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
18561
|
+
.knobs-hy-tick{bottom:2px;padding-left:4px}
|
|
18562
|
+
.knobs-hy-now{bottom:2px;color:var(--_k-accent-fg);font-weight:var(--_k-weight-label)}
|
|
18563
|
+
.knobs-hy-blabel{top:0;padding-left:4px;overflow:hidden;color:var(--_k-fg-2)}
|
|
18564
|
+
.knobs-hy-label{grid-column:1;align-self:center;min-width:0;margin-right:var(--_k-space-title);overflow:hidden;white-space:nowrap;
|
|
18565
|
+
font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
18566
|
+
.knobs-history[data-narrow] .knobs-hy-label{grid-column:2;align-self:end;margin:0}
|
|
18567
|
+
:is(.knobs-hy-label,.knobs-hy-blabel)[data-overflow]{-webkit-mask-image:var(--hy-fade);mask-image:var(--hy-fade)}
|
|
18568
|
+
.knobs-hy-track{position:relative;grid-column:2;height:var(--hy-lane)}
|
|
18569
|
+
.knobs-hy-layer{position:absolute;inset:0;grid-column:2;grid-row:1/-1;pointer-events:none}
|
|
18570
|
+
|
|
18571
|
+
/* Bands and month lines behind the lanes; today, the selection and the pointer's line over them. */
|
|
18572
|
+
.knobs-hy-band{position:absolute;top:0;bottom:0;background:var(--_k-fill);box-shadow:inset 1px 0 0 var(--_k-border-subtle)}
|
|
18573
|
+
:is(.knobs-hy-grid,.knobs-hy-today,.knobs-hy-hover,.knobs-hy-sel){position:absolute;left:0;top:calc(var(--hy-bandrow) + var(--hy-axis));bottom:0;width:1px;margin-left:-.5px}
|
|
18574
|
+
.knobs-hy-grid{background:var(--_k-border-subtle)}
|
|
18575
|
+
.knobs-hy-today{top:calc(var(--hy-bandrow) + var(--hy-axis) - 4px);background:var(--_k-accent)}
|
|
18576
|
+
.knobs-hy-hover{background:color-mix(in oklch,var(--_k-fg) 32%,transparent);opacity:0;transition:opacity calc(var(--_k-hover-out) * 1ms)}
|
|
18577
|
+
.knobs-hy-hover[data-on]{opacity:1;transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
18578
|
+
.knobs-hy-sel{background:var(--_k-fg-2);opacity:0}
|
|
18579
|
+
.knobs-hy-sel[data-on]{opacity:.6}
|
|
18580
|
+
|
|
18581
|
+
/* Spans: a soft bar in their state's tone, inferred ones hatched; the tag at the end. */
|
|
18582
|
+
.knobs-hy-span{position:absolute;top:calc((var(--hy-lane) - var(--hy-bar)) / 2);height:var(--hy-bar);border-radius:var(--_k-radius-sm);cursor:pointer;
|
|
18583
|
+
background:color-mix(in oklch,var(--hy-c) 24%,transparent);transition:background calc(var(--_k-hover-out) * 1ms),box-shadow calc(var(--_k-hover-out) * 1ms)}
|
|
18584
|
+
.knobs-hy-span[data-inferred]{background:repeating-linear-gradient(135deg,color-mix(in oklch,var(--hy-c) 34%,transparent) 0 2px,color-mix(in oklch,var(--hy-c) 9%,transparent) 2px 6px)}
|
|
18585
|
+
.knobs-hy-span[data-hover]{background-color:color-mix(in oklch,var(--hy-c) 34%,transparent);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
18586
|
+
.knobs-hy-span[data-selected]{box-shadow:inset 0 0 0 1px var(--hy-c)}
|
|
18587
|
+
.knobs-hy-span[data-clip-start]{border-top-left-radius:0;border-bottom-left-radius:0;-webkit-mask-image:linear-gradient(to right,transparent,#000 16px);mask-image:linear-gradient(to right,transparent,#000 16px)}
|
|
18588
|
+
.knobs-hy-span[data-clip-end]{border-top-right-radius:0;border-bottom-right-radius:0;-webkit-mask-image:linear-gradient(to left,transparent,#000 16px);mask-image:linear-gradient(to left,transparent,#000 16px)}
|
|
18589
|
+
.knobs-hy-tag{position:absolute;top:0;right:6px;font-size:var(--_k-type-small);line-height:var(--hy-bar);white-space:nowrap;pointer-events:none;
|
|
18590
|
+
color:color-mix(in oklch,var(--hy-c) 55%,var(--_k-fg))}
|
|
18591
|
+
.knobs-hy-tag[data-place="after"]{right:auto;left:calc(100% + 6px)}
|
|
18592
|
+
.knobs-hy-tag[data-place="none"]{visibility:hidden}
|
|
18593
|
+
|
|
18594
|
+
/* Event marks (their kind named in the tooltip): a dot for a commit, a diamond for a release, a cross for a deletion, a square for a
|
|
18595
|
+
channel's switch, a triangle for a finding; inferred ones hollow. */
|
|
18596
|
+
.knobs-hy-ev{position:absolute;top:0;width:16px;height:var(--hy-lane);margin-left:-8px;cursor:pointer;--hy-c:var(--_k-fg-2);--hy-s:9px}
|
|
18597
|
+
.knobs-hy-ev::before{content:"";position:absolute;left:50%;top:50%;width:var(--hy-s);height:var(--hy-s);margin:calc(var(--hy-s) / -2) 0 0 calc(var(--hy-s) / -2);
|
|
18598
|
+
border-radius:50%;background:var(--hy-c);transition:scale calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy)}
|
|
18599
|
+
.knobs-hy-ev[data-range]{width:calc(var(--hy-w) + 16px)}
|
|
18600
|
+
.knobs-hy-ev[data-range]::before{left:calc(8px - var(--hy-s) / 2);right:calc(8px - var(--hy-s) / 2);width:auto;margin-left:0;border-radius:var(--_k-round)}
|
|
18601
|
+
.knobs-hy-ev[data-type="release"]{--hy-s:8px}
|
|
18602
|
+
.knobs-hy-ev[data-type="release"]::before{border-radius:1.5px;rotate:45deg}
|
|
18603
|
+
.knobs-hy-ev[data-type="channel"]{--hy-s:8px}
|
|
18604
|
+
.knobs-hy-ev[data-type="channel"]::before{border-radius:2px}
|
|
18605
|
+
.knobs-hy-ev[data-type="audit"]{--hy-s:10px}
|
|
18606
|
+
.knobs-hy-ev[data-type="audit"]::before{border-radius:0;clip-path:polygon(50% 4%,100% 92%,0 92%)}
|
|
18607
|
+
.knobs-hy-ev[data-type="deletion"]::before{border-radius:0;rotate:45deg;
|
|
18608
|
+
background:linear-gradient(var(--hy-c),var(--hy-c)) center/100% 1.75px no-repeat,linear-gradient(var(--hy-c),var(--hy-c)) center/1.75px 100% no-repeat}
|
|
18609
|
+
.knobs-hy-ev[data-inferred]:not([data-type="audit"],[data-type="deletion"])::before{background:transparent;box-shadow:inset 0 0 0 1.5px var(--hy-c)}
|
|
18610
|
+
.knobs-hy-ev[data-inferred]:is([data-type="audit"],[data-type="deletion"])::before{opacity:.5}
|
|
18611
|
+
/* Held-back marks (commits, by default) come up while the pointer is on their lane, and stay with
|
|
18612
|
+
"Show commits" or while selected. */
|
|
18613
|
+
.knobs-hy-ev[data-quiet]{opacity:0;transition:opacity calc(var(--_k-hover-out) * 1ms)}
|
|
18614
|
+
.knobs-hy-track:hover .knobs-hy-ev[data-quiet]{opacity:.6;transition-duration:calc(var(--_k-motion-snappy-ms) * 1ms)}
|
|
18615
|
+
:is(.knobs-history[data-all] .knobs-hy-ev[data-quiet],.knobs-hy-ev[data-quiet]:is([data-selected],[data-hover])){opacity:1}
|
|
18616
|
+
.knobs-hy-ev[data-hover]::before{scale:1.25}
|
|
18617
|
+
.knobs-hy-ev[data-selected]::before{scale:1.3}
|
|
18618
|
+
.knobs-hy-ev[data-selected]::after{content:"";position:absolute;left:50%;top:50%;width:calc(var(--hy-s) + 9px);height:calc(var(--hy-s) + 9px);
|
|
18619
|
+
margin:calc((var(--hy-s) + 9px) / -2) 0 0 calc((var(--hy-s) + 9px) / -2);border-radius:50%;box-shadow:0 0 0 1.5px var(--hy-c)}
|
|
18620
|
+
.knobs-hy-ev[data-range][data-selected]::after{left:3px;right:3px;width:auto;margin-left:0;border-radius:var(--_k-round)}
|
|
18621
|
+
.knobs-history:focus-visible .knobs-hy-ev[data-selected]::after{box-shadow:0 0 0 2px var(--_k-focus)}
|
|
18622
|
+
.knobs-history:focus-visible .knobs-hy-span[data-selected]{box-shadow:inset 0 0 0 2px var(--_k-focus)}
|
|
18623
|
+
|
|
18624
|
+
/* ——— under the chart: "Show commits", and a legend only when inferred and verified both appear ——— */
|
|
18625
|
+
.knobs-hy-foot{display:flex;flex-wrap:wrap;align-items:center;gap:var(--_k-space-inside) var(--_k-space-title);margin-top:var(--_k-space-sibling);padding-left:var(--hy-gutter);
|
|
18626
|
+
font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
18627
|
+
.knobs-hy-legend{display:flex;flex-wrap:wrap;gap:var(--_k-space-inside) var(--_k-space-title)}
|
|
18628
|
+
.knobs-ui .knobs-hy-more{height:20px;margin-left:-6px;padding:0 6px;border-radius:var(--_k-radius-sm);color:var(--_k-fg-3);cursor:pointer;
|
|
18629
|
+
transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
18630
|
+
.knobs-ui .knobs-hy-more:is(:hover,:focus-visible){background:var(--_k-fill);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
18631
|
+
.knobs-hy-key{display:inline-flex;align-items:center;gap:6px;white-space:nowrap}
|
|
18632
|
+
.knobs-hy-swatch{flex:none;width:14px;height:8px;border-radius:2px;background:color-mix(in oklch,var(--hy-c) 34%,transparent)}
|
|
18633
|
+
.knobs-hy-swatch[data-inferred]{background:repeating-linear-gradient(135deg,color-mix(in oklch,var(--hy-c) 60%,transparent) 0 1.5px,transparent 1.5px 4px)}
|
|
18634
|
+
|
|
18635
|
+
/* ——— the detail: one area under the chart, its height on a spring ——— */
|
|
18636
|
+
.knobs-hy-detail{position:relative;overflow:hidden;user-select:text;-webkit-user-select:text}
|
|
18637
|
+
.knobs-hy-detail[data-moving]{-webkit-mask-image:linear-gradient(to bottom,#000 calc(100% - 16px),transparent);mask-image:linear-gradient(to bottom,#000 calc(100% - 16px),transparent)}
|
|
18638
|
+
.knobs-hy-dc{padding:var(--_k-space-title) 0 0 var(--hy-gutter);transition:opacity calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy)}
|
|
18639
|
+
.knobs-hy-dc:is([data-enter],[data-leave]){opacity:0}
|
|
18640
|
+
.knobs-hy-dc[data-leave]{position:absolute;left:0;right:0;top:0;transition-duration:calc(var(--_k-motion-snappy-ms) * .8ms)}
|
|
18641
|
+
.knobs-hy-dmeta{display:flex;flex-wrap:wrap;gap:0 var(--_k-space-sibling);font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
18642
|
+
.knobs-hy-dmeta>[data-tone]{color:var(--hy-c)}
|
|
18643
|
+
.knobs-hy-dtitle{margin-top:var(--_k-space-inside);font-size:var(--_k-type-body);line-height:var(--_k-leading-body);font-weight:var(--_k-weight-title);color:var(--_k-fg);text-wrap:balance}
|
|
18644
|
+
.knobs-hy-dtext{margin:var(--_k-space-inside) 0 0;max-width:68ch;font-size:var(--_k-type-body);line-height:var(--_k-leading-body);color:var(--_k-fg-2);text-wrap:pretty}
|
|
18645
|
+
.knobs-hy-dtext code{font:calc(1em - 1px) var(--_k-mono);padding:1px 4px;border-radius:var(--_k-radius-sm);background:var(--_k-fill)}
|
|
18646
|
+
.knobs-hy-dfoot{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:var(--_k-space-sibling)}
|
|
18647
|
+
.knobs-hy-dfoot:empty{display:none}
|
|
18648
|
+
.knobs-hy-evidence{display:inline-flex;align-items:center;height:20px;padding:0 6px;border-radius:var(--_k-radius-sm);background:var(--_k-fill);color:var(--_k-fg-2);
|
|
18649
|
+
font:var(--_k-type-small)/1 var(--_k-mono);text-decoration:none;transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
18650
|
+
a.knobs-hy-evidence:hover{background:var(--_k-fill-hover);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
18651
|
+
.knobs-ui .knobs-hy-jump{display:inline-flex;align-items:center;gap:4px;height:20px;padding:0 6px 0 8px;border-radius:var(--_k-radius-sm);color:var(--_k-fg-2);cursor:pointer;
|
|
18652
|
+
font-size:var(--_k-type-small);transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
18653
|
+
.knobs-ui .knobs-hy-jump:is(:hover,:focus-visible){background:var(--_k-fill);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
18654
|
+
.knobs-hy-jump svg{width:12px;height:12px}
|
|
18655
|
+
|
|
18656
|
+
/* ——— the pointer's tooltip: title, date, one line ——— */
|
|
18657
|
+
.knobs-hy-tip{position:absolute;left:0;top:0;max-width:280px;padding:6px 8px;border-radius:var(--_k-radius-md);background:var(--_k-bg-overlay);
|
|
18658
|
+
box-shadow:var(--_k-shadow-overlay),0 0 0 1px var(--_k-border);pointer-events:none;opacity:0;transition:opacity calc(var(--_k-hover-out) * 1ms)}
|
|
18659
|
+
.knobs-hy-tip[data-on]{opacity:1;transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
18660
|
+
.knobs-hy-tt{font-size:var(--_k-type-small);line-height:var(--_k-leading-small);font-weight:var(--_k-weight-label);color:var(--_k-fg);text-wrap:balance}
|
|
18661
|
+
:is(.knobs-hy-td,.knobs-hy-tl){font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
18662
|
+
.knobs-hy-tl{margin-top:2px;color:var(--_k-fg-2);text-wrap:pretty}
|
|
18663
|
+
.knobs-hy-tl:empty{display:none}
|
|
18664
|
+
|
|
18665
|
+
@media(prefers-reduced-motion:reduce){
|
|
18666
|
+
.knobs-history :where(*,::before,::after){transition-property:opacity,background,color,box-shadow!important}
|
|
18667
|
+
.knobs-hy-dc{transition-duration:calc(var(--_k-hover-out) * 1ms)!important}
|
|
18668
|
+
}
|
|
18669
|
+
`;
|
|
18670
|
+
|
|
18671
|
+
// src/ui/history-model.ts
|
|
18672
|
+
var MS_DAY = 86400000;
|
|
18673
|
+
var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
18674
|
+
var dayOf = (y, m, d) => Math.floor(Date.UTC(y, m, d) / MS_DAY);
|
|
18675
|
+
var parts = (day) => {
|
|
18676
|
+
const t = new Date(day * MS_DAY);
|
|
18677
|
+
return { y: t.getUTCFullYear(), m: t.getUTCMonth(), d: t.getUTCDate() };
|
|
18678
|
+
};
|
|
18679
|
+
var todayDay = (now = Date.now()) => {
|
|
18680
|
+
const t = new Date(now);
|
|
18681
|
+
return dayOf(t.getFullYear(), t.getMonth(), t.getDate());
|
|
18682
|
+
};
|
|
18683
|
+
var monthIndex = (name) => MONTHS.findIndex((m) => m.toLowerCase() === name.slice(0, 3).toLowerCase());
|
|
18684
|
+
function parseDay(text, year) {
|
|
18685
|
+
const s = text.trim();
|
|
18686
|
+
const iso = /^(\d{4})-(\d{2})-(\d{2})/.exec(s);
|
|
18687
|
+
if (iso) {
|
|
18688
|
+
const y = Number(iso[1]);
|
|
18689
|
+
const day = dayOf(y, Number(iso[2]) - 1, Number(iso[3]));
|
|
18690
|
+
return { from: day, to: day, year: y };
|
|
18414
18691
|
}
|
|
18415
|
-
|
|
18416
|
-
|
|
18417
|
-
function findPR(prs, n, repo, kind = "pr") {
|
|
18418
|
-
const entry = prs[n];
|
|
18419
|
-
const known = entry && (entry.kind ?? "pr") === kind ? entry : undefined;
|
|
18420
|
-
const path = kind === "issue" ? "issues" : "pull";
|
|
18421
|
-
const href = repo ? `https://github.com/${repo}/${path}/${n}` : undefined;
|
|
18422
|
-
const base = kind === "issue" ? { kind } : {};
|
|
18423
|
-
if (known)
|
|
18424
|
-
return known.href || !href ? { ...base, ...known } : { ...base, ...known, href };
|
|
18425
|
-
return href ? { ...base, href } : kind === "issue" ? { kind } : null;
|
|
18426
|
-
}
|
|
18427
|
-
var issueNumber = (href) => /^https?:\/\/[^/]+\/.+\/issues\/(\d+)\/?(?:[?#].*)?$/.exec(href)?.[1] ?? null;
|
|
18428
|
-
var isPullRequest = (href) => /\/(?:pull|merge_requests)\/\d+(?:[/?#]|$)/.test(href);
|
|
18429
|
-
function withKickers(sections, source, kickers = "pr") {
|
|
18430
|
-
if (kickers === false)
|
|
18431
|
-
return [...sections];
|
|
18432
|
-
const noun = typeof kickers === "object" ? kickers.noun.trim() : "PR";
|
|
18433
|
-
const counted = typeof kickers === "object" ? sections : sections.filter((s) => s.link && isPullRequest(s.link.href));
|
|
18434
|
-
return sections.map((s, i) => {
|
|
18435
|
-
const own = source[i]?.kicker;
|
|
18436
|
-
if (own !== undefined || s.kicker)
|
|
18437
|
-
return s;
|
|
18438
|
-
const at = counted.indexOf(s);
|
|
18439
|
-
return at < 0 ? s : { ...s, kicker: `${noun} ${at + 1} of ${counted.length}`.trim() };
|
|
18440
|
-
});
|
|
18441
|
-
}
|
|
18442
|
-
function tableStacks(stack, room, natural) {
|
|
18443
|
-
if (stack !== "auto")
|
|
18444
|
-
return stack === "always";
|
|
18445
|
-
return room > 0 && natural > room + 1;
|
|
18446
|
-
}
|
|
18447
|
-
function noteBarReserve(bar, inset, gap) {
|
|
18448
|
-
return Math.max(0, Math.ceil(bar)) + inset + gap;
|
|
18449
|
-
}
|
|
18450
|
-
function parseAt(at) {
|
|
18451
|
-
if (!at)
|
|
18692
|
+
const m = /^([A-Za-z]{3,9})\.?\s+(\d{1,2})(?:\s*[–—-]\s*(?:([A-Za-z]{3,9})\.?\s+)?(\d{1,2}))?(?:,?\s+(\d{4}))?$/.exec(s);
|
|
18693
|
+
if (!m)
|
|
18452
18694
|
return null;
|
|
18453
|
-
const
|
|
18454
|
-
|
|
18455
|
-
|
|
18456
|
-
|
|
18457
|
-
|
|
18458
|
-
|
|
18459
|
-
|
|
18460
|
-
return
|
|
18461
|
-
|
|
18462
|
-
|
|
18463
|
-
|
|
18464
|
-
|
|
18465
|
-
|
|
18466
|
-
return "just now";
|
|
18467
|
-
if (s < 3600)
|
|
18468
|
-
return `${Math.round(s / 60)}m ago`;
|
|
18469
|
-
if (s < 86400)
|
|
18470
|
-
return `${Math.round(s / 3600)}h ago`;
|
|
18471
|
-
if (s < 7 * 86400)
|
|
18472
|
-
return `${Math.round(s / 86400)}d ago`;
|
|
18473
|
-
return new Date(at).toISOString().slice(0, 10);
|
|
18474
|
-
}
|
|
18475
|
-
function statusLook(status) {
|
|
18476
|
-
const id = slug(status);
|
|
18477
|
-
const tone = id === "ready" || id === "merged" || id === "shipped" ? "good" : id === "needs-taste" || id === "needs-review" || id === "review" ? "warn" : id === "blocked" || id === "broken" || id === "failing" ? "bad" : id === "draft" || id === "wip" ? "dim" : "info";
|
|
18478
|
-
return { label: label(status), tone };
|
|
18479
|
-
}
|
|
18480
|
-
function verdictTone(id, index) {
|
|
18481
|
-
if (/^(ship|yes|good|approve|keep|lgtm)/.test(id))
|
|
18482
|
-
return "good";
|
|
18483
|
-
if (/^(change|fix|revise|tweak|redo)/.test(id))
|
|
18484
|
-
return "warn";
|
|
18485
|
-
if (/^(no|reject|drop|revert|bad)/.test(id))
|
|
18486
|
-
return "bad";
|
|
18487
|
-
if (/^(unsure|maybe|skip|later)/.test(id))
|
|
18488
|
-
return "dim";
|
|
18489
|
-
return index === 0 ? "good" : "dim";
|
|
18490
|
-
}
|
|
18491
|
-
var EMPTY_STATE = { steps: {} };
|
|
18492
|
-
function setFeedback(state, key, patch) {
|
|
18493
|
-
const next = { ...state.steps[key] };
|
|
18494
|
-
for (const [k, v] of Object.entries(patch)) {
|
|
18495
|
-
if (v === undefined)
|
|
18496
|
-
continue;
|
|
18497
|
-
if (v === null || v.trim() === "")
|
|
18498
|
-
delete next[k];
|
|
18499
|
-
else
|
|
18500
|
-
next[k] = v;
|
|
18501
|
-
}
|
|
18502
|
-
const steps = { ...state.steps };
|
|
18503
|
-
if (Object.keys(next).length)
|
|
18504
|
-
steps[key] = next;
|
|
18505
|
-
else
|
|
18506
|
-
delete steps[key];
|
|
18507
|
-
return { ...state, steps };
|
|
18508
|
-
}
|
|
18509
|
-
var hasContent = (n) => Boolean(n.text?.trim() || n.voice || n.ink?.paths.length);
|
|
18510
|
-
function addNote(state, note) {
|
|
18511
|
-
if (!hasContent(note))
|
|
18512
|
-
return state;
|
|
18513
|
-
return { ...state, notes: [...state.notes ?? [], note] };
|
|
18514
|
-
}
|
|
18515
|
-
function removeNote(state, id) {
|
|
18516
|
-
return { ...state, notes: (state.notes ?? []).filter((n) => n.id !== id) };
|
|
18517
|
-
}
|
|
18518
|
-
var notesOn = (state, key) => (state.notes ?? []).filter((n) => n.step === key);
|
|
18519
|
-
function isAnswered(step, state) {
|
|
18520
|
-
if (step.ask === "none")
|
|
18521
|
-
return false;
|
|
18522
|
-
const fb = state.steps[step.key];
|
|
18523
|
-
if (step.ask === "pick")
|
|
18524
|
-
return fb?.pick !== undefined;
|
|
18525
|
-
return Boolean(fb?.verdict || fb?.note || notesOn(state, step.key).length);
|
|
18526
|
-
}
|
|
18527
|
-
function digitChoice(step, fb, key) {
|
|
18528
|
-
if (step.ask !== "pick" || !/^[1-9]$/.test(key))
|
|
18529
|
-
return;
|
|
18530
|
-
const id = step.options[Number(key) - 1]?.id;
|
|
18531
|
-
if (id === undefined)
|
|
18532
|
-
return;
|
|
18533
|
-
return fb?.pick === id ? null : id;
|
|
18534
|
-
}
|
|
18535
|
-
function walkthroughProgress(w, state) {
|
|
18536
|
-
const asked = w.steps.filter((s) => s.ask !== "none");
|
|
18537
|
-
const done = asked.filter((s) => isAnswered(s, state));
|
|
18538
|
-
const next = asked.find((s) => !isAnswered(s, state));
|
|
18539
|
-
return { done: done.length, total: asked.length, next: next?.key ?? null };
|
|
18540
|
-
}
|
|
18541
|
-
function adjacentStep(keys, current, dir) {
|
|
18542
|
-
if (!keys.length)
|
|
18543
|
-
return;
|
|
18544
|
-
const i = current === null ? -1 : keys.indexOf(current);
|
|
18545
|
-
if (i < 0)
|
|
18546
|
-
return dir > 0 ? keys[0] : keys[keys.length - 1];
|
|
18547
|
-
return keys[Math.max(0, Math.min(keys.length - 1, i + dir))];
|
|
18695
|
+
const m1 = monthIndex(m[1]);
|
|
18696
|
+
const m2 = m[3] ? monthIndex(m[3]) : m1;
|
|
18697
|
+
if (m1 < 0 || m2 < 0)
|
|
18698
|
+
return null;
|
|
18699
|
+
const written = m[5] ? Number(m[5]) : undefined;
|
|
18700
|
+
const y = written ?? year;
|
|
18701
|
+
if (y === undefined)
|
|
18702
|
+
return null;
|
|
18703
|
+
const from = dayOf(y, m1, Number(m[2]));
|
|
18704
|
+
let to = m[4] ? dayOf(y, m2, Number(m[4])) : from;
|
|
18705
|
+
if (to < from)
|
|
18706
|
+
to = dayOf(y + 1, m2, Number(m[4]));
|
|
18707
|
+
return { from, to, ...written !== undefined ? { year: written } : {} };
|
|
18548
18708
|
}
|
|
18549
|
-
|
|
18550
|
-
|
|
18551
|
-
|
|
18552
|
-
const merge = last && last.what === event.what && last.step === event.step && (event.what === "seek" || event.what === "step") && event.at - last.at < 1500;
|
|
18553
|
-
const next = merge ? [...log.slice(0, -1), event] : [...log, event];
|
|
18554
|
-
return next.slice(-max);
|
|
18709
|
+
function formatDay(day) {
|
|
18710
|
+
const { y, m, d } = parts(day);
|
|
18711
|
+
return `${MONTHS[m]} ${d}, ${y}`;
|
|
18555
18712
|
}
|
|
18556
|
-
function
|
|
18557
|
-
|
|
18558
|
-
|
|
18713
|
+
function formatRange(from, to, open = false) {
|
|
18714
|
+
if (open)
|
|
18715
|
+
return `${formatDay(from)} – today`;
|
|
18716
|
+
if (to <= from)
|
|
18717
|
+
return formatDay(from);
|
|
18718
|
+
const a = parts(from);
|
|
18719
|
+
const b = parts(to);
|
|
18720
|
+
if (a.y === b.y && a.m === b.m)
|
|
18721
|
+
return `${MONTHS[a.m]} ${a.d}–${b.d}, ${a.y}`;
|
|
18722
|
+
if (a.y === b.y)
|
|
18723
|
+
return `${MONTHS[a.m]} ${a.d} – ${MONTHS[b.m]} ${b.d}, ${a.y}`;
|
|
18724
|
+
return `${formatDay(from)} – ${formatDay(to)}`;
|
|
18559
18725
|
}
|
|
18560
|
-
|
|
18561
|
-
|
|
18562
|
-
const
|
|
18563
|
-
|
|
18564
|
-
|
|
18726
|
+
var formatDays = (n) => `${n} ${n === 1 ? "day" : "days"}`;
|
|
18727
|
+
function guessTone(state) {
|
|
18728
|
+
const s = state.toLowerCase();
|
|
18729
|
+
if (/broken|bad|fail|missing|deleted|down/.test(s))
|
|
18730
|
+
return "bad";
|
|
18731
|
+
if (/warn|redirect|partial|beta|next|pre/.test(s))
|
|
18732
|
+
return "warn";
|
|
18733
|
+
if (/work|ok|good|fixed|live|green/.test(s))
|
|
18734
|
+
return "good";
|
|
18735
|
+
return "dim";
|
|
18565
18736
|
}
|
|
18566
|
-
|
|
18567
|
-
|
|
18568
|
-
|
|
18569
|
-
const
|
|
18570
|
-
const
|
|
18571
|
-
|
|
18572
|
-
|
|
18573
|
-
|
|
18574
|
-
|
|
18737
|
+
var bandLabel = (heading) => heading.replace(/^\s*\d+\s*[·.:)\-–]\s*/, "").replace(/\s*\([^)]*\)\s*$/, "").trim();
|
|
18738
|
+
function fromGroups(groups, today) {
|
|
18739
|
+
const events = [];
|
|
18740
|
+
const bands = [];
|
|
18741
|
+
const all = groups.flatMap((g) => g.events ?? g.rows ?? []);
|
|
18742
|
+
let year = all.map((e) => parseDay(e.date, 0)?.year).find((y) => y !== undefined) ?? parts(today).y;
|
|
18743
|
+
for (const g of groups) {
|
|
18744
|
+
let lo = Infinity;
|
|
18745
|
+
let hi = -Infinity;
|
|
18746
|
+
for (const e of g.events ?? g.rows ?? []) {
|
|
18747
|
+
const d = parseDay(e.date, year);
|
|
18748
|
+
if (!d)
|
|
18749
|
+
continue;
|
|
18750
|
+
if (d.year !== undefined)
|
|
18751
|
+
year = d.year;
|
|
18752
|
+
lo = Math.min(lo, d.from);
|
|
18753
|
+
hi = Math.max(hi, d.to);
|
|
18754
|
+
events.push({
|
|
18755
|
+
...e,
|
|
18756
|
+
date: iso(d.from),
|
|
18757
|
+
...d.to > d.from ? { to: iso(d.to) } : {},
|
|
18758
|
+
lane: e.lane ?? "code"
|
|
18759
|
+
});
|
|
18760
|
+
}
|
|
18761
|
+
const label = g.heading ? bandLabel(g.heading) : "";
|
|
18762
|
+
if (label && lo <= hi)
|
|
18763
|
+
bands.push({ from: iso(lo), to: iso(hi), label });
|
|
18575
18764
|
}
|
|
18576
|
-
return
|
|
18577
|
-
}
|
|
18578
|
-
function zoomWords(z) {
|
|
18579
|
-
const times = Math.round(1 / Math.max(0.001, Math.max(z.w, z.h)) * 10) / 10;
|
|
18580
|
-
return `${times}× at ${Math.round(z.u * 100)}%, ${Math.round(z.v * 100)}%`;
|
|
18765
|
+
return { events, bands };
|
|
18581
18766
|
}
|
|
18582
|
-
function
|
|
18583
|
-
|
|
18584
|
-
|
|
18585
|
-
const parts = [];
|
|
18586
|
-
if (m.file)
|
|
18587
|
-
parts.push(`${m.kind} ${m.file}`);
|
|
18588
|
-
else if (m.time !== undefined)
|
|
18589
|
-
parts.push(`${m.kind} at ${formatTime(m.time)}`);
|
|
18590
|
-
else
|
|
18591
|
-
parts.push(m.kind);
|
|
18592
|
-
if (m.chapter)
|
|
18593
|
-
parts[0] += ` in “${m.chapter}”`;
|
|
18594
|
-
if (m.loop)
|
|
18595
|
-
parts.push(`loop ${formatTime(m.loop.start)}–${formatTime(m.loop.end)}`);
|
|
18596
|
-
if (m.zoom)
|
|
18597
|
-
parts.push(`zoomed ${zoomWords(m.zoom)}`);
|
|
18598
|
-
if (m.mode)
|
|
18599
|
-
parts.push(m.mode);
|
|
18600
|
-
if (m.full)
|
|
18601
|
-
parts.push("full window");
|
|
18602
|
-
return parts.join(", ");
|
|
18767
|
+
function iso(day) {
|
|
18768
|
+
const { y, m, d } = parts(day);
|
|
18769
|
+
return `${y}-${String(m + 1).padStart(2, "0")}-${String(d).padStart(2, "0")}`;
|
|
18603
18770
|
}
|
|
18604
|
-
|
|
18605
|
-
|
|
18606
|
-
|
|
18607
|
-
|
|
18608
|
-
|
|
18609
|
-
|
|
18610
|
-
|
|
18611
|
-
|
|
18612
|
-
|
|
18613
|
-
|
|
18614
|
-
|
|
18615
|
-
|
|
18616
|
-
|
|
18617
|
-
|
|
18618
|
-
|
|
18619
|
-
|
|
18620
|
-
|
|
18621
|
-
|
|
18622
|
-
|
|
18623
|
-
|
|
18624
|
-
|
|
18625
|
-
|
|
18626
|
-
|
|
18627
|
-
|
|
18628
|
-
|
|
18629
|
-
|
|
18771
|
+
var startOfMonth = (day) => {
|
|
18772
|
+
const { y, m } = parts(day);
|
|
18773
|
+
return dayOf(y, m, 1);
|
|
18774
|
+
};
|
|
18775
|
+
var TYPE_TONE = { deletion: "bad" };
|
|
18776
|
+
function normalizeHistory(data, now = todayDay()) {
|
|
18777
|
+
const today = data.axis?.today && parseDay(data.axis.today, parts(now).y)?.from || now;
|
|
18778
|
+
const converted = data.groups?.length ? fromGroups(data.groups, today) : { events: [], bands: [] };
|
|
18779
|
+
const rawEvents = [...data.events ?? [], ...converted.events];
|
|
18780
|
+
const rawBands = [...data.bands ?? [], ...converted.bands];
|
|
18781
|
+
const year = parts(today).y;
|
|
18782
|
+
const day = (s) => s ? parseDay(s, year) ?? null : null;
|
|
18783
|
+
const states = new Map;
|
|
18784
|
+
const stateOf = (key) => {
|
|
18785
|
+
const known = states.get(key);
|
|
18786
|
+
if (known)
|
|
18787
|
+
return known;
|
|
18788
|
+
const def = data.states?.[key];
|
|
18789
|
+
const s = { key, label: def?.label ?? key, tone: def?.tone ?? guessTone(key) };
|
|
18790
|
+
states.set(key, s);
|
|
18791
|
+
return s;
|
|
18792
|
+
};
|
|
18793
|
+
const items = new Map;
|
|
18794
|
+
const laneOrder = [];
|
|
18795
|
+
const lanes = new Map;
|
|
18796
|
+
const lane = (id, label = id === "code" ? "Code" : id) => {
|
|
18797
|
+
let l = lanes.get(id);
|
|
18798
|
+
if (!l) {
|
|
18799
|
+
l = { id, label, spans: [], events: [] };
|
|
18800
|
+
lanes.set(id, l);
|
|
18801
|
+
laneOrder.push(id);
|
|
18802
|
+
}
|
|
18803
|
+
return l;
|
|
18804
|
+
};
|
|
18805
|
+
if (rawEvents.some((e) => !e.lane || e.lane === "code") && !data.lanes?.some((l) => l.id === "code"))
|
|
18806
|
+
lane("code");
|
|
18807
|
+
let inferred = false;
|
|
18808
|
+
for (const l of data.lanes ?? []) {
|
|
18809
|
+
const into = lane(l.id, l.label);
|
|
18810
|
+
(l.spans ?? []).forEach((s, i) => {
|
|
18811
|
+
const a = day(s.from);
|
|
18812
|
+
if (!a)
|
|
18813
|
+
return;
|
|
18814
|
+
const open = !s.to;
|
|
18815
|
+
const b = open ? null : day(s.to);
|
|
18816
|
+
const to = open ? today : b?.to ?? a.to;
|
|
18817
|
+
const st = stateOf(s.state);
|
|
18818
|
+
const days = Math.max(0, to - a.from);
|
|
18819
|
+
const own = s.label ?? data.states?.[s.state]?.tag ?? "{d}d";
|
|
18820
|
+
const tags = own === "" ? [] : [...new Set([own, "{d}d"])].map((t) => t.replace(/\{d\}/g, String(days)));
|
|
18821
|
+
const id = s.id ?? `${l.id}/${i}`;
|
|
18822
|
+
const item = {
|
|
18823
|
+
id,
|
|
18824
|
+
kind: "span",
|
|
18825
|
+
lane: l.id,
|
|
18826
|
+
from: a.from,
|
|
18827
|
+
to,
|
|
18828
|
+
open,
|
|
18829
|
+
title: s.title ?? `${l.label} · ${st.label}`,
|
|
18830
|
+
...s.detail ? { detail: s.detail } : {},
|
|
18831
|
+
tone: st.tone,
|
|
18832
|
+
inferred: s.provenance === "inferred",
|
|
18833
|
+
...s.provenance ? { provenance: s.provenance } : {},
|
|
18834
|
+
...s.source ? { source: s.source } : {},
|
|
18835
|
+
evidence: s.evidence ?? [],
|
|
18836
|
+
...s.step ? { step: s.step } : {},
|
|
18837
|
+
state: s.state,
|
|
18838
|
+
tags
|
|
18839
|
+
};
|
|
18840
|
+
if (item.inferred)
|
|
18841
|
+
inferred = true;
|
|
18842
|
+
into.spans.push(item);
|
|
18843
|
+
items.set(id, item);
|
|
18844
|
+
});
|
|
18845
|
+
}
|
|
18846
|
+
const types = [];
|
|
18847
|
+
const events = [];
|
|
18848
|
+
rawEvents.forEach((e, index) => {
|
|
18849
|
+
const a = day(e.date);
|
|
18850
|
+
if (!a)
|
|
18851
|
+
return;
|
|
18852
|
+
const to = Math.max(a.to, day(e.to)?.to ?? a.to);
|
|
18853
|
+
const into = lane(e.lane ?? "code");
|
|
18854
|
+
const id = e.id ?? `e${index}`;
|
|
18855
|
+
const item = {
|
|
18856
|
+
id,
|
|
18857
|
+
kind: "event",
|
|
18858
|
+
lane: into.id,
|
|
18859
|
+
from: a.from,
|
|
18860
|
+
to,
|
|
18861
|
+
open: false,
|
|
18862
|
+
title: e.title,
|
|
18863
|
+
...e.detail ? { detail: e.detail } : {},
|
|
18864
|
+
tone: e.tone ?? TYPE_TONE[e.type ?? ""] ?? "dim",
|
|
18865
|
+
...e.type ? { type: e.type } : {},
|
|
18866
|
+
inferred: e.provenance === "inferred",
|
|
18867
|
+
...e.provenance ? { provenance: e.provenance } : {},
|
|
18868
|
+
...e.source ? { source: e.source } : {},
|
|
18869
|
+
evidence: e.evidence ?? [],
|
|
18870
|
+
...e.step ? { step: e.step } : {},
|
|
18871
|
+
tags: []
|
|
18630
18872
|
};
|
|
18631
|
-
|
|
18632
|
-
|
|
18873
|
+
if (item.inferred)
|
|
18874
|
+
inferred = true;
|
|
18875
|
+
if (e.type && !types.includes(e.type))
|
|
18876
|
+
types.push(e.type);
|
|
18877
|
+
into.events.push(item);
|
|
18878
|
+
items.set(id, item);
|
|
18879
|
+
events.push({ item, index });
|
|
18880
|
+
});
|
|
18881
|
+
const bands = [];
|
|
18882
|
+
for (const b of rawBands) {
|
|
18883
|
+
const a = day(b.from);
|
|
18884
|
+
if (!a)
|
|
18885
|
+
continue;
|
|
18886
|
+
const z = b.to ? day(b.to) : null;
|
|
18887
|
+
bands.push({ from: a.from, to: z ? z.to : today, label: b.label });
|
|
18633
18888
|
}
|
|
18889
|
+
bands.sort((x, y) => x.from - y.from);
|
|
18890
|
+
const all = [...items.values()].flatMap((i) => [i.from, i.to]);
|
|
18891
|
+
for (const b of bands)
|
|
18892
|
+
all.push(b.from, b.to);
|
|
18893
|
+
const hasOpen = [...items.values()].some((i) => i.open);
|
|
18894
|
+
const lo = all.length ? Math.min(...all) : today - 90;
|
|
18895
|
+
let hi = all.length ? Math.max(...all) : today;
|
|
18896
|
+
if (hasOpen || data.axis?.today)
|
|
18897
|
+
hi = Math.max(hi, today);
|
|
18898
|
+
const from = day(data.axis?.from)?.from ?? startOfMonth(lo);
|
|
18899
|
+
const to = day(data.axis?.to)?.to ?? hi + Math.max(6, Math.round((hi - from) * 0.05));
|
|
18900
|
+
const laneIndex = (id) => laneOrder.indexOf(id);
|
|
18901
|
+
const order = eventOrder(events.map((e) => ({ ...e, laneIndex: laneIndex(e.item.lane) })));
|
|
18902
|
+
for (const l of lanes.values())
|
|
18903
|
+
l.events.sort((a, b) => a.from - b.from);
|
|
18904
|
+
return {
|
|
18905
|
+
from,
|
|
18906
|
+
to: Math.max(to, from + 1),
|
|
18907
|
+
today,
|
|
18908
|
+
lanes: laneOrder.map((id) => lanes.get(id)),
|
|
18909
|
+
events: order,
|
|
18910
|
+
bands,
|
|
18911
|
+
states: [...states.values()],
|
|
18912
|
+
types,
|
|
18913
|
+
inferred,
|
|
18914
|
+
verified: [...items.values()].some((i) => i.provenance === "verified"),
|
|
18915
|
+
items
|
|
18916
|
+
};
|
|
18634
18917
|
}
|
|
18635
|
-
|
|
18636
|
-
|
|
18637
|
-
|
|
18638
|
-
|
|
18639
|
-
const
|
|
18640
|
-
return
|
|
18918
|
+
var DEFAULT_MARKS = ["release", "deletion"];
|
|
18919
|
+
function quietEvents(events, marks) {
|
|
18920
|
+
if (marks === "all")
|
|
18921
|
+
return new Set;
|
|
18922
|
+
const quiet = events.filter((e) => e.type && !marks.includes(e.type));
|
|
18923
|
+
return quiet.length === events.length ? new Set : new Set(quiet.map((e) => e.id));
|
|
18641
18924
|
}
|
|
18642
|
-
var
|
|
18643
|
-
|
|
18644
|
-
|
|
18645
|
-
|
|
18646
|
-
|
|
18647
|
-
|
|
18648
|
-
|
|
18649
|
-
|
|
18650
|
-
|
|
18651
|
-
const
|
|
18652
|
-
|
|
18653
|
-
|
|
18654
|
-
const
|
|
18655
|
-
|
|
18656
|
-
|
|
18657
|
-
|
|
18658
|
-
|
|
18659
|
-
|
|
18660
|
-
|
|
18661
|
-
|
|
18662
|
-
|
|
18663
|
-
|
|
18664
|
-
|
|
18665
|
-
|
|
18666
|
-
|
|
18667
|
-
ask: s.ask,
|
|
18668
|
-
...option ? { pick: option.id, pickLabel: `${option.letter}: ${option.label}` } : {},
|
|
18669
|
-
...option && suggested && suggested.id !== option.id ? { suggested: `${suggested.letter}: ${suggested.label}` } : {},
|
|
18670
|
-
notes,
|
|
18671
|
-
...fb?.verdict ? { verdict: fb.verdict } : {},
|
|
18672
|
-
...fb?.note ? { note: fb.note } : {}
|
|
18673
|
-
});
|
|
18925
|
+
var PLURALS = {
|
|
18926
|
+
commit: "commits",
|
|
18927
|
+
release: "releases",
|
|
18928
|
+
deletion: "deletions",
|
|
18929
|
+
channel: "channel switches",
|
|
18930
|
+
audit: "findings"
|
|
18931
|
+
};
|
|
18932
|
+
function quietLabel(events, quiet) {
|
|
18933
|
+
const types = [];
|
|
18934
|
+
for (const e of events)
|
|
18935
|
+
if (quiet.has(e.id) && e.type && !types.includes(e.type))
|
|
18936
|
+
types.push(e.type);
|
|
18937
|
+
const names = types.map((t) => PLURALS[t] ?? `${t}s`);
|
|
18938
|
+
return names.length > 1 ? `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}` : names[0] ?? "";
|
|
18939
|
+
}
|
|
18940
|
+
function eventOrder(events) {
|
|
18941
|
+
return [...events].sort((a, b) => a.item.from - b.item.from || a.laneIndex - b.laneIndex || a.index - b.index).map((e) => e.item);
|
|
18942
|
+
}
|
|
18943
|
+
function stepEvent(order, current, dir, at) {
|
|
18944
|
+
if (!order.length)
|
|
18945
|
+
return null;
|
|
18946
|
+
const i = current === null ? -1 : order.findIndex((e) => e.id === current);
|
|
18947
|
+
if (i < 0 && at !== undefined) {
|
|
18948
|
+
const next = dir > 0 ? order.find((e) => e.from >= at) : [...order].reverse().find((e) => e.from <= at);
|
|
18949
|
+
return next ?? (dir > 0 ? order[order.length - 1] : order[0]);
|
|
18674
18950
|
}
|
|
18951
|
+
if (i < 0)
|
|
18952
|
+
return dir > 0 ? order[0] : order[order.length - 1];
|
|
18953
|
+
return order[Math.max(0, Math.min(order.length - 1, i + dir))];
|
|
18954
|
+
}
|
|
18955
|
+
function dayScale(from, to, width) {
|
|
18956
|
+
const span = Math.max(1, to - from);
|
|
18675
18957
|
return {
|
|
18676
|
-
|
|
18677
|
-
|
|
18678
|
-
|
|
18679
|
-
total: progress.total,
|
|
18680
|
-
...state.note ? { note: state.note } : {},
|
|
18681
|
-
steps,
|
|
18682
|
-
general: (state.notes ?? []).filter((n) => !n.step || !keys.has(n.step)).map(noteOut)
|
|
18958
|
+
x: (day) => (day - from) / span * width,
|
|
18959
|
+
day: (x) => from + x / Math.max(1, width) * span,
|
|
18960
|
+
f: (day) => (day - from) / span
|
|
18683
18961
|
};
|
|
18684
18962
|
}
|
|
18685
|
-
var
|
|
18686
|
-
function
|
|
18687
|
-
const
|
|
18688
|
-
|
|
18689
|
-
|
|
18690
|
-
|
|
18691
|
-
|
|
18692
|
-
|
|
18693
|
-
|
|
18694
|
-
|
|
18695
|
-
|
|
18696
|
-
}
|
|
18697
|
-
function feedbackMarkdown(fb, verdicts) {
|
|
18698
|
-
const name = (id) => verdicts?.find((v) => v.id === id)?.label ?? label(id);
|
|
18699
|
-
const lines = [
|
|
18700
|
-
`## Feedback: ${fb.title}`,
|
|
18701
|
-
"",
|
|
18702
|
-
`${fb.reviewed} of ${fb.total} reviewed · \`${fb.id}\``
|
|
18703
|
-
];
|
|
18704
|
-
if (fb.note)
|
|
18705
|
-
lines.push("", quote(fb.note));
|
|
18706
|
-
if (fb.general.length) {
|
|
18707
|
-
lines.push("", "### General");
|
|
18708
|
-
for (const n of fb.general)
|
|
18709
|
-
lines.push(`- ${noteLine(n)}`);
|
|
18963
|
+
var STEPS = [1, 2, 3, 6, 12];
|
|
18964
|
+
function monthTicks(from, to, width, minGap = 44) {
|
|
18965
|
+
const perMonth = width / Math.max(1, to - from) * 30.44;
|
|
18966
|
+
const step = STEPS.find((s) => s * perMonth >= minGap) ?? 12;
|
|
18967
|
+
const ticks = [];
|
|
18968
|
+
const { y } = parts(from);
|
|
18969
|
+
let { m } = parts(from);
|
|
18970
|
+
let d = dayOf(y, m, 1);
|
|
18971
|
+
if (d < from) {
|
|
18972
|
+
m++;
|
|
18973
|
+
d = dayOf(y, m, 1);
|
|
18710
18974
|
}
|
|
18711
|
-
|
|
18712
|
-
|
|
18713
|
-
if (
|
|
18714
|
-
|
|
18715
|
-
|
|
18716
|
-
|
|
18717
|
-
const head = [];
|
|
18718
|
-
if (s.pickLabel)
|
|
18719
|
-
head.push(`**Picked ${s.pickLabel}**${s.suggested ? ` (suggested ${s.suggested})` : ""}`);
|
|
18720
|
-
if (s.verdict)
|
|
18721
|
-
head.push(`**${name(s.verdict)}**`);
|
|
18722
|
-
if (s.note)
|
|
18723
|
-
head.push(`“${quote(s.note)}”`);
|
|
18724
|
-
const open = !head.length && !s.notes.length;
|
|
18725
|
-
lines.push(`- ${s.number} ${s.title}: ${open ? "_open_" : head.join(" · ") || `${s.notes.length} note${s.notes.length === 1 ? "" : "s"}`}`);
|
|
18726
|
-
for (const n of s.notes)
|
|
18727
|
-
lines.push(` - ${noteLine(n)}`);
|
|
18975
|
+
while (d <= to) {
|
|
18976
|
+
const p = parts(d);
|
|
18977
|
+
if (p.m % step === 0)
|
|
18978
|
+
ticks.push({ day: d, label: p.m === 0 ? String(p.y) : MONTHS[p.m] });
|
|
18979
|
+
m++;
|
|
18980
|
+
d = dayOf(y, m, 1);
|
|
18728
18981
|
}
|
|
18729
|
-
return
|
|
18730
|
-
`) + `
|
|
18731
|
-
`;
|
|
18732
|
-
}
|
|
18733
|
-
var escapeHtml = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
18734
|
-
function plainMarkdown(text) {
|
|
18735
|
-
return text.replace(/\[([^\]\n]+)\]\([^)\s]+\)/g, "$1").replace(/`([^`\n]+)`/g, "$1").replace(/\*\*([^*\n]+)\*\*/g, "$1").replace(/(^|[^*\w])\*([^*\n]+)\*(?!\w)/g, "$1$2").replace(/\s+/g, " ").trim();
|
|
18982
|
+
return ticks;
|
|
18736
18983
|
}
|
|
18737
|
-
function
|
|
18738
|
-
|
|
18739
|
-
|
|
18740
|
-
|
|
18741
|
-
|
|
18984
|
+
function fitLabels(labels, width, reserved = [], gap = 8) {
|
|
18985
|
+
let end = -Infinity;
|
|
18986
|
+
return labels.map(({ x, w }) => {
|
|
18987
|
+
const x1 = x + w;
|
|
18988
|
+
if (x < end + gap || x1 > width)
|
|
18989
|
+
return false;
|
|
18990
|
+
if (reserved.some((r) => x < r.x1 + gap && x1 > r.x0 - gap))
|
|
18991
|
+
return false;
|
|
18992
|
+
end = x1;
|
|
18993
|
+
return true;
|
|
18742
18994
|
});
|
|
18743
|
-
|
|
18744
|
-
|
|
18745
|
-
|
|
18746
|
-
|
|
18747
|
-
|
|
18748
|
-
|
|
18995
|
+
}
|
|
18996
|
+
function clipSpan(from, to, axisFrom, axisTo) {
|
|
18997
|
+
if (to < axisFrom || from > axisTo)
|
|
18998
|
+
return null;
|
|
18999
|
+
return {
|
|
19000
|
+
from: Math.max(from, axisFrom),
|
|
19001
|
+
to: Math.min(to, axisTo),
|
|
19002
|
+
clipStart: from < axisFrom,
|
|
19003
|
+
clipEnd: to > axisTo
|
|
19004
|
+
};
|
|
19005
|
+
}
|
|
19006
|
+
function fitTag(widths, spanWidth, roomAfter, pad = 6) {
|
|
19007
|
+
for (let i = 0;i < widths.length; i++)
|
|
19008
|
+
if (widths[i] + pad * 2 <= spanWidth)
|
|
19009
|
+
return { index: i, place: "inside" };
|
|
19010
|
+
for (let i = 0;i < widths.length; i++)
|
|
19011
|
+
if (widths[i] + pad <= roomAfter)
|
|
19012
|
+
return { index: i, place: "after" };
|
|
19013
|
+
return null;
|
|
19014
|
+
}
|
|
19015
|
+
function bandRoom(bands, x) {
|
|
19016
|
+
return bands.map((b, i) => {
|
|
19017
|
+
const next = bands.slice(i + 1).find((n) => n.from > b.from);
|
|
19018
|
+
const end = Math.min(b.to, next ? next.from : Infinity);
|
|
19019
|
+
return Math.max(0, x(end) - x(b.from));
|
|
18749
19020
|
});
|
|
18750
|
-
|
|
18751
|
-
|
|
18752
|
-
|
|
18753
|
-
|
|
19021
|
+
}
|
|
19022
|
+
function nearestEvent(events, day, tolerance) {
|
|
19023
|
+
let best = null;
|
|
19024
|
+
let gap = tolerance;
|
|
19025
|
+
for (const e of events) {
|
|
19026
|
+
const d = day < e.from ? e.from - day : day > e.to ? day - e.to : 0;
|
|
19027
|
+
if (d <= gap) {
|
|
19028
|
+
best = e;
|
|
19029
|
+
gap = d;
|
|
19030
|
+
}
|
|
19031
|
+
}
|
|
19032
|
+
return best;
|
|
19033
|
+
}
|
|
19034
|
+
function firstSentence(text) {
|
|
19035
|
+
if (!text)
|
|
19036
|
+
return "";
|
|
19037
|
+
const plain = text.replace(/`([^`]*)`/g, "$1").replace(/\*\*?([^*]+)\*\*?/g, "$1").trim();
|
|
19038
|
+
const m = /^.*?[.!?](?=\s|$)/.exec(plain);
|
|
19039
|
+
return (m ? m[0] : plain).trim();
|
|
18754
19040
|
}
|
|
18755
19041
|
|
|
18756
19042
|
// src/ui/history.ts
|
|
19043
|
+
init_look();
|
|
18757
19044
|
var HEIGHT2 = springMotion(SPRINGS.default);
|
|
18758
19045
|
var GO = svg('<path d="M5 5h6v6M11 5l-6 6"/>');
|
|
18759
19046
|
var TYPE_NAMES = {
|
|
@@ -18765,14 +19052,14 @@ var TYPE_NAMES = {
|
|
|
18765
19052
|
};
|
|
18766
19053
|
var typeName = (t) => TYPE_NAMES[t] ?? t.charAt(0).toUpperCase() + t.slice(1);
|
|
18767
19054
|
var REACH = 8;
|
|
18768
|
-
var
|
|
19055
|
+
var el2 = (doc, tag, cls) => {
|
|
18769
19056
|
const n = doc.createElement(tag);
|
|
18770
19057
|
if (cls)
|
|
18771
19058
|
n.className = cls;
|
|
18772
19059
|
return n;
|
|
18773
19060
|
};
|
|
18774
19061
|
var text = (doc, tag, cls, value) => {
|
|
18775
|
-
const n =
|
|
19062
|
+
const n = el2(doc, tag, cls);
|
|
18776
19063
|
n.textContent = value;
|
|
18777
19064
|
return n;
|
|
18778
19065
|
};
|
|
@@ -18788,22 +19075,22 @@ var when = (it) => it.kind === "span" ? `${formatRange(it.from, it.to, it.open)}
|
|
|
18788
19075
|
function history(container, options) {
|
|
18789
19076
|
const doc = container.ownerDocument;
|
|
18790
19077
|
const win = doc.defaultView;
|
|
18791
|
-
const root =
|
|
19078
|
+
const root = el2(doc, "div", "knobs-history");
|
|
18792
19079
|
applyLook2(root, options);
|
|
18793
19080
|
injectHistoryStyles(doc);
|
|
18794
19081
|
root.tabIndex = 0;
|
|
18795
19082
|
root.setAttribute("role", "group");
|
|
18796
19083
|
root.setAttribute("aria-label", options.label ?? "History");
|
|
18797
19084
|
root.setAttribute("aria-roledescription", "timeline");
|
|
18798
|
-
const chart =
|
|
18799
|
-
const foot =
|
|
18800
|
-
const detail =
|
|
19085
|
+
const chart = el2(doc, "div", "knobs-hy-chart");
|
|
19086
|
+
const foot = el2(doc, "div", "knobs-hy-foot");
|
|
19087
|
+
const detail = el2(doc, "div", "knobs-hy-detail");
|
|
18801
19088
|
detail.setAttribute("aria-live", "polite");
|
|
18802
|
-
const tip =
|
|
19089
|
+
const tip = el2(doc, "div", "knobs-hy-tip");
|
|
18803
19090
|
tip.setAttribute("aria-hidden", "true");
|
|
18804
|
-
const tipTitle =
|
|
18805
|
-
const tipDate =
|
|
18806
|
-
const tipLine =
|
|
19091
|
+
const tipTitle = el2(doc, "div", "knobs-hy-tt");
|
|
19092
|
+
const tipDate = el2(doc, "div", "knobs-hy-td");
|
|
19093
|
+
const tipLine = el2(doc, "div", "knobs-hy-tl");
|
|
18807
19094
|
tip.append(tipTitle, tipDate, tipLine);
|
|
18808
19095
|
root.append(chart, foot, detail, tip);
|
|
18809
19096
|
container.append(root);
|
|
@@ -18839,7 +19126,7 @@ function history(container, options) {
|
|
|
18839
19126
|
foot.textContent = "";
|
|
18840
19127
|
root.toggleAttribute("data-all", showAll);
|
|
18841
19128
|
if (quiet.size) {
|
|
18842
|
-
const t =
|
|
19129
|
+
const t = el2(doc, "button", "knobs-hy-more");
|
|
18843
19130
|
t.type = "button";
|
|
18844
19131
|
t.setAttribute("aria-pressed", String(showAll));
|
|
18845
19132
|
t.textContent = `${showAll ? "Hide" : "Show"} ${quietLabel(model.events, quiet)}`;
|
|
@@ -18852,13 +19139,13 @@ function history(container, options) {
|
|
|
18852
19139
|
foot.append(t);
|
|
18853
19140
|
}
|
|
18854
19141
|
if (model.inferred && model.verified) {
|
|
18855
|
-
const legend =
|
|
19142
|
+
const legend = el2(doc, "span", "knobs-hy-legend");
|
|
18856
19143
|
for (const [label, inferred] of [
|
|
18857
19144
|
["Verified", false],
|
|
18858
19145
|
["Inferred", true]
|
|
18859
19146
|
]) {
|
|
18860
|
-
const k =
|
|
18861
|
-
const sw =
|
|
19147
|
+
const k = el2(doc, "span", "knobs-hy-key");
|
|
19148
|
+
const sw = el2(doc, "span", "knobs-hy-swatch");
|
|
18862
19149
|
sw.dataset.tone = "dim";
|
|
18863
19150
|
if (inferred)
|
|
18864
19151
|
sw.dataset.inferred = "";
|
|
@@ -18881,7 +19168,7 @@ function history(container, options) {
|
|
|
18881
19168
|
tracks = new Map;
|
|
18882
19169
|
let gutter = 0;
|
|
18883
19170
|
if (!narrow) {
|
|
18884
|
-
const probe =
|
|
19171
|
+
const probe = el2(doc, "div", "knobs-hy-label");
|
|
18885
19172
|
chart.append(probe);
|
|
18886
19173
|
const font = fontOf(probe);
|
|
18887
19174
|
const gap = parseFloat(win.getComputedStyle(probe).marginRight) || 0;
|
|
@@ -18897,25 +19184,25 @@ function history(container, options) {
|
|
|
18897
19184
|
for (let i = 0;i < model.lanes.length; i++)
|
|
18898
19185
|
rows.push(...narrow ? ["auto", "var(--hy-lane)"] : ["var(--hy-lane)"]);
|
|
18899
19186
|
chart.style.gridTemplateRows = rows.join(" ");
|
|
18900
|
-
const behind =
|
|
19187
|
+
const behind = el2(doc, "div", "knobs-hy-layer");
|
|
18901
19188
|
behind.setAttribute("aria-hidden", "true");
|
|
18902
19189
|
for (const b of model.bands) {
|
|
18903
19190
|
const c = clipSpan(b.from, b.to, model.from, model.to);
|
|
18904
19191
|
if (!c)
|
|
18905
19192
|
continue;
|
|
18906
|
-
const band =
|
|
19193
|
+
const band = el2(doc, "div", "knobs-hy-band");
|
|
18907
19194
|
band.style.left = pct(c.from);
|
|
18908
19195
|
band.style.width = pct(model.from + (c.to - c.from));
|
|
18909
19196
|
behind.append(band);
|
|
18910
19197
|
}
|
|
18911
19198
|
const ticks = monthTicks(model.from, model.to, plotW);
|
|
18912
19199
|
for (const t of ticks) {
|
|
18913
|
-
const line =
|
|
19200
|
+
const line = el2(doc, "div", "knobs-hy-grid");
|
|
18914
19201
|
line.style.left = pct(t.day);
|
|
18915
19202
|
behind.append(line);
|
|
18916
19203
|
}
|
|
18917
19204
|
chart.append(behind);
|
|
18918
|
-
const axis =
|
|
19205
|
+
const axis = el2(doc, "div", "knobs-hy-axis");
|
|
18919
19206
|
axis.setAttribute("aria-hidden", "true");
|
|
18920
19207
|
const tickEls = ticks.map((t) => {
|
|
18921
19208
|
const n = text(doc, "span", "knobs-hy-tick", t.label);
|
|
@@ -18941,7 +19228,7 @@ function history(container, options) {
|
|
|
18941
19228
|
const label = text(doc, "div", "knobs-hy-label", lane.label);
|
|
18942
19229
|
label.style.gridRow = String(row);
|
|
18943
19230
|
label.title = lane.label;
|
|
18944
|
-
const track =
|
|
19231
|
+
const track = el2(doc, "div", "knobs-hy-track");
|
|
18945
19232
|
track.style.gridRow = String(narrow ? row + 1 : row);
|
|
18946
19233
|
track.dataset.lane = lane.id;
|
|
18947
19234
|
tracks.set(lane.id, track);
|
|
@@ -18949,7 +19236,7 @@ function history(container, options) {
|
|
|
18949
19236
|
const c = clipSpan(s.from, s.to, model.from, model.to);
|
|
18950
19237
|
if (!c)
|
|
18951
19238
|
continue;
|
|
18952
|
-
const b =
|
|
19239
|
+
const b = el2(doc, "button", "knobs-hy-span");
|
|
18953
19240
|
b.type = "button";
|
|
18954
19241
|
b.tabIndex = -1;
|
|
18955
19242
|
b.dataset.id = s.id;
|
|
@@ -18971,7 +19258,7 @@ function history(container, options) {
|
|
|
18971
19258
|
for (const e of lane.events) {
|
|
18972
19259
|
if (e.to < model.from || e.from > model.to)
|
|
18973
19260
|
continue;
|
|
18974
|
-
const b =
|
|
19261
|
+
const b = el2(doc, "button", "knobs-hy-ev");
|
|
18975
19262
|
b.type = "button";
|
|
18976
19263
|
b.tabIndex = -1;
|
|
18977
19264
|
b.dataset.id = e.id;
|
|
@@ -18993,15 +19280,15 @@ function history(container, options) {
|
|
|
18993
19280
|
}
|
|
18994
19281
|
chart.append(label, track);
|
|
18995
19282
|
});
|
|
18996
|
-
over =
|
|
19283
|
+
over = el2(doc, "div", "knobs-hy-layer");
|
|
18997
19284
|
over.setAttribute("aria-hidden", "true");
|
|
18998
19285
|
if (showToday) {
|
|
18999
|
-
const today =
|
|
19286
|
+
const today = el2(doc, "div", "knobs-hy-today");
|
|
19000
19287
|
today.style.left = pct(model.today);
|
|
19001
19288
|
over.append(today);
|
|
19002
19289
|
}
|
|
19003
|
-
selLine =
|
|
19004
|
-
hoverLine =
|
|
19290
|
+
selLine = el2(doc, "div", "knobs-hy-sel");
|
|
19291
|
+
hoverLine = el2(doc, "div", "knobs-hy-hover");
|
|
19005
19292
|
over.append(selLine, hoverLine);
|
|
19006
19293
|
chart.append(over);
|
|
19007
19294
|
const reserved = [];
|
|
@@ -19047,8 +19334,8 @@ function history(container, options) {
|
|
|
19047
19334
|
return moving;
|
|
19048
19335
|
});
|
|
19049
19336
|
function buildDetail(it) {
|
|
19050
|
-
const c =
|
|
19051
|
-
const meta =
|
|
19337
|
+
const c = el2(doc, "div", "knobs-hy-dc");
|
|
19338
|
+
const meta = el2(doc, "div", "knobs-hy-dmeta");
|
|
19052
19339
|
meta.append(text(doc, "time", "", when(it)));
|
|
19053
19340
|
const lane = model.lanes.find((l) => l.id === it.lane);
|
|
19054
19341
|
if (lane && (model.lanes.length > 1 || lane.id !== "code"))
|
|
@@ -19065,11 +19352,11 @@ function history(container, options) {
|
|
|
19065
19352
|
}
|
|
19066
19353
|
c.append(meta, text(doc, "div", "knobs-hy-dtitle", it.title));
|
|
19067
19354
|
if (it.detail) {
|
|
19068
|
-
const p =
|
|
19355
|
+
const p = el2(doc, "p", "knobs-hy-dtext");
|
|
19069
19356
|
p.innerHTML = inlineMarkdown(it.detail);
|
|
19070
19357
|
c.append(p);
|
|
19071
19358
|
}
|
|
19072
|
-
const foot =
|
|
19359
|
+
const foot = el2(doc, "div", "knobs-hy-dfoot");
|
|
19073
19360
|
for (const e of it.evidence) {
|
|
19074
19361
|
const tag = text(doc, e.href ? "a" : "span", "knobs-hy-evidence", e.label);
|
|
19075
19362
|
if (tag instanceof HTMLAnchorElement && e.href) {
|
|
@@ -19080,7 +19367,7 @@ function history(container, options) {
|
|
|
19080
19367
|
foot.append(tag);
|
|
19081
19368
|
}
|
|
19082
19369
|
if (it.step && options.onJump) {
|
|
19083
|
-
const go =
|
|
19370
|
+
const go = el2(doc, "button", "knobs-hy-jump");
|
|
19084
19371
|
go.type = "button";
|
|
19085
19372
|
go.append(text(doc, "span", "", "Go to step"));
|
|
19086
19373
|
go.insertAdjacentHTML("beforeend", GO);
|
|
@@ -19323,19 +19610,19 @@ init_selection_pill();
|
|
|
19323
19610
|
var GLYPH = {
|
|
19324
19611
|
caret: icon("caretRight", { small: true })
|
|
19325
19612
|
};
|
|
19326
|
-
var
|
|
19613
|
+
var el3 = (doc, tag, cls) => {
|
|
19327
19614
|
const n = doc.createElement(tag);
|
|
19328
19615
|
if (cls)
|
|
19329
19616
|
n.className = cls;
|
|
19330
19617
|
return n;
|
|
19331
19618
|
};
|
|
19332
19619
|
var text2 = (doc, tag, cls, value) => {
|
|
19333
|
-
const n =
|
|
19620
|
+
const n = el3(doc, tag, cls);
|
|
19334
19621
|
n.textContent = value;
|
|
19335
19622
|
return n;
|
|
19336
19623
|
};
|
|
19337
19624
|
var prose = (doc, tag, cls, value) => {
|
|
19338
|
-
const n =
|
|
19625
|
+
const n = el3(doc, tag, cls);
|
|
19339
19626
|
n.innerHTML = inlineMarkdown(value);
|
|
19340
19627
|
return n;
|
|
19341
19628
|
};
|
|
@@ -19391,10 +19678,10 @@ function mountHistory(host, m, steps) {
|
|
|
19391
19678
|
function mountStack(host, m, steps, findPR) {
|
|
19392
19679
|
const doc = host.ownerDocument;
|
|
19393
19680
|
const layout = stackLayout(m);
|
|
19394
|
-
const root =
|
|
19681
|
+
const root = el3(doc, "div", "knobs-wt-stack");
|
|
19395
19682
|
root.toggleAttribute("data-flat", layout.flat);
|
|
19396
19683
|
const lane = (kind) => {
|
|
19397
|
-
const s =
|
|
19684
|
+
const s = el3(doc, "span", "knobs-wt-lane");
|
|
19398
19685
|
s.dataset.rail = kind;
|
|
19399
19686
|
return s;
|
|
19400
19687
|
};
|
|
@@ -19405,9 +19692,9 @@ function mountStack(host, m, steps, findPR) {
|
|
|
19405
19692
|
};
|
|
19406
19693
|
const from = layout.trunk ? 0 : 1;
|
|
19407
19694
|
if (layout.trunk && layout.rows.length) {
|
|
19408
|
-
const row =
|
|
19695
|
+
const row = el3(doc, "div", "knobs-wt-srow");
|
|
19409
19696
|
row.dataset.trunk = "";
|
|
19410
|
-
const lanes =
|
|
19697
|
+
const lanes = el3(doc, "span", "knobs-wt-lanes");
|
|
19411
19698
|
lanes.setAttribute("aria-hidden", "true");
|
|
19412
19699
|
lanes.append(node(true));
|
|
19413
19700
|
row.append(lanes, text2(doc, "span", "knobs-wt-trunk", layout.trunk));
|
|
@@ -19415,9 +19702,9 @@ function mountStack(host, m, steps, findPR) {
|
|
|
19415
19702
|
}
|
|
19416
19703
|
for (const r of layout.rows) {
|
|
19417
19704
|
const it = r.item;
|
|
19418
|
-
const row =
|
|
19705
|
+
const row = el3(doc, "div", "knobs-wt-srow");
|
|
19419
19706
|
if (!layout.flat) {
|
|
19420
|
-
const lanes =
|
|
19707
|
+
const lanes = el3(doc, "span", "knobs-wt-lanes");
|
|
19421
19708
|
lanes.setAttribute("aria-hidden", "true");
|
|
19422
19709
|
for (let k = from;k < r.depth; k++)
|
|
19423
19710
|
lanes.append(lane(r.rails[k] ? "pass" : "none"));
|
|
@@ -19426,8 +19713,8 @@ function mountStack(host, m, steps, findPR) {
|
|
|
19426
19713
|
lanes.append(node(r.children > 0));
|
|
19427
19714
|
row.append(lanes);
|
|
19428
19715
|
}
|
|
19429
|
-
const main =
|
|
19430
|
-
const line =
|
|
19716
|
+
const main = el3(doc, "div", "knobs-wt-smain");
|
|
19717
|
+
const line = el3(doc, "div", "knobs-wt-sline");
|
|
19431
19718
|
if (it.order)
|
|
19432
19719
|
line.append(text2(doc, "span", "knobs-wt-sord", it.order));
|
|
19433
19720
|
const known = findPR?.(it.pr.replace(/^#/, "")) ?? null;
|
|
@@ -19458,7 +19745,7 @@ function mountStack(host, m, steps, findPR) {
|
|
|
19458
19745
|
main.append(line);
|
|
19459
19746
|
if (it.summary)
|
|
19460
19747
|
main.append(prose(doc, "p", "knobs-wt-ssummary", it.summary));
|
|
19461
|
-
const meta =
|
|
19748
|
+
const meta = el3(doc, "div", "knobs-wt-smeta");
|
|
19462
19749
|
if (it.head) {
|
|
19463
19750
|
const branch = r.offBase ? `${it.head} → ${r.offBase}` : it.head;
|
|
19464
19751
|
meta.append(text2(doc, "span", "knobs-wt-sbranch", branch));
|
|
@@ -19483,7 +19770,7 @@ function mountStack(host, m, steps, findPR) {
|
|
|
19483
19770
|
return { dispose: () => root.remove(), context: () => ({ kind: "stack" }) };
|
|
19484
19771
|
}
|
|
19485
19772
|
function delta(doc, d) {
|
|
19486
|
-
const s =
|
|
19773
|
+
const s = el3(doc, "span", "knobs-wt-delta");
|
|
19487
19774
|
if (typeof d === "string") {
|
|
19488
19775
|
s.textContent = d;
|
|
19489
19776
|
return s;
|
|
@@ -19523,13 +19810,13 @@ var cellAttrs = (td, c) => {
|
|
|
19523
19810
|
function mountTable(host, m, steps) {
|
|
19524
19811
|
const doc = host.ownerDocument;
|
|
19525
19812
|
const win = doc.defaultView;
|
|
19526
|
-
const box =
|
|
19527
|
-
const scroller =
|
|
19528
|
-
const table =
|
|
19813
|
+
const box = el3(doc, "div", "knobs-wt-tablebox");
|
|
19814
|
+
const scroller = el3(doc, "div", "knobs-wt-table");
|
|
19815
|
+
const table = el3(doc, "table");
|
|
19529
19816
|
if (m.caption)
|
|
19530
19817
|
table.append(text2(doc, "caption", "", m.caption));
|
|
19531
|
-
const head =
|
|
19532
|
-
const hr =
|
|
19818
|
+
const head = el3(doc, "thead");
|
|
19819
|
+
const hr = el3(doc, "tr");
|
|
19533
19820
|
for (const c of m.columns) {
|
|
19534
19821
|
const th = text2(doc, "th", "", c.label);
|
|
19535
19822
|
th.scope = "col";
|
|
@@ -19537,11 +19824,11 @@ function mountTable(host, m, steps) {
|
|
|
19537
19824
|
hr.append(th);
|
|
19538
19825
|
}
|
|
19539
19826
|
head.append(hr);
|
|
19540
|
-
const body =
|
|
19827
|
+
const body = el3(doc, "tbody");
|
|
19541
19828
|
for (const r of m.rows) {
|
|
19542
|
-
const tr =
|
|
19829
|
+
const tr = el3(doc, "tr");
|
|
19543
19830
|
for (const c of m.columns) {
|
|
19544
|
-
const td =
|
|
19831
|
+
const td = el3(doc, "td");
|
|
19545
19832
|
cellAttrs(td, c);
|
|
19546
19833
|
const cell = r[c.key];
|
|
19547
19834
|
if (cell) {
|
|
@@ -19556,24 +19843,24 @@ function mountTable(host, m, steps) {
|
|
|
19556
19843
|
}
|
|
19557
19844
|
table.append(head, body);
|
|
19558
19845
|
scroller.append(table);
|
|
19559
|
-
const stack =
|
|
19846
|
+
const stack = el3(doc, "div", "knobs-wt-tstack");
|
|
19560
19847
|
stack.setAttribute("role", "list");
|
|
19561
19848
|
if (m.caption)
|
|
19562
19849
|
stack.setAttribute("aria-label", m.caption);
|
|
19563
19850
|
const primary = m.columns.find((c) => c.key === m.primary) ?? m.columns[0];
|
|
19564
19851
|
for (const r of m.rows) {
|
|
19565
|
-
const item =
|
|
19852
|
+
const item = el3(doc, "div", "knobs-wt-trow");
|
|
19566
19853
|
item.setAttribute("role", "listitem");
|
|
19567
|
-
const title =
|
|
19854
|
+
const title = el3(doc, "div", "knobs-wt-ttitle");
|
|
19568
19855
|
if (primary) {
|
|
19569
19856
|
cellAttrs(title, primary);
|
|
19570
19857
|
const cell = r[primary.key];
|
|
19571
19858
|
if (cell)
|
|
19572
19859
|
cellContent(doc, title, cell);
|
|
19573
19860
|
}
|
|
19574
|
-
const line =
|
|
19861
|
+
const line = el3(doc, "div", "knobs-wt-tline");
|
|
19575
19862
|
line.append(title);
|
|
19576
|
-
const badges =
|
|
19863
|
+
const badges = el3(doc, "div", "knobs-wt-tbadges");
|
|
19577
19864
|
for (const c of m.columns) {
|
|
19578
19865
|
const cell = r[c.key];
|
|
19579
19866
|
if (c !== primary && cell?.badge)
|
|
@@ -19582,14 +19869,14 @@ function mountTable(host, m, steps) {
|
|
|
19582
19869
|
if (badges.childNodes.length)
|
|
19583
19870
|
line.append(badges);
|
|
19584
19871
|
item.append(line);
|
|
19585
|
-
const terms =
|
|
19872
|
+
const terms = el3(doc, "dl", "knobs-wt-tterms");
|
|
19586
19873
|
for (const c of m.columns) {
|
|
19587
19874
|
if (c === primary)
|
|
19588
19875
|
continue;
|
|
19589
19876
|
const cell = r[c.key];
|
|
19590
19877
|
if (!cell || cell.text === "" || cell.badge)
|
|
19591
19878
|
continue;
|
|
19592
|
-
const dd =
|
|
19879
|
+
const dd = el3(doc, "dd");
|
|
19593
19880
|
cellAttrs(dd, c);
|
|
19594
19881
|
dd.removeAttribute("data-align");
|
|
19595
19882
|
cellContent(doc, dd, cell);
|
|
@@ -19655,12 +19942,12 @@ function mountTable(host, m, steps) {
|
|
|
19655
19942
|
}
|
|
19656
19943
|
function chapterPills(host, chapters, io) {
|
|
19657
19944
|
const doc = host.ownerDocument;
|
|
19658
|
-
const list =
|
|
19945
|
+
const list = el3(doc, "div", "knobs-wt-chapters");
|
|
19659
19946
|
list.setAttribute("role", "group");
|
|
19660
19947
|
list.setAttribute("aria-label", "Chapters");
|
|
19661
19948
|
let at = -2;
|
|
19662
19949
|
const rows = chapters.map((c, i) => {
|
|
19663
|
-
const b =
|
|
19950
|
+
const b = el3(doc, "button", "knobs-wt-chapter");
|
|
19664
19951
|
b.type = "button";
|
|
19665
19952
|
b.append(text2(doc, "span", "knobs-wt-chapter-n", String(i + 1)));
|
|
19666
19953
|
b.append(text2(doc, "span", "knobs-wt-chapter-label", c.label));
|
|
@@ -19676,7 +19963,8 @@ function chapterPills(host, chapters, io) {
|
|
|
19676
19963
|
host.append(list);
|
|
19677
19964
|
const pill = selectionPill(list, {
|
|
19678
19965
|
selected: () => rows[at] ?? null,
|
|
19679
|
-
rows: () => rows
|
|
19966
|
+
rows: () => rows,
|
|
19967
|
+
spring: SPRINGS.snappy
|
|
19680
19968
|
});
|
|
19681
19969
|
const select = (i) => {
|
|
19682
19970
|
if (i === at)
|
|
@@ -19702,13 +19990,13 @@ function disclosure(host, options) {
|
|
|
19702
19990
|
const doc = host.ownerDocument;
|
|
19703
19991
|
const win = doc.defaultView;
|
|
19704
19992
|
let open = options.open === true;
|
|
19705
|
-
const root =
|
|
19706
|
-
const toggle =
|
|
19993
|
+
const root = el3(doc, "div", "knobs-wt-disc");
|
|
19994
|
+
const toggle = el3(doc, "button", "knobs-wt-disc-toggle");
|
|
19707
19995
|
toggle.type = "button";
|
|
19708
19996
|
toggle.innerHTML = GLYPH.caret;
|
|
19709
19997
|
toggle.append(text2(doc, "span", "", options.label));
|
|
19710
|
-
const viewport =
|
|
19711
|
-
const content =
|
|
19998
|
+
const viewport = el3(doc, "div", "knobs-wt-disc-body");
|
|
19999
|
+
const content = el3(doc, "div", "knobs-wt-disc-content");
|
|
19712
20000
|
content.append(options.content);
|
|
19713
20001
|
viewport.append(content);
|
|
19714
20002
|
root.append(toggle, viewport);
|
|
@@ -19787,12 +20075,12 @@ var prGlyph = (state) => state === "merged" ? svg12(PR_PATHS.merge) : state ===
|
|
|
19787
20075
|
var PR_STATE = { merged: "Merged", open: "Open", draft: "Draft", closed: "Closed" };
|
|
19788
20076
|
var ISSUE = "M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-88a16,16,0,1,1-16-16A16,16,0,0,1,144,128Z";
|
|
19789
20077
|
function prStateLabel(doc, state, checks, title) {
|
|
19790
|
-
const s =
|
|
20078
|
+
const s = el3(doc, "span", "knobs-wt-prstate");
|
|
19791
20079
|
s.dataset.state = state;
|
|
19792
20080
|
s.innerHTML = prGlyph(state);
|
|
19793
20081
|
s.append(text2(doc, "span", "", PR_STATE[state]));
|
|
19794
20082
|
if (checks) {
|
|
19795
|
-
const c =
|
|
20083
|
+
const c = el3(doc, "span", "knobs-wt-prchecks");
|
|
19796
20084
|
c.innerHTML = svg12(CHECK3);
|
|
19797
20085
|
c.title = "Checks pass";
|
|
19798
20086
|
c.setAttribute("aria-label", "checks pass");
|
|
@@ -19803,7 +20091,7 @@ function prStateLabel(doc, state, checks, title) {
|
|
|
19803
20091
|
return s;
|
|
19804
20092
|
}
|
|
19805
20093
|
function prChip(doc, n, pr) {
|
|
19806
|
-
const chip =
|
|
20094
|
+
const chip = el3(doc, pr.href ? "a" : "span", "knobs-wt-pr");
|
|
19807
20095
|
if (pr.href) {
|
|
19808
20096
|
chip.setAttribute("href", pr.href);
|
|
19809
20097
|
chip.setAttribute("target", "_blank");
|
|
@@ -19817,7 +20105,7 @@ function prChip(doc, n, pr) {
|
|
|
19817
20105
|
chip.innerHTML = issue ? svg12(ISSUE) : prGlyph(pr.state);
|
|
19818
20106
|
chip.append(text2(doc, "span", "", `#${n}`));
|
|
19819
20107
|
if (pr.checks) {
|
|
19820
|
-
const c =
|
|
20108
|
+
const c = el3(doc, "span", "knobs-wt-prchecks");
|
|
19821
20109
|
c.innerHTML = svg12(CHECK3);
|
|
19822
20110
|
chip.append(c);
|
|
19823
20111
|
}
|
|
@@ -19900,21 +20188,25 @@ var calm = springCss(SPRINGS.calm);
|
|
|
19900
20188
|
var state = springCss(SPRINGS.default);
|
|
19901
20189
|
var quick = springCss(FADE);
|
|
19902
20190
|
var fade2 = "-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 16px),transparent);mask-image:linear-gradient(to right,#000 calc(100% - 16px),transparent)";
|
|
19903
|
-
var walkthroughCss =
|
|
20191
|
+
var walkthroughCss = `${foundationCss}
|
|
19904
20192
|
:where(.knobs-walkthrough,.knobs-walkthrough *,.knobs-wt-slot,.knobs-wt-slot *){box-sizing:border-box}
|
|
19905
20193
|
.knobs-ui:is(.knobs-walkthrough,.knobs-wt-slot){--k-glass:0;--k-glow:0;--kw-quick:${quick.easing};--kw-quick-ms:${quick.duration}ms}
|
|
19906
20194
|
.knobs-ui.knobs-walkthrough{--kw-snappy:${snappy3.easing};--kw-snappy-ms:${snappy3.duration}ms;--kw-calm:${calm.easing};--kw-calm-ms:${calm.duration}ms;--kw-state:${state.easing};--kw-state-ms:${state.duration}ms;--kw-quick:${quick.easing};--kw-quick-ms:${quick.duration}ms;
|
|
19907
|
-
--kw-measure:var(--
|
|
20195
|
+
--kw-measure:var(--_kr-measure);--kw-width:var(--k-walkthrough-width,960px);--kw-rail-w:220px;--kw-rail-gap:calc(var(--_k-space-group) + var(--_k-space-column) / 1.5);--kw-good:var(--k-walkthrough-good,var(--_k-ok));--kw-info:var(--k-walkthrough-info,var(--_k-info));
|
|
19908
20196
|
--kw-card:var(--_ku-pane-bg);--kw-card-ring:var(--_ku-pane-ring);--kw-under:var(--_k-bg-page);--kw-ink:var(--k-walkthrough-ink,#ff375f);--kw-strong:var(--_k-weight-label);
|
|
19909
20197
|
container:knobs-wt / inline-size;display:block;width:100%;color:var(--_k-fg);font:var(--_k-weight) var(--_k-type-body)/var(--_k-leading-body) var(--_k-font);font-variant-numeric:tabular-nums;text-align:left;
|
|
19910
|
-
letter-spacing:
|
|
20198
|
+
letter-spacing:var(--_kr-track);-webkit-font-smoothing:antialiased}
|
|
19911
20199
|
.knobs-walkthrough [hidden]{display:none!important}
|
|
20200
|
+
/* Prose: wrapped to look set by hand, quotes hung in the margin (Safari). Data: figures that line
|
|
20201
|
+
up and can't be misread. */
|
|
20202
|
+
:where(.knobs-wt-summary,.knobs-wt-ssum,.knobs-wt-caption,.knobs-wt-more-text,.knobs-wt-fi-text,.knobs-wt-fi-answer,.knobs-wt-ocap,.knobs-wt-text,.knobs-wt-callout,.knobs-wt-ssummary,.knobs-wt-osum){text-wrap:pretty;hanging-punctuation:first allow-end last}
|
|
20203
|
+
:where(.knobs-wt-table table,.knobs-wt-tterms,.knobs-wt-facts,.knobs-wt-smeta,.knobs-wt-metaline,.knobs-wt-meta,.knobs-wt-pr,.knobs-wt-prstate,.knobs-wt-sord){font-feature-settings:var(--_kr-data)}
|
|
19912
20204
|
/* (0,0,1): a host's element rules stop here, and every class rule below wins over it. */
|
|
19913
|
-
:where(.knobs-walkthrough) :is(h1,h2,h3,p,ol,ul,dl,dd){margin:0;font:inherit;letter-spacing:
|
|
20205
|
+
:where(.knobs-walkthrough) :is(h1,h2,h3,p,ol,ul,dl,dd){margin:0;font:inherit;letter-spacing:var(--_kr-track)}
|
|
19914
20206
|
.knobs-walkthrough a{cursor:default;color:inherit;text-decoration:underline;text-decoration-color:color-mix(in srgb,currentColor 35%,transparent);text-underline-offset:2px;transition:text-decoration-color calc(var(--_k-hover-out) * 1ms)}
|
|
19915
20207
|
.knobs-walkthrough a:hover{text-decoration-color:currentColor}
|
|
19916
20208
|
.knobs-walkthrough code,.knobs-wt-slot code{font:var(--_k-type-label) var(--_k-mono);padding:1px 5px;border-radius:calc(var(--_k-radius) / 2);background:var(--_k-fill)}
|
|
19917
|
-
:where(.knobs-ui) :is(.knobs-wt-btn,.knobs-wt-pick,.knobs-wt-toc-row,button.knobs-wt-stitle,.knobs-wt-chapter,.knobs-wt-disc-toggle){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:
|
|
20209
|
+
:where(.knobs-ui) :is(.knobs-wt-btn,.knobs-wt-pick,.knobs-wt-toc-row,button.knobs-wt-stitle,.knobs-wt-chapter,.knobs-wt-disc-toggle){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:var(--_kr-track);
|
|
19918
20210
|
background:transparent;border:0;border-radius:0;margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
|
|
19919
20211
|
|
|
19920
20212
|
/* ——— layout: the contents rail sticky at the left edge (right if asked), and the column centred
|
|
@@ -20072,7 +20364,7 @@ var walkthroughCss = `
|
|
|
20072
20364
|
box-shadow:var(--_ku-pane-ring),var(--_k-shadow-overlay)}
|
|
20073
20365
|
.knobs-wt-on{padding-top:3px;color:var(--_k-fg-3);font-size:var(--_k-type-small);line-height:var(--_k-leading-small);white-space:nowrap;overflow:hidden;${fade2}}
|
|
20074
20366
|
.knobs-wt-row{display:flex;align-items:flex-end;gap:4px}
|
|
20075
|
-
:where(.knobs-ui) :is(.knobs-wt-input,.knobs-wt-tool,.knobs-wt-x,.knobs-wt-back){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:
|
|
20367
|
+
:where(.knobs-ui) :is(.knobs-wt-input,.knobs-wt-tool,.knobs-wt-x,.knobs-wt-back){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:var(--_kr-track);background:transparent;border:0;
|
|
20076
20368
|
border-radius:0;margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
|
|
20077
20369
|
.knobs-ui .knobs-wt-input{flex:1;min-width:0;height:32px;max-height:140px;padding:6px 0;resize:none;color:var(--_k-fg);caret-color:var(--_k-accent);line-height:var(--_k-leading-body);font-size:var(--_k-type-body)}
|
|
20078
20370
|
.knobs-ui .knobs-wt-input::placeholder{color:var(--_k-fg-3)}
|
|
@@ -20124,27 +20416,53 @@ var walkthroughCss = `
|
|
|
20124
20416
|
.knobs-wt-step[data-full] .knobs-wt-slot[data-kind="video"] .knobs-player{flex:1;min-height:0}
|
|
20125
20417
|
.knobs-wt-step[data-full] .knobs-wt-slot[data-kind="video"] .knobs-player-frame{flex:1;min-height:0;aspect-ratio:auto}
|
|
20126
20418
|
|
|
20127
|
-
/* ——— a taste test
|
|
20128
|
-
|
|
20129
|
-
|
|
20130
|
-
|
|
20131
|
-
.knobs-wt-
|
|
20132
|
-
.knobs-wt-option
|
|
20133
|
-
.knobs-wt-
|
|
20134
|
-
.knobs-
|
|
20135
|
-
|
|
20136
|
-
.knobs-
|
|
20137
|
-
.knobs-wt-
|
|
20138
|
-
.knobs-wt-
|
|
20139
|
-
.knobs-wt-
|
|
20140
|
-
.knobs-wt-
|
|
20141
|
-
|
|
20419
|
+
/* ——— options: a decision, or a taste test when Kit picks. Columns that fill whole rows (4 → 4, 2
|
|
20420
|
+
or 1 across; 3 → 3 or 1), a hairline between neighbours and between rows, no card of their own.
|
|
20421
|
+
Each: a kicker ("Option 1 · Recommended", the mark a quiet word in text colour), its title, a
|
|
20422
|
+
sentence, its media and what changes as facts. A pick dims the rest ——— */
|
|
20423
|
+
.knobs-wt-options{--kw-ogap:var(--_k-space-group);display:grid;grid-template-columns:repeat(var(--kw-cols,1),minmax(0,1fr));gap:var(--kw-ogap);min-width:0}
|
|
20424
|
+
.knobs-wt-option{position:relative;display:flex;flex-direction:column;gap:var(--_k-space-title);min-width:0}
|
|
20425
|
+
.knobs-wt-option[data-col]:not([data-col="0"])::before{content:"";position:absolute;top:0;bottom:0;left:calc(var(--kw-ogap) / -2);width:1px;background:var(--_k-border-subtle);pointer-events:none}
|
|
20426
|
+
.knobs-wt-option[data-col]:not([data-row="0"])::before{top:calc(var(--kw-ogap) / -2)}
|
|
20427
|
+
.knobs-wt-option[data-col]:not([data-last-row])::before{bottom:calc(var(--kw-ogap) / -2)}
|
|
20428
|
+
.knobs-wt-option[data-col]:not([data-row="0"])::after{content:"";position:absolute;top:calc(var(--kw-ogap) / -2);left:calc(var(--kw-ogap) / -2);right:calc(var(--kw-ogap) / -2);height:1px;background:var(--_k-border-subtle);pointer-events:none}
|
|
20429
|
+
.knobs-wt-option[data-col="0"]::after{left:0}
|
|
20430
|
+
.knobs-wt-option[data-end]::after{right:0}
|
|
20431
|
+
.knobs-wt-option>:not(.knobs-wt-ohead){transition:opacity var(--kw-snappy-ms) var(--kw-snappy)}
|
|
20432
|
+
.knobs-wt-options[data-has-pick] .knobs-wt-option:not([data-picked])>:not(.knobs-wt-ohead){opacity:.55}
|
|
20433
|
+
.knobs-wt-ohead{display:flex;flex-direction:column;gap:var(--_k-space-inside);min-width:0;text-align:left}
|
|
20434
|
+
.knobs-wt-okicker{display:flex;align-items:center;gap:var(--_k-space-inside);min-width:0;height:var(--_k-leading-small);color:var(--_k-fg-3);font-size:var(--_k-type-small);line-height:var(--_k-leading-small);white-space:nowrap}
|
|
20435
|
+
.knobs-wt-orec{color:var(--_k-fg);font-weight:var(--kw-strong)}
|
|
20436
|
+
.knobs-wt-orec::before{content:"·";margin-right:var(--_k-space-inside);color:var(--_k-fg-3);font-weight:var(--_k-weight)}
|
|
20437
|
+
.knobs-wt-otitle{color:var(--_k-fg);font-weight:var(--kw-strong);text-wrap:balance}
|
|
20438
|
+
.knobs-wt-osum{max-width:var(--kw-measure);color:var(--_k-fg-2)}
|
|
20439
|
+
/* Picking: the head is the radio, bled out so its words keep the column's edge; a fill under the
|
|
20440
|
+
pointer at once; a check at the kicker's end that fills when picked. */
|
|
20441
|
+
.knobs-ui .knobs-wt-pick{margin:-6px -8px;padding:6px 8px;border-radius:var(--_k-radius);cursor:default;transition:background calc(var(--_k-hover-out) * 1ms)}
|
|
20442
|
+
.knobs-ui .knobs-wt-pick:is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
20443
|
+
.knobs-ui .knobs-wt-pick:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
|
|
20444
|
+
.knobs-wt-check{flex:none;display:flex;width:16px;height:16px;margin-left:auto;align-items:center;justify-content:center;border-radius:50%;box-shadow:inset 0 0 0 1px var(--_k-border-strong);color:transparent;
|
|
20445
|
+
transition:background var(--kw-snappy-ms) var(--kw-snappy),color var(--kw-snappy-ms) var(--kw-snappy),box-shadow var(--kw-snappy-ms) var(--kw-snappy)}
|
|
20142
20446
|
.knobs-wt-check svg{width:12px;height:12px}
|
|
20143
|
-
.knobs-wt-option[data-picked] .knobs-wt-check{background:var(--_k-accent);color:var(--_k-
|
|
20144
|
-
|
|
20145
|
-
|
|
20146
|
-
.
|
|
20147
|
-
|
|
20447
|
+
.knobs-wt-option[data-picked] .knobs-wt-check{background:var(--_k-accent);box-shadow:none;color:var(--_k-on-accent)}
|
|
20448
|
+
|
|
20449
|
+
/* ——— facts: terms in a fixed gutter beside their values, sharing a baseline; groups under a
|
|
20450
|
+
heading, a hairline between them. Code and evidence are small mono chips. Too narrow for the
|
|
20451
|
+
gutter, each term sits over its value ——— */
|
|
20452
|
+
.knobs-wt-facts{--kw-term:144px;display:flex;flex-direction:column;gap:var(--_kr-block);min-width:0}
|
|
20453
|
+
.knobs-wt-fgroup{position:relative;display:flex;flex-direction:column;gap:var(--_k-space-title);min-width:0}
|
|
20454
|
+
.knobs-wt-fgroup+.knobs-wt-fgroup::before{content:"";position:absolute;left:0;right:0;top:calc(var(--_kr-block) / -2);height:1px;background:var(--_k-border-subtle);pointer-events:none}
|
|
20455
|
+
.knobs-wt-fhead{margin:0;color:var(--_k-fg);font-weight:var(--kw-strong);text-wrap:balance}
|
|
20456
|
+
.knobs-wt-fterms{display:grid;grid-template-columns:var(--kw-term) minmax(0,1fr);gap:var(--_k-space-sibling) var(--_kr-block);align-items:baseline;margin:0;min-width:0}
|
|
20457
|
+
.knobs-wt-fact{display:contents}
|
|
20458
|
+
.knobs-wt-fterms dt{margin:0;color:var(--_k-fg-3);font-size:var(--_k-type-small);line-height:var(--_k-leading-small);text-wrap:balance}
|
|
20459
|
+
.knobs-wt-fterms dd{display:flex;flex-direction:column;gap:var(--_k-space-inside);min-width:0;margin:0;color:var(--_k-fg-2);overflow-wrap:anywhere;text-wrap:pretty}
|
|
20460
|
+
.knobs-wt-fterms dd[data-code]{flex-flow:row wrap}
|
|
20461
|
+
.knobs-wt-fterms dd[data-code]>code{padding:2px 6px;line-height:var(--_k-leading-label);color:var(--_k-fg)}
|
|
20462
|
+
@container knobs-wt-slot (max-width:400px){
|
|
20463
|
+
.knobs-wt-fterms{display:flex;flex-direction:column;gap:var(--_k-space-sibling)}
|
|
20464
|
+
.knobs-wt-fact{display:flex;flex-direction:column;gap:var(--_k-space-inside)}
|
|
20465
|
+
}
|
|
20148
20466
|
|
|
20149
20467
|
/* ——— the rail: progress, contents, export ——— */
|
|
20150
20468
|
.knobs-wt-rail{position:sticky;top:var(--_k-pad-page);display:flex;flex-direction:column;gap:var(--_k-space-title);max-height:calc(100vh - var(--_k-pad-page) * 2);min-height:0;font-size:var(--_k-type-small);line-height:var(--_k-leading-small)}
|
|
@@ -20198,11 +20516,11 @@ var walkthroughCss = `
|
|
|
20198
20516
|
set apart by space, hairlines and their own colours. On a card (.knobs-surface) code drops its
|
|
20199
20517
|
well for hairlines, so nothing draws a fill and a ring of its own there ——— */
|
|
20200
20518
|
.knobs-ui.knobs-wt-slot{position:relative;display:flex;flex-direction:column;gap:var(--_k-space-sibling);min-width:0;container:knobs-wt-slot / inline-size;color:var(--_k-fg);
|
|
20201
|
-
font:var(--_k-weight) var(--_k-type-body)/var(--_k-leading-body) var(--_k-font);font-variant-numeric:tabular-nums;--kw-measure:var(--
|
|
20519
|
+
font:var(--_k-weight) var(--_k-type-body)/var(--_k-leading-body) var(--_k-font);font-variant-numeric:tabular-nums;--kw-measure:var(--_kr-measure);
|
|
20202
20520
|
--kw-good:var(--k-walkthrough-good,var(--_k-ok));--kw-info:var(--k-walkthrough-info,var(--_k-info));--kw-frame-r:var(--_k-radius-panel)}
|
|
20203
20521
|
.knobs-surface .knobs-wt-slot{--kw-frame-r:calc(var(--_k-radius-panel) - 4px)}
|
|
20204
20522
|
/* A slot's label: sentence case as written, small and faint, never a heading. */
|
|
20205
|
-
.knobs-wt-slot-label{display:block;min-width:0;overflow:hidden;white-space:nowrap;font:var(--_k-weight) var(--_k-type-small)/var(--_k-leading-small) var(--_k-font);
|
|
20523
|
+
.knobs-wt-slot-label{display:block;min-width:0;overflow:hidden;white-space:nowrap;font:var(--_k-weight) var(--_k-type-small)/var(--_k-leading-small) var(--_k-font);text-transform:none;color:var(--_k-fg-3);${fade2}}
|
|
20206
20524
|
.knobs-wt-body{min-width:0}
|
|
20207
20525
|
/* Media mounting after the first paint, and a picture still decoding, come into focus: a fade with
|
|
20208
20526
|
a small blur on the quick spring, from the top; at rest, nothing. */
|
|
@@ -20221,14 +20539,15 @@ var walkthroughCss = `
|
|
|
20221
20539
|
.knobs-wt-frame>:is(video,img){position:absolute;inset:0;display:block;width:100%;height:100%;object-fit:contain;margin:0;border:0}
|
|
20222
20540
|
.knobs-wt-frame[data-natural]{aspect-ratio:auto;background:transparent}
|
|
20223
20541
|
.knobs-wt-frame[data-natural]>img{position:static;height:auto}
|
|
20224
|
-
/* Diff, code and terminal: one plain
|
|
20225
|
-
|
|
20226
|
-
|
|
20542
|
+
/* Diff, code and terminal: one plain well, drawn by its hairline alone (a fill and a ring would be
|
|
20543
|
+
two edges for one box), the panel radius and room inside, so a diff's buttons and the code under
|
|
20544
|
+
them can breathe. */
|
|
20545
|
+
.knobs-wt-slot[data-kind="diff"]>.knobs-wt-body{padding:var(--_k-pad-card);border-radius:var(--_k-radius-panel);box-shadow:inset 0 0 0 1px var(--_k-border-subtle)}
|
|
20227
20546
|
/* The diff inside it: its header row 28 high on the container's padding, a hairline under it, and
|
|
20228
20547
|
no line of its own under the code (the container's ring closes it). */
|
|
20229
20548
|
.knobs-wt-slot[data-kind="diff"] .knobs-ui.knobs-diff:not([data-on="pane"]){--kd-head:28px;--kd-x:0px}
|
|
20230
20549
|
.knobs-wt-slot[data-kind="diff"] .knobs-ui.knobs-diff:not([data-on="pane"])::after{display:none}
|
|
20231
|
-
.knobs-wt-code{margin:0;padding:var(--_k-pad-card);max-height:420px;overflow:auto;border-radius:var(--_k-radius-panel);
|
|
20550
|
+
.knobs-wt-code{margin:0;padding:var(--_k-pad-card);max-height:420px;overflow:auto;border-radius:var(--_k-radius-panel);box-shadow:inset 0 0 0 1px var(--_k-border-subtle);color:var(--_k-fg);
|
|
20232
20551
|
font:var(--_k-type-label)/var(--_k-leading-body) var(--_k-mono);white-space:pre;tab-size:2;scrollbar-width:thin}
|
|
20233
20552
|
.knobs-surface :is(.knobs-wt-code,.knobs-wt-slot[data-kind="diff"]>.knobs-wt-body){padding-inline:0;border-radius:0;background:transparent;box-shadow:inset 0 1px 0 var(--_k-border-subtle),inset 0 -1px 0 var(--_k-border-subtle)}
|
|
20234
20553
|
.knobs-wt-code[data-terminal]{white-space:pre-wrap;overflow-wrap:anywhere}
|
|
@@ -20237,17 +20556,17 @@ var walkthroughCss = `
|
|
|
20237
20556
|
.knobs-wt-out{color:var(--_k-fg-2)}
|
|
20238
20557
|
.knobs-wt-metric{display:flex;flex-direction:column;gap:var(--_k-space-sibling)}
|
|
20239
20558
|
.knobs-wt-mlabel{color:var(--_k-fg-2)}
|
|
20240
|
-
.knobs-wt-mvalue{display:flex;align-items:baseline;flex-wrap:wrap;gap:var(--_k-space-sibling);font-
|
|
20559
|
+
.knobs-wt-mvalue{display:flex;align-items:baseline;flex-wrap:wrap;gap:var(--_k-space-sibling);font-feature-settings:var(--_kr-data)}
|
|
20241
20560
|
.knobs-wt-mfrom{color:var(--_k-fg-3)}
|
|
20242
20561
|
.knobs-wt-mto{font-size:var(--_k-type-title);line-height:var(--_k-leading-title);font-weight:var(--_k-weight-label)}
|
|
20243
20562
|
.knobs-wt-mof{color:var(--_k-fg-3)}
|
|
20244
20563
|
.knobs-wt-mdelta{--kw-tone:var(--_k-fg-2);font-size:var(--_k-type-small);font-weight:var(--_k-weight-label);color:var(--kw-tone)}
|
|
20245
20564
|
.knobs-wt-mdelta[data-tone="good"]{--kw-tone:var(--kw-good)}
|
|
20246
20565
|
.knobs-wt-mdelta[data-tone="bad"]{--kw-tone:var(--_k-danger)}
|
|
20247
|
-
.knobs-wt-mtrack{position:relative;height:4px;border-radius:2px;background:
|
|
20566
|
+
.knobs-wt-mtrack{position:relative;height:4px;border-radius:2px;background:var(--_k-fill-selected)}
|
|
20248
20567
|
.knobs-wt-mtrack>span{position:absolute;top:0;bottom:0;left:0;border-radius:inherit}
|
|
20249
20568
|
.knobs-wt-mtrack>.knobs-wt-mafter{background:var(--_k-accent)}
|
|
20250
|
-
.knobs-wt-mtrack>.knobs-wt-mbefore{background:
|
|
20569
|
+
.knobs-wt-mtrack>.knobs-wt-mbefore{background:var(--_k-fg-4)}
|
|
20251
20570
|
.knobs-wt-callout{--kw-tone:var(--kw-info);display:flex;gap:var(--_k-space-sibling);max-width:var(--kw-measure);color:var(--_k-fg)}
|
|
20252
20571
|
.knobs-wt-callout::before{content:"";flex:none;width:6px;height:6px;margin-top:7px;border-radius:50%;background:var(--kw-tone)}
|
|
20253
20572
|
.knobs-wt-callout[data-tone="good"]{--kw-tone:var(--kw-good)}
|
|
@@ -20313,7 +20632,7 @@ var walkthroughCss = `
|
|
|
20313
20632
|
.knobs-ui .knobs-wt-stitle{font-weight:var(--_k-weight-label);line-height:var(--kw-line);color:var(--_k-fg);text-align:left}
|
|
20314
20633
|
.knobs-ui button.knobs-wt-stitle{cursor:default;text-decoration:underline;text-decoration-color:transparent;text-underline-offset:3px;transition:text-decoration-color calc(var(--_k-hover-out) * 1ms)}
|
|
20315
20634
|
.knobs-ui :is(.knobs-wt-srow[data-go]:hover button.knobs-wt-stitle,button.knobs-wt-stitle:focus-visible){text-decoration-color:color-mix(in oklch,currentColor 45%,transparent);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
20316
|
-
.knobs-ui .knobs-wt-ssummary{margin:0;max-width:var(--
|
|
20635
|
+
.knobs-ui .knobs-wt-ssummary{margin:0;max-width:var(--kw-measure);color:var(--_k-fg-2)}
|
|
20317
20636
|
.knobs-wt-smeta{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 12px;font:var(--_k-weight) var(--_k-type-small)/var(--kw-line) var(--_k-mono);font-variant-numeric:tabular-nums;color:var(--_k-fg-3)}
|
|
20318
20637
|
.knobs-wt-delta{display:inline-flex;gap:6px}
|
|
20319
20638
|
.knobs-wt-add{color:var(--_k-diff-add)}
|
|
@@ -20529,7 +20848,7 @@ async function mountCompare(host, m, look, alive, onEvent) {
|
|
|
20529
20848
|
before: side(before, "Before"),
|
|
20530
20849
|
after: side(after, "After"),
|
|
20531
20850
|
mode,
|
|
20532
|
-
timeline:
|
|
20851
|
+
timeline: mediaKind(before.src) === "video" || mediaKind(after.src) === "video",
|
|
20533
20852
|
lazy: false,
|
|
20534
20853
|
...aspect ? { aspect } : {},
|
|
20535
20854
|
...m.strip ? { strip: true } : {},
|
|
@@ -20653,7 +20972,13 @@ async function mountVideo(host, m, look, alive, onEvent) {
|
|
|
20653
20972
|
...m.captions ? { captions: m.captions } : {},
|
|
20654
20973
|
...aspect ? { aspect } : {},
|
|
20655
20974
|
...m.label ? { label: m.label } : {},
|
|
20656
|
-
...m.chapters?.length ? {
|
|
20975
|
+
...m.chapters?.length ? {
|
|
20976
|
+
chapters: m.chapters.map((c) => ({
|
|
20977
|
+
label: c.label,
|
|
20978
|
+
start: c.start,
|
|
20979
|
+
...c.id ? { id: c.id } : {}
|
|
20980
|
+
}))
|
|
20981
|
+
} : {},
|
|
20657
20982
|
onPlay: (on, time) => {
|
|
20658
20983
|
marking?.kick();
|
|
20659
20984
|
onEvent?.({ what: on ? "play" : "pause", time: round2(time) });
|
|
@@ -20886,12 +21211,39 @@ function mountProse(host, m) {
|
|
|
20886
21211
|
host.append(el);
|
|
20887
21212
|
return { dispose: () => el.remove() };
|
|
20888
21213
|
}
|
|
21214
|
+
function mountFacts(host, m) {
|
|
21215
|
+
const box = renderFacts(host.ownerDocument, m.groups);
|
|
21216
|
+
host.append(box);
|
|
21217
|
+
return { dispose: () => box.remove() };
|
|
21218
|
+
}
|
|
21219
|
+
function mountDecision(host, m, slot) {
|
|
21220
|
+
const root = div(host.ownerDocument, "knobs-wt-options");
|
|
21221
|
+
const slots = [];
|
|
21222
|
+
const tiles = m.options.map((o) => optionTile(host.ownerDocument, o, {
|
|
21223
|
+
media: (tile) => {
|
|
21224
|
+
if (o.media)
|
|
21225
|
+
slots.push(slot(tile, o.media));
|
|
21226
|
+
}
|
|
21227
|
+
}).tile);
|
|
21228
|
+
root.append(...tiles);
|
|
21229
|
+
host.append(root);
|
|
21230
|
+
const unlayout = layoutOptions(root, tiles);
|
|
21231
|
+
return {
|
|
21232
|
+
pause: () => slots.forEach((s) => s.pause()),
|
|
21233
|
+
dispose: () => {
|
|
21234
|
+
unlayout();
|
|
21235
|
+
slots.forEach((s) => s.dispose());
|
|
21236
|
+
root.remove();
|
|
21237
|
+
}
|
|
21238
|
+
};
|
|
21239
|
+
}
|
|
20889
21240
|
var HEAVY = new Set(["compare", "video", "diff", "mount"]);
|
|
20890
21241
|
var RELEASES = new Set(["compare", "video"]);
|
|
20891
|
-
var labelOf2 = (m) => ("label" in m) && m.kind !== "metric" ? m.label : undefined;
|
|
20892
|
-
function mediaSlot(container,
|
|
21242
|
+
var labelOf2 = (m) => m.kind === "facts" ? m.title : ("label" in m) && m.kind !== "metric" ? m.label : undefined;
|
|
21243
|
+
function mediaSlot(container, given) {
|
|
20893
21244
|
const doc = container.ownerDocument;
|
|
20894
21245
|
injectWalkthroughStyles(doc);
|
|
21246
|
+
const options = reviewLook(given, container);
|
|
20895
21247
|
const look = {
|
|
20896
21248
|
...options.preset ? { preset: options.preset } : {},
|
|
20897
21249
|
...options.theme ? { theme: options.theme } : {}
|
|
@@ -20901,7 +21253,7 @@ function mediaSlot(container, options) {
|
|
|
20901
21253
|
root.dataset.kind = m.kind;
|
|
20902
21254
|
applyLook2(root, options);
|
|
20903
21255
|
const label = labelOf2(m);
|
|
20904
|
-
if (label && (m.kind === "video" || m.kind === "image" || m.kind === "code" || m.kind === "terminal")) {
|
|
21256
|
+
if (label && (m.kind === "video" || m.kind === "image" || m.kind === "code" || m.kind === "terminal" || m.kind === "facts")) {
|
|
20905
21257
|
const l = doc.createElement("span");
|
|
20906
21258
|
l.className = "knobs-wt-slot-label";
|
|
20907
21259
|
l.textContent = label;
|
|
@@ -20981,6 +21333,16 @@ function mediaSlot(container, options) {
|
|
|
20981
21333
|
return done(mountStack(host, m, options.steps, options.findPR));
|
|
20982
21334
|
case "table":
|
|
20983
21335
|
return done(mountTable(host, m, options.steps));
|
|
21336
|
+
case "facts":
|
|
21337
|
+
return done(mountFacts(host, m));
|
|
21338
|
+
case "decision":
|
|
21339
|
+
return done(mountDecision(host, m, (tile, media) => mediaSlot(tile, {
|
|
21340
|
+
...look,
|
|
21341
|
+
media,
|
|
21342
|
+
...options.lazy === false ? { lazy: false } : {},
|
|
21343
|
+
...options.steps ? { steps: options.steps } : {},
|
|
21344
|
+
...options.onEvent ? { onEvent: options.onEvent } : {}
|
|
21345
|
+
})));
|
|
20984
21346
|
case "mount": {
|
|
20985
21347
|
const cleanup = m.mount(host);
|
|
20986
21348
|
return done({ dispose: () => cleanup ? cleanup() : host.textContent = "" });
|
|
@@ -21035,14 +21397,14 @@ function mediaSlot(container, options) {
|
|
|
21035
21397
|
var CHECK4 = icon("check", { small: true });
|
|
21036
21398
|
|
|
21037
21399
|
// src/ui/walkthrough.ts
|
|
21038
|
-
var
|
|
21400
|
+
var el4 = (doc, tag, cls) => {
|
|
21039
21401
|
const n = doc.createElement(tag);
|
|
21040
21402
|
if (cls)
|
|
21041
21403
|
n.className = cls;
|
|
21042
21404
|
return n;
|
|
21043
21405
|
};
|
|
21044
21406
|
var prose2 = (doc, tag, cls, text) => {
|
|
21045
|
-
const n =
|
|
21407
|
+
const n = el4(doc, tag, cls);
|
|
21046
21408
|
n.innerHTML = inlineMarkdown(text);
|
|
21047
21409
|
return n;
|
|
21048
21410
|
};
|
|
@@ -21067,9 +21429,10 @@ var DOCK_GAP = 12;
|
|
|
21067
21429
|
var CALM2 = springMotion(SPRINGS.calm);
|
|
21068
21430
|
var SMOOTH = springMotion(SPRINGS.smooth);
|
|
21069
21431
|
var BACK_MS = 4500;
|
|
21070
|
-
function tasteTest(container,
|
|
21432
|
+
function tasteTest(container, given) {
|
|
21071
21433
|
const doc = container.ownerDocument;
|
|
21072
21434
|
injectWalkthroughStyles(doc);
|
|
21435
|
+
const options = reviewLook(given, container);
|
|
21073
21436
|
const look = lookOf2(options);
|
|
21074
21437
|
const list = options.options.map((o, i) => ("letter" in o) ? o : {
|
|
21075
21438
|
id: o.id ?? String.fromCharCode(97 + i),
|
|
@@ -21077,66 +21440,55 @@ function tasteTest(container, options) {
|
|
|
21077
21440
|
letter: String.fromCharCode(65 + i),
|
|
21078
21441
|
...o.caption ? { caption: o.caption } : {},
|
|
21079
21442
|
...o.media !== undefined ? { media: fillMedia(o.media) } : {},
|
|
21080
|
-
|
|
21443
|
+
...o.facts ? { facts: fillFacts(o.facts) } : {},
|
|
21444
|
+
suggested: o.suggested === true || o.recommended === true
|
|
21081
21445
|
});
|
|
21082
21446
|
const pickable = options.pickable !== false;
|
|
21083
21447
|
let picked = pickable ? options.picked ?? null : null;
|
|
21084
|
-
const root =
|
|
21448
|
+
const root = el4(doc, "div", "knobs-wt-options");
|
|
21085
21449
|
if (pickable) {
|
|
21086
21450
|
root.setAttribute("role", "radiogroup");
|
|
21087
21451
|
root.setAttribute("aria-label", "Options");
|
|
21088
21452
|
} else
|
|
21089
21453
|
root.dataset.static = "";
|
|
21090
|
-
root.style.setProperty("--kw-n", String(Math.min(list.length, 3)));
|
|
21091
21454
|
if (!container.closest(".knobs-ui"))
|
|
21092
21455
|
applyLook2(root, options);
|
|
21093
21456
|
const slots = [];
|
|
21094
21457
|
const tiles = list.map((o, i) => {
|
|
21095
|
-
const
|
|
21096
|
-
|
|
21097
|
-
|
|
21098
|
-
|
|
21099
|
-
|
|
21100
|
-
|
|
21101
|
-
|
|
21102
|
-
|
|
21103
|
-
|
|
21104
|
-
|
|
21105
|
-
|
|
21106
|
-
|
|
21107
|
-
|
|
21108
|
-
|
|
21109
|
-
|
|
21110
|
-
|
|
21111
|
-
|
|
21112
|
-
|
|
21113
|
-
s.textContent = "Suggested";
|
|
21114
|
-
pick.append(s);
|
|
21115
|
-
}
|
|
21116
|
-
if (i < 9) {
|
|
21117
|
-
const key = el3(doc, "span", "knobs-wt-key");
|
|
21118
|
-
key.textContent = String(i + 1);
|
|
21119
|
-
pick.append(key);
|
|
21120
|
-
}
|
|
21121
|
-
const check = el3(doc, "span", "knobs-wt-check");
|
|
21122
|
-
check.innerHTML = CHECK4;
|
|
21123
|
-
pick.append(check);
|
|
21124
|
-
pick.addEventListener("click", () => {
|
|
21125
|
-
const next = picked === o.id ? null : o.id;
|
|
21126
|
-
api.pick(next);
|
|
21127
|
-
options.onPick?.(next);
|
|
21458
|
+
const button = pickable ? el4(doc, "button", "knobs-wt-pick") : undefined;
|
|
21459
|
+
const { tile, kicker } = optionTile(doc, {
|
|
21460
|
+
number: `Option ${i + 1}`,
|
|
21461
|
+
title: o.label,
|
|
21462
|
+
...o.caption ? { summary: o.caption } : {},
|
|
21463
|
+
recommended: o.suggested,
|
|
21464
|
+
...o.facts ? { facts: o.facts } : {}
|
|
21465
|
+
}, {
|
|
21466
|
+
...button ? { head: button } : {},
|
|
21467
|
+
media: (t) => {
|
|
21468
|
+
if (o.media)
|
|
21469
|
+
slots.push(mediaSlot(t, {
|
|
21470
|
+
...look,
|
|
21471
|
+
media: o.media,
|
|
21472
|
+
...options.lazy === false ? { lazy: false } : {},
|
|
21473
|
+
...options.steps ? { steps: options.steps } : {}
|
|
21474
|
+
}));
|
|
21475
|
+
}
|
|
21128
21476
|
});
|
|
21129
|
-
|
|
21130
|
-
|
|
21131
|
-
|
|
21132
|
-
|
|
21133
|
-
|
|
21134
|
-
|
|
21135
|
-
|
|
21136
|
-
|
|
21137
|
-
|
|
21138
|
-
|
|
21139
|
-
|
|
21477
|
+
const pick = button ?? el4(doc, "button");
|
|
21478
|
+
if (button) {
|
|
21479
|
+
button.type = "button";
|
|
21480
|
+
button.setAttribute("role", "radio");
|
|
21481
|
+
if (i < 9)
|
|
21482
|
+
button.setAttribute("aria-keyshortcuts", String(i + 1));
|
|
21483
|
+
const check = el4(doc, "span", "knobs-wt-check");
|
|
21484
|
+
check.innerHTML = CHECK4;
|
|
21485
|
+
kicker.append(check);
|
|
21486
|
+
button.addEventListener("click", () => {
|
|
21487
|
+
const next = picked === o.id ? null : o.id;
|
|
21488
|
+
api.pick(next);
|
|
21489
|
+
options.onPick?.(next);
|
|
21490
|
+
});
|
|
21491
|
+
}
|
|
21140
21492
|
root.append(tile);
|
|
21141
21493
|
return { tile, pick };
|
|
21142
21494
|
});
|
|
@@ -21150,6 +21502,7 @@ function tasteTest(container, options) {
|
|
|
21150
21502
|
};
|
|
21151
21503
|
sync();
|
|
21152
21504
|
container.append(root);
|
|
21505
|
+
const unlayout = layoutOptions(root, tiles.map((t) => t.tile));
|
|
21153
21506
|
const api = {
|
|
21154
21507
|
element: root,
|
|
21155
21508
|
get picked() {
|
|
@@ -21162,6 +21515,7 @@ function tasteTest(container, options) {
|
|
|
21162
21515
|
},
|
|
21163
21516
|
pause: () => slots.forEach((s) => s.pause()),
|
|
21164
21517
|
dispose() {
|
|
21518
|
+
unlayout();
|
|
21165
21519
|
slots.forEach((s) => s.dispose());
|
|
21166
21520
|
root.remove();
|
|
21167
21521
|
}
|
|
@@ -21178,13 +21532,14 @@ function scrollerOf(el) {
|
|
|
21178
21532
|
}
|
|
21179
21533
|
return doc.scrollingElement ?? doc.documentElement;
|
|
21180
21534
|
}
|
|
21181
|
-
function walkthrough(container,
|
|
21535
|
+
function walkthrough(container, given) {
|
|
21182
21536
|
const doc = container.ownerDocument;
|
|
21183
21537
|
const win = doc.defaultView;
|
|
21184
21538
|
injectWalkthroughStyles(doc);
|
|
21539
|
+
const options = reviewLook(given, container);
|
|
21185
21540
|
const look = lookOf2(options);
|
|
21186
21541
|
const storage = options.storage === undefined ? browserStorage() : options.storage;
|
|
21187
|
-
const root =
|
|
21542
|
+
const root = el4(doc, "div", "knobs-walkthrough");
|
|
21188
21543
|
applyLook2(root, options);
|
|
21189
21544
|
container.append(root);
|
|
21190
21545
|
let page = normalizeWalkthrough(options.doc);
|
|
@@ -21397,7 +21752,7 @@ function walkthrough(container, options) {
|
|
|
21397
21752
|
return true;
|
|
21398
21753
|
return false;
|
|
21399
21754
|
};
|
|
21400
|
-
const scrim =
|
|
21755
|
+
const scrim = el4(doc, "div", "knobs-wt-scrim");
|
|
21401
21756
|
scrim.addEventListener("click", () => setFull(false));
|
|
21402
21757
|
const fullRect = () => ({
|
|
21403
21758
|
x: DOCK_INSET,
|
|
@@ -21447,7 +21802,7 @@ function walkthrough(container, options) {
|
|
|
21447
21802
|
}
|
|
21448
21803
|
if (!full) {
|
|
21449
21804
|
const r = v.card.getBoundingClientRect();
|
|
21450
|
-
const hold =
|
|
21805
|
+
const hold = el4(doc, "div", "knobs-wt-hold-card");
|
|
21451
21806
|
hold.style.height = `${r.height}px`;
|
|
21452
21807
|
v.card.before(hold);
|
|
21453
21808
|
v.card.dataset.full = "";
|
|
@@ -21597,7 +21952,7 @@ function walkthrough(container, options) {
|
|
|
21597
21952
|
toast("Couldn't reach the microphone", { tone: "warn", look, doc });
|
|
21598
21953
|
}
|
|
21599
21954
|
};
|
|
21600
|
-
const backPill =
|
|
21955
|
+
const backPill = el4(doc, "button", "knobs-wt-back");
|
|
21601
21956
|
backPill.type = "button";
|
|
21602
21957
|
let backTo = null;
|
|
21603
21958
|
let backTimer = 0;
|
|
@@ -21617,7 +21972,7 @@ function walkthrough(container, options) {
|
|
|
21617
21972
|
return;
|
|
21618
21973
|
backTo = key;
|
|
21619
21974
|
backPill.innerHTML = ICON.back;
|
|
21620
|
-
const words =
|
|
21975
|
+
const words = el4(doc, "span", "knobs-wt-back-label");
|
|
21621
21976
|
words.textContent = `Back to ${plainMarkdown(v.step.title)}`;
|
|
21622
21977
|
backPill.append(words);
|
|
21623
21978
|
backPill.dataset.on = "";
|
|
@@ -21625,16 +21980,16 @@ function walkthrough(container, options) {
|
|
|
21625
21980
|
backTimer = win.setTimeout(hideBack, BACK_MS);
|
|
21626
21981
|
};
|
|
21627
21982
|
const bar = (() => {
|
|
21628
|
-
const dock =
|
|
21629
|
-
const box =
|
|
21630
|
-
const on =
|
|
21631
|
-
const row =
|
|
21632
|
-
const input =
|
|
21983
|
+
const dock = el4(doc, "div", "knobs-wt-dock");
|
|
21984
|
+
const box = el4(doc, "div", "knobs-wt-notebar");
|
|
21985
|
+
const on = el4(doc, "div", "knobs-wt-on");
|
|
21986
|
+
const row = el4(doc, "div", "knobs-wt-row");
|
|
21987
|
+
const input = el4(doc, "textarea", "knobs-wt-input");
|
|
21633
21988
|
input.rows = 1;
|
|
21634
21989
|
input.setAttribute("aria-label", "Note");
|
|
21635
|
-
const attach =
|
|
21990
|
+
const attach = el4(doc, "div", "knobs-wt-attach");
|
|
21636
21991
|
const btn = (icon, label, run) => {
|
|
21637
|
-
const b =
|
|
21992
|
+
const b = el4(doc, "button", "knobs-wt-tool");
|
|
21638
21993
|
b.type = "button";
|
|
21639
21994
|
b.innerHTML = icon;
|
|
21640
21995
|
b.setAttribute("aria-label", label);
|
|
@@ -21645,17 +22000,17 @@ function walkthrough(container, options) {
|
|
|
21645
22000
|
const fullBtn = btn(ICON.full, "Full window · f", () => setFull(!full));
|
|
21646
22001
|
const penBtn = btn(ICON.pen, "Draw on the media", () => setInk(!ink.on));
|
|
21647
22002
|
const micBtn = btn(ICON.mic, "Record a voice note", () => void toggleVoice());
|
|
21648
|
-
const micTime =
|
|
22003
|
+
const micTime = el4(doc, "span", "knobs-wt-rec");
|
|
21649
22004
|
micBtn.append(micTime);
|
|
21650
22005
|
const sendBtn = btn(ICON.send, "Send · Enter", () => send());
|
|
21651
22006
|
sendBtn.dataset.send = "";
|
|
21652
22007
|
row.append(input, attach, fullBtn, penBtn, micBtn, sendBtn);
|
|
21653
22008
|
box.append(on, row);
|
|
21654
|
-
const launch =
|
|
22009
|
+
const launch = el4(doc, "button", "knobs-wt-launch");
|
|
21655
22010
|
launch.type = "button";
|
|
21656
22011
|
launch.innerHTML = ICON.pen;
|
|
21657
22012
|
launch.append("Note");
|
|
21658
|
-
const kbd =
|
|
22013
|
+
const kbd = el4(doc, "kbd");
|
|
21659
22014
|
kbd.textContent = "n";
|
|
21660
22015
|
launch.append(kbd);
|
|
21661
22016
|
launch.setAttribute("aria-label", "Write a note (n)");
|
|
@@ -21707,9 +22062,9 @@ function walkthrough(container, options) {
|
|
|
21707
22062
|
}
|
|
21708
22063
|
});
|
|
21709
22064
|
const chip = (text, clear) => {
|
|
21710
|
-
const c =
|
|
22065
|
+
const c = el4(doc, "span", "knobs-wt-chip-a");
|
|
21711
22066
|
c.append(text);
|
|
21712
|
-
const x =
|
|
22067
|
+
const x = el4(doc, "button", "knobs-wt-x");
|
|
21713
22068
|
x.type = "button";
|
|
21714
22069
|
x.innerHTML = ICON.close;
|
|
21715
22070
|
x.setAttribute("aria-label", `Remove ${text}`);
|
|
@@ -21858,9 +22213,9 @@ function walkthrough(container, options) {
|
|
|
21858
22213
|
}
|
|
21859
22214
|
};
|
|
21860
22215
|
const renderNote = (v, n, entering = false) => {
|
|
21861
|
-
const row =
|
|
22216
|
+
const row = el4(doc, "div", "knobs-wt-n");
|
|
21862
22217
|
row.dataset.id = n.id;
|
|
21863
|
-
const meta =
|
|
22218
|
+
const meta = el4(doc, "span", "knobs-wt-n-meta");
|
|
21864
22219
|
const m = n.context.media;
|
|
21865
22220
|
meta.textContent = m?.time !== undefined ? formatTime(m.time) : m?.file ? m.file.split("/").pop() : "·";
|
|
21866
22221
|
if (m?.time !== undefined) {
|
|
@@ -21876,11 +22231,11 @@ function walkthrough(container, options) {
|
|
|
21876
22231
|
});
|
|
21877
22232
|
}
|
|
21878
22233
|
row.append(meta);
|
|
21879
|
-
const body =
|
|
22234
|
+
const body = el4(doc, "div", "knobs-wt-n-body");
|
|
21880
22235
|
if (n.text)
|
|
21881
22236
|
body.append(prose2(doc, "p", "knobs-wt-n-text", n.text));
|
|
21882
22237
|
if (n.voice) {
|
|
21883
|
-
const a =
|
|
22238
|
+
const a = el4(doc, "audio", "knobs-wt-n-voice");
|
|
21884
22239
|
a.controls = true;
|
|
21885
22240
|
a.preload = "none";
|
|
21886
22241
|
a.src = n.voice.url;
|
|
@@ -21900,7 +22255,7 @@ function walkthrough(container, options) {
|
|
|
21900
22255
|
body.append(thumb);
|
|
21901
22256
|
}
|
|
21902
22257
|
row.append(body);
|
|
21903
|
-
const x =
|
|
22258
|
+
const x = el4(doc, "button", "knobs-wt-x");
|
|
21904
22259
|
x.type = "button";
|
|
21905
22260
|
x.innerHTML = ICON.close;
|
|
21906
22261
|
x.setAttribute("aria-label", "Delete this note");
|
|
@@ -21940,9 +22295,9 @@ function walkthrough(container, options) {
|
|
|
21940
22295
|
row.addEventListener("transitionend", done, { once: true });
|
|
21941
22296
|
win.setTimeout(done, 600);
|
|
21942
22297
|
};
|
|
21943
|
-
const metaLine = (cls) =>
|
|
22298
|
+
const metaLine = (cls) => el4(doc, "div", `knobs-wt-metaline ${cls}`);
|
|
21944
22299
|
const metaText = (line, text, cls) => {
|
|
21945
|
-
const s =
|
|
22300
|
+
const s = el4(doc, "span", cls);
|
|
21946
22301
|
s.textContent = text;
|
|
21947
22302
|
line.append(s);
|
|
21948
22303
|
return s;
|
|
@@ -21956,7 +22311,7 @@ function walkthrough(container, options) {
|
|
|
21956
22311
|
line.append(chip);
|
|
21957
22312
|
return chip;
|
|
21958
22313
|
}
|
|
21959
|
-
const a =
|
|
22314
|
+
const a = el4(doc, "a");
|
|
21960
22315
|
a.href = href;
|
|
21961
22316
|
a.target = "_blank";
|
|
21962
22317
|
a.rel = "noreferrer";
|
|
@@ -21966,9 +22321,9 @@ function walkthrough(container, options) {
|
|
|
21966
22321
|
return a;
|
|
21967
22322
|
};
|
|
21968
22323
|
const feedItem = (f, now) => {
|
|
21969
|
-
const li =
|
|
22324
|
+
const li = el4(doc, "li", "knobs-wt-fi");
|
|
21970
22325
|
li.dataset.kind = f.kind;
|
|
21971
|
-
const q =
|
|
22326
|
+
const q = el4(doc, "div", "knobs-wt-fi-q");
|
|
21972
22327
|
const meta = metaLine("knobs-wt-fi-meta");
|
|
21973
22328
|
if (f.at !== null) {
|
|
21974
22329
|
const when = metaText(meta, ago(f.at, now));
|
|
@@ -21977,7 +22332,7 @@ function walkthrough(container, options) {
|
|
|
21977
22332
|
const step = f.step ? page.steps.find((s) => s.key === f.step) : undefined;
|
|
21978
22333
|
if (step) {
|
|
21979
22334
|
const section = page.sections.find((s) => s.id === step.section);
|
|
21980
|
-
const b =
|
|
22335
|
+
const b = el4(doc, "button", "knobs-wt-fi-step");
|
|
21981
22336
|
b.type = "button";
|
|
21982
22337
|
b.textContent = section?.link?.label ?? plainMarkdown(step.title);
|
|
21983
22338
|
b.title = `Go to “${plainMarkdown(step.title)}”`;
|
|
@@ -21999,19 +22354,19 @@ function walkthrough(container, options) {
|
|
|
21999
22354
|
q.append(prose2(doc, "p", "knobs-wt-fi-text", f.text));
|
|
22000
22355
|
li.append(q);
|
|
22001
22356
|
if (f.q || f.trail.length) {
|
|
22002
|
-
const a =
|
|
22357
|
+
const a = el4(doc, "div", "knobs-wt-fi-a");
|
|
22003
22358
|
if (f.q)
|
|
22004
22359
|
a.append(prose2(doc, "div", "knobs-wt-fi-answer", f.text));
|
|
22005
22360
|
if (f.trail.length) {
|
|
22006
|
-
const list =
|
|
22361
|
+
const list = el4(doc, "ol", "knobs-wt-trail");
|
|
22007
22362
|
for (const line of f.trail) {
|
|
22008
22363
|
const i = line.indexOf(" · ");
|
|
22009
|
-
const row =
|
|
22010
|
-
const words =
|
|
22364
|
+
const row = el4(doc, "li");
|
|
22365
|
+
const words = el4(doc, "span", "knobs-wt-trail-text");
|
|
22011
22366
|
words.textContent = i < 0 ? line : line.slice(i + 3);
|
|
22012
22367
|
row.append(words);
|
|
22013
22368
|
if (i >= 0) {
|
|
22014
|
-
const at =
|
|
22369
|
+
const at = el4(doc, "span", "knobs-wt-trail-at");
|
|
22015
22370
|
at.textContent = line.slice(0, i);
|
|
22016
22371
|
row.append(at);
|
|
22017
22372
|
}
|
|
@@ -22045,11 +22400,11 @@ function walkthrough(container, options) {
|
|
|
22045
22400
|
root.dataset.rail = options.rail === false ? "none" : options.rail === "right" ? "right" : "left";
|
|
22046
22401
|
root.toggleAttribute("data-cards", options.cards === true);
|
|
22047
22402
|
root.toggleAttribute("data-review", review);
|
|
22048
|
-
const layout =
|
|
22049
|
-
const main =
|
|
22050
|
-
const flow =
|
|
22051
|
-
const head =
|
|
22052
|
-
const h1 =
|
|
22403
|
+
const layout = el4(doc, "div", "knobs-wt-layout");
|
|
22404
|
+
const main = el4(doc, "div", "knobs-wt-main");
|
|
22405
|
+
const flow = el4(doc, "div", "knobs-wt-flow");
|
|
22406
|
+
const head = el4(doc, "header", "knobs-wt-head");
|
|
22407
|
+
const h1 = el4(doc, "h1", "knobs-wt-title");
|
|
22053
22408
|
h1.textContent = page.title;
|
|
22054
22409
|
head.append(h1);
|
|
22055
22410
|
if (page.summary)
|
|
@@ -22057,13 +22412,13 @@ function walkthrough(container, options) {
|
|
|
22057
22412
|
if (page.more)
|
|
22058
22413
|
head.append(more(page.more));
|
|
22059
22414
|
if (page.meta.length) {
|
|
22060
|
-
const meta =
|
|
22415
|
+
const meta = el4(doc, "div", "knobs-wt-meta");
|
|
22061
22416
|
for (const m of page.meta) {
|
|
22062
|
-
const s =
|
|
22417
|
+
const s = el4(doc, "span");
|
|
22063
22418
|
s.append(m.label);
|
|
22064
|
-
const b =
|
|
22419
|
+
const b = el4(doc, "b");
|
|
22065
22420
|
if (m.href) {
|
|
22066
|
-
const a =
|
|
22421
|
+
const a = el4(doc, "a");
|
|
22067
22422
|
a.href = m.href;
|
|
22068
22423
|
a.target = "_blank";
|
|
22069
22424
|
a.rel = "noreferrer";
|
|
@@ -22079,14 +22434,14 @@ function walkthrough(container, options) {
|
|
|
22079
22434
|
flow.append(head);
|
|
22080
22435
|
let feedEl = null;
|
|
22081
22436
|
if (page.feed.length) {
|
|
22082
|
-
feedEl =
|
|
22437
|
+
feedEl = el4(doc, "section", "knobs-wt-feed");
|
|
22083
22438
|
feedEl.id = "wt-latest";
|
|
22084
22439
|
feedEl.setAttribute("aria-label", "Updates and answers");
|
|
22085
|
-
const fh =
|
|
22086
|
-
const h =
|
|
22440
|
+
const fh = el4(doc, "header", "knobs-wt-feed-head");
|
|
22441
|
+
const h = el4(doc, "h2", "knobs-wt-feed-title");
|
|
22087
22442
|
h.textContent = page.feed.some((f) => f.kind === "qa") ? "Updates & Q&A" : "Updates";
|
|
22088
22443
|
fh.append(h);
|
|
22089
|
-
const list =
|
|
22444
|
+
const list = el4(doc, "ol", "knobs-wt-feed-list");
|
|
22090
22445
|
const now = Date.now();
|
|
22091
22446
|
for (const f of page.feed)
|
|
22092
22447
|
list.append(feedItem(f, now));
|
|
@@ -22096,21 +22451,21 @@ function walkthrough(container, options) {
|
|
|
22096
22451
|
let rail = null;
|
|
22097
22452
|
count = fill = tocEl = null;
|
|
22098
22453
|
if (options.rail !== false) {
|
|
22099
|
-
rail =
|
|
22454
|
+
rail = el4(doc, "aside", "knobs-wt-rail");
|
|
22100
22455
|
rail.setAttribute("aria-label", "Contents");
|
|
22101
|
-
tocEl =
|
|
22456
|
+
tocEl = el4(doc, "nav", "knobs-wt-toc");
|
|
22102
22457
|
tocEl.setAttribute("aria-label", "Contents");
|
|
22103
22458
|
if (feedEl) {
|
|
22104
|
-
const group =
|
|
22459
|
+
const group = el4(doc, "div", "knobs-wt-toc-group");
|
|
22105
22460
|
const target = feedEl;
|
|
22106
22461
|
group.append(tocRow("Latest", false, () => target.scrollIntoView({ block: "start", behavior: reduced() ? "auto" : "smooth" })));
|
|
22107
22462
|
tocEl.append(group);
|
|
22108
22463
|
}
|
|
22109
22464
|
if (review) {
|
|
22110
|
-
const progress =
|
|
22111
|
-
count =
|
|
22112
|
-
const meter =
|
|
22113
|
-
fill =
|
|
22465
|
+
const progress = el4(doc, "div", "knobs-wt-progress");
|
|
22466
|
+
count = el4(doc, "div", "knobs-wt-count");
|
|
22467
|
+
const meter = el4(doc, "div", "knobs-wt-bar");
|
|
22468
|
+
fill = el4(doc, "div", "knobs-wt-fill");
|
|
22114
22469
|
meter.append(fill);
|
|
22115
22470
|
progress.append(count, meter);
|
|
22116
22471
|
rail.append(progress);
|
|
@@ -22119,15 +22474,15 @@ function walkthrough(container, options) {
|
|
|
22119
22474
|
if (review)
|
|
22120
22475
|
rail.append(actions(true));
|
|
22121
22476
|
if (review && options.keys !== false) {
|
|
22122
|
-
const keys =
|
|
22477
|
+
const keys = el4(doc, "div", "knobs-wt-keys");
|
|
22123
22478
|
keys.innerHTML = "<kbd>j</kbd> <kbd>k</kbd> steps · <kbd>←</kbd> <kbd>→</kbd> seek · <kbd>f</kbd> full · <kbd>n</kbd> note" + (page.steps.some((s) => s.ask === "pick") ? " · <kbd>1</kbd>–<kbd>9</kbd> pick" : "");
|
|
22124
22479
|
rail.append(keys);
|
|
22125
22480
|
}
|
|
22126
22481
|
}
|
|
22127
22482
|
for (const section of page.sections) {
|
|
22128
|
-
const sec =
|
|
22483
|
+
const sec = el4(doc, "section", "knobs-wt-section");
|
|
22129
22484
|
sec.id = `wt-${section.id}`;
|
|
22130
|
-
const sh =
|
|
22485
|
+
const sh = el4(doc, "header", "knobs-wt-shead");
|
|
22131
22486
|
const status = review ? section.status : undefined;
|
|
22132
22487
|
if (section.kicker || section.link || status) {
|
|
22133
22488
|
const meta = metaLine("knobs-wt-kicker");
|
|
@@ -22141,10 +22496,10 @@ function walkthrough(container, options) {
|
|
|
22141
22496
|
}
|
|
22142
22497
|
sh.append(meta);
|
|
22143
22498
|
}
|
|
22144
|
-
const h2 =
|
|
22499
|
+
const h2 = el4(doc, "h2", "knobs-wt-heading");
|
|
22145
22500
|
h2.innerHTML = inlineMarkdown(section.title);
|
|
22146
22501
|
sh.append(h2);
|
|
22147
|
-
const intro =
|
|
22502
|
+
const intro = el4(doc, "div", "knobs-wt-sintro");
|
|
22148
22503
|
intro.append(sh);
|
|
22149
22504
|
if (section.summary)
|
|
22150
22505
|
intro.append(prose2(doc, "p", "knobs-wt-ssum", section.summary));
|
|
@@ -22153,7 +22508,7 @@ function walkthrough(container, options) {
|
|
|
22153
22508
|
sec.append(intro);
|
|
22154
22509
|
let group = null;
|
|
22155
22510
|
if (tocEl) {
|
|
22156
|
-
group =
|
|
22511
|
+
group = el4(doc, "div", "knobs-wt-toc-group");
|
|
22157
22512
|
const first = section.steps[0]?.key;
|
|
22158
22513
|
group.append(tocRow(plainMarkdown(section.title), false, () => first ? go(first) : undefined, section.id));
|
|
22159
22514
|
tocEl.append(group);
|
|
@@ -22181,7 +22536,8 @@ function walkthrough(container, options) {
|
|
|
22181
22536
|
if (tocEl)
|
|
22182
22537
|
pill = selectionPill(tocEl, {
|
|
22183
22538
|
selected: () => viewOf(current)?.toc ?? null,
|
|
22184
|
-
rows: () => tocRows
|
|
22539
|
+
rows: () => tocRows,
|
|
22540
|
+
spring: SPRINGS.snappy
|
|
22185
22541
|
});
|
|
22186
22542
|
syncProgress();
|
|
22187
22543
|
current = null;
|
|
@@ -22195,8 +22551,8 @@ function walkthrough(container, options) {
|
|
|
22195
22551
|
}));
|
|
22196
22552
|
};
|
|
22197
22553
|
const more = (text) => {
|
|
22198
|
-
const box =
|
|
22199
|
-
const body =
|
|
22554
|
+
const box = el4(doc, "div", "knobs-wt-more");
|
|
22555
|
+
const body = el4(doc, "div", "knobs-wt-more-text");
|
|
22200
22556
|
for (const para of text.split(/\n\s*\n/))
|
|
22201
22557
|
if (para.trim())
|
|
22202
22558
|
body.append(prose2(doc, "p", "", para.trim()));
|
|
@@ -22204,25 +22560,25 @@ function walkthrough(container, options) {
|
|
|
22204
22560
|
return box;
|
|
22205
22561
|
};
|
|
22206
22562
|
const tocRow = (label, sub, run, section) => {
|
|
22207
|
-
const b =
|
|
22563
|
+
const b = el4(doc, "button", sub ? "knobs-wt-toc-row knobs-wt-toc-sub" : "knobs-wt-toc-row");
|
|
22208
22564
|
b.type = "button";
|
|
22209
22565
|
if (section)
|
|
22210
22566
|
b.dataset.section = section;
|
|
22211
|
-
const t =
|
|
22567
|
+
const t = el4(doc, "span", "knobs-wt-toc-label");
|
|
22212
22568
|
t.textContent = label;
|
|
22213
22569
|
b.title = label;
|
|
22214
22570
|
b.append(t);
|
|
22215
22571
|
if (review)
|
|
22216
|
-
b.append(
|
|
22572
|
+
b.append(el4(doc, "span", "knobs-wt-toc-end"));
|
|
22217
22573
|
b.addEventListener("mousedown", (ev) => ev.preventDefault());
|
|
22218
22574
|
b.addEventListener("click", run);
|
|
22219
22575
|
tocRows.push(b);
|
|
22220
22576
|
return b;
|
|
22221
22577
|
};
|
|
22222
22578
|
const actions = (rail) => {
|
|
22223
|
-
const row =
|
|
22579
|
+
const row = el4(doc, "div", "knobs-wt-actions");
|
|
22224
22580
|
const btn = (text, run, primary = false) => {
|
|
22225
|
-
const b =
|
|
22581
|
+
const b = el4(doc, "button", "knobs-wt-btn");
|
|
22226
22582
|
b.type = "button";
|
|
22227
22583
|
b.textContent = text;
|
|
22228
22584
|
if (primary)
|
|
@@ -22260,13 +22616,13 @@ function walkthrough(container, options) {
|
|
|
22260
22616
|
}
|
|
22261
22617
|
};
|
|
22262
22618
|
const stepView = (step, group) => {
|
|
22263
|
-
const card =
|
|
22619
|
+
const card = el4(doc, "article", options.cards ? "knobs-wt-step knobs-surface" : "knobs-wt-step");
|
|
22264
22620
|
card.id = `wt-${step.key.replace("/", "--")}`;
|
|
22265
22621
|
card.tabIndex = -1;
|
|
22266
22622
|
card.dataset.key = step.key;
|
|
22267
22623
|
card.setAttribute("aria-label", plainMarkdown(step.title));
|
|
22268
|
-
const head =
|
|
22269
|
-
const h3 =
|
|
22624
|
+
const head = el4(doc, "header", "knobs-wt-step-head");
|
|
22625
|
+
const h3 = el4(doc, "h3", "knobs-wt-stepTitle");
|
|
22270
22626
|
h3.innerHTML = inlineMarkdown(step.title);
|
|
22271
22627
|
head.append(h3);
|
|
22272
22628
|
if (step.caption)
|
|
@@ -22278,7 +22634,7 @@ function walkthrough(container, options) {
|
|
|
22278
22634
|
const onEvent = (e) => record({ ...e, step: step.key });
|
|
22279
22635
|
let media = null;
|
|
22280
22636
|
if (step.media.length) {
|
|
22281
|
-
media =
|
|
22637
|
+
media = el4(doc, "div", "knobs-wt-media");
|
|
22282
22638
|
media.style.setProperty("--kw-n", String(Math.min(step.media.length, step.columns)));
|
|
22283
22639
|
for (const m of step.media)
|
|
22284
22640
|
slots.push(mediaSlot(media, { ...look, media: m, onEvent, steps: stepLinks, findPR: findPRIn }));
|
|
@@ -22298,7 +22654,7 @@ function walkthrough(container, options) {
|
|
|
22298
22654
|
});
|
|
22299
22655
|
media ??= taste.element;
|
|
22300
22656
|
}
|
|
22301
|
-
const notes =
|
|
22657
|
+
const notes = el4(doc, "div", "knobs-wt-notes");
|
|
22302
22658
|
if (review)
|
|
22303
22659
|
card.append(notes);
|
|
22304
22660
|
card.addEventListener("focusin", () => {
|
|
@@ -22312,7 +22668,7 @@ function walkthrough(container, options) {
|
|
|
22312
22668
|
if (group) {
|
|
22313
22669
|
toc = tocRow(plainMarkdown(step.title), true, () => go(step.key));
|
|
22314
22670
|
if (review) {
|
|
22315
|
-
glyph =
|
|
22671
|
+
glyph = el4(doc, "span", "knobs-wt-toc-glyph");
|
|
22316
22672
|
toc.querySelector(".knobs-wt-toc-end").append(glyph);
|
|
22317
22673
|
}
|
|
22318
22674
|
group.append(toc);
|