@@ -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