Skip to content

Commit b16a1f2

Browse files
committed
Merge branch 'hparson-math-mode-fix-1'
2 parents 035ca88 + c572fbd commit b16a1f2

4 files changed

Lines changed: 82 additions & 96 deletions

File tree

bases/rsptx/interactives/package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bases/rsptx/interactives/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"handsontable": "7.2.2",
4444
"jexcel": "^3.9.1",
4545
"jquery-ui": "1.10.4",
46-
"micro-parsons": "https://github.com/amy21206/micro-parsons-element/releases/download/v0.1.6/micro-parsons-0.1.6.tgz",
46+
"micro-parsons": "https://github.com/RunestoneInteractive/micro-parsons-element/releases/download/v0.1.7/micro-parsons-0.1.7.tgz",
4747
"select2": "^4.1.0-rc.0",
4848
"sql.js": "1.5.0",
4949
"vega-embed": "3.14.0",

bases/rsptx/interactives/runestone/hparsons/js/BlockFeedback.js

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export default class BlockFeedback extends HParsonsFeedback {
99
this.messageDiv = document.createElement("div");
1010
this.hparsons.outerDiv.appendChild(this.messageDiv);
1111
}
12+
1213
customizeUI() {
1314
this.hparsons.runButton.textContent = "Check Me";
1415
}
@@ -18,12 +19,9 @@ export default class BlockFeedback extends HParsonsFeedback {
1819
this.solved = false;
1920
// TODO: not sure what is the best way to do this
2021
this.grader = new BlockBasedGrader();
21-
let solutionBlocks = [];
22-
for (let i = 0; i < this.hparsons.blockAnswer.length; ++i) {
23-
solutionBlocks.push(this.hparsons.originalBlocks[this.hparsons.blockAnswer[i]]);
24-
}
25-
this.solution = solutionBlocks;
26-
this.grader.solution = solutionBlocks;
22+
const solutionIndices = this.hparsons.blockAnswer.map(Number);
23+
this.solution = solutionIndices;
24+
this.grader.solution = solutionIndices;
2725
this.answerArea = this.hparsons.hparsonsInput.querySelector('.drop-area');
2826
}
2927

