@@ -33,7 +33,7 @@ async function collectData(qfile, qname, qprefix) {
3333 } ;
3434 } ) ;
3535 // }
36- return res
36+ return res ;
3737}
3838
3939// Get the different input elements by tag and return object with values.
@@ -85,12 +85,16 @@ function send(qfile, qname, qprefix) {
8585 renameIframeHolders ( ) ;
8686 let question = json . questionrender ;
8787 const inputs = json . questioninputs ;
88+ const seed = json . questionseed ;
8889 let correctAnswers = '' ;
8990 // Show correct answers.
9091 for ( const [ name , input ] of Object . entries ( inputs ) ) {
9192 question = question . replace ( `[[input:${ name } ]]` , input . render ) ;
9293 // question = question.replaceAll(`${inputPrefix}`,`${qprefix+inputPrefix}`);
9394 question = question . replace ( `[[validation:${ name } ]]` , `<span name='${ qprefix + validationPrefix + name } '></span>` ) ;
95+ // This is a bit of a hack. The question render returns an <a href="..."> calling the download function with
96+ // two arguments. We add the additional arguments that we need for context (question definition) here.
97+ question = question . replace ( / j a v a s c r i p t : d o w n l o a d \( ( [ ^ , ] + ?) , ( [ ^ , ] + ?) \) / , `javascript:download($1,$2, '${ qfile } ', '${ qname } ', '${ qprefix } ', ${ seed } )` ) ;
9498 if ( input . samplesolutionrender && name !== 'remember' ) {
9599 // Display render of answer and matching user input to produce the answer.
96100 correctAnswers += `<p>
@@ -113,9 +117,10 @@ function send(qfile, qname, qprefix) {
113117 }
114118 // Convert Moodle plot filenames to API filenames.
115119 for ( const [ name , file ] of Object . entries ( json . questionassets ) ) {
116- question = question . replace ( name , `plots/${ file } ` ) ;
117- json . questionsamplesolutiontext = json . questionsamplesolutiontext . replace ( name , `plots/${ file } ` ) ;
118- correctAnswers = correctAnswers . replace ( name , `plots/${ file } ` ) ;
120+ const plotUrl = getPlotUrl ( file ) ;
121+ question = question . replace ( name , plotUrl ) ;
122+ json . questionsamplesolutiontext = json . questionsamplesolutiontext . replace ( name , plotUrl ) ;
123+ correctAnswers = correctAnswers . replace ( name , plotUrl ) ;
119124 }
120125
121126 question = replaceFeedbackTags ( question , qprefix ) ;
@@ -135,7 +140,6 @@ function send(qfile, qname, qprefix) {
135140 if ( currentTimeout ) {
136141 window . clearTimeout ( currentTimeout ) ;
137142 }
138- console . log ( event . target ) ;
139143 timeOutHandler [ event . target . id ] = window . setTimeout ( validate . bind ( null , event . target , qfile , qname , qprefix ) , 1000 ) ;
140144 } ;
141145 }
@@ -203,7 +207,6 @@ function validate(element, qfile, qname, qprefix) {
203207 renameIframeHolders ( ) ;
204208 const validationHTML = json . validation ;
205209 const element = document . getElementsByName ( `${ qprefix + validationPrefix + answerName } ` ) [ 0 ] ;
206- console . log ( element ) ;
207210 element . innerHTML = validationHTML ;
208211 if ( validationHTML ) {
209212 element . classList . add ( 'validation' ) ;
@@ -268,7 +271,7 @@ function answer(qfile, qname, qprefix, seed) {
268271 // Replace tags and plots in specific feedback and then display.
269272 if ( json . specificfeedback ) {
270273 for ( const [ name , file ] of Object . entries ( json . gradingassets ) ) {
271- json . specificfeedback = json . specificfeedback . replace ( name , `plots/ ${ file } ` ) ;
274+ json . specificfeedback = json . specificfeedback . replace ( name , getPlotUrl ( file ) ) ;
272275 }
273276 json . specificfeedback = replaceFeedbackTags ( json . specificfeedback , qprefix ) ;
274277 specificFeedbackElement . innerHTML = json . specificfeedback ;
@@ -279,7 +282,7 @@ function answer(qfile, qname, qprefix, seed) {
279282 // Replace plots in tagged feedback and then display.
280283 for ( let [ name , fb ] of Object . entries ( feedback ) ) {
281284 for ( const [ name , file ] of Object . entries ( json . gradingassets ) ) {
282- fb = fb . replace ( name , `plots/ ${ file } ` ) ;
285+ fb = fb . replace ( name , getPlotUrl ( file ) ) ;
283286 }
284287 const elements = document . getElementsByName ( `${ qprefix + feedbackPrefix + name } ` ) ;
285288 if ( elements . length > 0 ) {
@@ -326,6 +329,43 @@ function answer(qfile, qname, qprefix, seed) {
326329 } ) ;
327330}
328331
332+ function download ( filename , fileid , qfile , qname , qprefix , seed ) {
333+ const http = new XMLHttpRequest ( ) ;
334+ const url = stack_api_url + '/download' ;
335+ http . open ( "POST" , url , true ) ;
336+ http . setRequestHeader ( 'Content-Type' , 'application/json' ) ;
337+ // Something funky going on with closures and callbacks. This seems
338+ // to be the easiest way to pass through the file details.
339+ http . filename = filename ;
340+ http . fileid = fileid ;
341+ http . onreadystatechange = function ( ) {
342+ if ( http . readyState == 4 ) {
343+ try {
344+ // Only download the file once. Replace call to download controller with link
345+ // to downloaded file.
346+ const blob = new Blob ( [ http . responseText ] , { type : 'application/octet-binary' , endings : 'native' } ) ;
347+ // We're matching the three additional arguments that are added in the send function here.
348+ const selector = CSS . escape ( `javascript\:download\(\'${ http . filename } \'\, ${ http . fileid } \, \'${ qfile } \'\, \'${ qname } \'\, \'${ qprefix } \'\, ${ seed } \)` ) ;
349+ const linkElements = document . querySelectorAll ( `a[href^=${ selector } ]` ) ;
350+ const link = linkElements [ 0 ] ;
351+ link . setAttribute ( 'href' , URL . createObjectURL ( blob ) ) ;
352+ link . setAttribute ( 'download' , filename ) ;
353+ link . click ( ) ;
354+ }
355+ catch ( e ) {
356+ document . getElementById ( 'errors' ) . innerText = http . responseText ;
357+ return ;
358+ }
359+ }
360+ } ;
361+ collectData ( qfile , qname , qprefix ) . then ( ( data ) => {
362+ data . filename = filename ;
363+ data . fileid = fileid ;
364+ data . seed = seed ;
365+ http . send ( JSON . stringify ( data ) ) ;
366+ } ) ;
367+ }
368+
329369// Save contents of question editor locally.
330370function saveState ( key , value ) {
331371 if ( typeof ( Storage ) !== "undefined" ) {
@@ -350,15 +390,21 @@ function renameIframeHolders() {
350390}
351391
352392function createIframes ( iframes ) {
393+ const corsFragment = "/cors.php?name=" ;
394+
353395 for ( const iframe of iframes ) {
354- create_iframe ( ...iframe ) ;
396+ create_iframe (
397+ iframe [ 0 ] ,
398+ iframe [ 1 ] . replaceAll ( corsFragment , stack_api_url + corsFragment ) ,
399+ ...iframe . slice ( 2 )
400+ ) ;
355401 }
356402}
357403
358404// Replace feedback tags in some text with an approproately named HTML div.
359405function replaceFeedbackTags ( text , qprefix ) {
360406 let result = text ;
361- const feedbackTags = text . match ( / \[ \[ f e e d b a c k : .* \] \] / g) ;
407+ const feedbackTags = text . match ( / \[ \[ f e e d b a c k : .* ? \] \] / g) ;
362408 if ( feedbackTags ) {
363409 for ( const tag of feedbackTags ) {
364410 // Part name is between '[[feedback:' and ']]'.
@@ -377,7 +423,7 @@ async function getQuestionFile(questionURL, questionName) {
377423 res = loadQuestionFromFile ( result , questionName ) ;
378424 } ) ;
379425 }
380- return res
426+ return res ;
381427}
382428
383429function loadQuestionFromFile ( fileContents , questionName ) {
@@ -451,7 +497,7 @@ function createQuestionBlocks() {
451497function addCollapsibles ( ) {
452498 var collapsibles = document . querySelectorAll ( ".level2>h2, .stack>h2" ) ;
453499 for ( let i = 0 ; i < collapsibles . length ; i ++ ) {
454- collapsibles [ i ] . addEventListener ( "click" , function ( ) { collapseFunc ( this ) } ) ;
500+ collapsibles [ i ] . addEventListener ( "click" , ( ) => collapseFunc ( this ) ) ;
455501 }
456502}
457503
@@ -462,4 +508,8 @@ function collapseFunc(e){
462508function stackSetup ( ) {
463509 createQuestionBlocks ( ) ;
464510 addCollapsibles ( ) ;
465- }
511+ }
512+
513+ function getPlotUrl ( file ) {
514+ return `${ stack_api_url } /plots/${ file } ` ;
515+ }
0 commit comments