@bendyline/squisq 2.13.2 → 2.13.3

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.
Files changed (32) hide show
  1. package/dist/{Doc-CYTLtFEu.d.ts → Doc-BPhdJFS_.d.ts} +33 -2
  2. package/dist/{ImageEditDoc-DZ4k_O10.d.ts → ImageEditDoc-DDx-sw_S.d.ts} +1 -1
  3. package/dist/{chunk-YY3OQ3MI.js → chunk-5FUBK3DK.js} +234 -216
  4. package/dist/{chunk-HE3HQZWU.js → chunk-6NWVLLMI.js} +104 -1
  5. package/dist/{chunk-3A6YM375.js → chunk-D3JQMZUT.js} +8 -8
  6. package/dist/chunk-DOOUVVOM.js +78 -0
  7. package/dist/{chunk-56TW6HE6.js → chunk-F3LRTRST.js} +15 -10
  8. package/dist/{chunk-7ZXCS3UU.js → chunk-JFJZ4NS6.js} +2 -2
  9. package/dist/{chunk-2GN67Q2T.js → chunk-JOBMPMY5.js} +1 -1
  10. package/dist/{chunk-HGCIIXHP.js → chunk-POZCKPCO.js} +235 -4
  11. package/dist/{chunk-FUQYQWQR.js → chunk-VX7MPZOK.js} +5 -5
  12. package/dist/doc/index.d.ts +94 -14
  13. package/dist/doc/index.js +15 -7
  14. package/dist/fence/index.d.ts +1 -1
  15. package/dist/generate/index.d.ts +1 -1
  16. package/dist/imageEdit/index.d.ts +3 -3
  17. package/dist/index.d.ts +5 -5
  18. package/dist/index.js +25 -17
  19. package/dist/jsonForm/index.d.ts +1 -1
  20. package/dist/markdown/index.js +11 -12
  21. package/dist/{materializePageSection-CDps_xUd.d.ts → materializePageSection-DnQi-0TA.d.ts} +1 -1
  22. package/dist/mediaEdit/index.d.ts +2 -2
  23. package/dist/narration/index.d.ts +1 -1
  24. package/dist/narration/index.js +5 -5
  25. package/dist/recommend/index.js +3 -2
  26. package/dist/schemas/index.d.ts +4 -4
  27. package/dist/{themeLibrary-CMm_jh3a.d.ts → themeLibrary-DM7eSK1w.d.ts} +1 -1
  28. package/dist/timing/index.d.ts +1 -1
  29. package/dist/transform/index.d.ts +2 -2
  30. package/dist/transform/index.js +6 -6
  31. package/package.json +1 -1
  32. package/dist/chunk-Y25EJEZN.js +0 -178
@@ -10,6 +10,7 @@ import {
10
10
  extractEmbeddedVideos,
11
11
  extractHostedVideos,
12
12
  extractImages,
13
+ extractMediaFromContents,
13
14
  extractRichListItems,
14
15
  extractTableFromContents,
15
16
  isEligibleAsciiFenceLang,
@@ -19,15 +20,19 @@ import {
19
20
  isExplicitTimelineLang,
20
21
  isExplicitTreeLang,
21
22
  isMaterializedMediaNode,
23
+ isMediaAnnotationName,
22
24
  isPictureFence,
25
+ isPictureParagraph,
23
26
  markdownBlockSeparatorLines,
27
+ parseAnnotationInner,
24
28
  parseAsciiDiagram,
25
29
  parseAsciiTimeline,
30
+ parseStandaloneAnnotation,
26
31
  pickAutoTemplate,
27
32
  profileBlockContents,
28
33
  renderMarkdownBlocksHtml,
29
34
  treeToTemplateData
30
- } from "./chunk-HGCIIXHP.js";
35
+ } from "./chunk-POZCKPCO.js";
31
36
  import {
32
37
  anchorPoint,
33
38
  base64ToUtf8,
@@ -58,33 +63,25 @@ import {
58
63
  validateTheme,
59
64
  withAlpha
60
65
  } from "./chunk-WNEH3BZP.js";
61
- import {
62
- mediaEditValuesFromAttributes,
63
- mediaEditValuesFromParams,
64
- parseMediaEdits
65
- } from "./chunk-L7QZORLB.js";
66
66
  import {
67
67
  TABLE_FED_TEMPLATES,
68
68
  isContainerTemplate,
69
- matchTrailingTemplateAnnotation,
70
- resolveTemplateName,
71
- splitKeyValueToken,
72
- tokenizeAttrTokens
73
- } from "./chunk-Y25EJEZN.js";
69
+ resolveTemplateName
70
+ } from "./chunk-DOOUVVOM.js";
74
71
  import {
75
72
  KNOWN_BLOCK_META_KEYS,
76
73
  coerceAnnotationValues,
77
74
  extractPlainText,
78
75
  isSvgIslandLang,
79
76
  isVideoEmbedUrl,
77
+ matchTrailingTemplateAnnotation,
80
78
  mediaKindForUrl,
81
79
  parseNumber,
82
80
  parseSvgIsland,
83
- parseTimeSeconds,
84
81
  parseVideoEmbedUrl,
85
82
  readFrontmatterThemeId,
86
83
  walkMarkdownTree
87
- } from "./chunk-HE3HQZWU.js";
84
+ } from "./chunk-6NWVLLMI.js";
88
85
  import {
89
86
  resolveBlockTransition
90
87
  } from "./chunk-P53Z4RYC.js";
