@bendyline/squisq 2.7.0 → 2.8.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-7Z5T3CUI.js → chunk-KEAU5NEM.js} +1 -1
- package/dist/{chunk-KOU5WRVN.js → chunk-PTK2SW2V.js} +1578 -14
- package/dist/{chunk-GSJEGMKF.js → chunk-V6NV4GDE.js} +7 -4
- package/dist/doc/index.d.ts +650 -2
- package/dist/doc/index.js +71 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +77 -11
- package/dist/narration/index.js +2 -2
- package/package.json +1 -1
|
@@ -2,28 +2,38 @@ 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,
|
|
16
|
+
hasTemplate,
|
|
13
17
|
isDataFence,
|
|
14
18
|
isShapeName,
|
|
15
19
|
lintTemplateParams,
|
|
16
20
|
markdownToDoc,
|
|
21
|
+
materializeBlockLayersWithRuntime,
|
|
17
22
|
nearestName,
|
|
18
23
|
normalizeShapeKind,
|
|
19
24
|
parseDataFence,
|
|
20
25
|
parseNarrationTimingJson,
|
|
21
26
|
parseStandaloneAnnotation,
|
|
27
|
+
placeLayersInRect,
|
|
22
28
|
readCustomThemesFromFrontmatter,
|
|
29
|
+
resolvePersistentLayers,
|
|
23
30
|
templateRegistry,
|
|
24
31
|
writeCustomTemplatesToFrontmatter,
|
|
25
32
|
writeCustomThemesToFrontmatter
|
|
26
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-V6NV4GDE.js";
|
|
34
|
+
import {
|
|
35
|
+
iconMarker
|
|
36
|
+
} from "./chunk-7ZAAICW4.js";
|
|
27
37
|
import {
|
|
28
38
|
ASCII_TREE_VOCAB,
|
|
29
39
|
ASCII_VOCAB,
|
|
@@ -49,11 +59,18 @@ import {
|
|
|
49
59
|
FRONTMATTER_CUSTOM_TEMPLATES_KEY,
|
|
50
60
|
FRONTMATTER_CUSTOM_THEMES_KEY,
|
|
51
61
|
hexHueDegrees,
|
|
62
|
+
oklchDarken,
|
|
63
|
+
oklchLighten,
|
|
64
|
+
relativeLuminance,
|
|
52
65
|
resolveFontFamily,
|
|
53
|
-
resolveTheme
|
|
66
|
+
resolveTheme,
|
|
67
|
+
withAlpha
|
|
54
68
|
} from "./chunk-SBAX4ZPO.js";
|
|
55
69
|
import {
|
|
56
70
|
VIEWPORT_PRESETS,
|
|
71
|
+
calculateFontScale,
|
|
72
|
+
createTemplateContext,
|
|
73
|
+
getViewportOrientation,
|
|
57
74
|
isTemplateBlock
|
|
58
75
|
} from "./chunk-BAOV476U.js";
|
|
59
76
|
import {
|
|
@@ -662,6 +679,332 @@ function removeTransitionParams(attrs) {
|
|
|
662
679
|
};
|
|
663
680
|
}
|
|
664
681
|
|
|
682
|
+
// src/doc/buildPreviewDoc.ts
|
|
683
|
+
function extractRichText(node) {
|
|
684
|
+
if (node.type === "inlineIcon") {
|
|
685
|
+
const icon = node;
|
|
686
|
+
return iconMarker(icon.family, icon.name);
|
|
687
|
+
}
|
|
688
|
+
if ("value" in node && typeof node.value === "string") {
|
|
689
|
+
return node.value;
|
|
690
|
+
}
|
|
691
|
+
const children = getChildren(node);
|
|
692
|
+
const separator = node.type === "list" || node.type === "listItem" ? "\n" : "";
|
|
693
|
+
return children.map(extractRichText).join(separator);
|
|
694
|
+
}
|
|
695
|
+
function extractBodyText(contents) {
|
|
696
|
+
if (!contents || contents.length === 0) return "";
|
|
697
|
+
const parts = [];
|
|
698
|
+
for (const node of contents) {
|
|
699
|
+
if (node.type === "code" && node.lang?.trim().toLowerCase() === "mermaid") continue;
|
|
700
|
+
parts.push(extractRichText(node));
|
|
701
|
+
}
|
|
702
|
+
return parts.join("\n").trim();
|
|
703
|
+
}
|
|
704
|
+
function parseDim(raw) {
|
|
705
|
+
if (raw === void 0) return void 0;
|
|
706
|
+
const n = parseFloat(raw);
|
|
707
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
708
|
+
}
|
|
709
|
+
function extractBlockImages(contents) {
|
|
710
|
+
if (!contents || contents.length === 0) return [];
|
|
711
|
+
const images = [];
|
|
712
|
+
function walkHtml(node) {
|
|
713
|
+
if (!node || typeof node !== "object") return;
|
|
714
|
+
const n = node;
|
|
715
|
+
if (n.type === "htmlElement" && n.tagName.toLowerCase() === "img") {
|
|
716
|
+
const attrs = n.attributes;
|
|
717
|
+
const src = attrs?.src;
|
|
718
|
+
if (typeof src === "string" && src) {
|
|
719
|
+
images.push({
|
|
720
|
+
src,
|
|
721
|
+
alt: typeof attrs?.alt === "string" ? attrs.alt : "",
|
|
722
|
+
width: parseDim(attrs?.width),
|
|
723
|
+
height: parseDim(attrs?.height)
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (Array.isArray(n.children)) {
|
|
728
|
+
for (const child of n.children) walkHtml(child);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
function walk(node) {
|
|
732
|
+
if ("type" in node && node.type === "image" && "url" in node) {
|
|
733
|
+
const img = node;
|
|
734
|
+
if (img.url) {
|
|
735
|
+
images.push({ src: img.url, alt: img.alt ?? "" });
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
if ("type" in node && (node.type === "htmlBlock" || node.type === "htmlInline")) {
|
|
739
|
+
const html = node;
|
|
740
|
+
for (const child of html.htmlChildren ?? []) walkHtml(child);
|
|
741
|
+
}
|
|
742
|
+
for (const child of getChildren(node)) {
|
|
743
|
+
walk(child);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
for (const node of contents) {
|
|
747
|
+
walk(node);
|
|
748
|
+
}
|
|
749
|
+
return images;
|
|
750
|
+
}
|
|
751
|
+
function collectAllDocImages(blocks) {
|
|
752
|
+
const seen = /* @__PURE__ */ new Set();
|
|
753
|
+
const images = [];
|
|
754
|
+
function walkBlocks(blockList) {
|
|
755
|
+
for (const block of blockList) {
|
|
756
|
+
for (const img of extractBlockImages(block.contents)) {
|
|
757
|
+
if (!seen.has(img.src)) {
|
|
758
|
+
seen.add(img.src);
|
|
759
|
+
images.push(img);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
if (block.children) {
|
|
763
|
+
walkBlocks(block.children);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
walkBlocks(blocks);
|
|
768
|
+
return images;
|
|
769
|
+
}
|
|
770
|
+
function extractListItems(contents) {
|
|
771
|
+
if (!contents) return [];
|
|
772
|
+
const items = [];
|
|
773
|
+
for (const node of contents) {
|
|
774
|
+
if (node.type === "list") {
|
|
775
|
+
for (const item of node.children) {
|
|
776
|
+
const text = extractPlainText(item).trim();
|
|
777
|
+
if (text) items.push(text);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return items;
|
|
782
|
+
}
|
|
783
|
+
function getTemplateDefaults(templateName, headingText, block) {
|
|
784
|
+
const body = extractBodyText(block.contents);
|
|
785
|
+
switch (templateName) {
|
|
786
|
+
case "statHighlight":
|
|
787
|
+
return deriveTemplateInputs(templateName, headingText, block.contents) ?? {
|
|
788
|
+
stat: headingText,
|
|
789
|
+
description: body || headingText
|
|
790
|
+
};
|
|
791
|
+
case "quote":
|
|
792
|
+
return { quote: body || headingText };
|
|
793
|
+
case "fullBleedQuote":
|
|
794
|
+
case "pullQuote":
|
|
795
|
+
return deriveTemplateInputs(templateName, headingText, block.contents) ?? {
|
|
796
|
+
text: body || headingText
|
|
797
|
+
};
|
|
798
|
+
case "factCard":
|
|
799
|
+
return { fact: headingText, explanation: body || headingText };
|
|
800
|
+
case "comparisonBar":
|
|
801
|
+
return { leftLabel: "A", leftValue: 60, rightLabel: "B", rightValue: 40 };
|
|
802
|
+
case "list": {
|
|
803
|
+
const items = extractListItems(block.contents);
|
|
804
|
+
return { items: items.length > 0 ? items : ["Item 1", "Item 2", "Item 3"] };
|
|
805
|
+
}
|
|
806
|
+
case "definitionCard":
|
|
807
|
+
return { term: headingText, definition: body || headingText };
|
|
808
|
+
case "dateEvent":
|
|
809
|
+
return { date: headingText, description: body || headingText };
|
|
810
|
+
case "leftFeature":
|
|
811
|
+
case "rightFeature": {
|
|
812
|
+
const images = extractBlockImages(block.contents);
|
|
813
|
+
const img = images[0];
|
|
814
|
+
return {
|
|
815
|
+
imageSrc: img?.src ?? "",
|
|
816
|
+
imageAlt: img?.alt || headingText,
|
|
817
|
+
imageWidth: img?.width,
|
|
818
|
+
imageHeight: img?.height,
|
|
819
|
+
title: headingText,
|
|
820
|
+
body: body || headingText
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
default:
|
|
824
|
+
return {};
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
function blockToSlide(block, index, knownTemplates, documentTitle) {
|
|
828
|
+
const headingText = block.sourceHeading ? extractPlainText(block.sourceHeading) : block.title || documentTitle || "";
|
|
829
|
+
const implicitSectionHeader = block.template === "sectionHeader" && block.autoTemplate !== true && !!block.sourceHeading && !block.sourceHeading.templateAnnotation?.template;
|
|
830
|
+
const requestedTemplate = implicitSectionHeader ? "content" : block.template ?? "content";
|
|
831
|
+
const isCustomTemplate = knownTemplates?.has(requestedTemplate) ?? false;
|
|
832
|
+
const recognized = hasTemplate(requestedTemplate) || isCustomTemplate;
|
|
833
|
+
const template = recognized ? requestedTemplate : "sectionHeader";
|
|
834
|
+
const defaults = getTemplateDefaults(template, headingText, block);
|
|
835
|
+
const templateOverrides = omitStringBlockMeta(block.templateOverrides);
|
|
836
|
+
const coercedTemplateOverrides = templateOverrides ? coerceTemplateParams(template, templateOverrides).input : void 0;
|
|
837
|
+
const {
|
|
838
|
+
id: _id,
|
|
839
|
+
startTime: _st,
|
|
840
|
+
duration: _d,
|
|
841
|
+
audioSegment: _as,
|
|
842
|
+
layers: _l,
|
|
843
|
+
transition: _tr,
|
|
844
|
+
template: _t,
|
|
845
|
+
title: _ti,
|
|
846
|
+
children: _c,
|
|
847
|
+
contents: _co,
|
|
848
|
+
sourceHeading: _sh,
|
|
849
|
+
templateOverrides: _to,
|
|
850
|
+
templateData: _td,
|
|
851
|
+
...extraFields
|
|
852
|
+
} = block;
|
|
853
|
+
return {
|
|
854
|
+
id: block.id,
|
|
855
|
+
template,
|
|
856
|
+
duration: block.duration,
|
|
857
|
+
audioSegment: 0,
|
|
858
|
+
// Respect the block's authored transition (set via the toolbar / on-canvas
|
|
859
|
+
// properties palette → `{…}` block attrs). Only fall back to a default fade
|
|
860
|
+
// for blocks past the first when the author hasn't chosen one; the first
|
|
861
|
+
// block has no previous slide to transition in from.
|
|
862
|
+
transition: block.transition ?? (index > 0 ? { type: "fade", duration: 0.5 } : void 0),
|
|
863
|
+
title: headingText,
|
|
864
|
+
// Preserve body nodes on every slide. Built-in templates ignore this
|
|
865
|
+
// structural field, while the canonical materializer uses it to retain
|
|
866
|
+
// authored rich elements (Mermaid fences today; other media can follow)
|
|
867
|
+
// independently of the selected visual template.
|
|
868
|
+
...block.contents ? { contents: block.contents } : {},
|
|
869
|
+
// Custom templates additionally consume child blocks through tokens.
|
|
870
|
+
...isCustomTemplate && block.children ? { children: block.children } : {},
|
|
871
|
+
...defaults,
|
|
872
|
+
...extraFields,
|
|
873
|
+
// Structured body data (```json data fences, GFM tables for dataTable)
|
|
874
|
+
// carries typed values; `{[…]}` string overrides win last so an explicit
|
|
875
|
+
// annotation param can still pin any field.
|
|
876
|
+
//
|
|
877
|
+
// Block-meta keys (transition, startTime, duration, …) are the exception:
|
|
878
|
+
// they were already coerced to typed block fields above (e.g.
|
|
879
|
+
// `block.transition` → `{ type, duration, direction }`). Their raw string
|
|
880
|
+
// form also rides along in `templateData`/`templateOverrides` because the
|
|
881
|
+
// author wrote them inside `{[…]}`; left un-stripped, that string would
|
|
882
|
+
// spread back over the typed value here and clobber it — turning
|
|
883
|
+
// `transition=vortex` into the string `"vortex"`, which the player can't
|
|
884
|
+
// animate. Omit them from the content spreads so the typed fields win.
|
|
885
|
+
...omitBlockMeta(block.templateData),
|
|
886
|
+
...coercedTemplateOverrides
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
var BLOCK_META_KEYS = new Set(Object.keys(KNOWN_BLOCK_META_KEYS));
|
|
890
|
+
function omitBlockMeta(data) {
|
|
891
|
+
if (!data) return data;
|
|
892
|
+
let hit = false;
|
|
893
|
+
const out = {};
|
|
894
|
+
for (const key of Object.keys(data)) {
|
|
895
|
+
if (BLOCK_META_KEYS.has(key)) {
|
|
896
|
+
hit = true;
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
out[key] = data[key];
|
|
900
|
+
}
|
|
901
|
+
return hit ? out : data;
|
|
902
|
+
}
|
|
903
|
+
function omitStringBlockMeta(data) {
|
|
904
|
+
if (!data) return data;
|
|
905
|
+
let hit = false;
|
|
906
|
+
const out = {};
|
|
907
|
+
for (const key of Object.keys(data)) {
|
|
908
|
+
if (BLOCK_META_KEYS.has(key)) {
|
|
909
|
+
hit = true;
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
out[key] = data[key];
|
|
913
|
+
}
|
|
914
|
+
return hit ? out : data;
|
|
915
|
+
}
|
|
916
|
+
var IMAGE_MOTIONS = [
|
|
917
|
+
"zoomIn",
|
|
918
|
+
"zoomOut",
|
|
919
|
+
"panLeft",
|
|
920
|
+
"panRight"
|
|
921
|
+
];
|
|
922
|
+
function documentTitleFromFileName(fileName) {
|
|
923
|
+
if (!fileName) return "";
|
|
924
|
+
const base = fileName.split(/[\\/]/).pop() ?? "";
|
|
925
|
+
return base.replace(/\.[^.]+$/, "").trim();
|
|
926
|
+
}
|
|
927
|
+
function resolveDocumentTitle(doc, provided) {
|
|
928
|
+
const frontmatterTitle = doc.frontmatter?.title;
|
|
929
|
+
if (typeof frontmatterTitle === "string" && frontmatterTitle.trim()) {
|
|
930
|
+
return frontmatterTitle.trim();
|
|
931
|
+
}
|
|
932
|
+
return provided?.trim() ?? "";
|
|
933
|
+
}
|
|
934
|
+
function buildPreviewDoc(doc, options) {
|
|
935
|
+
const flat = flattenRenderableBlocks(doc.blocks);
|
|
936
|
+
const allImages = collectAllDocImages(doc.blocks);
|
|
937
|
+
const usedImageSrcs = /* @__PURE__ */ new Set();
|
|
938
|
+
const knownTemplates = doc.customTemplates ? new Set(doc.customTemplates.map((d) => d.name)) : void 0;
|
|
939
|
+
const documentTitle = resolveDocumentTitle(doc, options?.documentTitle);
|
|
940
|
+
const slides = [];
|
|
941
|
+
let motionIndex = 0;
|
|
942
|
+
for (let i = 0; i < flat.length; i++) {
|
|
943
|
+
const block = flat[i];
|
|
944
|
+
const blockImages = extractBlockImages(block.contents);
|
|
945
|
+
const slide = blockToSlide(block, i, knownTemplates, documentTitle);
|
|
946
|
+
if (blockImages.length > 0 && slide.template === "sectionHeader") {
|
|
947
|
+
const img = blockImages[0];
|
|
948
|
+
usedImageSrcs.add(img.src);
|
|
949
|
+
slide.template = "imageWithCaption";
|
|
950
|
+
slide.imageSrc = img.src;
|
|
951
|
+
slide.imageAlt = img.alt;
|
|
952
|
+
slide.caption = slide.title;
|
|
953
|
+
slide.captionPosition = "bottom";
|
|
954
|
+
slide.ambientMotion = IMAGE_MOTIONS[motionIndex++ % IMAGE_MOTIONS.length];
|
|
955
|
+
} else if (blockImages.length > 0) {
|
|
956
|
+
const img = blockImages[0];
|
|
957
|
+
usedImageSrcs.add(img.src);
|
|
958
|
+
if (!slide.accentImage) {
|
|
959
|
+
slide.accentImage = {
|
|
960
|
+
src: img.src,
|
|
961
|
+
alt: img.alt,
|
|
962
|
+
position: "left-strip",
|
|
963
|
+
ambientMotion: IMAGE_MOTIONS[motionIndex++ % IMAGE_MOTIONS.length]
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
slides.push(slide);
|
|
968
|
+
}
|
|
969
|
+
const unusedImages = options?.interleaveImages ?? true ? allImages.filter((img) => !usedImageSrcs.has(img.src)) : [];
|
|
970
|
+
if (unusedImages.length > 0 && slides.length > 0) {
|
|
971
|
+
const interval = Math.max(2, Math.floor(slides.length / (unusedImages.length + 1)));
|
|
972
|
+
let insertOffset = 0;
|
|
973
|
+
for (let imgIdx = 0; imgIdx < unusedImages.length; imgIdx++) {
|
|
974
|
+
const insertAt = Math.min((imgIdx + 1) * interval + insertOffset, slides.length);
|
|
975
|
+
const img = unusedImages[imgIdx];
|
|
976
|
+
slides.splice(insertAt, 0, {
|
|
977
|
+
id: `img-interleave-${imgIdx}`,
|
|
978
|
+
template: "imageWithCaption",
|
|
979
|
+
duration: 5,
|
|
980
|
+
audioSegment: 0,
|
|
981
|
+
imageSrc: img.src,
|
|
982
|
+
imageAlt: img.alt,
|
|
983
|
+
ambientMotion: IMAGE_MOTIONS[motionIndex++ % IMAGE_MOTIONS.length],
|
|
984
|
+
transition: { type: "fade", duration: 0.5 }
|
|
985
|
+
});
|
|
986
|
+
insertOffset++;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
let t = 0;
|
|
990
|
+
for (const slide of slides) {
|
|
991
|
+
slide.startTime = t;
|
|
992
|
+
t += slide.duration;
|
|
993
|
+
}
|
|
994
|
+
const audio = doc.audio?.segments?.length > 0 ? doc.audio : {
|
|
995
|
+
segments: t > 0 ? [{ src: "", name: "preview", duration: t, startTime: 0 }] : []
|
|
996
|
+
};
|
|
997
|
+
return {
|
|
998
|
+
// Preserve document-wide capabilities (custom themes, persistent layers,
|
|
999
|
+
// scheduled media, frontmatter, captions, and future schema fields).
|
|
1000
|
+
// Preview preparation should replace only the slide/timing projection.
|
|
1001
|
+
...doc,
|
|
1002
|
+
duration: t,
|
|
1003
|
+
blocks: slides,
|
|
1004
|
+
audio
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
|
|
665
1008
|
// src/doc/resolveDocTheme.ts
|
|
666
1009
|
function resolveThemeForDoc(doc, explicitId, registry) {
|
|
667
1010
|
const id = explicitId ?? doc?.themeId ?? readFrontmatterThemeId(doc?.frontmatter);
|
|
@@ -1914,21 +2257,29 @@ var PAGE_BASE_CSS = `
|
|
|
1914
2257
|
.squisq-page-items { list-style: none; margin: 0; padding: 0; counter-reset: squisq-item; max-width: 40em; }
|
|
1915
2258
|
.squisq-page-items li {
|
|
1916
2259
|
counter-increment: squisq-item;
|
|
1917
|
-
|
|
1918
|
-
|
|
2260
|
+
/* Grid + baseline alignment keeps the marker locked to the first text line
|
|
2261
|
+
regardless of marker size, body font, or line-height \u2014 an absolutely
|
|
2262
|
+
positioned marker with a fixed top offset drifts as soon as either
|
|
2263
|
+
changes. */
|
|
2264
|
+
display: grid;
|
|
2265
|
+
grid-template-columns: 3.4em 1fr;
|
|
2266
|
+
align-items: baseline;
|
|
2267
|
+
padding: 0.9em 0;
|
|
1919
2268
|
font-size: 1.1rem;
|
|
1920
2269
|
}
|
|
1921
2270
|
.squisq-page-items li + li { border-top: 1px solid var(--squisq-page-divider-color); }
|
|
1922
2271
|
.squisq-page-items li::before {
|
|
1923
2272
|
content: counter(squisq-item, decimal-leading-zero);
|
|
1924
|
-
position: absolute;
|
|
1925
|
-
left: 0;
|
|
1926
|
-
top: 0.85em;
|
|
1927
2273
|
font-family: var(--squisq-page-title-font);
|
|
1928
2274
|
font-weight: 700;
|
|
1929
2275
|
color: var(--squisq-page-accent);
|
|
1930
2276
|
font-size: 1.15em;
|
|
1931
2277
|
}
|
|
2278
|
+
/* Item bodies are rendered markdown: neutralize UA paragraph margins (which
|
|
2279
|
+
would otherwise push the first line below the marker) and space blocks. */
|
|
2280
|
+
.squisq-page-item-body { min-width: 0; }
|
|
2281
|
+
.squisq-page-item-body > * { margin: 0; }
|
|
2282
|
+
.squisq-page-item-body > * + * { margin-top: 0.6em; }
|
|
1932
2283
|
.squisq-page[data-numerals='mono'] .squisq-page-items li::before { font-family: var(--squisq-page-mono-font); }
|
|
1933
2284
|
.squisq-page-items-title { font-size: 1.9rem; }
|
|
1934
2285
|
|
|
@@ -2101,6 +2452,1186 @@ ${varLines}
|
|
|
2101
2452
|
${PAGE_BASE_CSS}`;
|
|
2102
2453
|
}
|
|
2103
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 hasBody = Array.isArray(first.contents) && first.contents.length > 0;
|
|
3290
|
+
return titleShaped || !hasBody ? 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
|
+
|
|
2104
3635
|
// src/doc/applyNarrationTiming.ts
|
|
2105
3636
|
var MIN_BLOCK_SIMILARITY = 0.5;
|
|
2106
3637
|
var MIN_V1_SCRIPT_SIMILARITY = 0.8;
|
|
@@ -3215,7 +4746,7 @@ function middleOut(from, to) {
|
|
|
3215
4746
|
const mid = Math.floor((lo + hi) / 2);
|
|
3216
4747
|
return rows.sort((a, b) => Math.abs(a - mid) - Math.abs(b - mid) || a - b);
|
|
3217
4748
|
}
|
|
3218
|
-
function
|
|
4749
|
+
function clamp2(value, lo, hi) {
|
|
3219
4750
|
return Math.max(lo, Math.min(hi, value));
|
|
3220
4751
|
}
|
|
3221
4752
|
function drawVertical(args, s, t, exitCol, enterCol, dir) {
|
|
@@ -3232,8 +4763,8 @@ function drawVertical(args, s, t, exitCol, enterCol, dir) {
|
|
|
3232
4763
|
};
|
|
3233
4764
|
const straightOk = exitCol >= t.c0 + 1 && exitCol <= t.c1 - 1 && exitCol >= s.c0 + 1 && exitCol <= s.c1 - 1;
|
|
3234
4765
|
if (straightOk || gap < 2) {
|
|
3235
|
-
const sCol =
|
|
3236
|
-
const tCol =
|
|
4766
|
+
const sCol = clamp2(exitCol, s.c0 + 1, s.c1 - 1);
|
|
4767
|
+
const tCol = clamp2(exitCol, t.c0 + 1, t.c1 - 1);
|
|
3237
4768
|
junction(exitRow, sCol, dir === 1 ? "bottom" : "top");
|
|
3238
4769
|
junction(enterRow, tCol, dir === 1 ? "top" : "bottom");
|
|
3239
4770
|
if (sCol === tCol && gap >= 1) {
|
|
@@ -3265,7 +4796,7 @@ function drawVertical(args, s, t, exitCol, enterCol, dir) {
|
|
|
3265
4796
|
}
|
|
3266
4797
|
return;
|
|
3267
4798
|
}
|
|
3268
|
-
const midRow =
|
|
4799
|
+
const midRow = clamp2(
|
|
3269
4800
|
Math.floor((exitRow + enterRow) / 2) + stagger,
|
|
3270
4801
|
Math.min(firstFree, lastFree),
|
|
3271
4802
|
Math.max(firstFree, lastFree)
|
|
@@ -3366,8 +4897,8 @@ function drawHorizontal(args, s, t, exitRow, enterRow, dir) {
|
|
|
3366
4897
|
};
|
|
3367
4898
|
const straightOk = exitRow >= t.r0 + 1 && exitRow <= t.r1 - 1 && exitRow >= s.r0 + 1 && exitRow <= s.r1 - 1;
|
|
3368
4899
|
if (straightOk || gap < 2) {
|
|
3369
|
-
const sRow =
|
|
3370
|
-
const tRow =
|
|
4900
|
+
const sRow = clamp2(exitRow, s.r0 + 1, s.r1 - 1);
|
|
4901
|
+
const tRow = clamp2(exitRow, t.r0 + 1, t.r1 - 1);
|
|
3371
4902
|
junction(sRow, exitCol, dir === 1 ? "right" : "left");
|
|
3372
4903
|
junction(tRow, enterCol, dir === 1 ? "left" : "right");
|
|
3373
4904
|
if (sRow === tRow && gap >= 1) {
|
|
@@ -3400,7 +4931,7 @@ function drawHorizontal(args, s, t, exitRow, enterRow, dir) {
|
|
|
3400
4931
|
}
|
|
3401
4932
|
return;
|
|
3402
4933
|
}
|
|
3403
|
-
const midCol =
|
|
4934
|
+
const midCol = clamp2(
|
|
3404
4935
|
Math.floor((exitCol + enterCol) / 2) + args.stagger,
|
|
3405
4936
|
Math.min(firstFree, lastFree),
|
|
3406
4937
|
Math.max(firstFree, lastFree)
|
|
@@ -4028,6 +5559,8 @@ export {
|
|
|
4028
5559
|
MAX_COVER_SLIDE_DURATION_SECONDS,
|
|
4029
5560
|
resolveCoverSlideSettings,
|
|
4030
5561
|
docToMarkdown,
|
|
5562
|
+
documentTitleFromFileName,
|
|
5563
|
+
buildPreviewDoc,
|
|
4031
5564
|
resolveThemeForDoc,
|
|
4032
5565
|
isTemplatedPageBlock,
|
|
4033
5566
|
resolvePageBlock,
|
|
@@ -4039,6 +5572,37 @@ export {
|
|
|
4039
5572
|
pageStyleDataAttributes,
|
|
4040
5573
|
PAGE_BASE_CSS,
|
|
4041
5574
|
buildPageCss,
|
|
5575
|
+
DASHBOARD_ZOOM_LEVELS,
|
|
5576
|
+
normalizeDashboardZoom,
|
|
5577
|
+
desiredCellZoom,
|
|
5578
|
+
resolveDashboardZooms,
|
|
5579
|
+
validateDashboardLayoutDefinition,
|
|
5580
|
+
layoutCapacity,
|
|
5581
|
+
transposeCells,
|
|
5582
|
+
resolveLayoutCells,
|
|
5583
|
+
FRONTMATTER_DASHBOARD_LAYOUTS_KEY,
|
|
5584
|
+
readDashboardLayoutsFromFrontmatter,
|
|
5585
|
+
writeDashboardLayoutsToFrontmatter,
|
|
5586
|
+
BUILTIN_DASHBOARD_LAYOUTS,
|
|
5587
|
+
getDashboardLayoutSummaries,
|
|
5588
|
+
listDashboardLayouts,
|
|
5589
|
+
DASHBOARD_AUTO_LAYOUT_ID,
|
|
5590
|
+
resolveDashboardLayoutDefinition,
|
|
5591
|
+
chooseDashboardLayout,
|
|
5592
|
+
DASHBOARD_STYLE_IDS,
|
|
5593
|
+
DEFAULT_DASHBOARD_STYLE,
|
|
5594
|
+
DASHBOARD_STYLES,
|
|
5595
|
+
resolveDashboardStyleId,
|
|
5596
|
+
dashboardCanvasFill,
|
|
5597
|
+
dashboardCellAccent,
|
|
5598
|
+
buildDashboardCellChrome,
|
|
5599
|
+
stripsBlockBackdrop,
|
|
5600
|
+
stripBlockBackdropLayer,
|
|
5601
|
+
DASHBOARD_FRONTMATTER_KEYS,
|
|
5602
|
+
DEFAULT_DASHBOARD_SETTINGS,
|
|
5603
|
+
resolveDashboardSettings,
|
|
5604
|
+
materializeDashboard,
|
|
5605
|
+
composeDashboardLayers,
|
|
4042
5606
|
applyNarrationTiming,
|
|
4043
5607
|
scoreTextSimilarity,
|
|
4044
5608
|
resolveAudioMapping,
|