@@ -38,7 +36,7 @@ export default class BlockFeedback extends HParsonsFeedback {
3836
let act = {
3937
scheme: "block",
4038
correct: this.grader.graderState == 'correct' ? "T" : "F",
41-
answer: this.hparsons.hparsonsInput.getParsonsTextArray(),
39+
answer: this.hparsons.hparsonsInput.getBlockIndices(),
4240
percent: this.grader.percent
4341
}
4442
let logData = {
@@ -57,7 +55,7 @@ export default class BlockFeedback extends HParsonsFeedback {
5755
if (!this.solved) {
5856
this.checkCount++;
5957
this.clearFeedback();
60-
this.grader.answer = this.hparsons.hparsonsInput.getParsonsTextArray();
58+
this.grader.answer = this.hparsons.hparsonsInput.getBlockIndices();
6159
this.grade = this.grader.grade();
6260
if (this.grade == "correct") {
6361
this.hparsons.runButton.disabled = true;
@@ -100,7 +98,7 @@ export default class BlockFeedback extends HParsonsFeedback {
10098
var notInSolution = [];
10199
for (let i = 0; i < answerBlocks.length; i++) {
102100
var block = answerBlocks[i];
103-
var index = this.solution.indexOf(block.textContent);
101+
var index = this.solution.indexOf(Number(block.dataset.index));
104102
if (index == -1) {
105103
notInSolution.push(block);
106104
} else {

bases/rsptx/interactives/runestone/hparsons/js/hparsons.js

Lines changed: 68 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export default class HParsons extends RunestoneBase {
2727
this.isBlockGrading = this.parseBooleanAttribute(orig, "data-blockanswer");
2828
this.language = orig.getAttribute("data-language");
2929
// Detect math mode
30-
if (this.language === undefined && orig.textContent.includes('span class="process-math"')) {
30+
if ((this.language == null) && orig.textContent.includes('span class="process-math"')) {
3131
this.language = "math";
3232
}
3333
if (this.isBlockGrading) {
@@ -54,9 +54,6 @@ export default class HParsons extends RunestoneBase {
5454
this.controlDiv = null;
5555
this.processContent(this.code);
5656

57-
this.microParsonToRaw = new Map();
58-
this.simulatedSolution = [];
59-
6057
// Change to factory when more execution based feedback is included
6158
if (this.isBlockGrading) {
6259
this.feedbackController = new BlockFeedback(this);
@@ -131,7 +128,7 @@ export default class HParsons extends RunestoneBase {
131128
this.hparsonsInput = this.outerDiv.querySelector("micro-parsons");
132129
this.renderMathInBlocks();
133130
// Change "code" to "answer" in parsons direction for non-code languages
134-
if (this.language === undefined || this.language === "math") {
131+
if (this.language == null || this.language === "math") {
135132
this.outerDiv.querySelectorAll(".hparsons-tip").forEach(el => {
136133
if (el.textContent.includes("our code")) {
137134
el.textContent = el.textContent.replace("our code", "our answer");
@@ -192,54 +189,9 @@ export default class HParsons extends RunestoneBase {
192189
const blocks = document.querySelectorAll(`#${this.divid}-container .parsons-block`);
193190
blocks.forEach(block => {
194191
block.innerHTML = this.decodeHTMLEntities(block.innerHTML);
192+
this.queueMathJax(block);
195193
});
196-
197-
if (window.MathJax && MathJax.typesetPromise) {
198-
MathJax.typesetPromise().then(() => this.simulateSolution());
199-
}
200-
}, 0);
201-
}
202-
203-
/*
204-
This function performs a simulated "correct answer" ordering using the
205-
correct block indices specified in `this.blockAnswer`. It looks ahead
206-
at the rendered content from the MicroParsons widget to build:
207-
- this.simulatedSolution: an array of correctly ordered rendered strings
208-
- this.microParsonToRaw: a Map that links rendered HTML (from MicroParsons)
209-
to their original raw `<m>` source strings from PreTeXt
210-
211-
This is called after MathJax renders the math blocks to ensure the mapping
212-
is built from the final, visible DOM state. It is needed for grading
213-
math-mode Parsons problems, where rendered symbols (e.g., “\(\alpha\)”) must
214-
be matched against author-defined symbolic content.
215-
*/
216-
simulateSolution() {
217-
if (
218-
this.simulatedSolution.length > 0 &&
219-
this.microParsonToRaw instanceof Map &&
220-
this.microParsonToRaw.size > 0
221-
) { // Already initialized from local storage
222-
this.feedbackController.solution = this.simulatedSolution;
223-
this.feedbackController.grader.solution = this.simulatedSolution;
224-
return;
225-
}
226-
227-
this.microParsonToRaw = new Map();
228-
229-
const allBlocks = Array.from(
230-
this.outerDiv.querySelectorAll("micro-parsons .parsons-block")
231-
);
232-
if (!this.blockAnswer || allBlocks.length === 0) return;
233-
234-
const rendered = this.hparsonsInput.getParsonsTextArray();
235-
const raw = this.originalBlocks;
236-
const correctOrder = this.blockAnswer.map(Number);
237-
238-
this.simulatedSolution = correctOrder.map(i => rendered[i]);
239-
rendered.forEach((r, i) => this.microParsonToRaw.set(r, raw[i].trim()));
240-
241-
this.feedbackController.solution = this.simulatedSolution;
242-
this.feedbackController.grader.solution = this.simulatedSolution;
194+
}, 10);
243195
}
244196

245197
// Return previous answers in local storage
@@ -268,59 +220,95 @@ export default class HParsons extends RunestoneBase {
268220
}
269221
*/
270222
if (serverData.answer) {
271-
this.hparsonsInput.restoreAnswer(serverData.answer.blocks);
223+
const blocks = serverData.answer.blocks ?? serverData.answer;
224+
225+
if (Array.isArray(blocks) && blocks.length > 0) {
226+
const first = blocks[0];
227+
228+
// Prefer indices (numbers or numeric strings)
229+
const looksNumeric = (typeof first === "number") || (/^\d+$/.test(String(first)));
230+
if (looksNumeric && this.hparsonsInput.restoreAnswerByIndices) {
231+
this.hparsonsInput.restoreAnswerByIndices(blocks.map(Number));
232+
} else {
233+
this.hparsonsInput.restoreAnswer(blocks);
234+
}
235+
}
272236
}
273237
if (serverData.count) {
274238
this.feedbackController.checkCount = serverData.count;
275239
}
276240
}
241+
242+
243+
244+
245+
246+
247+
248+
249+
250+
251+
252+
253+
254+
255+
256+
257+
258+
259+
260+
261+
262+
263+
264+
265+
266+
267+
268+
269+
270+
271+
272+
273+
277274
// RunestoneBase: Load what is in local storage
278275
checkLocalStorage() {
279276
if (this.graderactive) {
280277
// Zihan: I think this means the component is still loading?
281278
return;
282279
}
283280
let localData = this.localData();
284-
if (localData.answer) {
281+
if (localData.answerIndices && this.hparsonsInput.restoreAnswerByIndices) {
282+
this.hparsonsInput.restoreAnswerByIndices(localData.answerIndices.map(Number));
283+
} else if (localData.answer) {
284+
// Legacy restore (string-based)
285285
this.hparsonsInput.restoreAnswer(localData.answer);
286+
287+
// Best-effort migration: persist indices after restoring
288+
if (this.isBlockGrading) {
289+
const migrated = this.hparsonsInput.getBlockIndices();
290+
localData.answerIndices = migrated;
291+
localStorage.setItem(this.storageId, JSON.stringify(localData));
292+
}
286293
}
287294
if (localData.count) {
288295
this.feedbackController.checkCount = localData.count;
289296
}
290-
if (localData.simulatedSolution) {
291-
this.simulatedSolution = localData.simulatedSolution;
292-
}
293-
if (localData.microParsonToRaw) {
294-
this.microParsonToRaw = new Map(Object.entries(localData.microParsonToRaw));
295-
} else {
296-
this.microParsonToRaw = new Map();
297-
}
298297
}
299298
// RunestoneBase: Set the state of the problem in local storage
300299
setLocalStorage(data) {
301300
let currentState = {};
302301
if (data == undefined) {
303-
let userAnswer = this.hparsonsInput.getParsonsTextArray();
304-
305-
// In math mode, convert microParsons to raw before caching
306-
// Additionally, save the solution and microParson ➜ Raw map.
307-
if (this.language === "math") {
308-
userAnswer = userAnswer.map(sym => this.microParsonToRaw.get(sym));
309-
currentState = {
310-
answer: userAnswer,
311-
simulatedSolution: this.simulatedSolution,
312-
microParsonToRaw: Object.fromEntries(this.microParsonToRaw),
313-
};
314-
} else {
315-
currentState = {
316-
answer: userAnswer,
317-
};
318-
}
319-
302+
320303
if (this.isBlockGrading) {
321-
// if this is block grading, add number of previous attempts too
304+
const answerIndices = this.hparsonsInput.getBlockIndices();
305+
currentState = { answerIndices: answerIndices };
322306
currentState.count = this.feedbackController.checkCount;
307+
} else {
308+
const userAnswer = this.hparsonsInput.getParsonsTextArray();
309+
currentState = { answer: userAnswer };
323310
}
311+
324312
} else {
325313
currentState = data;
326314
}

0 commit comments

Comments
 (0)