@bendyline/squisq 2.7.1 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-WN27GHA3.js → chunk-HEYR3TPL.js} +1 -1
- package/dist/{chunk-FPAS63KT.js → chunk-KIAEJDZB.js} +1502 -10
- package/dist/{chunk-ENNNQIYV.js → chunk-W2GIFNLN.js} +1892 -1796
- package/dist/doc/index.d.ts +675 -3
- package/dist/doc/index.js +74 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +74 -4
- package/dist/narration/index.js +2 -2
- package/package.json +1 -1
|
@@ -2,29 +2,35 @@ import {
|
|
|
2
2
|
SHAPE_NAMES,
|
|
3
3
|
buildChartData,
|
|
4
4
|
buildNarrationScript,
|
|
5
|
+
buildRegistry,
|
|
5
6
|
coerceTemplateParams,
|
|
6
7
|
deriveTemplateInputs,
|
|
8
|
+
expandPersistentLayers,
|
|
7
9
|
extractBodyPlainText,
|
|
8
10
|
extractRichListItems,
|
|
9
11
|
flattenBlocks,
|
|
10
12
|
flattenRenderableBlocks,
|
|
11
13
|
getBlockBodyText,
|
|
12
14
|
getPinnedBlockMeta,
|
|
15
|
+
getThemeFont,
|
|
13
16
|
hasTemplate,
|
|
14
17
|
isDataFence,
|
|
15
18
|
isShapeName,
|
|
16
19
|
lintTemplateParams,
|
|
17
20
|
markdownToDoc,
|
|
21
|
+
materializeBlockLayersWithRuntime,
|
|
18
22
|
nearestName,
|
|
19
23
|
normalizeShapeKind,
|
|
20
24
|
parseDataFence,
|
|
21
25
|
parseNarrationTimingJson,
|
|
22
26
|
parseStandaloneAnnotation,
|
|
27
|
+
placeLayersInRect,
|
|
23
28
|
readCustomThemesFromFrontmatter,
|
|
29
|
+
resolvePersistentLayers,
|
|
24
30
|
templateRegistry,
|
|
25
31
|
writeCustomTemplatesToFrontmatter,
|
|
26
32
|
writeCustomThemesToFrontmatter
|
|
27
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-W2GIFNLN.js";
|
|
28
34
|
import {
|
|
29
35
|
iconMarker
|
|
30
36
|
} from "./chunk-7ZAAICW4.js";
|
|
@@ -53,11 +59,18 @@ import {
|
|
|
53
59
|
FRONTMATTER_CUSTOM_TEMPLATES_KEY,
|
|
54
60
|
FRONTMATTER_CUSTOM_THEMES_KEY,
|
|
55
61
|
hexHueDegrees,
|
|
62
|
+
oklchDarken,
|
|
63
|
+
oklchLighten,
|
|
64
|
+
relativeLuminance,
|
|
56
65
|
resolveFontFamily,
|
|
57
|
-
resolveTheme
|
|
66
|
+
resolveTheme,
|
|
67
|
+
withAlpha
|
|
58
68
|
} from "./chunk-SBAX4ZPO.js";
|
|
59
69
|
import {
|
|
60
70
|
VIEWPORT_PRESETS,
|
|
71
|
+
calculateFontScale,
|
|
72
|
+
createTemplateContext,
|
|
73
|
+
getViewportOrientation,
|
|
61
74
|
isTemplateBlock
|
|
62
75
|
} from "./chunk-BAOV476U.js";
|
|
63
76
|
import {
|
|
@@ -953,7 +966,7 @@ function buildPreviewDoc(doc, options) {
|
|
|
953
966
|
}
|
|
954
967
|
slides.push(slide);
|
|
955
968
|
}
|
|
956
|
-
const unusedImages = allImages.filter((img) => !usedImageSrcs.has(img.src));
|
|
969
|
+
const unusedImages = options?.interleaveImages ?? true ? allImages.filter((img) => !usedImageSrcs.has(img.src)) : [];
|
|
957
970
|
if (unusedImages.length > 0 && slides.length > 0) {
|
|
958
971
|
const interval = Math.max(2, Math.floor(slides.length / (unusedImages.length + 1)));
|
|
959
972
|
let insertOffset = 0;
|
|
@@ -2439,6 +2452,1452 @@ ${varLines}
|
|
|
2439
2452
|
${PAGE_BASE_CSS}`;
|
|
2440
2453
|
}
|
|
2441
2454
|
|
|
2455
|
+
// src/doc/dashboard/dashboardZoom.ts
|
|
2456
|
+
var DASHBOARD_ZOOM_LEVELS = [1, 1.5, 2];
|
|
2457
|
+
function normalizeDashboardZoom(value) {
|
|
2458
|
+
const raw = typeof value === "number" ? value : typeof value === "string" ? Number(value.trim().replace(/%$/, "")) : Number.NaN;
|
|
2459
|
+
if (!Number.isFinite(raw)) return void 0;
|
|
2460
|
+
const multiplier = raw > 3 ? raw / 100 : raw;
|
|
2461
|
+
return DASHBOARD_ZOOM_LEVELS.find((level) => Math.abs(level - multiplier) < 1e-3);
|
|
2462
|
+
}
|
|
2463
|
+
var ZOOM_ELIGIBLE_TEMPLATES = /* @__PURE__ */ new Set([
|
|
2464
|
+
"content",
|
|
2465
|
+
"list",
|
|
2466
|
+
"quote",
|
|
2467
|
+
"definitionCard",
|
|
2468
|
+
"factCard",
|
|
2469
|
+
"dateEvent"
|
|
2470
|
+
]);
|
|
2471
|
+
var ZOOM_200_MAX_CHARS = 160;
|
|
2472
|
+
var ZOOM_150_MAX_CHARS = 360;
|
|
2473
|
+
function desiredCellZoom(candidate) {
|
|
2474
|
+
if (!candidate.template || !ZOOM_ELIGIBLE_TEMPLATES.has(candidate.template)) return 1;
|
|
2475
|
+
if (candidate.textLength < ZOOM_200_MAX_CHARS) return 2;
|
|
2476
|
+
if (candidate.textLength < ZOOM_150_MAX_CHARS) return 1.5;
|
|
2477
|
+
return 1;
|
|
2478
|
+
}
|
|
2479
|
+
function resolveDashboardZooms(candidates, mode) {
|
|
2480
|
+
const levels = candidates.map((candidate) => {
|
|
2481
|
+
if (candidate.explicit !== void 0) return candidate.explicit;
|
|
2482
|
+
return mode === "auto" ? desiredCellZoom(candidate) : 1;
|
|
2483
|
+
});
|
|
2484
|
+
const boostVotes = /* @__PURE__ */ new Map();
|
|
2485
|
+
candidates.forEach((candidate, index) => {
|
|
2486
|
+
const level = levels[index];
|
|
2487
|
+
if (level === 1) return;
|
|
2488
|
+
const weight = candidate.explicit !== void 0 ? 2 : 1;
|
|
2489
|
+
boostVotes.set(level, (boostVotes.get(level) ?? 0) + weight);
|
|
2490
|
+
});
|
|
2491
|
+
if (boostVotes.size <= 1) return levels;
|
|
2492
|
+
let winner = 2;
|
|
2493
|
+
let winnerVotes = -1;
|
|
2494
|
+
for (const [level, votes] of boostVotes) {
|
|
2495
|
+
if (votes > winnerVotes || votes === winnerVotes && level > winner) {
|
|
2496
|
+
winner = level;
|
|
2497
|
+
winnerVotes = votes;
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
return candidates.map((candidate, index) => {
|
|
2501
|
+
if (candidate.explicit !== void 0) return candidate.explicit;
|
|
2502
|
+
return levels[index] === 1 ? 1 : winner;
|
|
2503
|
+
});
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
// src/doc/dashboard/DashboardLayout.ts
|
|
2507
|
+
var MAX_CELLS = 32;
|
|
2508
|
+
var MAX_BLOCK_ASSIGNMENT = 64;
|
|
2509
|
+
var NAME_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
2510
|
+
function parsePercent(value) {
|
|
2511
|
+
if (typeof value === "number") {
|
|
2512
|
+
return Number.isFinite(value) ? value : void 0;
|
|
2513
|
+
}
|
|
2514
|
+
if (typeof value !== "string") return void 0;
|
|
2515
|
+
const trimmed = value.trim().replace(/%$/, "");
|
|
2516
|
+
if (trimmed.length === 0) return void 0;
|
|
2517
|
+
const parsed = Number(trimmed);
|
|
2518
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
2519
|
+
}
|
|
2520
|
+
function formatPercent(value) {
|
|
2521
|
+
const rounded = Math.round(value * 1e3) / 1e3;
|
|
2522
|
+
return `${rounded}%`;
|
|
2523
|
+
}
|
|
2524
|
+
function validateCell(input, path, errors) {
|
|
2525
|
+
if (!input || typeof input !== "object") {
|
|
2526
|
+
errors.push({ path, message: "cell must be an object" });
|
|
2527
|
+
return void 0;
|
|
2528
|
+
}
|
|
2529
|
+
const cell = input;
|
|
2530
|
+
const x = parsePercent(cell.x);
|
|
2531
|
+
const y = parsePercent(cell.y);
|
|
2532
|
+
const width = parsePercent(cell.width);
|
|
2533
|
+
const height = parsePercent(cell.height);
|
|
2534
|
+
if (x === void 0 || x < 0 || x > 100) {
|
|
2535
|
+
errors.push({ path: `${path}.x`, message: "x must be a percent between 0 and 100" });
|
|
2536
|
+
return void 0;
|
|
2537
|
+
}
|
|
2538
|
+
if (y === void 0 || y < 0 || y > 100) {
|
|
2539
|
+
errors.push({ path: `${path}.y`, message: "y must be a percent between 0 and 100" });
|
|
2540
|
+
return void 0;
|
|
2541
|
+
}
|
|
2542
|
+
if (width === void 0 || width <= 0 || width > 100) {
|
|
2543
|
+
errors.push({ path: `${path}.width`, message: "width must be a percent greater than 0" });
|
|
2544
|
+
return void 0;
|
|
2545
|
+
}
|
|
2546
|
+
if (height === void 0 || height <= 0 || height > 100) {
|
|
2547
|
+
errors.push({ path: `${path}.height`, message: "height must be a percent greater than 0" });
|
|
2548
|
+
return void 0;
|
|
2549
|
+
}
|
|
2550
|
+
const out = {
|
|
2551
|
+
x: formatPercent(x),
|
|
2552
|
+
y: formatPercent(y),
|
|
2553
|
+
width: formatPercent(width),
|
|
2554
|
+
height: formatPercent(height)
|
|
2555
|
+
};
|
|
2556
|
+
if (cell.block !== void 0) {
|
|
2557
|
+
const block = typeof cell.block === "string" ? Number(cell.block) : cell.block;
|
|
2558
|
+
if (typeof block !== "number" || !Number.isInteger(block) || block < 1 || block > MAX_BLOCK_ASSIGNMENT) {
|
|
2559
|
+
errors.push({
|
|
2560
|
+
path: `${path}.block`,
|
|
2561
|
+
message: `block must be an integer between 1 and ${MAX_BLOCK_ASSIGNMENT}`
|
|
2562
|
+
});
|
|
2563
|
+
return void 0;
|
|
2564
|
+
}
|
|
2565
|
+
out.block = block;
|
|
2566
|
+
}
|
|
2567
|
+
if (cell.zoom !== void 0) {
|
|
2568
|
+
const zoom = normalizeDashboardZoom(cell.zoom);
|
|
2569
|
+
if (zoom === void 0) {
|
|
2570
|
+
errors.push({
|
|
2571
|
+
path: `${path}.zoom`,
|
|
2572
|
+
message: "zoom must be 1, 1.5, or 2 (or 100/150/200 percent)"
|
|
2573
|
+
});
|
|
2574
|
+
return void 0;
|
|
2575
|
+
}
|
|
2576
|
+
out.zoom = zoom;
|
|
2577
|
+
}
|
|
2578
|
+
return out;
|
|
2579
|
+
}
|
|
2580
|
+
function validateCellArray(input, path, errors) {
|
|
2581
|
+
if (!Array.isArray(input)) {
|
|
2582
|
+
errors.push({ path, message: "must be an array of cells" });
|
|
2583
|
+
return void 0;
|
|
2584
|
+
}
|
|
2585
|
+
if (input.length < 1 || input.length > MAX_CELLS) {
|
|
2586
|
+
errors.push({ path, message: `must contain between 1 and ${MAX_CELLS} cells` });
|
|
2587
|
+
return void 0;
|
|
2588
|
+
}
|
|
2589
|
+
const cells = [];
|
|
2590
|
+
for (let i = 0; i < input.length; i++) {
|
|
2591
|
+
const cell = validateCell(input[i], `${path}[${i}]`, errors);
|
|
2592
|
+
if (!cell) return void 0;
|
|
2593
|
+
cells.push(cell);
|
|
2594
|
+
}
|
|
2595
|
+
return cells;
|
|
2596
|
+
}
|
|
2597
|
+
function validateTitleSlot(input, errors) {
|
|
2598
|
+
if (!input || typeof input !== "object") {
|
|
2599
|
+
errors.push({ path: "titleSlot", message: "titleSlot must be an object" });
|
|
2600
|
+
return void 0;
|
|
2601
|
+
}
|
|
2602
|
+
const slot = input;
|
|
2603
|
+
const placement = slot.placement === "bottom" ? "bottom" : slot.placement === "top" ? "top" : void 0;
|
|
2604
|
+
if (placement === void 0 && slot.placement !== void 0) {
|
|
2605
|
+
errors.push({ path: "titleSlot.placement", message: "placement must be 'top' or 'bottom'" });
|
|
2606
|
+
return void 0;
|
|
2607
|
+
}
|
|
2608
|
+
const height = parsePercent(slot.height);
|
|
2609
|
+
if (height === void 0 || height <= 0 || height > 40) {
|
|
2610
|
+
errors.push({ path: "titleSlot.height", message: "height must be a percent between 0 and 40" });
|
|
2611
|
+
return void 0;
|
|
2612
|
+
}
|
|
2613
|
+
return { placement: placement ?? "top", height: formatPercent(height) };
|
|
2614
|
+
}
|
|
2615
|
+
function validateDashboardLayoutDefinition(input) {
|
|
2616
|
+
const errors = [];
|
|
2617
|
+
if (!input || typeof input !== "object") {
|
|
2618
|
+
return { valid: false, errors: [{ path: "", message: "definition must be an object" }] };
|
|
2619
|
+
}
|
|
2620
|
+
const def = input;
|
|
2621
|
+
const name = typeof def.name === "string" ? def.name.trim().toLowerCase() : "";
|
|
2622
|
+
if (!NAME_PATTERN.test(name)) {
|
|
2623
|
+
errors.push({
|
|
2624
|
+
path: "name",
|
|
2625
|
+
message: "name must be a slug (lowercase letters, digits, hyphens)"
|
|
2626
|
+
});
|
|
2627
|
+
}
|
|
2628
|
+
const label = typeof def.label === "string" ? def.label.trim() : "";
|
|
2629
|
+
if (label.length === 0) {
|
|
2630
|
+
errors.push({ path: "label", message: "label must be a non-empty string" });
|
|
2631
|
+
}
|
|
2632
|
+
const cellsInput = def.cells;
|
|
2633
|
+
let landscape;
|
|
2634
|
+
let portrait;
|
|
2635
|
+
let square;
|
|
2636
|
+
if (!cellsInput || typeof cellsInput !== "object") {
|
|
2637
|
+
errors.push({ path: "cells", message: "cells must be an object with a landscape array" });
|
|
2638
|
+
} else {
|
|
2639
|
+
landscape = validateCellArray(cellsInput.landscape, "cells.landscape", errors);
|
|
2640
|
+
if (cellsInput.portrait !== void 0) {
|
|
2641
|
+
portrait = validateCellArray(cellsInput.portrait, "cells.portrait", errors);
|
|
2642
|
+
}
|
|
2643
|
+
if (cellsInput.square !== void 0) {
|
|
2644
|
+
square = validateCellArray(cellsInput.square, "cells.square", errors);
|
|
2645
|
+
}
|
|
2646
|
+
if (landscape) {
|
|
2647
|
+
if (portrait && portrait.length !== landscape.length) {
|
|
2648
|
+
errors.push({
|
|
2649
|
+
path: "cells.portrait",
|
|
2650
|
+
message: "portrait must define the same number of cells as landscape"
|
|
2651
|
+
});
|
|
2652
|
+
}
|
|
2653
|
+
if (square && square.length !== landscape.length) {
|
|
2654
|
+
errors.push({
|
|
2655
|
+
path: "cells.square",
|
|
2656
|
+
message: "square must define the same number of cells as landscape"
|
|
2657
|
+
});
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
let titleSlot;
|
|
2662
|
+
if (def.titleSlot !== void 0) {
|
|
2663
|
+
titleSlot = validateTitleSlot(def.titleSlot, errors);
|
|
2664
|
+
}
|
|
2665
|
+
if (errors.length > 0 || !landscape) {
|
|
2666
|
+
return { valid: false, errors };
|
|
2667
|
+
}
|
|
2668
|
+
const layout2 = {
|
|
2669
|
+
name,
|
|
2670
|
+
label,
|
|
2671
|
+
cells: {
|
|
2672
|
+
landscape,
|
|
2673
|
+
...portrait ? { portrait } : {},
|
|
2674
|
+
...square ? { square } : {}
|
|
2675
|
+
}
|
|
2676
|
+
};
|
|
2677
|
+
if (typeof def.description === "string" && def.description.trim()) {
|
|
2678
|
+
layout2.description = def.description.trim();
|
|
2679
|
+
}
|
|
2680
|
+
if (titleSlot) layout2.titleSlot = titleSlot;
|
|
2681
|
+
if (def.auto === false) layout2.auto = false;
|
|
2682
|
+
return { valid: true, errors, layout: layout2 };
|
|
2683
|
+
}
|
|
2684
|
+
function layoutCapacity(def) {
|
|
2685
|
+
return def.cells.landscape.length;
|
|
2686
|
+
}
|
|
2687
|
+
function transposeCells(cells) {
|
|
2688
|
+
return cells.map((cell) => ({
|
|
2689
|
+
x: cell.y,
|
|
2690
|
+
y: cell.x,
|
|
2691
|
+
width: cell.height,
|
|
2692
|
+
height: cell.width,
|
|
2693
|
+
...cell.block !== void 0 ? { block: cell.block } : {},
|
|
2694
|
+
...cell.zoom !== void 0 ? { zoom: cell.zoom } : {}
|
|
2695
|
+
}));
|
|
2696
|
+
}
|
|
2697
|
+
function resolveLayoutCells(def, orientation, contentRect) {
|
|
2698
|
+
const cells = orientation === "portrait" ? def.cells.portrait ?? transposeCells(def.cells.landscape) : orientation === "square" ? def.cells.square ?? def.cells.landscape : def.cells.landscape;
|
|
2699
|
+
return cells.map((cell) => {
|
|
2700
|
+
const x = parsePercent(cell.x) ?? 0;
|
|
2701
|
+
const y = parsePercent(cell.y) ?? 0;
|
|
2702
|
+
const width = parsePercent(cell.width) ?? 0;
|
|
2703
|
+
const height = parsePercent(cell.height) ?? 0;
|
|
2704
|
+
const resolved = {
|
|
2705
|
+
rect: {
|
|
2706
|
+
x: contentRect.x + x / 100 * contentRect.width,
|
|
2707
|
+
y: contentRect.y + y / 100 * contentRect.height,
|
|
2708
|
+
width: Math.max(1, width / 100 * contentRect.width),
|
|
2709
|
+
height: Math.max(1, height / 100 * contentRect.height)
|
|
2710
|
+
}
|
|
2711
|
+
};
|
|
2712
|
+
if (cell.block !== void 0) resolved.block = cell.block;
|
|
2713
|
+
const zoom = normalizeDashboardZoom(cell.zoom);
|
|
2714
|
+
if (zoom !== void 0) resolved.zoom = zoom;
|
|
2715
|
+
return resolved;
|
|
2716
|
+
});
|
|
2717
|
+
}
|
|
2718
|
+
|
|
2719
|
+
// src/doc/dashboard/dashboardLayoutsFrontmatter.ts
|
|
2720
|
+
var FRONTMATTER_DASHBOARD_LAYOUTS_KEY = "squisq-dashboard-layouts";
|
|
2721
|
+
var LONG_TO_SHORT = {
|
|
2722
|
+
label: "lb",
|
|
2723
|
+
description: "ds",
|
|
2724
|
+
cells: "ce",
|
|
2725
|
+
landscape: "ls",
|
|
2726
|
+
portrait: "pt",
|
|
2727
|
+
square: "sq",
|
|
2728
|
+
width: "wd",
|
|
2729
|
+
height: "hg",
|
|
2730
|
+
block: "bk",
|
|
2731
|
+
zoom: "zo",
|
|
2732
|
+
titleSlot: "ts",
|
|
2733
|
+
placement: "pl",
|
|
2734
|
+
auto: "au"
|
|
2735
|
+
};
|
|
2736
|
+
var SHORT_TO_LONG = Object.fromEntries(
|
|
2737
|
+
Object.entries(LONG_TO_SHORT).map(([long, short]) => [short, long])
|
|
2738
|
+
);
|
|
2739
|
+
function renameKeys(value, map2) {
|
|
2740
|
+
if (Array.isArray(value)) return value.map((v) => renameKeys(v, map2));
|
|
2741
|
+
if (value && typeof value === "object") {
|
|
2742
|
+
const out = {};
|
|
2743
|
+
for (const [k, v] of Object.entries(value)) {
|
|
2744
|
+
out[map2[k] ?? k] = renameKeys(v, map2);
|
|
2745
|
+
}
|
|
2746
|
+
return out;
|
|
2747
|
+
}
|
|
2748
|
+
return value;
|
|
2749
|
+
}
|
|
2750
|
+
function readDashboardLayoutsFromFrontmatter(frontmatter) {
|
|
2751
|
+
if (!frontmatter) return void 0;
|
|
2752
|
+
const candidates = normalizeCandidates(frontmatter[FRONTMATTER_DASHBOARD_LAYOUTS_KEY]);
|
|
2753
|
+
if (!candidates) return void 0;
|
|
2754
|
+
const out = [];
|
|
2755
|
+
for (const entry of candidates) {
|
|
2756
|
+
const result = validateDashboardLayoutDefinition(entry);
|
|
2757
|
+
if (result.layout) out.push(result.layout);
|
|
2758
|
+
}
|
|
2759
|
+
return out.length > 0 ? out : void 0;
|
|
2760
|
+
}
|
|
2761
|
+
function writeDashboardLayoutsToFrontmatter(layouts, options) {
|
|
2762
|
+
if (!layouts || layouts.length === 0) return void 0;
|
|
2763
|
+
const map2 = {};
|
|
2764
|
+
for (const def of layouts) {
|
|
2765
|
+
const { name, ...rest } = def;
|
|
2766
|
+
map2[name] = renameKeys(rest, LONG_TO_SHORT);
|
|
2767
|
+
}
|
|
2768
|
+
return JSON.stringify(map2, null, options?.pretty ? 2 : void 0);
|
|
2769
|
+
}
|
|
2770
|
+
function expandCompactMap(map2) {
|
|
2771
|
+
return Object.entries(map2).map(([name, raw]) => {
|
|
2772
|
+
const expanded = renameKeys(raw && typeof raw === "object" ? raw : {}, SHORT_TO_LONG);
|
|
2773
|
+
return { name, ...expanded };
|
|
2774
|
+
});
|
|
2775
|
+
}
|
|
2776
|
+
function fromParsed(parsed) {
|
|
2777
|
+
if (Array.isArray(parsed)) return parsed;
|
|
2778
|
+
if (parsed && typeof parsed === "object")
|
|
2779
|
+
return expandCompactMap(parsed);
|
|
2780
|
+
return null;
|
|
2781
|
+
}
|
|
2782
|
+
function tryJson(s) {
|
|
2783
|
+
try {
|
|
2784
|
+
return JSON.parse(s);
|
|
2785
|
+
} catch {
|
|
2786
|
+
return void 0;
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
function normalizeCandidates(raw) {
|
|
2790
|
+
if (raw == null) return null;
|
|
2791
|
+
if (Array.isArray(raw)) return raw;
|
|
2792
|
+
if (typeof raw === "object") return expandCompactMap(raw);
|
|
2793
|
+
if (typeof raw !== "string") return null;
|
|
2794
|
+
const parsed = tryJson(raw.trim());
|
|
2795
|
+
if (parsed !== void 0) return fromParsed(parsed);
|
|
2796
|
+
return null;
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
// src/doc/dashboard/builtinDashboardLayouts.ts
|
|
2800
|
+
var GAP = 2;
|
|
2801
|
+
function pct(value) {
|
|
2802
|
+
return `${Math.round(value * 1e3) / 1e3}%`;
|
|
2803
|
+
}
|
|
2804
|
+
function gridCells(cols, rows, gap = GAP) {
|
|
2805
|
+
const cellW = (100 - (cols - 1) * gap) / cols;
|
|
2806
|
+
const cellH = (100 - (rows - 1) * gap) / rows;
|
|
2807
|
+
const cells = [];
|
|
2808
|
+
for (let row = 0; row < rows; row++) {
|
|
2809
|
+
for (let col = 0; col < cols; col++) {
|
|
2810
|
+
cells.push({
|
|
2811
|
+
x: pct(col * (cellW + gap)),
|
|
2812
|
+
y: pct(row * (cellH + gap)),
|
|
2813
|
+
width: pct(cellW),
|
|
2814
|
+
height: pct(cellH)
|
|
2815
|
+
});
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
return cells;
|
|
2819
|
+
}
|
|
2820
|
+
function heroRow(heroWidth, companions, gap = GAP) {
|
|
2821
|
+
const railX = heroWidth + gap;
|
|
2822
|
+
const railW = 100 - railX;
|
|
2823
|
+
const cellH = (100 - (companions - 1) * gap) / companions;
|
|
2824
|
+
const cells = [
|
|
2825
|
+
{ x: "0%", y: "0%", width: pct(heroWidth), height: "100%" }
|
|
2826
|
+
];
|
|
2827
|
+
for (let i = 0; i < companions; i++) {
|
|
2828
|
+
cells.push({
|
|
2829
|
+
x: pct(railX),
|
|
2830
|
+
y: pct(i * (cellH + gap)),
|
|
2831
|
+
width: pct(railW),
|
|
2832
|
+
height: pct(cellH)
|
|
2833
|
+
});
|
|
2834
|
+
}
|
|
2835
|
+
return cells;
|
|
2836
|
+
}
|
|
2837
|
+
function heroBand(heroHeight, below, gap = GAP) {
|
|
2838
|
+
const rowY = heroHeight + gap;
|
|
2839
|
+
const rowH = 100 - rowY;
|
|
2840
|
+
const cellW = (100 - (below - 1) * gap) / below;
|
|
2841
|
+
const cells = [
|
|
2842
|
+
{ x: "0%", y: "0%", width: "100%", height: pct(heroHeight) }
|
|
2843
|
+
];
|
|
2844
|
+
for (let i = 0; i < below; i++) {
|
|
2845
|
+
cells.push({
|
|
2846
|
+
x: pct(i * (cellW + gap)),
|
|
2847
|
+
y: pct(rowY),
|
|
2848
|
+
width: pct(cellW),
|
|
2849
|
+
height: pct(rowH)
|
|
2850
|
+
});
|
|
2851
|
+
}
|
|
2852
|
+
return cells;
|
|
2853
|
+
}
|
|
2854
|
+
function mosaicCells(heroWidth, gap = GAP) {
|
|
2855
|
+
const gridX = heroWidth + gap;
|
|
2856
|
+
const gridW = 100 - gridX;
|
|
2857
|
+
const cellW = (gridW - gap) / 2;
|
|
2858
|
+
const cellH = (100 - gap) / 2;
|
|
2859
|
+
const cells = [
|
|
2860
|
+
{ x: "0%", y: "0%", width: pct(heroWidth), height: "100%" }
|
|
2861
|
+
];
|
|
2862
|
+
for (let row = 0; row < 2; row++) {
|
|
2863
|
+
for (let col = 0; col < 2; col++) {
|
|
2864
|
+
cells.push({
|
|
2865
|
+
x: pct(gridX + col * (cellW + gap)),
|
|
2866
|
+
y: pct(row * (cellH + gap)),
|
|
2867
|
+
width: pct(cellW),
|
|
2868
|
+
height: pct(cellH)
|
|
2869
|
+
});
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
return cells;
|
|
2873
|
+
}
|
|
2874
|
+
var BUILTIN_DASHBOARD_LAYOUTS = Object.freeze([
|
|
2875
|
+
{
|
|
2876
|
+
name: "focus-1",
|
|
2877
|
+
label: "Focus",
|
|
2878
|
+
description: "One block fills the dashboard.",
|
|
2879
|
+
cells: { landscape: [{ x: "0%", y: "0%", width: "100%", height: "100%" }] }
|
|
2880
|
+
},
|
|
2881
|
+
{
|
|
2882
|
+
name: "split-2",
|
|
2883
|
+
label: "Split",
|
|
2884
|
+
description: "Two blocks side by side.",
|
|
2885
|
+
cells: { landscape: gridCells(2, 1) }
|
|
2886
|
+
},
|
|
2887
|
+
{
|
|
2888
|
+
name: "hero-left",
|
|
2889
|
+
label: "Hero + 2",
|
|
2890
|
+
description: "A large lead block with two companions.",
|
|
2891
|
+
cells: {
|
|
2892
|
+
landscape: heroRow(58, 2),
|
|
2893
|
+
portrait: [
|
|
2894
|
+
{ x: "0%", y: "0%", width: "100%", height: pct(46) },
|
|
2895
|
+
{ x: "0%", y: pct(48), width: "100%", height: pct(25) },
|
|
2896
|
+
{ x: "0%", y: pct(75), width: "100%", height: pct(25) }
|
|
2897
|
+
]
|
|
2898
|
+
}
|
|
2899
|
+
},
|
|
2900
|
+
{
|
|
2901
|
+
name: "grid-2x2",
|
|
2902
|
+
label: "Grid 2\xD72",
|
|
2903
|
+
description: "Four blocks in a balanced grid.",
|
|
2904
|
+
cells: { landscape: gridCells(2, 2) }
|
|
2905
|
+
},
|
|
2906
|
+
{
|
|
2907
|
+
name: "hero-top",
|
|
2908
|
+
label: "Hero band",
|
|
2909
|
+
description: "A wide lead band with three blocks below.",
|
|
2910
|
+
cells: {
|
|
2911
|
+
landscape: heroBand(46, 3),
|
|
2912
|
+
portrait: [
|
|
2913
|
+
{ x: "0%", y: "0%", width: "100%", height: pct(40) },
|
|
2914
|
+
{ x: "0%", y: pct(42), width: "100%", height: pct(18) },
|
|
2915
|
+
{ x: "0%", y: pct(62), width: "100%", height: pct(18) },
|
|
2916
|
+
{ x: "0%", y: pct(82), width: "100%", height: pct(18) }
|
|
2917
|
+
]
|
|
2918
|
+
},
|
|
2919
|
+
auto: false
|
|
2920
|
+
},
|
|
2921
|
+
{
|
|
2922
|
+
name: "mosaic-5",
|
|
2923
|
+
label: "Mosaic",
|
|
2924
|
+
description: "A hero block beside a 2\xD72 grid.",
|
|
2925
|
+
cells: {
|
|
2926
|
+
landscape: mosaicCells(58),
|
|
2927
|
+
portrait: [
|
|
2928
|
+
{ x: "0%", y: "0%", width: "100%", height: pct(40) },
|
|
2929
|
+
{ x: "0%", y: pct(42), width: pct(49), height: pct(27) },
|
|
2930
|
+
{ x: pct(51), y: pct(42), width: pct(49), height: pct(27) },
|
|
2931
|
+
{ x: "0%", y: pct(71), width: pct(49), height: pct(29) },
|
|
2932
|
+
{ x: pct(51), y: pct(71), width: pct(49), height: pct(29) }
|
|
2933
|
+
]
|
|
2934
|
+
}
|
|
2935
|
+
},
|
|
2936
|
+
{
|
|
2937
|
+
name: "grid-3x2",
|
|
2938
|
+
label: "Grid 3\xD72",
|
|
2939
|
+
description: "Six blocks in three columns.",
|
|
2940
|
+
cells: { landscape: gridCells(3, 2) }
|
|
2941
|
+
},
|
|
2942
|
+
{
|
|
2943
|
+
name: "grid-3x3",
|
|
2944
|
+
label: "Grid 3\xD73",
|
|
2945
|
+
description: "Nine blocks in a square grid.",
|
|
2946
|
+
cells: { landscape: gridCells(3, 3), square: gridCells(3, 3) }
|
|
2947
|
+
},
|
|
2948
|
+
{
|
|
2949
|
+
name: "grid-4x3",
|
|
2950
|
+
label: "Grid 4\xD73",
|
|
2951
|
+
description: "Twelve blocks in four columns.",
|
|
2952
|
+
cells: { landscape: gridCells(4, 3) }
|
|
2953
|
+
},
|
|
2954
|
+
{
|
|
2955
|
+
name: "grid-4x4",
|
|
2956
|
+
label: "Grid 4\xD74",
|
|
2957
|
+
description: "Sixteen blocks \u2014 the densest wall.",
|
|
2958
|
+
cells: { landscape: gridCells(4, 4) }
|
|
2959
|
+
}
|
|
2960
|
+
]);
|
|
2961
|
+
function summarize(def, custom) {
|
|
2962
|
+
return {
|
|
2963
|
+
id: def.name,
|
|
2964
|
+
label: def.label,
|
|
2965
|
+
...def.description ? { description: def.description } : {},
|
|
2966
|
+
capacity: layoutCapacity(def),
|
|
2967
|
+
custom
|
|
2968
|
+
};
|
|
2969
|
+
}
|
|
2970
|
+
function getDashboardLayoutSummaries() {
|
|
2971
|
+
return BUILTIN_DASHBOARD_LAYOUTS.map((def) => summarize(def, false));
|
|
2972
|
+
}
|
|
2973
|
+
function listDashboardLayouts(doc) {
|
|
2974
|
+
const customs = readDashboardLayoutsFromFrontmatter(doc?.frontmatter) ?? [];
|
|
2975
|
+
const customNames = new Set(customs.map((def) => def.name));
|
|
2976
|
+
return [
|
|
2977
|
+
...customs.map((def) => summarize(def, true)),
|
|
2978
|
+
...BUILTIN_DASHBOARD_LAYOUTS.filter((def) => !customNames.has(def.name)).map(
|
|
2979
|
+
(def) => summarize(def, false)
|
|
2980
|
+
)
|
|
2981
|
+
];
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
// src/doc/dashboard/chooseDashboardLayout.ts
|
|
2985
|
+
var DASHBOARD_AUTO_LAYOUT_ID = "auto";
|
|
2986
|
+
function resolveDashboardLayoutDefinition(id, customLayouts) {
|
|
2987
|
+
const normalized = id.trim().toLowerCase();
|
|
2988
|
+
if (!normalized) return void 0;
|
|
2989
|
+
return customLayouts?.find((def) => def.name === normalized) ?? BUILTIN_DASHBOARD_LAYOUTS.find((def) => def.name === normalized);
|
|
2990
|
+
}
|
|
2991
|
+
function chooseDashboardLayout(blockCount, _orientation, customLayouts) {
|
|
2992
|
+
const pool = [
|
|
2993
|
+
...(customLayouts ?? []).filter((def) => def.auto !== false),
|
|
2994
|
+
...BUILTIN_DASHBOARD_LAYOUTS.filter((def) => def.auto !== false)
|
|
2995
|
+
];
|
|
2996
|
+
const ladder = [...pool].sort((a, b) => layoutCapacity(a) - layoutCapacity(b));
|
|
2997
|
+
const needed = Math.max(1, blockCount);
|
|
2998
|
+
const fit = ladder.find((def) => layoutCapacity(def) >= needed);
|
|
2999
|
+
if (fit) return fit;
|
|
3000
|
+
const max = ladder.reduce((acc, def) => Math.max(acc, layoutCapacity(def)), 0);
|
|
3001
|
+
return ladder.find((def) => layoutCapacity(def) === max) ?? BUILTIN_DASHBOARD_LAYOUTS[0];
|
|
3002
|
+
}
|
|
3003
|
+
|
|
3004
|
+
// src/doc/dashboard/dashboardStyle.ts
|
|
3005
|
+
var DASHBOARD_STYLE_IDS = ["basic", "card", "panel", "accent"];
|
|
3006
|
+
var DEFAULT_DASHBOARD_STYLE = "basic";
|
|
3007
|
+
var DASHBOARD_STYLES = Object.freeze([
|
|
3008
|
+
{
|
|
3009
|
+
id: "basic",
|
|
3010
|
+
label: "Basic",
|
|
3011
|
+
description: "Blocks fill their cells edge to edge."
|
|
3012
|
+
},
|
|
3013
|
+
{
|
|
3014
|
+
id: "card",
|
|
3015
|
+
label: "Cards",
|
|
3016
|
+
description: "Each block sits on a raised, rounded card."
|
|
3017
|
+
},
|
|
3018
|
+
{
|
|
3019
|
+
id: "panel",
|
|
3020
|
+
label: "Panels",
|
|
3021
|
+
description: "Flat outlined panels with an accent rule."
|
|
3022
|
+
},
|
|
3023
|
+
{
|
|
3024
|
+
id: "accent",
|
|
3025
|
+
label: "Accent cards",
|
|
3026
|
+
description: "Cards tinted with the theme's accent colors in rotation."
|
|
3027
|
+
}
|
|
3028
|
+
]);
|
|
3029
|
+
function resolveDashboardStyleId(value) {
|
|
3030
|
+
if (typeof value !== "string") return void 0;
|
|
3031
|
+
const normalized = value.trim().toLowerCase();
|
|
3032
|
+
if (!normalized) return void 0;
|
|
3033
|
+
if (normalized === "none" || normalized === "default" || normalized === "flat") return "basic";
|
|
3034
|
+
if (normalized === "cards") return "card";
|
|
3035
|
+
if (normalized === "panels" || normalized === "outline") return "panel";
|
|
3036
|
+
if (normalized === "accents" || normalized === "accent-cards") return "accent";
|
|
3037
|
+
return DASHBOARD_STYLE_IDS.includes(normalized) ? normalized : void 0;
|
|
3038
|
+
}
|
|
3039
|
+
function clamp(value, min, max) {
|
|
3040
|
+
return Math.min(max, Math.max(min, value));
|
|
3041
|
+
}
|
|
3042
|
+
function isLight(color) {
|
|
3043
|
+
return relativeLuminance(color) > 0.45;
|
|
3044
|
+
}
|
|
3045
|
+
function dashboardCanvasFill(style, theme) {
|
|
3046
|
+
const background = theme.colors.background;
|
|
3047
|
+
if (style === "basic" || style === "panel") return background;
|
|
3048
|
+
return isLight(background) ? oklchDarken(background, 0.05) : oklchLighten(background, 0.04);
|
|
3049
|
+
}
|
|
3050
|
+
function dashboardCellAccent(theme, index) {
|
|
3051
|
+
const schemes = Object.values(theme.colorSchemes ?? {});
|
|
3052
|
+
if (schemes.length === 0) return theme.colors.primary;
|
|
3053
|
+
const scheme = schemes[(index % schemes.length + schemes.length) % schemes.length];
|
|
3054
|
+
return scheme.accent || scheme.text || theme.colors.primary;
|
|
3055
|
+
}
|
|
3056
|
+
function cellMetrics(style, theme, rect) {
|
|
3057
|
+
const minAxis = Math.max(1, Math.min(rect.width, rect.height));
|
|
3058
|
+
const themeRadius = theme.style?.borderRadius ?? 14;
|
|
3059
|
+
if (style === "panel") {
|
|
3060
|
+
return {
|
|
3061
|
+
edge: clamp(minAxis * 0.014, 3, 12),
|
|
3062
|
+
radius: clamp(themeRadius * 0.45, 0, minAxis * 0.05)
|
|
3063
|
+
};
|
|
3064
|
+
}
|
|
3065
|
+
return {
|
|
3066
|
+
// Card styles reserve a wider ring: the elevation is painted inside the
|
|
3067
|
+
// layout cell (a cell renders as its own clipped SVG), so the shadow
|
|
3068
|
+
// needs room to fall without being cut flat at the cell boundary.
|
|
3069
|
+
edge: clamp(minAxis * 0.03, 6, 26),
|
|
3070
|
+
radius: clamp(themeRadius, 6, minAxis * 0.1)
|
|
3071
|
+
};
|
|
3072
|
+
}
|
|
3073
|
+
function radiusPct(radius, box) {
|
|
3074
|
+
if (radius <= 0) return void 0;
|
|
3075
|
+
const x = Math.round(radius / Math.max(1, box.width) * 1e5) / 1e3;
|
|
3076
|
+
const y = Math.round(radius / Math.max(1, box.height) * 1e5) / 1e3;
|
|
3077
|
+
return `${x}% / ${y}%`;
|
|
3078
|
+
}
|
|
3079
|
+
function buildDashboardCellChrome(style, options) {
|
|
3080
|
+
const { theme, rect, index } = options;
|
|
3081
|
+
if (style === "basic") return null;
|
|
3082
|
+
const { edge, radius } = cellMetrics(style, theme, rect);
|
|
3083
|
+
const cardRect = {
|
|
3084
|
+
x: rect.x + edge,
|
|
3085
|
+
y: rect.y + edge,
|
|
3086
|
+
width: Math.max(1, rect.width - edge * 2),
|
|
3087
|
+
height: Math.max(1, rect.height - edge * 2)
|
|
3088
|
+
};
|
|
3089
|
+
const local = {
|
|
3090
|
+
x: edge,
|
|
3091
|
+
y: edge,
|
|
3092
|
+
width: cardRect.width,
|
|
3093
|
+
height: cardRect.height
|
|
3094
|
+
};
|
|
3095
|
+
const minAxis = Math.max(1, Math.min(rect.width, rect.height));
|
|
3096
|
+
const hairline = Math.max(1, Math.round(minAxis * 4e-3));
|
|
3097
|
+
const accent = dashboardCellAccent(theme, index);
|
|
3098
|
+
const layers = [];
|
|
3099
|
+
if (style !== "panel") {
|
|
3100
|
+
const steps = [
|
|
3101
|
+
{ spread: 0.45, dy: 0.5, alpha: 0.05 },
|
|
3102
|
+
{ spread: 0.25, dy: 0.42, alpha: 0.05 },
|
|
3103
|
+
{ spread: 0.08, dy: 0.3, alpha: 0.06 }
|
|
3104
|
+
];
|
|
3105
|
+
steps.forEach((step, stepIndex) => {
|
|
3106
|
+
const spread = edge * step.spread;
|
|
3107
|
+
const dy = edge * step.dy;
|
|
3108
|
+
layers.push({
|
|
3109
|
+
type: "shape",
|
|
3110
|
+
id: `cell-shadow-${stepIndex}`,
|
|
3111
|
+
content: {
|
|
3112
|
+
shape: "rect",
|
|
3113
|
+
fill: withAlpha(theme.colors.text, step.alpha),
|
|
3114
|
+
borderRadius: radius + spread
|
|
3115
|
+
},
|
|
3116
|
+
position: {
|
|
3117
|
+
x: local.x - spread,
|
|
3118
|
+
y: local.y - spread + dy,
|
|
3119
|
+
width: local.width + spread * 2,
|
|
3120
|
+
height: local.height + spread * 2
|
|
3121
|
+
}
|
|
3122
|
+
});
|
|
3123
|
+
});
|
|
3124
|
+
}
|
|
3125
|
+
layers.push({
|
|
3126
|
+
type: "shape",
|
|
3127
|
+
id: "cell-surface",
|
|
3128
|
+
content: {
|
|
3129
|
+
shape: "rect",
|
|
3130
|
+
// The surface stays the theme background; the canvas is what moves
|
|
3131
|
+
// (see `dashboardCanvasFill`), so cards read as raised paper in both
|
|
3132
|
+
// light and dark themes without inventing a palette slot.
|
|
3133
|
+
fill: theme.colors.background,
|
|
3134
|
+
borderRadius: radius
|
|
3135
|
+
},
|
|
3136
|
+
position: { x: local.x, y: local.y, width: local.width, height: local.height }
|
|
3137
|
+
});
|
|
3138
|
+
const overlayLayers = [];
|
|
3139
|
+
if (style === "accent") {
|
|
3140
|
+
overlayLayers.push({
|
|
3141
|
+
type: "shape",
|
|
3142
|
+
id: "cell-accent-wash",
|
|
3143
|
+
content: { shape: "rect", fill: withAlpha(accent, 0.1), borderRadius: radius },
|
|
3144
|
+
position: { x: 0, y: 0, width: "100%", height: "100%" }
|
|
3145
|
+
});
|
|
3146
|
+
}
|
|
3147
|
+
if (style === "accent" || style === "panel") {
|
|
3148
|
+
const barHeight = Math.max(2, Math.round(minAxis * 0.014));
|
|
3149
|
+
overlayLayers.push({
|
|
3150
|
+
type: "shape",
|
|
3151
|
+
id: "cell-accent-bar",
|
|
3152
|
+
content: { shape: "rect", fill: accent },
|
|
3153
|
+
position: { x: 0, y: 0, width: "100%", height: barHeight }
|
|
3154
|
+
});
|
|
3155
|
+
}
|
|
3156
|
+
const strokeWidth = style === "panel" ? hairline : Math.max(1, hairline * 0.6);
|
|
3157
|
+
overlayLayers.push({
|
|
3158
|
+
type: "shape",
|
|
3159
|
+
id: "cell-border",
|
|
3160
|
+
content: {
|
|
3161
|
+
shape: "rect",
|
|
3162
|
+
fill: "none",
|
|
3163
|
+
stroke: style === "panel" ? withAlpha(accent, 0.45) : withAlpha(theme.colors.text, 0.14),
|
|
3164
|
+
strokeWidth,
|
|
3165
|
+
borderRadius: radius
|
|
3166
|
+
},
|
|
3167
|
+
position: {
|
|
3168
|
+
x: strokeWidth / 2,
|
|
3169
|
+
y: strokeWidth / 2,
|
|
3170
|
+
width: Math.max(1, local.width - strokeWidth),
|
|
3171
|
+
height: Math.max(1, local.height - strokeWidth)
|
|
3172
|
+
}
|
|
3173
|
+
});
|
|
3174
|
+
const chrome = {
|
|
3175
|
+
cardRect,
|
|
3176
|
+
// The block fills the card: templates bring their own padding, and a
|
|
3177
|
+
// padded well inside the card reads as a box inside a box.
|
|
3178
|
+
contentRect: cardRect,
|
|
3179
|
+
layers,
|
|
3180
|
+
overlayLayers,
|
|
3181
|
+
radius
|
|
3182
|
+
};
|
|
3183
|
+
const contentRadius = radiusPct(radius, cardRect);
|
|
3184
|
+
if (contentRadius) chrome.contentRadiusPct = contentRadius;
|
|
3185
|
+
return chrome;
|
|
3186
|
+
}
|
|
3187
|
+
function stripsBlockBackdrop(style) {
|
|
3188
|
+
return style !== "basic";
|
|
3189
|
+
}
|
|
3190
|
+
function stripBlockBackdropLayer(layers, theme) {
|
|
3191
|
+
const first = layers[0];
|
|
3192
|
+
if (!first || first.type !== "shape") return [...layers];
|
|
3193
|
+
const { content: content2, position } = first;
|
|
3194
|
+
const fill = typeof content2.fill === "string" ? content2.fill.trim().toLowerCase() : "";
|
|
3195
|
+
const fullBleed = content2.shape === "rect" && (position.x === 0 || position.x === "0%") && (position.y === 0 || position.y === "0%") && position.width === "100%" && position.height === "100%";
|
|
3196
|
+
if (!fullBleed || content2.gradient || content2.pattern) return [...layers];
|
|
3197
|
+
if (fill !== theme.colors.background.trim().toLowerCase()) return [...layers];
|
|
3198
|
+
return layers.slice(1);
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
// src/doc/dashboard/dashboardSettings.ts
|
|
3202
|
+
var DASHBOARD_FRONTMATTER_KEYS = Object.freeze({
|
|
3203
|
+
layout: { canonical: "squisq-dashboard-layout", legacy: "dashboard-layout" },
|
|
3204
|
+
showTitle: { canonical: "squisq-dashboard-title", legacy: "dashboard-title" },
|
|
3205
|
+
zoom: { canonical: "squisq-dashboard-zoom", legacy: "dashboard-zoom" },
|
|
3206
|
+
style: { canonical: "squisq-dashboard-style", legacy: "dashboard-style" }
|
|
3207
|
+
});
|
|
3208
|
+
var DEFAULT_DASHBOARD_SETTINGS = Object.freeze({
|
|
3209
|
+
layout: DASHBOARD_AUTO_LAYOUT_ID,
|
|
3210
|
+
showTitle: true,
|
|
3211
|
+
zoom: "auto",
|
|
3212
|
+
style: DEFAULT_DASHBOARD_STYLE
|
|
3213
|
+
});
|
|
3214
|
+
function readSetting2(frontmatter, keys) {
|
|
3215
|
+
if (!frontmatter) return void 0;
|
|
3216
|
+
return Object.prototype.hasOwnProperty.call(frontmatter, keys.canonical) ? frontmatter[keys.canonical] : frontmatter[keys.legacy];
|
|
3217
|
+
}
|
|
3218
|
+
function resolveBoolean2(value) {
|
|
3219
|
+
if (typeof value === "boolean") return value;
|
|
3220
|
+
if (typeof value !== "string") return void 0;
|
|
3221
|
+
const normalized = value.trim().toLowerCase();
|
|
3222
|
+
if (["true", "yes", "on", "show", "visible"].includes(normalized)) return true;
|
|
3223
|
+
if (["false", "no", "off", "hide", "hidden"].includes(normalized)) return false;
|
|
3224
|
+
return void 0;
|
|
3225
|
+
}
|
|
3226
|
+
function resolveLayoutId(value) {
|
|
3227
|
+
if (typeof value !== "string") return void 0;
|
|
3228
|
+
const normalized = value.trim().toLowerCase();
|
|
3229
|
+
if (!normalized) return void 0;
|
|
3230
|
+
if (normalized === "auto" || normalized === "default") return DASHBOARD_AUTO_LAYOUT_ID;
|
|
3231
|
+
return normalized;
|
|
3232
|
+
}
|
|
3233
|
+
function resolveZoomMode(value) {
|
|
3234
|
+
if (typeof value === "boolean") return value ? "auto" : "off";
|
|
3235
|
+
if (typeof value !== "string") return void 0;
|
|
3236
|
+
const normalized = value.trim().toLowerCase();
|
|
3237
|
+
if (["auto", "on", "true", "yes"].includes(normalized)) return "auto";
|
|
3238
|
+
if (["off", "false", "no", "none", "100", "100%", "1"].includes(normalized)) return "off";
|
|
3239
|
+
return void 0;
|
|
3240
|
+
}
|
|
3241
|
+
function resolveDashboardSettings(frontmatter, overrides = {}) {
|
|
3242
|
+
const resolved = {
|
|
3243
|
+
layout: resolveLayoutId(readSetting2(frontmatter, DASHBOARD_FRONTMATTER_KEYS.layout)) ?? DEFAULT_DASHBOARD_SETTINGS.layout,
|
|
3244
|
+
showTitle: resolveBoolean2(readSetting2(frontmatter, DASHBOARD_FRONTMATTER_KEYS.showTitle)) ?? DEFAULT_DASHBOARD_SETTINGS.showTitle,
|
|
3245
|
+
zoom: resolveZoomMode(readSetting2(frontmatter, DASHBOARD_FRONTMATTER_KEYS.zoom)) ?? DEFAULT_DASHBOARD_SETTINGS.zoom,
|
|
3246
|
+
style: resolveDashboardStyleId(readSetting2(frontmatter, DASHBOARD_FRONTMATTER_KEYS.style)) ?? DEFAULT_DASHBOARD_SETTINGS.style
|
|
3247
|
+
};
|
|
3248
|
+
return {
|
|
3249
|
+
layout: resolveLayoutId(overrides.layout) ?? resolved.layout,
|
|
3250
|
+
showTitle: overrides.showTitle ?? resolved.showTitle,
|
|
3251
|
+
zoom: overrides.zoom ?? resolved.zoom,
|
|
3252
|
+
style: resolveDashboardStyleId(overrides.style) ?? resolved.style
|
|
3253
|
+
};
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3256
|
+
// src/doc/dashboard/materializeDashboard.ts
|
|
3257
|
+
var DEFAULT_TITLE_SLOT = { placement: "top", height: "9%" };
|
|
3258
|
+
var TITLE_CONTENT_GAP = 0.015;
|
|
3259
|
+
var CANVAS_MARGIN = 0.02;
|
|
3260
|
+
function percentNumber(value) {
|
|
3261
|
+
const parsed = Number(value.trim().replace(/%$/, ""));
|
|
3262
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
3263
|
+
}
|
|
3264
|
+
function pctOf(value, total) {
|
|
3265
|
+
return `${Math.round(value / Math.max(1, total) * 1e5) / 1e3}%`;
|
|
3266
|
+
}
|
|
3267
|
+
function rectToPct(rect, viewport) {
|
|
3268
|
+
return {
|
|
3269
|
+
left: pctOf(rect.x, viewport.width),
|
|
3270
|
+
top: pctOf(rect.y, viewport.height),
|
|
3271
|
+
width: pctOf(rect.width, viewport.width),
|
|
3272
|
+
height: pctOf(rect.height, viewport.height)
|
|
3273
|
+
};
|
|
3274
|
+
}
|
|
3275
|
+
function resolveDashboardTitle(doc, provided) {
|
|
3276
|
+
const frontmatterTitle = doc.frontmatter?.title;
|
|
3277
|
+
if (typeof frontmatterTitle === "string" && frontmatterTitle.trim()) {
|
|
3278
|
+
return frontmatterTitle.trim();
|
|
3279
|
+
}
|
|
3280
|
+
return provided?.trim() ?? "";
|
|
3281
|
+
}
|
|
3282
|
+
function dedupeLeadingTitleBlock(candidates, titleText) {
|
|
3283
|
+
if (candidates.length === 0 || !titleText) return candidates;
|
|
3284
|
+
const first = candidates[0];
|
|
3285
|
+
const firstTitle = typeof first.title === "string" ? first.title.trim().toLowerCase() : "";
|
|
3286
|
+
if (!firstTitle || firstTitle !== titleText.trim().toLowerCase()) return candidates;
|
|
3287
|
+
const template = first.template;
|
|
3288
|
+
const titleShaped = template === "title" || template === "sectionHeader" || template === "cover";
|
|
3289
|
+
const hasBody2 = Array.isArray(first.contents) && first.contents.length > 0;
|
|
3290
|
+
return titleShaped || !hasBody2 ? candidates.slice(1) : candidates;
|
|
3291
|
+
}
|
|
3292
|
+
function buildTitleLayers(titleText, bandViewport, slot, theme, canvasViewport, margin) {
|
|
3293
|
+
const context = createTemplateContext(theme, 0, 1, canvasViewport);
|
|
3294
|
+
const canvasFontScale = calculateFontScale(canvasViewport);
|
|
3295
|
+
const fontSize = Math.min(44 * canvasFontScale, bandViewport.height * 0.52);
|
|
3296
|
+
const accentWidth = Math.max(4, Math.round(6 * canvasFontScale));
|
|
3297
|
+
const textX = margin + accentWidth + Math.max(10, Math.round(accentWidth * 2.5));
|
|
3298
|
+
return [
|
|
3299
|
+
{
|
|
3300
|
+
type: "shape",
|
|
3301
|
+
id: "dashboard-title-accent",
|
|
3302
|
+
content: { shape: "rect", fill: theme.colors.primary },
|
|
3303
|
+
position: {
|
|
3304
|
+
x: margin,
|
|
3305
|
+
y: "26%",
|
|
3306
|
+
width: accentWidth,
|
|
3307
|
+
height: "48%"
|
|
3308
|
+
}
|
|
3309
|
+
},
|
|
3310
|
+
{
|
|
3311
|
+
type: "text",
|
|
3312
|
+
id: "dashboard-title-text",
|
|
3313
|
+
content: {
|
|
3314
|
+
text: titleText,
|
|
3315
|
+
style: {
|
|
3316
|
+
fontSize,
|
|
3317
|
+
fontFamily: getThemeFont(context, "title"),
|
|
3318
|
+
fontWeight: "bold",
|
|
3319
|
+
color: theme.colors.text,
|
|
3320
|
+
textAlign: "left",
|
|
3321
|
+
verticalAlign: "middle",
|
|
3322
|
+
maxLines: 1
|
|
3323
|
+
}
|
|
3324
|
+
},
|
|
3325
|
+
position: {
|
|
3326
|
+
x: textX,
|
|
3327
|
+
y: 0,
|
|
3328
|
+
width: Math.max(1, bandViewport.width - textX - margin),
|
|
3329
|
+
height: "100%"
|
|
3330
|
+
}
|
|
3331
|
+
},
|
|
3332
|
+
{
|
|
3333
|
+
// Hairline separating the band from the content area.
|
|
3334
|
+
type: "shape",
|
|
3335
|
+
id: "dashboard-title-rule",
|
|
3336
|
+
content: { shape: "rect", fill: withAlpha(theme.colors.text, 0.14) },
|
|
3337
|
+
position: {
|
|
3338
|
+
x: 0,
|
|
3339
|
+
y: slot.placement === "bottom" ? 0 : bandViewport.height - 2,
|
|
3340
|
+
width: "100%",
|
|
3341
|
+
height: 2
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
];
|
|
3345
|
+
}
|
|
3346
|
+
function materializeDashboard(doc, options = {}) {
|
|
3347
|
+
const theme = options.theme ?? DEFAULT_THEME;
|
|
3348
|
+
const viewport = options.viewport ?? VIEWPORT_PRESETS.landscape;
|
|
3349
|
+
const orientation = getViewportOrientation(viewport);
|
|
3350
|
+
const customLayouts = options.customLayouts ?? readDashboardLayoutsFromFrontmatter(doc.frontmatter);
|
|
3351
|
+
const customTemplates = options.customTemplates ?? doc.customTemplates;
|
|
3352
|
+
const diagnostics = [];
|
|
3353
|
+
const settings = resolveDashboardSettings(doc.frontmatter, {
|
|
3354
|
+
layout: typeof options.layout === "string" ? options.layout : void 0,
|
|
3355
|
+
showTitle: options.showTitle,
|
|
3356
|
+
zoom: options.zoom,
|
|
3357
|
+
...options.style !== void 0 ? { style: options.style } : {}
|
|
3358
|
+
});
|
|
3359
|
+
const style = settings.style;
|
|
3360
|
+
let layoutDef;
|
|
3361
|
+
let layoutSource = "auto";
|
|
3362
|
+
if (options.layout && typeof options.layout === "object") {
|
|
3363
|
+
const validated = validateDashboardLayoutDefinition(options.layout);
|
|
3364
|
+
if (validated.layout) {
|
|
3365
|
+
layoutDef = validated.layout;
|
|
3366
|
+
layoutSource = "option";
|
|
3367
|
+
} else {
|
|
3368
|
+
diagnostics.push({
|
|
3369
|
+
type: "unknown-layout",
|
|
3370
|
+
message: "Inline dashboard layout definition is invalid; falling back to auto.",
|
|
3371
|
+
requestedLayout: options.layout.name
|
|
3372
|
+
});
|
|
3373
|
+
}
|
|
3374
|
+
} else if (settings.layout !== DASHBOARD_AUTO_LAYOUT_ID) {
|
|
3375
|
+
const resolved = resolveDashboardLayoutDefinition(settings.layout, customLayouts);
|
|
3376
|
+
if (resolved) {
|
|
3377
|
+
layoutDef = resolved;
|
|
3378
|
+
layoutSource = typeof options.layout === "string" && options.layout.trim() ? "option" : "frontmatter";
|
|
3379
|
+
} else {
|
|
3380
|
+
diagnostics.push({
|
|
3381
|
+
type: "unknown-layout",
|
|
3382
|
+
message: `Unknown dashboard layout "${settings.layout}"; falling back to auto.`,
|
|
3383
|
+
requestedLayout: settings.layout
|
|
3384
|
+
});
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
const preview = buildPreviewDoc(doc, {
|
|
3388
|
+
documentTitle: options.documentTitle,
|
|
3389
|
+
interleaveImages: false
|
|
3390
|
+
});
|
|
3391
|
+
let candidates = preview.blocks ?? [];
|
|
3392
|
+
if (candidates.length === 0) {
|
|
3393
|
+
diagnostics.push({
|
|
3394
|
+
type: "empty-doc",
|
|
3395
|
+
message: "The document has no renderable blocks; the dashboard has no cells."
|
|
3396
|
+
});
|
|
3397
|
+
}
|
|
3398
|
+
const titleText = settings.showTitle ? resolveDashboardTitle(doc, options.documentTitle) : "";
|
|
3399
|
+
const hasTitleBand = titleText.length > 0;
|
|
3400
|
+
if (hasTitleBand) {
|
|
3401
|
+
candidates = dedupeLeadingTitleBlock(candidates, titleText);
|
|
3402
|
+
}
|
|
3403
|
+
if (!layoutDef) {
|
|
3404
|
+
layoutDef = chooseDashboardLayout(candidates.length, orientation, customLayouts);
|
|
3405
|
+
layoutSource = "auto";
|
|
3406
|
+
}
|
|
3407
|
+
const margin = CANVAS_MARGIN * Math.min(viewport.width, viewport.height);
|
|
3408
|
+
let contentRect = {
|
|
3409
|
+
x: margin,
|
|
3410
|
+
y: margin,
|
|
3411
|
+
width: Math.max(1, viewport.width - margin * 2),
|
|
3412
|
+
height: Math.max(1, viewport.height - margin * 2)
|
|
3413
|
+
};
|
|
3414
|
+
let title2 = null;
|
|
3415
|
+
if (hasTitleBand) {
|
|
3416
|
+
const slot = layoutDef.titleSlot ?? DEFAULT_TITLE_SLOT;
|
|
3417
|
+
const bandPct = Math.min(40, Math.max(2, percentNumber(slot.height)));
|
|
3418
|
+
const bandHeight = bandPct / 100 * viewport.height;
|
|
3419
|
+
const gap = TITLE_CONTENT_GAP * viewport.height;
|
|
3420
|
+
const titleRect = slot.placement === "bottom" ? { x: 0, y: viewport.height - bandHeight, width: viewport.width, height: bandHeight } : { x: 0, y: 0, width: viewport.width, height: bandHeight };
|
|
3421
|
+
contentRect = slot.placement === "bottom" ? {
|
|
3422
|
+
x: margin,
|
|
3423
|
+
y: margin,
|
|
3424
|
+
width: Math.max(1, viewport.width - margin * 2),
|
|
3425
|
+
height: Math.max(1, viewport.height - bandHeight - gap - margin)
|
|
3426
|
+
} : {
|
|
3427
|
+
x: margin,
|
|
3428
|
+
y: bandHeight + gap,
|
|
3429
|
+
width: Math.max(1, viewport.width - margin * 2),
|
|
3430
|
+
height: Math.max(1, viewport.height - bandHeight - gap - margin)
|
|
3431
|
+
};
|
|
3432
|
+
const bandViewport = {
|
|
3433
|
+
width: Math.max(1, Math.round(titleRect.width)),
|
|
3434
|
+
height: Math.max(1, Math.round(titleRect.height)),
|
|
3435
|
+
name: "Dashboard title"
|
|
3436
|
+
};
|
|
3437
|
+
title2 = {
|
|
3438
|
+
text: titleText,
|
|
3439
|
+
rect: titleRect,
|
|
3440
|
+
rectPct: rectToPct(titleRect, viewport),
|
|
3441
|
+
viewport: bandViewport,
|
|
3442
|
+
// The band spans the canvas, but its type starts on the cell area's
|
|
3443
|
+
// left edge so the title and the first column share one margin.
|
|
3444
|
+
layers: buildTitleLayers(titleText, bandViewport, slot, theme, viewport, margin)
|
|
3445
|
+
};
|
|
3446
|
+
}
|
|
3447
|
+
const resolvedCells = resolveLayoutCells(layoutDef, orientation, contentRect);
|
|
3448
|
+
const assignments = [];
|
|
3449
|
+
const explicitlyClaimed = /* @__PURE__ */ new Set();
|
|
3450
|
+
resolvedCells.forEach((cell, cellIndex) => {
|
|
3451
|
+
if (cell.block === void 0) return;
|
|
3452
|
+
const candidateIndex = cell.block - 1;
|
|
3453
|
+
if (candidateIndex < 0 || candidateIndex >= candidates.length) {
|
|
3454
|
+
diagnostics.push({
|
|
3455
|
+
type: "invalid-cell-assignment",
|
|
3456
|
+
message: `Cell ${cellIndex + 1} of layout "${layoutDef.name}" requests block ${cell.block}, but the document has ${candidates.length} block(s); the cell is left empty.`
|
|
3457
|
+
});
|
|
3458
|
+
return;
|
|
3459
|
+
}
|
|
3460
|
+
explicitlyClaimed.add(candidateIndex);
|
|
3461
|
+
assignments.push({ cellIndex, rect: cell.rect, candidateIndex, explicitZoom: cell.zoom });
|
|
3462
|
+
});
|
|
3463
|
+
const filled = /* @__PURE__ */ new Set();
|
|
3464
|
+
let cursor = 0;
|
|
3465
|
+
resolvedCells.forEach((cell, cellIndex) => {
|
|
3466
|
+
if (cell.block !== void 0) return;
|
|
3467
|
+
while (cursor < candidates.length && explicitlyClaimed.has(cursor)) cursor++;
|
|
3468
|
+
if (cursor >= candidates.length) return;
|
|
3469
|
+
filled.add(cursor);
|
|
3470
|
+
assignments.push({
|
|
3471
|
+
cellIndex,
|
|
3472
|
+
rect: cell.rect,
|
|
3473
|
+
candidateIndex: cursor,
|
|
3474
|
+
explicitZoom: cell.zoom
|
|
3475
|
+
});
|
|
3476
|
+
cursor++;
|
|
3477
|
+
});
|
|
3478
|
+
const hidden = [];
|
|
3479
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
3480
|
+
if (!explicitlyClaimed.has(i) && !filled.has(i)) hidden.push(i);
|
|
3481
|
+
}
|
|
3482
|
+
if (hidden.length > 0) {
|
|
3483
|
+
diagnostics.push({
|
|
3484
|
+
type: "overflow",
|
|
3485
|
+
message: `${hidden.length} block(s) exceed the "${layoutDef.name}" layout's ${resolvedCells.length} cell(s) and are not rendered.`,
|
|
3486
|
+
hiddenBlockIds: hidden.map((i) => String(candidates[i].id ?? i))
|
|
3487
|
+
});
|
|
3488
|
+
}
|
|
3489
|
+
const orderedAssignments = assignments.sort((a, b) => a.cellIndex - b.cellIndex);
|
|
3490
|
+
const zooms = resolveDashboardZooms(
|
|
3491
|
+
orderedAssignments.map(({ candidateIndex, explicitZoom }) => {
|
|
3492
|
+
const block = candidates[candidateIndex];
|
|
3493
|
+
const title3 = typeof block.title === "string" ? block.title : "";
|
|
3494
|
+
return {
|
|
3495
|
+
template: resolveTemplateName(block.template ?? ""),
|
|
3496
|
+
textLength: title3.length + getBlockBodyText(block).length,
|
|
3497
|
+
...explicitZoom !== void 0 ? { explicit: explicitZoom } : {}
|
|
3498
|
+
};
|
|
3499
|
+
}),
|
|
3500
|
+
settings.zoom
|
|
3501
|
+
);
|
|
3502
|
+
const registry = customTemplates && customTemplates.length > 0 ? buildRegistry(customTemplates) : templateRegistry;
|
|
3503
|
+
const cells = orderedAssignments.map(
|
|
3504
|
+
({ cellIndex, rect, candidateIndex }, orderIndex) => {
|
|
3505
|
+
const block = candidates[candidateIndex];
|
|
3506
|
+
const zoom = zooms[orderIndex];
|
|
3507
|
+
const chrome = buildDashboardCellChrome(style, { theme, rect, index: cellIndex });
|
|
3508
|
+
const contentRect2 = chrome ? chrome.contentRect : rect;
|
|
3509
|
+
const cellViewport = {
|
|
3510
|
+
width: Math.max(1, Math.round(contentRect2.width)),
|
|
3511
|
+
height: Math.max(1, Math.round(contentRect2.height)),
|
|
3512
|
+
name: `Dashboard cell ${cellIndex + 1}`
|
|
3513
|
+
};
|
|
3514
|
+
const cellContext = createTemplateContext(
|
|
3515
|
+
theme,
|
|
3516
|
+
candidateIndex,
|
|
3517
|
+
candidates.length,
|
|
3518
|
+
cellViewport
|
|
3519
|
+
);
|
|
3520
|
+
if (zoom !== 1) cellContext.fontScale *= zoom;
|
|
3521
|
+
const result = materializeBlockLayersWithRuntime(
|
|
3522
|
+
block,
|
|
3523
|
+
{
|
|
3524
|
+
theme,
|
|
3525
|
+
viewport: cellViewport,
|
|
3526
|
+
persistentLayers: false,
|
|
3527
|
+
blockIndex: candidateIndex,
|
|
3528
|
+
totalBlocks: candidates.length,
|
|
3529
|
+
failureMode: options.failureMode
|
|
3530
|
+
},
|
|
3531
|
+
{ registry, templateContext: cellContext }
|
|
3532
|
+
);
|
|
3533
|
+
const layers = stripsBlockBackdrop(style) ? stripBlockBackdropLayer(result.layers, theme) : result.layers;
|
|
3534
|
+
let frame;
|
|
3535
|
+
if (chrome) {
|
|
3536
|
+
const frameViewport = {
|
|
3537
|
+
width: Math.max(1, Math.round(rect.width)),
|
|
3538
|
+
height: Math.max(1, Math.round(rect.height)),
|
|
3539
|
+
name: `Dashboard cell ${cellIndex + 1} frame`
|
|
3540
|
+
};
|
|
3541
|
+
frame = {
|
|
3542
|
+
rect,
|
|
3543
|
+
rectPct: rectToPct(rect, viewport),
|
|
3544
|
+
viewport: frameViewport,
|
|
3545
|
+
layers: chrome.layers,
|
|
3546
|
+
overlayLayers: chrome.overlayLayers,
|
|
3547
|
+
overlayViewport: {
|
|
3548
|
+
width: cellViewport.width,
|
|
3549
|
+
height: cellViewport.height,
|
|
3550
|
+
name: `Dashboard cell ${cellIndex + 1} overlay`
|
|
3551
|
+
},
|
|
3552
|
+
...chrome.contentRadiusPct ? { contentRadiusPct: chrome.contentRadiusPct } : {}
|
|
3553
|
+
};
|
|
3554
|
+
}
|
|
3555
|
+
return {
|
|
3556
|
+
index: cellIndex,
|
|
3557
|
+
block,
|
|
3558
|
+
blockIndex: candidateIndex,
|
|
3559
|
+
layers,
|
|
3560
|
+
rect: contentRect2,
|
|
3561
|
+
rectPct: rectToPct(contentRect2, viewport),
|
|
3562
|
+
...frame ? { frame } : {},
|
|
3563
|
+
viewport: cellViewport,
|
|
3564
|
+
zoom,
|
|
3565
|
+
source: result.source,
|
|
3566
|
+
...result.diagnostic ? { diagnostic: result.diagnostic } : {}
|
|
3567
|
+
};
|
|
3568
|
+
}
|
|
3569
|
+
);
|
|
3570
|
+
const persistent = resolvePersistentLayers(doc, theme);
|
|
3571
|
+
const canvasFill = dashboardCanvasFill(style, theme);
|
|
3572
|
+
const backdrop = {
|
|
3573
|
+
fill: canvasFill,
|
|
3574
|
+
bottomLayers: [
|
|
3575
|
+
{
|
|
3576
|
+
type: "shape",
|
|
3577
|
+
id: "dashboard-backdrop",
|
|
3578
|
+
content: { shape: "rect", fill: canvasFill },
|
|
3579
|
+
position: { x: 0, y: 0, width: "100%", height: "100%" }
|
|
3580
|
+
},
|
|
3581
|
+
...expandPersistentLayers(persistent?.bottomLayers, theme)
|
|
3582
|
+
],
|
|
3583
|
+
topLayers: expandPersistentLayers(persistent?.topLayers, theme)
|
|
3584
|
+
};
|
|
3585
|
+
return {
|
|
3586
|
+
layout: layoutDef,
|
|
3587
|
+
layoutSource,
|
|
3588
|
+
style,
|
|
3589
|
+
viewport,
|
|
3590
|
+
cells,
|
|
3591
|
+
title: title2,
|
|
3592
|
+
backdrop,
|
|
3593
|
+
diagnostics
|
|
3594
|
+
};
|
|
3595
|
+
}
|
|
3596
|
+
function composeDashboardLayers(materialization) {
|
|
3597
|
+
const layers = [...materialization.backdrop.bottomLayers];
|
|
3598
|
+
for (const cell of materialization.cells) {
|
|
3599
|
+
if (cell.frame) {
|
|
3600
|
+
layers.push(
|
|
3601
|
+
...placeLayersInRect(
|
|
3602
|
+
cell.frame.layers,
|
|
3603
|
+
cell.frame.viewport,
|
|
3604
|
+
cell.frame.rect,
|
|
3605
|
+
`cell-${cell.index}-frame`
|
|
3606
|
+
)
|
|
3607
|
+
);
|
|
3608
|
+
}
|
|
3609
|
+
layers.push(...placeLayersInRect(cell.layers, cell.viewport, cell.rect, `cell-${cell.index}`));
|
|
3610
|
+
if (cell.frame && cell.frame.overlayLayers.length > 0) {
|
|
3611
|
+
layers.push(
|
|
3612
|
+
...placeLayersInRect(
|
|
3613
|
+
cell.frame.overlayLayers,
|
|
3614
|
+
cell.frame.overlayViewport,
|
|
3615
|
+
cell.rect,
|
|
3616
|
+
`cell-${cell.index}-overlay`
|
|
3617
|
+
)
|
|
3618
|
+
);
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
if (materialization.title) {
|
|
3622
|
+
layers.push(
|
|
3623
|
+
...placeLayersInRect(
|
|
3624
|
+
materialization.title.layers,
|
|
3625
|
+
materialization.title.viewport,
|
|
3626
|
+
materialization.title.rect,
|
|
3627
|
+
"dashboard-title"
|
|
3628
|
+
)
|
|
3629
|
+
);
|
|
3630
|
+
}
|
|
3631
|
+
layers.push(...materialization.backdrop.topLayers);
|
|
3632
|
+
return layers;
|
|
3633
|
+
}
|
|
3634
|
+
|
|
3635
|
+
// src/doc/flashcards/materializeFlashcards.ts
|
|
3636
|
+
var GENERIC_ANSWER_TITLES = /* @__PURE__ */ new Set([
|
|
3637
|
+
"answer",
|
|
3638
|
+
"back",
|
|
3639
|
+
"solution",
|
|
3640
|
+
"explanation",
|
|
3641
|
+
"correct answer",
|
|
3642
|
+
"correct",
|
|
3643
|
+
"distractor",
|
|
3644
|
+
"option"
|
|
3645
|
+
]);
|
|
3646
|
+
function normalizeMarker(value) {
|
|
3647
|
+
return typeof value === "string" ? value.trim().toLowerCase().replace(/[\s_-]+/g, "") : "";
|
|
3648
|
+
}
|
|
3649
|
+
function markerFromValue(value) {
|
|
3650
|
+
const normalized = normalizeMarker(value);
|
|
3651
|
+
if (normalized === "flashcard" || normalized === "card" || normalized === "basicflashcard") {
|
|
3652
|
+
return "basic";
|
|
3653
|
+
}
|
|
3654
|
+
if (normalized === "multiplechoiceflashcard" || normalized === "multiplechoice" || normalized === "quiz") {
|
|
3655
|
+
return "multiple-choice";
|
|
3656
|
+
}
|
|
3657
|
+
if (normalized === "group" || normalized === "flashcardgroup" || normalized === "deck") {
|
|
3658
|
+
return "group";
|
|
3659
|
+
}
|
|
3660
|
+
return void 0;
|
|
3661
|
+
}
|
|
3662
|
+
function resolveFlashcardMarker(block) {
|
|
3663
|
+
const metadataMarker = markerFromValue(block.metadata?.study) ?? markerFromValue(block.metadata?.["study-mode"]);
|
|
3664
|
+
if (metadataMarker) return metadataMarker;
|
|
3665
|
+
for (const className of block.classes ?? []) {
|
|
3666
|
+
const classMarker = markerFromValue(className);
|
|
3667
|
+
if (classMarker) return classMarker;
|
|
3668
|
+
}
|
|
3669
|
+
return markerFromValue(block.template);
|
|
3670
|
+
}
|
|
3671
|
+
function hasTitle(block) {
|
|
3672
|
+
return typeof block.title === "string" && block.title.trim().length > 0;
|
|
3673
|
+
}
|
|
3674
|
+
function hasBody(block) {
|
|
3675
|
+
return (block.contents?.length ?? 0) > 0 || (block.media?.length ?? 0) > 0 || (block.layers?.length ?? 0) > 0;
|
|
3676
|
+
}
|
|
3677
|
+
function hasOwnContent(block) {
|
|
3678
|
+
return hasTitle(block) || hasBody(block);
|
|
3679
|
+
}
|
|
3680
|
+
function isGenericAnswerBlock(block) {
|
|
3681
|
+
return hasTitle(block) && GENERIC_ANSWER_TITLES.has(block.title.trim().toLowerCase());
|
|
3682
|
+
}
|
|
3683
|
+
function selfOnly(block) {
|
|
3684
|
+
const { children: _children, ...self } = block;
|
|
3685
|
+
return self;
|
|
3686
|
+
}
|
|
3687
|
+
function titleOnly(block) {
|
|
3688
|
+
const {
|
|
3689
|
+
children: _children,
|
|
3690
|
+
contents: _contents,
|
|
3691
|
+
media: _media,
|
|
3692
|
+
layers: _layers,
|
|
3693
|
+
...title2
|
|
3694
|
+
} = block;
|
|
3695
|
+
return title2;
|
|
3696
|
+
}
|
|
3697
|
+
function bodyOnly(block) {
|
|
3698
|
+
const { children: _children, title: _title, ...body } = block;
|
|
3699
|
+
return body;
|
|
3700
|
+
}
|
|
3701
|
+
function face(...blocks) {
|
|
3702
|
+
return { blocks };
|
|
3703
|
+
}
|
|
3704
|
+
function isTrue(value) {
|
|
3705
|
+
if (value === true) return true;
|
|
3706
|
+
if (typeof value !== "string") return false;
|
|
3707
|
+
return ["true", "yes", "on", "1", "correct"].includes(value.trim().toLowerCase());
|
|
3708
|
+
}
|
|
3709
|
+
function isCorrectChoice(block) {
|
|
3710
|
+
return isTrue(block.metadata?.correct) || isTrue(block.metadata?.answer) || (block.classes ?? []).some((className) => normalizeMarker(className) === "correct");
|
|
3711
|
+
}
|
|
3712
|
+
function looksLikeCompleteCard(block) {
|
|
3713
|
+
const marker = resolveFlashcardMarker(block);
|
|
3714
|
+
if (marker === "group") return false;
|
|
3715
|
+
if (marker === "basic" || marker === "multiple-choice") return true;
|
|
3716
|
+
if (isGenericAnswerBlock(block)) return false;
|
|
3717
|
+
const children = block.children ?? [];
|
|
3718
|
+
if (children.length === 0) return hasTitle(block) && hasBody(block);
|
|
3719
|
+
if (children.length === 1) {
|
|
3720
|
+
return hasOwnContent(block) && hasOwnContent(children[0]);
|
|
3721
|
+
}
|
|
3722
|
+
return children.every(hasOwnContent);
|
|
3723
|
+
}
|
|
3724
|
+
function hasExplicitCardDescendant(block) {
|
|
3725
|
+
for (const child of block.children ?? []) {
|
|
3726
|
+
const marker = resolveFlashcardMarker(child);
|
|
3727
|
+
if (marker === "basic" || marker === "multiple-choice") return true;
|
|
3728
|
+
if (hasExplicitCardDescendant(child)) return true;
|
|
3729
|
+
}
|
|
3730
|
+
return false;
|
|
3731
|
+
}
|
|
3732
|
+
function shouldTreatAsImplicitGroup(block) {
|
|
3733
|
+
const children = block.children ?? [];
|
|
3734
|
+
if (children.length === 0) return false;
|
|
3735
|
+
if (hasExplicitCardDescendant(block)) return true;
|
|
3736
|
+
if (hasBody(block)) return false;
|
|
3737
|
+
if (children.length === 1) {
|
|
3738
|
+
return looksLikeCompleteCard(children[0]) && !isGenericAnswerBlock(children[0]);
|
|
3739
|
+
}
|
|
3740
|
+
return children.filter(looksLikeCompleteCard).length >= 2;
|
|
3741
|
+
}
|
|
3742
|
+
function explanationFor(block) {
|
|
3743
|
+
return hasBody(block) ? face(bodyOnly(block)) : void 0;
|
|
3744
|
+
}
|
|
3745
|
+
function materializeBasic(block, diagnostics) {
|
|
3746
|
+
const children = block.children ?? [];
|
|
3747
|
+
let front;
|
|
3748
|
+
let back;
|
|
3749
|
+
let label;
|
|
3750
|
+
let explanation;
|
|
3751
|
+
if (children.length === 0) {
|
|
3752
|
+
front = hasTitle(block) ? face(titleOnly(block)) : face();
|
|
3753
|
+
back = hasBody(block) ? face(bodyOnly(block)) : face();
|
|
3754
|
+
} else if (children.length === 1) {
|
|
3755
|
+
front = hasOwnContent(block) ? face(selfOnly(block)) : face();
|
|
3756
|
+
back = hasOwnContent(children[0]) ? face(children[0]) : face();
|
|
3757
|
+
} else {
|
|
3758
|
+
front = hasOwnContent(children[0]) ? face(children[0]) : face();
|
|
3759
|
+
back = face(...children.slice(1).filter(hasOwnContent));
|
|
3760
|
+
label = hasTitle(block) ? block.title.trim() : void 0;
|
|
3761
|
+
explanation = explanationFor(block);
|
|
3762
|
+
}
|
|
3763
|
+
if (front.blocks.length === 0) {
|
|
3764
|
+
diagnostics.push({
|
|
3765
|
+
severity: "error",
|
|
3766
|
+
code: "empty-front",
|
|
3767
|
+
message: `Flashcard "${block.id}" has no content for its front`,
|
|
3768
|
+
blockId: block.id
|
|
3769
|
+
});
|
|
3770
|
+
}
|
|
3771
|
+
if (back.blocks.length === 0) {
|
|
3772
|
+
diagnostics.push({
|
|
3773
|
+
severity: "error",
|
|
3774
|
+
code: "empty-back",
|
|
3775
|
+
message: `Flashcard "${block.id}" has no content for its answer`,
|
|
3776
|
+
blockId: block.id
|
|
3777
|
+
});
|
|
3778
|
+
}
|
|
3779
|
+
if (front.blocks.length === 0 || back.blocks.length === 0) return void 0;
|
|
3780
|
+
return {
|
|
3781
|
+
id: block.id,
|
|
3782
|
+
sourceBlockId: block.id,
|
|
3783
|
+
kind: "basic",
|
|
3784
|
+
...label ? { label } : {},
|
|
3785
|
+
front,
|
|
3786
|
+
back,
|
|
3787
|
+
...explanation ? { explanation } : {}
|
|
3788
|
+
};
|
|
3789
|
+
}
|
|
3790
|
+
function materializeMultipleChoice(block, diagnostics) {
|
|
3791
|
+
const children = block.children ?? [];
|
|
3792
|
+
const question = children[0];
|
|
3793
|
+
const answerBlocks = children.slice(1).filter(hasOwnContent);
|
|
3794
|
+
if (!question || !hasOwnContent(question)) {
|
|
3795
|
+
diagnostics.push({
|
|
3796
|
+
severity: "error",
|
|
3797
|
+
code: "empty-front",
|
|
3798
|
+
message: `Multiple-choice flashcard "${block.id}" has no question block`,
|
|
3799
|
+
blockId: block.id
|
|
3800
|
+
});
|
|
3801
|
+
}
|
|
3802
|
+
if (answerBlocks.length === 0) {
|
|
3803
|
+
diagnostics.push({
|
|
3804
|
+
severity: "error",
|
|
3805
|
+
code: "empty-back",
|
|
3806
|
+
message: `Multiple-choice flashcard "${block.id}" has no answer choices`,
|
|
3807
|
+
blockId: block.id
|
|
3808
|
+
});
|
|
3809
|
+
}
|
|
3810
|
+
if (!question || !hasOwnContent(question) || answerBlocks.length === 0) return void 0;
|
|
3811
|
+
const explicitlyCorrect = answerBlocks.filter(isCorrectChoice);
|
|
3812
|
+
if (explicitlyCorrect.length > 1) {
|
|
3813
|
+
diagnostics.push({
|
|
3814
|
+
severity: "warning",
|
|
3815
|
+
code: "multiple-correct-answers",
|
|
3816
|
+
message: `Multiple-choice flashcard "${block.id}" marks more than one answer correct; the first marker wins`,
|
|
3817
|
+
blockId: block.id
|
|
3818
|
+
});
|
|
3819
|
+
}
|
|
3820
|
+
const correctBlock = explicitlyCorrect[0] ?? answerBlocks[0];
|
|
3821
|
+
if (answerBlocks.length < 2) {
|
|
3822
|
+
diagnostics.push({
|
|
3823
|
+
severity: "warning",
|
|
3824
|
+
code: "multiple-choice-needs-distractor",
|
|
3825
|
+
message: `Multiple-choice flashcard "${block.id}" needs at least one distractor`,
|
|
3826
|
+
blockId: block.id
|
|
3827
|
+
});
|
|
3828
|
+
}
|
|
3829
|
+
const choices = answerBlocks.map((answer, index) => ({
|
|
3830
|
+
id: `${block.id}-choice-${index + 1}`,
|
|
3831
|
+
sourceBlockId: answer.id,
|
|
3832
|
+
content: face(answer),
|
|
3833
|
+
correct: answer === correctBlock
|
|
3834
|
+
}));
|
|
3835
|
+
const explanation = explanationFor(block);
|
|
3836
|
+
return {
|
|
3837
|
+
id: block.id,
|
|
3838
|
+
sourceBlockId: block.id,
|
|
3839
|
+
kind: "multiple-choice",
|
|
3840
|
+
...hasTitle(block) ? { label: block.title.trim() } : {},
|
|
3841
|
+
front: face(question),
|
|
3842
|
+
back: face(correctBlock),
|
|
3843
|
+
choices,
|
|
3844
|
+
...explanation ? { explanation } : {}
|
|
3845
|
+
};
|
|
3846
|
+
}
|
|
3847
|
+
function resolveDeckTitle(doc) {
|
|
3848
|
+
const frontmatterTitle = doc.frontmatter?.title;
|
|
3849
|
+
if (typeof frontmatterTitle === "string" && frontmatterTitle.trim()) {
|
|
3850
|
+
return frontmatterTitle.trim();
|
|
3851
|
+
}
|
|
3852
|
+
return doc.startBlock?.title?.trim() || void 0;
|
|
3853
|
+
}
|
|
3854
|
+
function materializeFlashcards(doc, options = {}) {
|
|
3855
|
+
const source = options.source ?? "auto";
|
|
3856
|
+
const cards = [];
|
|
3857
|
+
const diagnostics = [];
|
|
3858
|
+
const visit = (block) => {
|
|
3859
|
+
const marker = resolveFlashcardMarker(block);
|
|
3860
|
+
if (marker === "group") {
|
|
3861
|
+
for (const child of block.children ?? []) visit(child);
|
|
3862
|
+
return;
|
|
3863
|
+
}
|
|
3864
|
+
if (marker === "basic") {
|
|
3865
|
+
const card = materializeBasic(block, diagnostics);
|
|
3866
|
+
if (card) cards.push(card);
|
|
3867
|
+
return;
|
|
3868
|
+
}
|
|
3869
|
+
if (marker === "multiple-choice") {
|
|
3870
|
+
const card = materializeMultipleChoice(block, diagnostics);
|
|
3871
|
+
if (card) cards.push(card);
|
|
3872
|
+
return;
|
|
3873
|
+
}
|
|
3874
|
+
if (source === "explicit") {
|
|
3875
|
+
for (const child of block.children ?? []) visit(child);
|
|
3876
|
+
return;
|
|
3877
|
+
}
|
|
3878
|
+
if (shouldTreatAsImplicitGroup(block)) {
|
|
3879
|
+
for (const child of block.children ?? []) visit(child);
|
|
3880
|
+
return;
|
|
3881
|
+
}
|
|
3882
|
+
if (looksLikeCompleteCard(block)) {
|
|
3883
|
+
const card = materializeBasic(block, diagnostics);
|
|
3884
|
+
if (card) cards.push(card);
|
|
3885
|
+
return;
|
|
3886
|
+
}
|
|
3887
|
+
for (const child of block.children ?? []) visit(child);
|
|
3888
|
+
};
|
|
3889
|
+
for (const block of doc.blocks) visit(block);
|
|
3890
|
+
const resolvedTitle = resolveDeckTitle(doc);
|
|
3891
|
+
const authoredTitle = typeof doc.frontmatter?.title === "string" ? doc.frontmatter.title.trim() : "";
|
|
3892
|
+
const firstCardTitle = cards[0]?.front.blocks[0]?.title?.trim();
|
|
3893
|
+
const title2 = resolvedTitle && (authoredTitle || resolvedTitle !== firstCardTitle) ? resolvedTitle : void 0;
|
|
3894
|
+
return {
|
|
3895
|
+
...title2 ? { title: title2 } : {},
|
|
3896
|
+
cards,
|
|
3897
|
+
diagnostics
|
|
3898
|
+
};
|
|
3899
|
+
}
|
|
3900
|
+
|
|
2442
3901
|
// src/doc/applyNarrationTiming.ts
|
|
2443
3902
|
var MIN_BLOCK_SIMILARITY = 0.5;
|
|
2444
3903
|
var MIN_V1_SCRIPT_SIMILARITY = 0.8;
|
|
@@ -3553,7 +5012,7 @@ function middleOut(from, to) {
|
|
|
3553
5012
|
const mid = Math.floor((lo + hi) / 2);
|
|
3554
5013
|
return rows.sort((a, b) => Math.abs(a - mid) - Math.abs(b - mid) || a - b);
|
|
3555
5014
|
}
|
|
3556
|
-
function
|
|
5015
|
+
function clamp2(value, lo, hi) {
|
|
3557
5016
|
return Math.max(lo, Math.min(hi, value));
|
|
3558
5017
|
}
|
|
3559
5018
|
function drawVertical(args, s, t, exitCol, enterCol, dir) {
|
|
@@ -3570,8 +5029,8 @@ function drawVertical(args, s, t, exitCol, enterCol, dir) {
|
|
|
3570
5029
|
};
|
|
3571
5030
|
const straightOk = exitCol >= t.c0 + 1 && exitCol <= t.c1 - 1 && exitCol >= s.c0 + 1 && exitCol <= s.c1 - 1;
|
|
3572
5031
|
if (straightOk || gap < 2) {
|
|
3573
|
-
const sCol =
|
|
3574
|
-
const tCol =
|
|
5032
|
+
const sCol = clamp2(exitCol, s.c0 + 1, s.c1 - 1);
|
|
5033
|
+
const tCol = clamp2(exitCol, t.c0 + 1, t.c1 - 1);
|
|
3575
5034
|
junction(exitRow, sCol, dir === 1 ? "bottom" : "top");
|
|
3576
5035
|
junction(enterRow, tCol, dir === 1 ? "top" : "bottom");
|
|
3577
5036
|
if (sCol === tCol && gap >= 1) {
|
|
@@ -3603,7 +5062,7 @@ function drawVertical(args, s, t, exitCol, enterCol, dir) {
|
|
|
3603
5062
|
}
|
|
3604
5063
|
return;
|
|
3605
5064
|
}
|
|
3606
|
-
const midRow =
|
|
5065
|
+
const midRow = clamp2(
|
|
3607
5066
|
Math.floor((exitRow + enterRow) / 2) + stagger,
|
|
3608
5067
|
Math.min(firstFree, lastFree),
|
|
3609
5068
|
Math.max(firstFree, lastFree)
|
|
@@ -3704,8 +5163,8 @@ function drawHorizontal(args, s, t, exitRow, enterRow, dir) {
|
|
|
3704
5163
|
};
|
|
3705
5164
|
const straightOk = exitRow >= t.r0 + 1 && exitRow <= t.r1 - 1 && exitRow >= s.r0 + 1 && exitRow <= s.r1 - 1;
|
|
3706
5165
|
if (straightOk || gap < 2) {
|
|
3707
|
-
const sRow =
|
|
3708
|
-
const tRow =
|
|
5166
|
+
const sRow = clamp2(exitRow, s.r0 + 1, s.r1 - 1);
|
|
5167
|
+
const tRow = clamp2(exitRow, t.r0 + 1, t.r1 - 1);
|
|
3709
5168
|
junction(sRow, exitCol, dir === 1 ? "right" : "left");
|
|
3710
5169
|
junction(tRow, enterCol, dir === 1 ? "left" : "right");
|
|
3711
5170
|
if (sRow === tRow && gap >= 1) {
|
|
@@ -3738,7 +5197,7 @@ function drawHorizontal(args, s, t, exitRow, enterRow, dir) {
|
|
|
3738
5197
|
}
|
|
3739
5198
|
return;
|
|
3740
5199
|
}
|
|
3741
|
-
const midCol =
|
|
5200
|
+
const midCol = clamp2(
|
|
3742
5201
|
Math.floor((exitCol + enterCol) / 2) + args.stagger,
|
|
3743
5202
|
Math.min(firstFree, lastFree),
|
|
3744
5203
|
Math.max(firstFree, lastFree)
|
|
@@ -4379,6 +5838,39 @@ export {
|
|
|
4379
5838
|
pageStyleDataAttributes,
|
|
4380
5839
|
PAGE_BASE_CSS,
|
|
4381
5840
|
buildPageCss,
|
|
5841
|
+
DASHBOARD_ZOOM_LEVELS,
|
|
5842
|
+
normalizeDashboardZoom,
|
|
5843
|
+
desiredCellZoom,
|
|
5844
|
+
resolveDashboardZooms,
|
|
5845
|
+
validateDashboardLayoutDefinition,
|
|
5846
|
+
layoutCapacity,
|
|
5847
|
+
transposeCells,
|
|
5848
|
+
resolveLayoutCells,
|
|
5849
|
+
FRONTMATTER_DASHBOARD_LAYOUTS_KEY,
|
|
5850
|
+
readDashboardLayoutsFromFrontmatter,
|
|
5851
|
+
writeDashboardLayoutsToFrontmatter,
|
|
5852
|
+
BUILTIN_DASHBOARD_LAYOUTS,
|
|
5853
|
+
getDashboardLayoutSummaries,
|
|
5854
|
+
listDashboardLayouts,
|
|
5855
|
+
DASHBOARD_AUTO_LAYOUT_ID,
|
|
5856
|
+
resolveDashboardLayoutDefinition,
|
|
5857
|
+
chooseDashboardLayout,
|
|
5858
|
+
DASHBOARD_STYLE_IDS,
|
|
5859
|
+
DEFAULT_DASHBOARD_STYLE,
|
|
5860
|
+
DASHBOARD_STYLES,
|
|
5861
|
+
resolveDashboardStyleId,
|
|
5862
|
+
dashboardCanvasFill,
|
|
5863
|
+
dashboardCellAccent,
|
|
5864
|
+
buildDashboardCellChrome,
|
|
5865
|
+
stripsBlockBackdrop,
|
|
5866
|
+
stripBlockBackdropLayer,
|
|
5867
|
+
DASHBOARD_FRONTMATTER_KEYS,
|
|
5868
|
+
DEFAULT_DASHBOARD_SETTINGS,
|
|
5869
|
+
resolveDashboardSettings,
|
|
5870
|
+
materializeDashboard,
|
|
5871
|
+
composeDashboardLayers,
|
|
5872
|
+
resolveFlashcardMarker,
|
|
5873
|
+
materializeFlashcards,
|
|
4382
5874
|
applyNarrationTiming,
|
|
4383
5875
|
scoreTextSimilarity,
|
|
4384
5876
|
resolveAudioMapping,
|