Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | 37x 7331x 2535x 2078x 2078x 7331x 6x 6x 6x 7331x 724x 724x 724x 724x 1x 724x 724x 14x 724x 724x 7331x 7331x 7331x 7331x 7331x 7331x 7331x 47x 7331x 28x 17x 17x 22x 7331x | const tinyMCEUtility = (htmlString) => {
/**
* Does this editor HTML hold anything a reader would actually see?
*
* Modified by Allan Ninal — lifted out of TinyMCETextEditor's change handler so
* the editors can ask the same question before saving. Text-less content is still
* real content: an image, a table, a rule or a formula is a perfectly good answer,
* and a check that only counted characters would throw those away.
*/
const hasEditorContent = (content) => {
if (!content) return false;
const plainText = content.replace(/<[^>]+>/g, "").replace(/ /gi, " ");
return Boolean(plainText.trim()) || /<(img|table|hr|math-field)[\s/>]/i.test(content);
};
const tinyMCEtoString = (htmlString) => {
// Create a temporary DOM element to parse the HTML
const tempElement = document.createElement("div");
tempElement.innerHTML = htmlString;
// Extract and return the plain text content
return tempElement.textContent || tempElement.innerText || "";
};
// Plain textContent collapses rendered math (e.g. MathLive's superscript markup) down to its
// visible characters, so two formulas that differ only in structure (an exponent vs. an inline
// multiplier) can end up as the same string. Swap each math formula's rendered DOM for its
// literal LaTeX source before extracting text so structurally different formulas compare distinct.
//
// Image-only choices have no textContent at all — every `<p><img src="…"></p>` would otherwise
// collapse to "" and look identical for duplicate detection and correct-answer matching. Swap
// each image for a stable [[img:src]] token (same idea as the formula substitution above).
const getChoiceComparisonKey = (htmlString) => {
Iif (htmlString == null) return "";
const tempElement = document.createElement("div");
tempElement.innerHTML = typeof htmlString === "string" ? htmlString : String(htmlString);
tempElement.querySelectorAll(".mfe-formula[data-latex]").forEach((formulaNode) => {
formulaNode.textContent = `[[formula:${formulaNode.getAttribute("data-latex")}]]`;
});
tempElement.querySelectorAll("math-field[data-latex]").forEach((mathNode) => {
mathNode.textContent = `[[formula:${mathNode.getAttribute("data-latex")}]]`;
});
tempElement.querySelectorAll("img[src]").forEach((imgNode) => {
imgNode.textContent = `[[img:${imgNode.getAttribute("src")}]]`;
});
const text = tempElement.textContent || tempElement.innerText || "";
return text.trim().toLowerCase();
};
const dragDropHTML =
'<span style="width: 100px; border: 1px dashed #000; margin: 0px 5px; border-radius: 5px; background-color: #e5e5e5; padding: 5px 15px;" contenteditable="false"> Drop here </span>';
const dropDownMenuHTML = `<span style="width: 100px; border: 1px dashed #000; margin: 0px 5px; border-radius: 5px; background-color: #e5e5e5; padding: 5px 15px;" contenteditable="false"> Menu here </span>`;
const regexBlank = /__blank__/g;
const regexDropHere = /<span[^>]*>\s*Drop here\s*<\/span>/gi;
const regexMenuHere = /<span[^>]*>\s*Menu here\s*<\/span>/gi;
/**
* Embedded Multiple-Choice: unlike the drop-down/drag-drop markers above, the
* choice text IS the paragraph text — the span wraps the words the teacher
* highlighted rather than a placeholder — so no string<->HTML round trip is
* needed for storage. `id` is what correctness is graded against (not the
* text), so two identical-looking phrases marked separately stay unambiguous.
*/
const EMBEDDED_CHOICE_ATTR = "data-embedded-choice-id";
// A full outline border, not text-decoration/border-bottom: a math formula
// renders as a `.mfe-formula { display: inline-block; }` chip (see
// math-formula/styles.css), and text-decoration on an ancestor never paints
// through an atomic inline-level descendant like an inline-block. A border
// on the wrapper's own box always renders regardless of what's inside it.
//
// display:inline-block on the wrapper itself, not plain inline: a plain
// inline element's border is sized to the LINE BOX (roughly font
// ascent/descent), not to a taller inline-block descendant — a fraction or
// square root inside a formula choice is taller than a text line, so the
// border cut straight through the middle of it instead of enclosing it.
// inline-block sizes the box to its actual (taller) content instead.
const buildEmbeddedChoiceHTML = (id, text) =>
`<span ${EMBEDDED_CHOICE_ATTR}="${id}" class="embedded-choice-mark" contenteditable="false" ` +
`style="display:inline-block;vertical-align:middle;border:2px dashed;padding:1px 3px;border-radius:3px;">${text}</span>`;
const extractEmbeddedChoices = (html) => {
if (!html || typeof document === "undefined") return [];
const container = document.createElement("div");
container.innerHTML = html;
return Array.from(container.querySelectorAll(`[${EMBEDDED_CHOICE_ATTR}]`)).map((node) => ({
id: node.getAttribute(EMBEDDED_CHOICE_ATTR),
// innerHTML, not textContent — a marked choice can be a math formula
// (<span class="mfe-formula" data-latex="...">rendered markup</span>),
// whose visible glyphs are meaningless as flattened plain text.
text: node.innerHTML || "",
}));
};
return {
hasEditorContent,
tinyMCEtoString,
getChoiceComparisonKey,
dragDropHTML,
dropDownMenuHTML,
regexBlank,
regexDropHere,
regexMenuHere,
EMBEDDED_CHOICE_ATTR,
buildEmbeddedChoiceHTML,
extractEmbeddedChoices,
};
};
export default tinyMCEUtility;
|