Skip to content

Commit 14cc745

Browse files
authored
Update hparsons.js
1 parent 9947675 commit 14cc745

1 file changed

Lines changed: 67 additions & 76 deletions

File tree

  • bases/rsptx/interactives/runestone/hparsons/js

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

Lines changed: 67 additions & 76 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");
@@ -195,53 +192,11 @@ export default class HParsons extends RunestoneBase {
195192
});
196193

197194
if (window.MathJax && MathJax.typesetPromise) {
198-
MathJax.typesetPromise().then(() => this.simulateSolution());
195+
MathJax.typesetPromise();
199196
}
200197
}, 0);
201198
}
202199

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;
243-
}
244-
245200
// Return previous answers in local storage
246201
//
247202
localData() {
@@ -268,59 +223,95 @@ export default class HParsons extends RunestoneBase {
268223
}
269224
*/
270225
if (serverData.answer) {
271-
this.hparsonsInput.restoreAnswer(serverData.answer.blocks);
226+
const blocks = serverData.answer.blocks ?? serverData.answer;
227+
228+
if (Array.isArray(blocks) && blocks.length > 0) {
229+
const first = blocks[0];
230+
231+
// Prefer indices (numbers or numeric strings)
232+
const looksNumeric = (typeof first === "number") || (/^\d+$/.test(String(first)));
233+
if (looksNumeric && this.hparsonsInput.restoreAnswerByIndices) {
234+
this.hparsonsInput.restoreAnswerByIndices(blocks.map(Number));
235+
} else {
236+
this.hparsonsInput.restoreAnswer(blocks);
237+
}
238+
}
272239
}
273240
if (serverData.count) {
274241
this.feedbackController.checkCount = serverData.count;
275242
}
276243
}
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+
274+
275+
276+
277277
// RunestoneBase: Load what is in local storage
278278
checkLocalStorage() {
279279
if (this.graderactive) {
280280
// Zihan: I think this means the component is still loading?
281281
return;
282282
}
283283
let localData = this.localData();
284-
if (localData.answer) {
284+
if (localData.answerIndices && this.hparsonsInput.restoreAnswerByIndices) {
285+
this.hparsonsInput.restoreAnswerByIndices(localData.answerIndices.map(Number));
286+
} else if (localData.answer) {
287+
// Legacy restore (string-based)
285288
this.hparsonsInput.restoreAnswer(localData.answer);
289+
290+
// Best-effort migration: persist indices after restoring
291+
if (this.isBlockGrading) {
292+
const migrated = this.hparsonsInput.getBlockIndices();
293+
localData.answerIndices = migrated;
294+
localStorage.setItem(this.storageId, JSON.stringify(localData));
295+
}
286296
}
287297
if (localData.count) {
288298
this.feedbackController.checkCount = localData.count;
289299
}
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-
}
298300
}
299301
// RunestoneBase: Set the state of the problem in local storage
300302
setLocalStorage(data) {
301303
let currentState = {};
302304
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-
305+
320306
if (this.isBlockGrading) {
321-
// if this is block grading, add number of previous attempts too
307+
const answerIndices = this.hparsonsInput.getBlockIndices();
308+
currentState = { answerIndices: answerIndices };
322309
currentState.count = this.feedbackController.checkCount;
310+
} else {
311+
const userAnswer = this.hparsonsInput.getParsonsTextArray();
312+
currentState = { answer: userAnswer };
323313
}
314+
324315
} else {
325316
currentState = data;
326317
}

0 commit comments

Comments
 (0)