@bendyline/squisq 2.8.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  expectedSyllablesAt,
3
3
  wordPosAtExpectedSyllables
4
- } from "./chunk-V6NV4GDE.js";
4
+ } from "./chunk-W2GIFNLN.js";
5
5
 
6
6
  // src/narration/types.ts
7
7
  var DEFAULT_FEATURE_CONFIG = Object.freeze({
@@ -30,7 +30,7 @@ import {
30
30
  templateRegistry,
31
31
  writeCustomTemplatesToFrontmatter,
32
32
  writeCustomThemesToFrontmatter
33
- } from "./chunk-V6NV4GDE.js";
33
+ } from "./chunk-W2GIFNLN.js";
34
34
  import {
35
35
  iconMarker
36
36
  } from "./chunk-7ZAAICW4.js";
@@ -3286,8 +3286,8 @@ function dedupeLeadingTitleBlock(candidates, titleText) {
3286
3286
  if (!firstTitle || firstTitle !== titleText.trim().toLowerCase()) return candidates;
3287
3287
  const template = first.template;
3288
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;
3289
+ const hasBody2 = Array.isArray(first.contents) && first.contents.length > 0;
3290
+ return titleShaped || !hasBody2 ? candidates.slice(1) : candidates;
3291
3291
  }
3292
3292
  function buildTitleLayers(titleText, bandViewport, slot, theme, canvasViewport, margin) {
3293
3293
  const context = createTemplateContext(theme, 0, 1, canvasViewport);
@@ -3632,6 +3632,272 @@ function composeDashboardLayers(materialization) {
3632
3632
  return layers;
3633
3633
  }
3634
3634
 
3635
+ // src/doc/flashcards/materializeFlashcards.ts
3636
+ var GENERIC_ANSWER_TITLES = /* @__PURE__ */ new Set([
3637
+ "answer",
3638
+ "back",
3639
+ "solution",
3640
+ "explanation",
3641
+ "correct answer",
3642
+ "correct",
3643
+ "distractor",
3644
+ "option"
3645
+ ]);
3646
+ function normalizeMarker(value) {
3647
+ return typeof value === "string" ? value.trim().toLowerCase().replace(/[\s_-]+/g, "") : "";
3648
+ }
3649
+ function markerFromValue(value) {
3650
+ const normalized = normalizeMarker(value);
3651
+ if (normalized === "flashcard" || normalized === "card" || normalized === "basicflashcard") {
3652
+ return "basic";
3653
+ }
3654
+ if (normalized === "multiplechoiceflashcard" || normalized === "multiplechoice" || normalized === "quiz") {
3655
+ return "multiple-choice";
3656
+ }
3657
+ if (normalized === "group" || normalized === "flashcardgroup" || normalized === "deck") {
3658
+ return "group";
3659
+ }
3660
+ return void 0;
3661
+ }
3662
+ function resolveFlashcardMarker(block) {
3663
+ const metadataMarker = markerFromValue(block.metadata?.study) ?? markerFromValue(block.metadata?.["study-mode"]);
3664
+ if (metadataMarker) return metadataMarker;
3665
+ for (const className of block.classes ?? []) {
3666
+ const classMarker = markerFromValue(className);
3667
+ if (classMarker) return classMarker;
3668
+ }
3669
+ return markerFromValue(block.template);
3670
+ }
3671
+ function hasTitle(block) {
3672
+ return typeof block.title === "string" && block.title.trim().length > 0;
3673
+ }
3674
+ function hasBody(block) {
3675
+ return (block.contents?.length ?? 0) > 0 || (block.media?.length ?? 0) > 0 || (block.layers?.length ?? 0) > 0;
3676
+ }
3677
+ function hasOwnContent(block) {
3678
+ return hasTitle(block) || hasBody(block);
3679
+ }
3680
+ function isGenericAnswerBlock(block) {
3681
+ return hasTitle(block) && GENERIC_ANSWER_TITLES.has(block.title.trim().toLowerCase());
3682
+ }
3683
+ function selfOnly(block) {
3684
+ const { children: _children, ...self } = block;
3685
+ return self;
3686
+ }
3687
+ function titleOnly(block) {
3688
+ const {
3689
+ children: _children,
3690
+ contents: _contents,
3691
+ media: _media,
3692
+ layers: _layers,
3693
+ ...title2
3694
+ } = block;
3695
+ return title2;
3696
+ }
3697
+ function bodyOnly(block) {
3698
+ const { children: _children, title: _title, ...body } = block;
3699
+ return body;
3700
+ }
3701
+ function face(...blocks) {
3702
+ return { blocks };
3703
+ }
3704
+ function isTrue(value) {
3705
+ if (value === true) return true;
3706
+ if (typeof value !== "string") return false;
3707
+ return ["true", "yes", "on", "1", "correct"].includes(value.trim().toLowerCase());
3708
+ }
3709
+ function isCorrectChoice(block) {
3710
+ return isTrue(block.metadata?.correct) || isTrue(block.metadata?.answer) || (block.classes ?? []).some((className) => normalizeMarker(className) === "correct");
3711
+ }
3712
+ function looksLikeCompleteCard(block) {
3713
+ const marker = resolveFlashcardMarker(block);
3714
+ if (marker === "group") return false;
3715
+ if (marker === "basic" || marker === "multiple-choice") return true;
3716
+ if (isGenericAnswerBlock(block)) return false;
3717
+ const children = block.children ?? [];
3718
+ if (children.length === 0) return hasTitle(block) && hasBody(block);
3719
+ if (children.length === 1) {
3720
+ return hasOwnContent(block) && hasOwnContent(children[0]);
3721
+ }
3722
+ return children.every(hasOwnContent);
3723
+ }
3724
+ function hasExplicitCardDescendant(block) {
3725
+ for (const child of block.children ?? []) {
3726
+ const marker = resolveFlashcardMarker(child);
3727
+ if (marker === "basic" || marker === "multiple-choice") return true;
3728
+ if (hasExplicitCardDescendant(child)) return true;
3729
+ }
3730
+ return false;
3731
+ }
3732
+ function shouldTreatAsImplicitGroup(block) {
3733
+ const children = block.children ?? [];
3734
+ if (children.length === 0) return false;
3735
+ if (hasExplicitCardDescendant(block)) return true;
3736
+ if (hasBody(block)) return false;
3737
+ if (children.length === 1) {
3738
+ return looksLikeCompleteCard(children[0]) && !isGenericAnswerBlock(children[0]);
3739
+ }
3740
+ return children.filter(looksLikeCompleteCard).length >= 2;
3741
+ }
3742
+ function explanationFor(block) {
3743
+ return hasBody(block) ? face(bodyOnly(block)) : void 0;
3744
+ }
3745
+ function materializeBasic(block, diagnostics) {
3746
+ const children = block.children ?? [];
3747
+ let front;
3748
+ let back;
3749
+ let label;
3750
+ let explanation;
3751
+ if (children.length === 0) {
3752
+ front = hasTitle(block) ? face(titleOnly(block)) : face();
3753
+ back = hasBody(block) ? face(bodyOnly(block)) : face();
3754
+ } else if (children.length === 1) {
3755
+ front = hasOwnContent(block) ? face(selfOnly(block)) : face();
3756
+ back = hasOwnContent(children[0]) ? face(children[0]) : face();
3757
+ } else {
3758
+ front = hasOwnContent(children[0]) ? face(children[0]) : face();
3759
+ back = face(...children.slice(1).filter(hasOwnContent));
3760
+ label = hasTitle(block) ? block.title.trim() : void 0;
3761
+ explanation = explanationFor(block);
3762
+ }
3763
+ if (front.blocks.length === 0) {
3764
+ diagnostics.push({
3765
+ severity: "error",
3766
+ code: "empty-front",
3767
+ message: `Flashcard "${block.id}" has no content for its front`,
3768
+ blockId: block.id
3769
+ });
3770
+ }
3771
+ if (back.blocks.length === 0) {
3772
+ diagnostics.push({
3773
+ severity: "error",
3774
+ code: "empty-back",
3775
+ message: `Flashcard "${block.id}" has no content for its answer`,
3776
+ blockId: block.id
3777
+ });
3778
+ }
3779
+ if (front.blocks.length === 0 || back.blocks.length === 0) return void 0;
3780
+ return {
3781
+ id: block.id,
3782
+ sourceBlockId: block.id,
3783
+ kind: "basic",
3784
+ ...label ? { label } : {},
3785
+ front,
3786
+ back,
3787
+ ...explanation ? { explanation } : {}
3788
+ };
3789
+ }
3790
+ function materializeMultipleChoice(block, diagnostics) {
3791
+ const children = block.children ?? [];
3792
+ const question = children[0];
3793
+ const answerBlocks = children.slice(1).filter(hasOwnContent);
3794
+ if (!question || !hasOwnContent(question)) {
3795
+ diagnostics.push({
3796
+ severity: "error",
3797
+ code: "empty-front",
3798
+ message: `Multiple-choice flashcard "${block.id}" has no question block`,
3799
+ blockId: block.id
3800
+ });
3801
+ }
3802
+ if (answerBlocks.length === 0) {
3803
+ diagnostics.push({
3804
+ severity: "error",
3805
+ code: "empty-back",
3806
+ message: `Multiple-choice flashcard "${block.id}" has no answer choices`,
3807
+ blockId: block.id
3808
+ });
3809
+ }
3810
+ if (!question || !hasOwnContent(question) || answerBlocks.length === 0) return void 0;
3811
+ const explicitlyCorrect = answerBlocks.filter(isCorrectChoice);
3812
+ if (explicitlyCorrect.length > 1) {
3813
+ diagnostics.push({
3814
+ severity: "warning",
3815
+ code: "multiple-correct-answers",
3816
+ message: `Multiple-choice flashcard "${block.id}" marks more than one answer correct; the first marker wins`,
3817
+ blockId: block.id
3818
+ });
3819
+ }
3820
+ const correctBlock = explicitlyCorrect[0] ?? answerBlocks[0];
3821
+ if (answerBlocks.length < 2) {
3822
+ diagnostics.push({
3823
+ severity: "warning",
3824
+ code: "multiple-choice-needs-distractor",
3825
+ message: `Multiple-choice flashcard "${block.id}" needs at least one distractor`,
3826
+ blockId: block.id
3827
+ });
3828
+ }
3829
+ const choices = answerBlocks.map((answer, index) => ({
3830
+ id: `${block.id}-choice-${index + 1}`,
3831
+ sourceBlockId: answer.id,
3832
+ content: face(answer),
3833
+ correct: answer === correctBlock
3834
+ }));
3835
+ const explanation = explanationFor(block);
3836
+ return {
3837
+ id: block.id,
3838
+ sourceBlockId: block.id,
3839
+ kind: "multiple-choice",
3840
+ ...hasTitle(block) ? { label: block.title.trim() } : {},
3841
+ front: face(question),
3842
+ back: face(correctBlock),
3843
+ choices,
3844
+ ...explanation ? { explanation } : {}
3845
+ };
3846
+ }
3847
+ function resolveDeckTitle(doc) {
3848
+ const frontmatterTitle = doc.frontmatter?.title;
3849
+ if (typeof frontmatterTitle === "string" && frontmatterTitle.trim()) {
3850
+ return frontmatterTitle.trim();
3851
+ }
3852
+ return doc.startBlock?.title?.trim() || void 0;
3853
+ }
3854
+ function materializeFlashcards(doc, options = {}) {
3855
+ const source = options.source ?? "auto";
3856
+ const cards = [];
3857
+ const diagnostics = [];
3858
+ const visit = (block) => {
3859
+ const marker = resolveFlashcardMarker(block);
3860
+ if (marker === "group") {
3861
+ for (const child of block.children ?? []) visit(child);
3862
+ return;
3863
+ }
3864
+ if (marker === "basic") {
3865
+ const card = materializeBasic(block, diagnostics);
3866
+ if (card) cards.push(card);
3867
+ return;
3868
+ }
3869
+ if (marker === "multiple-choice") {
3870
+ const card = materializeMultipleChoice(block, diagnostics);
3871
+ if (card) cards.push(card);
3872
+ return;
3873
+ }
3874
+ if (source === "explicit") {
3875
+ for (const child of block.children ?? []) visit(child);
3876
+ return;
3877
+ }
3878
+ if (shouldTreatAsImplicitGroup(block)) {
3879
+ for (const child of block.children ?? []) visit(child);
3880
+ return;
3881
+ }
3882
+ if (looksLikeCompleteCard(block)) {
3883
+ const card = materializeBasic(block, diagnostics);
3884
+ if (card) cards.push(card);
3885
+ return;
3886
+ }
3887
+ for (const child of block.children ?? []) visit(child);
3888
+ };
3889
+ for (const block of doc.blocks) visit(block);
3890
+ const resolvedTitle = resolveDeckTitle(doc);
3891
+ const authoredTitle = typeof doc.frontmatter?.title === "string" ? doc.frontmatter.title.trim() : "";
3892
+ const firstCardTitle = cards[0]?.front.blocks[0]?.title?.trim();
3893
+ const title2 = resolvedTitle && (authoredTitle || resolvedTitle !== firstCardTitle) ? resolvedTitle : void 0;
3894
+ return {
3895
+ ...title2 ? { title: title2 } : {},
3896
+ cards,
3897
+ diagnostics
3898
+ };
3899
+ }
3900
+
3635
3901
  // src/doc/applyNarrationTiming.ts
3636
3902
  var MIN_BLOCK_SIMILARITY = 0.5;
3637
3903
  var MIN_V1_SCRIPT_SIMILARITY = 0.8;
@@ -5603,6 +5869,8 @@ export {
5603
5869
  resolveDashboardSettings,
5604
5870
  materializeDashboard,
5605
5871
  composeDashboardLayers,
5872
+ resolveFlashcardMarker,
5873
+ materializeFlashcards,
5606
5874
  applyNarrationTiming,
5607
5875
  scoreTextSimilarity,
5608
5876
  resolveAudioMapping,