From 2055965ad73a9aae404660c1fa59be5d2eeb179c Mon Sep 17 00:00:00 2001 From: Anders Weinstein Date: Fri, 24 Oct 2025 17:50:29 -0400 Subject: [PATCH 1/4] popups: make audio-only play on click; stop empty meaning pre-empting translation --- src/resources/common.ts | 2 ++ src/resources/pool.ts | 17 +++++------------ src/resources/questions/common.ts | 9 +++++++++ src/utils/xml.ts | 10 ++++++---- 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/resources/common.ts b/src/resources/common.ts index 2a1b5ed..a4a48db 100644 --- a/src/resources/common.ts +++ b/src/resources/common.ts @@ -348,6 +348,8 @@ export function standardContentManipulations($: any) { DOM.renameAttribute($, 'video captions', 'srclang', 'language_code'); DOM.rename($, 'extra', 'popup'); + // simplifies handling within popup: + DOM.eliminateLevel($, 'meaning > material'); handleTheorems($); handleFormulaMathML($); diff --git a/src/resources/pool.ts b/src/resources/pool.ts index e607e3b..7da5529 100644 --- a/src/resources/pool.ts +++ b/src/resources/pool.ts @@ -11,6 +11,7 @@ import { getChildren, getDescendants, isBlankText, + emptyOrDummyContent, } from './questions/common'; import * as DOM from 'src/utils/dom'; import { replaceAll } from 'src/utils/common'; @@ -73,15 +74,15 @@ export class Pool extends Resource { const legacyId = pool.id; const tagId = pool.id; - let prefixContent: any[] = []; + let prefixContent: any; let poolQuestionNumber = 1; pool.children.forEach((c: any) => { if (c.type === 'content') { - prefixContent = c.children; + prefixContent = c; } else if (c.type !== 'title') { const subType = Formative.determineSubType(c); - if (!isEmptyContent(prefixContent)) - c.stem.content = [...prefixContent, c.stem.content]; + if (!emptyOrDummyContent(prefixContent)) + c.stem.content = [...prefixContent.children, c.stem.content]; const pooledActivity = Formative.toActivity( c, subType, @@ -133,14 +134,6 @@ export class Pool extends Resource { } } -const isEmptyContent = (c: any) => - // echo often fills in dummy placeholder paragraph with blank text piece - c.children === undefined || - c.children.length === 0 || - (c.children.length === 1 && - c.children[0].type === 'p' && - c.children[0].children.every(isBlankText)); - // // Convert a pool section which may contain multiple questions into // a single merged multi-input or response_multi question w/original diff --git a/src/resources/questions/common.ts b/src/resources/questions/common.ts index d1efab0..ca4d826 100644 --- a/src/resources/questions/common.ts +++ b/src/resources/questions/common.ts @@ -142,6 +142,15 @@ export function wrapLooseText(children: any, trace = false) { return children; } +// for content-bearing elements that may be filled in with dummy empty paragraph +// arg is containing parent that may have array of of content elements as children +export const emptyOrDummyContent = (c: any) => + c?.children === undefined || + c.children.length === 0 || + (c.children.length === 1 && + c.children[0].type === 'p' && + c.children[0].children.every(isBlankText)); + /* * Recursively iterate through children, finding any input_ref pointing at the * redundantInputId, remove it, and return the result. diff --git a/src/utils/xml.ts b/src/utils/xml.ts index 0e70888..7f021f8 100644 --- a/src/utils/xml.ts +++ b/src/utils/xml.ts @@ -7,6 +7,7 @@ import { parseMathJaxFormulas } from './mathjax-parser'; import { ProjectSummary } from 'src/project'; import { unescapeWhiteSpace } from 'src/resources/common'; import * as cheerio from 'cheerio'; +import { emptyOrDummyContent } from 'src/resources/questions/common'; // eslint-disable-next-line @typescript-eslint/no-var-requires const xmlParser = require('./parser'); @@ -295,15 +296,16 @@ export function toJSON( top().audioType = pronunciation.contenttype; } - if (meaning !== null) { - const material = getOneOfType(meaning.children, 'material'); - top().content = material.children; - } else if (translation !== null) { + if (meaning && !emptyOrDummyContent(meaning)) { + top().content = meaning.children; + } else if (translation && !emptyOrDummyContent(translation)) { top().content = translation.children; } else if (elementContent.length > 0) { top().content = elementContent; } else { top().content = [{ text: ' ' }]; + // audio-only popups should trigger on click, not hover + if (pronunciation) top().trigger = 'click'; } } } From 2a93fb43bd75455f15362b2fce706c36a1b65c32 Mon Sep 17 00:00:00 2001 From: Anders Weinstein Date: Fri, 24 Oct 2025 18:00:30 -0400 Subject: [PATCH 2/4] remove unused import --- src/resources/pool.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/resources/pool.ts b/src/resources/pool.ts index 7da5529..86b7540 100644 --- a/src/resources/pool.ts +++ b/src/resources/pool.ts @@ -10,7 +10,6 @@ import { getChild, getChildren, getDescendants, - isBlankText, emptyOrDummyContent, } from './questions/common'; import * as DOM from 'src/utils/dom'; From b0008df3994f4bcfd4ef0153db4f027b6ec2a82a Mon Sep 17 00:00:00 2001 From: Anders Weinstein Date: Sun, 26 Oct 2025 06:16:16 -0400 Subject: [PATCH 3/4] recode empty content test --- src/resources/pool.ts | 10 +++++----- src/resources/questions/common.ts | 17 +++++++++-------- src/utils/xml.ts | 6 +++--- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/resources/pool.ts b/src/resources/pool.ts index 86b7540..13a1083 100644 --- a/src/resources/pool.ts +++ b/src/resources/pool.ts @@ -73,15 +73,15 @@ export class Pool extends Resource { const legacyId = pool.id; const tagId = pool.id; - let prefixContent: any; + let prefixContent: any[] = []; let poolQuestionNumber = 1; pool.children.forEach((c: any) => { - if (c.type === 'content') { - prefixContent = c; + if (c.type === 'content' && !emptyOrDummyContent(c.children)) { + prefixContent = c.children; } else if (c.type !== 'title') { const subType = Formative.determineSubType(c); - if (!emptyOrDummyContent(prefixContent)) - c.stem.content = [...prefixContent.children, c.stem.content]; + // prepend pool-wide prologue to question stem, safe if empty default + c.stem.content = [...prefixContent, ...c.stem.content]; const pooledActivity = Formative.toActivity( c, subType, diff --git a/src/resources/questions/common.ts b/src/resources/questions/common.ts index ca4d826..cc7410d 100644 --- a/src/resources/questions/common.ts +++ b/src/resources/questions/common.ts @@ -142,14 +142,15 @@ export function wrapLooseText(children: any, trace = false) { return children; } -// for content-bearing elements that may be filled in with dummy empty paragraph -// arg is containing parent that may have array of of content elements as children -export const emptyOrDummyContent = (c: any) => - c?.children === undefined || - c.children.length === 0 || - (c.children.length === 1 && - c.children[0].type === 'p' && - c.children[0].children.every(isBlankText)); +// To deal with content-bearing elements that may be filled in with dummy empty paragraph. +// "content" = array of content elements, normally a containing parent element's +// children, or nullish for case of no content. +export const emptyOrDummyContent = (content: any[] | undefined | null) => + content == null || + content.length === 0 || + (content.length === 1 && + content[0].type === 'p' && + content[0].children.every(isBlankText)); /* * Recursively iterate through children, finding any input_ref pointing at the diff --git a/src/utils/xml.ts b/src/utils/xml.ts index 7f021f8..126b3e2 100644 --- a/src/utils/xml.ts +++ b/src/utils/xml.ts @@ -296,11 +296,11 @@ export function toJSON( top().audioType = pronunciation.contenttype; } - if (meaning && !emptyOrDummyContent(meaning)) { + if (!emptyOrDummyContent(meaning?.children)) { top().content = meaning.children; - } else if (translation && !emptyOrDummyContent(translation)) { + } else if (!emptyOrDummyContent(translation?.children)) { top().content = translation.children; - } else if (elementContent.length > 0) { + } else if (!emptyOrDummyContent(elementContent)) { top().content = elementContent; } else { top().content = [{ text: ' ' }]; From ef14aca50a3972d2242afc53180ba89d9a39b69e Mon Sep 17 00:00:00 2001 From: Anders Weinstein Date: Sun, 26 Oct 2025 06:49:02 -0400 Subject: [PATCH 4/4] fix stem reference handling pool prologue content --- src/resources/pool.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/resources/pool.ts b/src/resources/pool.ts index 13a1083..91f2f2a 100644 --- a/src/resources/pool.ts +++ b/src/resources/pool.ts @@ -76,12 +76,16 @@ export class Pool extends Resource { let prefixContent: any[] = []; let poolQuestionNumber = 1; pool.children.forEach((c: any) => { - if (c.type === 'content' && !emptyOrDummyContent(c.children)) { + if (c.type === 'content') { prefixContent = c.children; } else if (c.type !== 'title') { + // question: prepend any pool-wide prologue to stem + if (!emptyOrDummyContent(prefixContent)) { + const stem = getChild(c, 'stem'); + stem.children = [...prefixContent, ...stem.children]; + } + const subType = Formative.determineSubType(c); - // prepend pool-wide prologue to question stem, safe if empty default - c.stem.content = [...prefixContent, ...c.stem.content]; const pooledActivity = Formative.toActivity( c, subType,