@@ -2965,6 +2962,17 @@ function rightFeature(input, context) {
2965
2962
  return buildFeatureLayers(input, context, "right");
2966
2963
  }
2967
2964
 
2965
+ // src/doc/templates/mediaFeatureBlock.ts
2966
+ function topFeature(input, context) {
2967
+ return contentBlock({ ...input, template: "content" }, context, { omitPictures: true });
2968
+ }
2969
+ function bottomFeature(input, context) {
2970
+ return contentBlock({ ...input, template: "content" }, context, { omitPictures: true });
2971
+ }
2972
+ function mediaOnly(input, context) {
2973
+ return contentBlock({ ...input, template: "content" }, context);
2974
+ }
2975
+
2968
2976
  // src/doc/templates/mapBlock.ts
2969
2977
  function mapBlock(input, context) {
2970
2978
  const {
@@ -5524,6 +5532,30 @@ var BLOCK_MEDIA_LAYOUT_POLICIES = {
5524
5532
  additionalMediaLayout: "overlay-inset",
5525
5533
  unconsumedMedia: "retain-native-layout"
5526
5534
  },
5535
+ topFeature: {
5536
+ summary: "Body media spans the full width across the top; the heading and complete body take a band below sized to the text. Without media it is a content slide.",
5537
+ noMedia: "template-default",
5538
+ ownership: "native-slot",
5539
+ nativeLayout: "feature-top",
5540
+ additionalMediaLayout: "overlay-inset",
5541
+ unconsumedMedia: "retain-native-layout"
5542
+ },
5543
+ bottomFeature: {
5544
+ summary: "The heading and complete body take a band across the top sized to the text; body media spans the full width below. Without media it is a content slide.",
5545
+ noMedia: "template-default",
5546
+ ownership: "native-slot",
5547
+ nativeLayout: "feature-bottom",
5548
+ additionalMediaLayout: "overlay-inset",
5549
+ unconsumedMedia: "retain-native-layout"
5550
+ },
5551
+ mediaOnly: {
5552
+ summary: "Body media fills the whole slide with no text; without media the heading and body render as a content slide.",
5553
+ noMedia: "template-default",
5554
+ ownership: "native-slot",
5555
+ nativeLayout: "full-frame",
5556
+ additionalMediaLayout: "overlay-inset",
5557
+ unconsumedMedia: "retain-native-layout"
5558
+ },
5527
5559
  map: {
5528
5560
  summary: "The map canvas is the primary visual; other media must not reflow its geography.",
5529
5561
  noMedia: "intrinsic-visual",
@@ -6198,12 +6230,144 @@ function leastObstructedMediaRect(layers, viewport) {
6198
6230
  return score < bestScore ? candidate : best;
6199
6231
  });
6200
6232
  }
6233
+ var MEDIA_BANDS = {
6234
+ "feature-top": "top",
6235
+ "feature-bottom": "bottom",
6236
+ "full-frame": "full"
6237
+ };
6238
+ var BLOCK_GAP_EM = 0.55;
6239
+ function textLayer(layers, id) {
6240
+ const layer = layers.find((candidate) => candidate.id === id);
6241
+ return layer?.type === "text" ? layer : void 0;
6242
+ }
6243
+ function mediaBandLayout(layers, viewport, band, aspect) {
6244
+ const backdrop = layers.filter((layer) => isFullViewportBackground(layer, viewport));
6245
+ const title = textLayer(layers, "title");
6246
+ const body = textLayer(layers, "body");
6247
+ if (band === "full" || !title && !body) {
6248
+ return { layers: backdrop, mediaRect: rect(viewport, 0, 0, 1, 1), framed: false };
6249
+ }
6250
+ const { width, height } = viewport;
6251
+ const unit = Math.min(width, height);
6252
+ const margin = unit * 0.045;
6253
+ const gap = unit * 0.03;
6254
+ const innerGap = title && body ? unit * 0.02 : 0;
6255
+ const textWidth = width - margin * 2;
6256
+ const maxBandHeight = height * (body ? 0.4 : 0.2);
6257
+ const titleLineHeight = title?.content.style.lineHeight ?? 1.15;
6258
+ const titleBaseSize = Math.max(
6259
+ title?.content.style.fontSize ?? 0,
6260
+ Math.round((body?.content.style.fontSize ?? 0) * 1.2)
6261
+ );
6262
+ const titleWrapWidth = title ? Math.min(
6263
+ textWidth * 0.86,
6264
+ fitWrapWidth(
6265
+ title.content.text,
6266
+ titleBaseSize,
6267
+ textWidth,
6268
+ title.content.style.fontFamily ?? ""
6269
+ )
6270
+ ) : textWidth;
6271
+ const titleFit = title ? fitProse({
6272
+ text: title.content.text,
6273
+ baseFontSize: titleBaseSize,
6274
+ minFontSize: Math.min(titleBaseSize, unit * 0.03),
6275
+ maxWidthPx: titleWrapWidth,
6276
+ maxHeightPx: body ? maxBandHeight * 0.35 : maxBandHeight,
6277
+ lineHeight: titleLineHeight
6278
+ }) : void 0;
6279
+ const titleHeight = titleFit?.heightPx ?? 0;
6280
+ const bodyRoom = maxBandHeight - titleHeight - innerGap;
6281
+ const bodyLineHeight = body?.content.style.lineHeight ?? 1.35;
6282
+ const blockGaps = body ? (body.content.text.match(/\n[^\S\n]*\n/g) ?? []).length : 0;
6283
+ const bodyBaseSize = body?.content.style.fontSize ?? 0;
6284
+ const headroomLines = 1;
6285
+ const bodyText3 = body?.content.text.replace(/\n[^\S\n]*\n/g, "\n") ?? "";
6286
+ const bodyFit = body ? fitProse({
6287
+ text: bodyText3,
6288
+ baseFontSize: bodyBaseSize,
6289
+ minFontSize: Math.min(bodyBaseSize, unit * 0.016),
6290
+ // HTML wraps on real glyphs; wide ones (CJK, emoji) fill lines in
6291
+ // half the characters the estimate assumes, so count lines narrower.
6292
+ maxWidthPx: fitWrapWidth(
6293
+ bodyText3,
6294
+ bodyBaseSize,
6295
+ textWidth,
6296
+ body.content.style.fontFamily ?? ""
6297
+ ),
6298
+ maxHeightPx: bodyRoom - (blockGaps * BLOCK_GAP_EM + headroomLines * bodyLineHeight) * bodyBaseSize,
6299
+ lineHeight: bodyLineHeight
6300
+ }) : void 0;
6301
+ const bodyHeight = bodyFit ? Math.min(
6302
+ bodyRoom,
6303
+ bodyFit.heightPx + (blockGaps * BLOCK_GAP_EM + headroomLines * bodyLineHeight) * bodyFit.fontSize
6304
+ ) : 0;
6305
+ const bandHeight = titleHeight + innerGap + bodyHeight;
6306
+ const roomForMedia = height - margin - gap - bandHeight;
6307
+ const mediaHeight = aspect && Number.isFinite(aspect) && aspect > 0 ? Math.min(roomForMedia, width / aspect) : roomForMedia;
6308
+ const offset = (roomForMedia - mediaHeight) / 2;
6309
+ const mediaTop = band === "top" ? offset : margin + offset + bandHeight + gap;
6310
+ const bandTop = band === "top" ? mediaTop + mediaHeight + gap : margin + offset;
6311
+ const placed = [...backdrop];
6312
+ if (title && titleFit) {
6313
+ placed.push({
6314
+ ...title,
6315
+ position: {
6316
+ x: margin,
6317
+ y: bandTop,
6318
+ width: titleWrapWidth,
6319
+ height: titleHeight,
6320
+ anchor: "top-left"
6321
+ },
6322
+ content: {
6323
+ ...title.content,
6324
+ style: {
6325
+ ...title.content.style,
6326
+ fontSize: titleFit.fontSize,
6327
+ lineHeight: titleLineHeight,
6328
+ ...titleFit.maxLines ? { maxLines: titleFit.maxLines } : {}
6329
+ }
6330
+ }
6331
+ });
6332
+ }
6333
+ if (body && bodyFit) {
6334
+ placed.push({
6335
+ ...body,
6336
+ position: {
6337
+ x: margin,
6338
+ y: bandTop + titleHeight + innerGap,
6339
+ width: textWidth,
6340
+ height: bodyHeight,
6341
+ anchor: "top-left"
6342
+ },
6343
+ content: {
6344
+ ...body.content,
6345
+ style: {
6346
+ ...body.content.style,
6347
+ fontSize: bodyFit.fontSize,
6348
+ lineHeight: bodyLineHeight,
6349
+ ...bodyFit.maxLines ? { maxLines: bodyFit.maxLines } : {}
6350
+ }
6351
+ }
6352
+ });
6353
+ }
6354
+ return {
6355
+ layers: placed,
6356
+ mediaRect: { x: 0, y: mediaTop, width, height: mediaHeight },
6357
+ framed: false
6358
+ };
6359
+ }
6201
6360
  function resolveSupplementalMediaLayout(layers, template, viewport, mediaCount, aspectRatios, featureOptions = {}) {
6202
6361
  const policy = getBlockMediaLayoutPolicy(template);
6203
6362
  const shape = mediaShape(mediaCount, aspectRatios);
6204
6363
  const alreadyOwnsMedia = layers.some(
6205
6364
  (layer) => layer.type === "image" || layer.type === "video" || layer.type === "videoEmbed" || layer.type === "map" || layer.type === "mermaid" || layer.type === "svg"
6206
6365
  );
6366
+ const band = policy?.nativeLayout ? MEDIA_BANDS[policy.nativeLayout] : void 0;
6367
+ if (band && !alreadyOwnsMedia) {
6368
+ const aspect = mediaCount === 1 ? featureOptions.knownAspectRatios?.[0] : void 0;
6369
+ return mediaBandLayout(layers, viewport, band, aspect);
6370
+ }
6207
6371
  if (!alreadyOwnsMedia && (policy?.nativeLayout === "feature-left" || policy?.nativeLayout === "feature-right")) {
6208
6372
  const { position } = featureMediaSlot(
6209
6373
  policy.nativeLayout === "feature-left" ? "left" : "right",
@@ -7802,7 +7966,7 @@ function shapeLayers(s, x, y, w, h, context, defaultStroke, defaultFill, fontAdj
7802
7966
  const fill = s.fill ?? defaultFill;
7803
7967
  if (s.kind === "text") {
7804
7968
  out.push(
7805
- textLayer(
7969
+ textLayer2(
7806
7970
  `shape-${s.id}`,
7807
7971
  s.text ?? s.label ?? "",
7808
7972
  x + w / 2,
@@ -7861,7 +8025,7 @@ function shapeLayers(s, x, y, w, h, context, defaultStroke, defaultFill, fontAdj
7861
8025
  }
7862
8026
  if (s.label) {
7863
8027
  out.push(
7864
- textLayer(
8028
+ textLayer2(
7865
8029
  `shape-label-${s.id}`,
7866
8030
  s.label,
7867
8031
  x + w / 2,
@@ -7876,7 +8040,7 @@ function shapeLayers(s, x, y, w, h, context, defaultStroke, defaultFill, fontAdj
7876
8040
  }
7877
8041
  if (s.sublabel) {
7878
8042
  out.push(
7879
- textLayer(
8043
+ textLayer2(
7880
8044
  `shape-sublabel-${s.id}`,
7881
8045
  s.sublabel,
7882
8046
  x + w / 2,
@@ -7891,7 +8055,7 @@ function shapeLayers(s, x, y, w, h, context, defaultStroke, defaultFill, fontAdj
7891
8055
  }
7892
8056
  return out;
7893
8057
  }
7894
- function textLayer(id, text, cx, cy, width, context, color, bold, fontAdj) {
8058
+ function textLayer2(id, text, cx, cy, width, context, color, bold, fontAdj) {
7895
8059
  return {
7896
8060
  type: "text",
7897
8061
  id,
@@ -8436,6 +8600,9 @@ var templateRegistry = {
8436
8600
  imageWithCaption,
8437
8601
  leftFeature,
8438
8602
  rightFeature,
8603
+ topFeature,
8604
+ bottomFeature,
8605
+ mediaOnly,
8439
8606
  map: mapBlock,
8440
8607
  fullBleedQuote,
8441
8608
  list: listBlock,
@@ -8754,6 +8921,9 @@ var TEMPLATE_INPUT_DESCRIPTORS = {
8754
8921
  ],
8755
8922
  leftFeature: FEATURE_DESCRIPTORS(),
8756
8923
  rightFeature: FEATURE_DESCRIPTORS(),
8924
+ topFeature: [{ key: "title", description: "Heading shown with the body under the media" }],
8925
+ bottomFeature: [{ key: "title", description: "Heading shown with the body above the media" }],
8926
+ mediaOnly: [{ key: "title", description: "Heading, shown only when the block has no media" }],
8757
8927
  map: [
8758
8928
  {
8759
8929
  key: "center",
@@ -9450,11 +9620,13 @@ function appendRichContentLayers(layers, block, theme, viewport, mediaAspectRati
9450
9620
  const timedLayers = applyEmbeddedVideoTiming(layers, block);
9451
9621
  const items = collectRichMediaItems(timedLayers, block);
9452
9622
  if (items.length === 0) return timedLayers;
9453
- const aspects = items.map((item) => {
9623
+ const knownAspects = items.map((item) => {
9454
9624
  const key = item.kind === "image" || item.kind === "video" ? item.src : item.kind === "mermaid" ? item.source : void 0;
9455
9625
  const intrinsic = key ? mediaAspectRatios?.[key] : void 0;
9456
- return intrinsic && Number.isFinite(intrinsic) && intrinsic > 0 ? intrinsic : item.aspectRatio;
9626
+ if (intrinsic && Number.isFinite(intrinsic) && intrinsic > 0) return intrinsic;
9627
+ return item.kind === "video" ? void 0 : item.aspectRatio;
9457
9628
  });
9629
+ const aspects = items.map((item, index) => knownAspects[index] ?? item.aspectRatio);
9458
9630
  const layout = block.summaryLayout === "feature" ? resolveFeatureSummaryLayout(timedLayers, viewport, aspects) : resolveSupplementalMediaLayout(
9459
9631
  timedLayers,
9460
9632
  block.template ? resolveTemplateName(block.template) : void 0,
@@ -9463,7 +9635,8 @@ function appendRichContentLayers(layers, block, theme, viewport, mediaAspectRati
9463
9635
  aspects,
9464
9636
  {
9465
9637
  summaryFeature: block.summaryMode === "headings-and-features" && !timedLayers.some((layer) => layer.id === "feature-body"),
9466
- stackColumns: createTemplateContext(theme, 0, 1, viewport).layout.stackColumns
9638
+ stackColumns: createTemplateContext(theme, 0, 1, viewport).layout.stackColumns,
9639
+ knownAspectRatios: knownAspects
9467
9640
  }
9468
9641
  );
9469
9642
  const cells = gridCells(layout.mediaRect, items.length, viewport, layout.framed);
@@ -9625,6 +9798,18 @@ var TEMPLATE_METADATA = {
9625
9798
  label: "Right Feature",
9626
9799
  description: "Image on the right, title and body text stacked on the left."
9627
9800
  },
9801
+ topFeature: {
9802
+ label: "Top Feature",
9803
+ description: "Image or video across the full width at the top, title and body text below."
9804
+ },
9805
+ bottomFeature: {
9806
+ label: "Bottom Feature",
9807
+ description: "Title and body text at the top, image or video across the full width below."
9808
+ },
9809
+ mediaOnly: {
9810
+ label: "Media Only",
9811
+ description: "The image or video fills the slide, with no title, text or caption."
9812
+ },
9628
9813
  map: {
9629
9814
  label: "Map",
9630
9815
  description: "Embeds an interactive map centered on a geographic location."
@@ -9790,6 +9975,24 @@ var TEMPLATE_AUTHORING_METADATA = {
9790
9975
  safeForContentFirst: false,
9791
9976
  placement: "heading"
9792
9977
  },
9978
+ topFeature: {
9979
+ role: "media",
9980
+ bodyPolicy: "complete",
9981
+ safeForContentFirst: false,
9982
+ placement: "heading"
9983
+ },
9984
+ bottomFeature: {
9985
+ role: "media",
9986
+ bodyPolicy: "complete",
9987
+ safeForContentFirst: false,
9988
+ placement: "heading"
9989
+ },
9990
+ mediaOnly: {
9991
+ role: "media",
9992
+ bodyPolicy: "ignored",
9993
+ safeForContentFirst: false,
9994
+ placement: "heading"
9995
+ },
9793
9996
  map: {
9794
9997
  role: "spatial",
9795
9998
  bodyPolicy: "structured",
@@ -10896,193 +11099,6 @@ function extractTableData(table) {
10896
11099
  };
10897
11100
  }
10898
11101
 
10899
- // src/doc/standaloneAnnotation.ts
10900
- function parseAnnotationInner(inner) {
10901
- const tokens = tokenizeAttrTokens(inner.trim());
10902
- const firstIsParam = tokens.length > 0 && tokens[0].indexOf("=") > 0;
10903
- const template = firstIsParam || tokens.length === 0 ? void 0 : tokens[0];
10904
- const startIdx = firstIsParam ? 0 : 1;
10905
- const params = {};
10906
- for (let i = startIdx; i < tokens.length; i++) {
10907
- const kv = splitKeyValueToken(tokens[i]);
10908
- if (kv) params[kv.key] = kv.value;
10909
- }
10910
- return { template, params };
10911
- }
10912
- function parseStandaloneAnnotation(node) {
10913
- if (node.type !== "paragraph") return null;
10914
- const children = node.children ?? [];
10915
- if (children.length !== 1 || children[0].type !== "text") return null;
10916
- const text = children[0].value.trim();
10917
- const match = matchTrailingTemplateAnnotation(text);
10918
- if (!match || match.index !== 0) return null;
10919
- return parseAnnotationInner(match.inner);
10920
- }
10921
-
10922
- // src/doc/mediaAnnotations.ts
10923
- var MEDIA_TEMPLATES = /* @__PURE__ */ new Set(["audio", "video", "media"]);
10924
- function isMediaAnnotationName(name) {
10925
- return name != null && MEDIA_TEMPLATES.has(name);
10926
- }
10927
- function time(raw) {
10928
- if (raw == null) return null;
10929
- return parseTimeSeconds(raw);
10930
- }
10931
- function videoPlacement(params) {
10932
- const explicit = params.placement ?? params.presentation;
10933
- if (explicit === "content") return "content";
10934
- if (explicit === "pip" || explicit === "picture-in-picture") return "picture-in-picture";
10935
- if (explicit === "overlay" || explicit === "full-frame") return "overlay";
10936
- if (params.pip === "" || params.pip === "true") return "picture-in-picture";
10937
- if (params.overlay === "" || params.overlay === "true") return "overlay";
10938
- return void 0;
10939
- }
10940
- function lockToBlock(raw) {
10941
- return raw !== "false" && raw !== "0";
10942
- }
10943
- function videoPipSize(raw) {
10944
- return raw === "small" || raw === "large" ? raw : void 0;
10945
- }
10946
- function videoPipShape(raw) {
10947
- return raw === "square" || raw === "wide" ? raw : void 0;
10948
- }
10949
- function videoPipPosition(raw) {
10950
- return raw === "top-left" || raw === "top-right" || raw === "bottom-left" || raw === "bottom-right" ? raw : void 0;
10951
- }
10952
- function toMediaClip(parsed, id) {
10953
- const { template, params } = parsed;
10954
- if (!template || !MEDIA_TEMPLATES.has(template)) return null;
10955
- const src = params.src;
10956
- if (!src) return null;
10957
- const kind = template === "video" ? "video" : "audio";
10958
- const anchor = params.anchor === "document" || params.span === "document" ? "document" : "block";
10959
- const clip = {
10960
- id,
10961
- src,
10962
- kind,
10963
- startAt: time(params.startAt ?? params.startTime) ?? 0,
10964
- anchor
10965
- };
10966
- const placement = kind === "video" ? videoPlacement(params) : void 0;
10967
- if (placement && placement !== "content") clip.placement = placement;
10968
- const pipSize = videoPipSize(params.pipSize);
10969
- const pipShape = videoPipShape(params.pipShape);
10970
- const pipPosition = videoPipPosition(params.pipPosition);
10971
- if (pipSize) clip.pipSize = pipSize;
10972
- if (pipShape) clip.pipShape = pipShape;
10973
- if (pipPosition) clip.pipPosition = pipPosition;
10974
- if (kind === "video" && placement && placement !== "content" && params.lockToBlock != null) {
10975
- const locked = lockToBlock(params.lockToBlock);
10976
- clip.lockToBlock = locked;
10977
- clip.anchor = locked ? "block" : "document";
10978
- if (locked) clip.startAt = 0;
10979
- }
10980
- const clipStart = time(params.clipStart);
10981
- const clipEnd = time(params.clipEnd);
10982
- if (clipStart != null) clip.clipStart = clipStart;
10983
- if (clipEnd != null) clip.clipEnd = clipEnd;
10984
- if (clip.lockToBlock === true) delete clip.clipEnd;
10985
- if (params.spillover === "true" || params.spillover === "") clip.spillover = true;
10986
- const edits = parseMediaEdits(mediaEditValuesFromParams(params));
10987
- if (edits) clip.edits = edits;
10988
- return clip;
10989
- }
10990
- function firstVideoElement(children) {
10991
- const visit = (nodes) => {
10992
- for (const child of nodes) {
10993
- if (child.type !== "htmlElement") continue;
10994
- if (child.tagName === "video") return child;
10995
- const nested = visit(child.children);
10996
- if (nested) return nested;
10997
- }
10998
- return null;
10999
- };
11000
- return visit(children);
11001
- }
11002
- function standaloneHtmlVideo(node) {
11003
- if (node.type === "htmlBlock") {
11004
- if (!/^\s*<video\b/i.test(node.rawHtml)) return null;
11005
- const element = firstVideoElement(node.htmlChildren);
11006
- return element ? { element, raw: node.rawHtml.trim() } : null;
11007
- }
11008
- if (node.type !== "paragraph" || !node.children.every((child) => child.type === "htmlInline")) {
11009
- return null;
11010
- }
11011
- const raw = node.children.map((child) => child.type === "htmlInline" ? child.rawHtml : "").join("");
11012
- if (!/^\s*<video\b[\s\S]*<\/video>\s*$/i.test(raw)) return null;
11013
- for (const child of node.children) {
11014
- if (child.type !== "htmlInline") continue;
11015
- const element = firstVideoElement(child.htmlChildren);
11016
- if (element) return { element, raw: raw.trim() };
11017
- }
11018
- return null;
11019
- }
11020
- function htmlVideoClip(node, id) {
11021
- const authored = standaloneHtmlVideo(node);
11022
- if (!authored) return null;
11023
- const { element } = authored;
11024
- const placement = videoPlacement({
11025
- placement: element.attributes["data-squisq-video-placement"] ?? ""
11026
- });
11027
- if (!placement || placement === "content") return null;
11028
- const src = element.attributes.src;
11029
- if (!src) return null;
11030
- const locked = lockToBlock(element.attributes["data-squisq-video-lock-to-block"]);
11031
- const clip = {
11032
- id,
11033
- src,
11034
- kind: "video",
11035
- placement,
11036
- lockToBlock: locked,
11037
- startAt: locked ? 0 : time(element.attributes["data-squisq-video-start-at"]) ?? 0,
11038
- anchor: locked ? "block" : "document"
11039
- };
11040
- const pipSize = videoPipSize(element.attributes["data-squisq-video-pip-size"]);
11041
- const pipShape = videoPipShape(element.attributes["data-squisq-video-pip-shape"]);
11042
- const pipPosition = videoPipPosition(element.attributes["data-squisq-video-pip-position"]);
11043
- if (pipSize) clip.pipSize = pipSize;
11044
- if (pipShape) clip.pipShape = pipShape;
11045
- if (pipPosition) clip.pipPosition = pipPosition;
11046
- const clipStart = time(element.attributes["data-squisq-video-clip-start"]);
11047
- const clipEnd = time(element.attributes["data-squisq-video-clip-end"]);
11048
- if (clipStart != null) clip.clipStart = clipStart;
11049
- if (!locked && clipEnd != null) clip.clipEnd = clipEnd;
11050
- const edits = parseMediaEdits(mediaEditValuesFromAttributes("video", element.attributes));
11051
- if (edits) clip.edits = edits;
11052
- return clip;
11053
- }
11054
- function extractMediaFromContents(contents, makeId, blockId) {
11055
- const media = [];
11056
- const documentMedia = [];
11057
- const remaining = [];
11058
- let index = 0;
11059
- let originalIndex = 0;
11060
- for (const node of contents ?? []) {
11061
- const parsed = parseStandaloneAnnotation(node);
11062
- const clip = parsed ? toMediaClip(parsed, "") : htmlVideoClip(node, "");
11063
- if (clip) {
11064
- clip.id = makeId(clip.src, index++);
11065
- const line = node.position?.start.line;
11066
- if (line != null) clip.sourceLine = line;
11067
- if (blockId !== void 0) {
11068
- const authoredHtml = standaloneHtmlVideo(node);
11069
- const rawText = node.type === "paragraph" && node.children?.[0]?.type === "text" ? node.children[0].value.trim() : authoredHtml?.raw;
11070
- clip.origin = {
11071
- blockId,
11072
- index: originalIndex,
11073
- ...rawText !== void 0 ? { raw: rawText } : {},
11074
- ...authoredHtml ? { format: "html" } : {}
11075
- };
11076
- }
11077
- (clip.anchor === "document" ? documentMedia : media).push(clip);
11078
- } else {
11079
- remaining.push(node);
11080
- }
11081
- originalIndex++;
11082
- }
11083
- return { media, documentMedia, remaining };
11084
- }
11085
-
11086
11102
  // src/doc/annotationBlocks.ts
11087
11103
  function applyBlockMeta(block, m) {
11088
11104
  if (m.build) block.build = m.build;
@@ -12084,7 +12100,7 @@ function resolveBuildTimeline(steps, spec, duration, timing) {
12084
12100
  let consumed = 0;
12085
12101
  let previous = 0;
12086
12102
  const matched = timing?.sourceText === buildSourceText(steps) && timing.starts.length === steps.length && timing.starts.every(
12087
- (time2, i) => Number.isFinite(time2) && time2 >= 0 && (i === 0 || time2 >= timing.starts[i - 1])
12103
+ (time, i) => Number.isFinite(time) && time >= 0 && (i === 0 || time >= timing.starts[i - 1])
12088
12104
  );
12089
12105
  const starts = steps.map((_, i) => {
12090
12106
  const automatic = intro + Math.max(0, latest - intro) * consumed / Math.max(1, total);
@@ -12101,15 +12117,15 @@ function resolveBuildTimeline(steps, spec, duration, timing) {
12101
12117
  );
12102
12118
  return { starts, entrance, compressed: steps.length > 0 && end < steps.length * 0.8 };
12103
12119
  }
12104
- function buildCursorAtTime(timeline, time2) {
12120
+ function buildCursorAtTime(timeline, time) {
12105
12121
  let revealed = 0;
12106
12122
  for (const start of timeline.starts) {
12107
- if (time2 < start) break;
12123
+ if (time < start) break;
12108
12124
  revealed++;
12109
12125
  }
12110
12126
  return {
12111
12127
  revealed,
12112
- progress: revealed === 0 ? 0 : timeline.entrance === 0 ? 1 : Math.min(1, Math.max(0, (time2 - timeline.starts[revealed - 1]) / timeline.entrance))
12128
+ progress: revealed === 0 ? 0 : timeline.entrance === 0 ? 1 : Math.min(1, Math.max(0, (time - timeline.starts[revealed - 1]) / timeline.entrance))
12113
12129
  };
12114
12130
  }
12115
12131
  function buildTargetState(index, steps, cursor, spec, motion = true) {
@@ -12180,13 +12196,13 @@ function resolveDocBuildTiming(doc, blocks) {
12180
12196
  }
12181
12197
 
12182
12198
  // src/doc/templates/contentBlock.ts
12183
- function bodyNodes(context) {
12199
+ function bodyNodes(context, omitPictures) {
12184
12200
  if (context.block?.summaryMode === "headings-and-features") return [];
12185
12201
  return (context.block?.contents ?? []).filter(
12186
12202
  // Mermaid source and hosted-video links are already materialized as
12187
12203
  // visual layers (a diagram, a player). Repeating them as prose makes the
12188
12204
  // loss-averse default noisy without preserving any additional meaning.
12189
- (node) => !isMaterializedMediaNode(node)
12205
+ (node) => !isMaterializedMediaNode(node) && !(omitPictures && isPictureParagraph(node))
12190
12206
  );
12191
12207
  }
12192
12208
  function listParagraphs(list, level = 0) {
@@ -12231,10 +12247,10 @@ function bodyFontSize(body, context) {
12231
12247
  const base = body.length > 1800 ? 18 : body.length > 1100 ? 21 : body.length > 650 ? 24 : 28;
12232
12248
  return themedFontSize(base, context, false);
12233
12249
  }
12234
- function contentBlock(input, context) {
12250
+ function contentBlock(input, context, options = {}) {
12235
12251
  const { theme } = context;
12236
12252
  const title = input.title ?? context.block?.title ?? "";
12237
- const markdown = bodyNodes(context);
12253
+ const markdown = bodyNodes(context, options.omitPictures === true);
12238
12254
  const paragraphs = completeBodyParagraphs(markdown);
12239
12255
  const body = paragraphs.map(plainParagraphText).join("\n");
12240
12256
  const builds = planContentBuilds(markdown, input.build ?? context.block?.build);
@@ -12336,6 +12352,9 @@ export {
12336
12352
  imageWithCaption,
12337
12353
  leftFeature,
12338
12354
  rightFeature,
12355
+ topFeature,
12356
+ bottomFeature,
12357
+ mediaOnly,
12339
12358
  mapBlock,
12340
12359
  fullBleedQuote,
12341
12360
  listBlock,
@@ -12392,7 +12411,6 @@ export {
12392
12411
  parseYamlSubset,
12393
12412
  findFirstTable,
12394
12413
  extractTableData,
12395
- parseStandaloneAnnotation,
12396
12414
  getPinnedBlockMeta,
12397
12415
  markdownToDoc,
12398
12416
  flattenBlocks,