Skip to content

Commit 8b18c3a

Browse files
geoo89rbeezer
authored andcommitted
STACK: Fix relative links returned by API in JS library (PR #2701)
1 parent da1fba0 commit 8b18c3a

File tree

1 file changed

+63
-13
lines changed

1 file changed

+63
-13
lines changed

js/pretext-stack/stackapicalls.js

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -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(/javascript:download\(([^,]+?),([^,]+?)\)/, `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.
330370
function saveState(key, value) {
331371
if (typeof(Storage) !== "undefined") {
@@ -350,15 +390,21 @@ function renameIframeHolders() {
350390
}
351391

352392
function 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.
359405
function replaceFeedbackTags(text, qprefix) {
360406
let result = text;
361-
const feedbackTags = text.match(/\[\[feedback:.*\]\]/g);
407+
const feedbackTags = text.match(/\[\[feedback:.*?\]\]/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

383429
function loadQuestionFromFile(fileContents, questionName) {
@@ -451,7 +497,7 @@ function createQuestionBlocks() {
451497
function 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){
462508
function stackSetup(){
463509
createQuestionBlocks();
464510
addCollapsibles();
465-
}
511+
}
512+
513+
function getPlotUrl(file) {
514+
return `${stack_api_url}/plots/${file}`;
515+
}

0 commit comments

Comments
 (0)