Latest build deployed.

This commit is contained in:
2026-08-01 15:00:14 +00:00
parent e43fb0d0ff
commit 331f133dfe
1278 changed files with 19041 additions and 18362 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
+3 -3
View File
File diff suppressed because one or more lines are too long
Executable → Regular
+1 -1
View File
File diff suppressed because one or more lines are too long
+3 -3
View File
File diff suppressed because one or more lines are too long
-552
View File
@@ -1,552 +0,0 @@
function escapeHTML(text) {
if (!text) { return "" }
the_ans = text;
the_ans = the_ans.replace(/&/g, "&");
the_ans = the_ans.replace(/<([a-zA-Z])/g, '< $1');
return the_ans
}
function uNescapeHTML(text) {
if (!text) { return "" }
the_ans = text;
the_ans = the_ans.replace(/&lt; /g, "<");
the_ans = the_ans.replace(/&lt;/g, "<");
the_ans = the_ans.replace(/&gt;/g, ">");
the_ans = the_ans.replace(/&amp;/g, "&");
the_ans = the_ans.replace(/<([a-zA-Z])/g, "< $1");
return the_ans
}
function dollars_to_slashparen(text) {
the_ans = text;
the_ans = the_ans.replace(/(^|\s|-)\$([^\$\f\r\n]+)\$(\s|\.|,|;|:|\?|!|$)/g, "$1\\($2\\)$3");
//twice, for $5$-$6$
the_ans = the_ans.replace(/(^|\s|-)\$([^\$\f\r\n]+)\$(\s|\.|,|;|:|\?|!|-|$)/g, "$1\\($2\\)$3");
return the_ans
}
/* The structure of a reading question is an erticle with an id (this_ques_id),
containing:
#this_ques_id_text the answer given (in a div with that id)
#this_ques_id_text_hidden the answer in a raw form (in a hidden div)
#this_ques_id_text_input the answer in a textinput with that id
Try to do all operations by id, and not by carrying objects around.
*/
var reading_questions = document.querySelectorAll("section.reading-questions article.exercise-like, #boelkins-ACS .main #content > section:first-of-type > section:first-of-type > .project-like li");
var reading_answers = {};
console.log('reading_questions.length', reading_questions.length);
function make_submit_button() {
if (document.getElementById("rq_submit")) { // don't make the button if it already exists
console.log("button exists", document.getElementById("rq_submit"));
return
}
last_reading_question = reading_questions[reading_questions.length - 1];
answer_button_holder = document.createElement('div');
answer_button_holder.setAttribute('class', 'rq_submit_wrapper');
answer_button_holder.innerHTML = rq_submit_button;
last_reading_question.insertAdjacentElement("afterend", answer_button_holder);
}
function save_reading_questions() {
rq_data = {"action": "save", "user": uname, "pw": emanu, "pI": pageIdentifier, "type": "readingquestions", "rq": JSON.stringify(reading_questions_object)}
$.ajax({
url: "https://aimath.org/cgi-bin/u/highlights.py",
type: "post",
data: JSON.stringify(rq_data),
dataType: "json",
success: function(data) {
console.log("something", data, "back from highlight");
alert(data);
},
error: function(errMsg) {
console.log("seems to be an error?",errMsg);
alert("Error\n" + errMsg);
}
});
console.log("just ajax sent", JSON.stringify(reading_questions_object));
}
// no point in handling reading questions if there are not any
if (reading_questions.length) {
// retrieve the existing reading questions, if they exist
var reading_questions_object_id = pageIdentifier + "___" + "rq";
var reading_questions_object = localStorage.getObject(reading_questions_object_id);
var reading_questions_all_answered = false;
var reading_questions_submitted = false;
if (!reading_questions_object) {
reading_questions_object = {}
}
if (Object.keys(reading_questions_object).length >= reading_questions.length) {
console.log("Object.keys(reading_questions_object)",Object.keys(reading_questions_object));
console.log("reading_questions", reading_questions);
console.log("all reading questions have previously been answered");
reading_questions_all_answered = true;
}
answer_css = document.createElement('style');
answer_css.type = "text/css";
answer_css.id = "highlight_css";
document.head.appendChild(answer_css);
var css_for_ans = '#rq_submit { background: #FDD; padding: 3px 5px; border-radius: 0.5em}\n';
css_for_ans += '#rq_submit.submitted { background: #EFE; color: #BBB}';
css_for_ans += '.rq_submit_wrapper { margin-top: 0.5em; float: right}';
answer_css.innerHTML = css_for_ans;
rq_answer_label = '<span'
rq_answer_label += ' class="readingquestion_make_answer addcontent';
rq_answer_label += ' ' + role + '"';
// rq_answer_label += ' style="margin-left:1em; font-size:80%; color:#a0a;"';
rq_answer_label += '>';
if (role == "instructor") {
rq_answer_label += 'Responses&rarr;';
} else {
rq_answer_label += 'Click twice to edit...';
}
rq_answer_label +='</span>';
var rq_submit_button = '<span';
rq_submit_button += ' class="submit"';
rq_submit_button += ' id="rq_submit"';
rq_submit_button += '>';
rq_submit_button += 'Submit answers';
rq_submit_button +='</span>';
// make reading quesitons active, and insert answers if available
for (var j=0; j < reading_questions.length; ++j) {
var reading_question = reading_questions[j];
var reading_question_id = reading_question.id;
rq_answer_id = reading_question_id + "_text";
// var existing_content = localStorage.getObject(rq_answer_id);
var existing_content = reading_questions_object[rq_answer_id];
if (existing_content && role == "student") {
$('#'+reading_question_id).find(".readingquestion_make_answer").addClass("hidecontrols");
var this_ques_id_text = reading_question_id + "_text";
var this_ques_id_controls = reading_question_id + "_controls";
var answer_div = '<div';
answer_div += ' id="' + this_ques_id_text + '"';
answer_div += ' class="given_answer has_am process-math processme"';
answer_div += '>';
answer_div += dollars_to_slashparen(escapeHTML(existing_content)) + " ";
answer_div += '</div>';
/* need to save the original so that mathjax does not change it */
var hidden_answer_div = '<div';
hidden_answer_div += ' id="' + this_ques_id_text + '_hidden' + '"';
hidden_answer_div += ' class="tex2jax_ignore asciimath2jax_ignore" style="display: none">';
hidden_answer_div += escapeHTML(existing_content);
hidden_answer_div += '</div>';
var this_rq_controls = '<div id="' + this_ques_id_controls + '" class="input_controls hidecontrols">';
this_rq_controls += '<span class="action save_item rq_save">preview</span>';
// this_rq_controls += '<span class="action clear_item rq_delete">delete</span>';
this_rq_controls += '<span class="action amhelp">typing math?</span>';
this_rq_controls += '</div>'
var this_rq_answer_and_controls = document.createElement('div');
this_rq_answer_and_controls.setAttribute('style', 'width:80%; padding-left:10%; padding-right:10%; margin-top:0.5em;');
this_rq_answer_and_controls.setAttribute('class', 'rq_answer');
this_rq_answer_and_controls.innerHTML = hidden_answer_div + answer_div + this_rq_controls;
console.log("appending to ", reading_question_id);
$('#'+reading_question_id).append(this_rq_answer_and_controls);
// this.parentNode.insertAdjacentElement("afterend", this_rq_answer_and_controls);
/* typeset the math in the reading questions answers */
// MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
if (mjvers && mjvers < 3) {
MathJax.Hub.Queue(['Typeset', MathJax.Hub]);
} else if (mjvers > 3) {
MathJax.typesetPromise();
}
} else if(role == "instructor" || role == "student") {
var this_answer_link = document.createElement('div');
this_answer_link.innerHTML = rq_answer_label;
console.log("inserting afterend of",reading_question);
// reading_question.insertAdjacentElement("afterend", this_answer_link);
reading_question.append(this_answer_link);
} else {
console.log("should not be here");
}
}
function allow_student_answers(){
/* make a new blank area to answer a question */
$('.readingquestion_make_answer.student').mousedown(function(e){
console.log(".readingquestion_make_answer student");
// $(this).addClass("hidecontrols");
// var this_ques_id = this.parentNode.parentNode.id;
var this_ques_id = this.parentNode.parentNode.id;
var this_ques_id_text = this_ques_id + "_text";
var this_ques_id_controls = this_ques_id + "_controls";
console.log(".rq", this_ques_id);
answer_textarea = '<textarea';
answer_textarea += ' class="rq_answer_text"'
answer_textarea += ' id="' + this_ques_id_text + '_input"'
answer_textarea += ' rows="' + '3' + '"';
answer_textarea += ' style="width:95%; height: 63px;"';
answer_textarea += '>';
answer_textarea += '</textarea>';
var this_rq_controls = '<div id="' + this_ques_id_controls + '" class="input_controls" style="margin-bottom:-1.9em;">';
this_rq_controls += '<span class="action save_item rq_save">preview</span>';
// this_rq_controls += '<span class="action clear_item rq_delete">delete</span>';
this_rq_controls += '<span class="action amhelp">typing math?</span>';
this_rq_controls += '</div>'
var this_rq_answer_and_controls = document.createElement('div');
this_rq_answer_and_controls.setAttribute('class', 'rq_answer editing');
this_rq_answer_and_controls.setAttribute('style', 'width:80%; padding-left:10%; padding-right:10%; margin-top:0.5em;');
this_rq_answer_and_controls.innerHTML = answer_textarea + this_rq_controls;
this.parentNode.insertAdjacentElement("afterend", this_rq_answer_and_controls);
this.remove();
// MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
console.log("adding other keypress listener");
var this_textarea = document.getElementById(this_ques_id_text + "_input");
console.log("to", this_textarea);
this_textarea.addEventListener("keypress", function() {
// if(this_textarea.scrollTop != 0){
// console.log("this_textarea.scrollHeight", this_textarea.scrollHeight, "this_textarea.scrollTop", this_textarea.scrollTop);
// console.log("this_textarea.clientHeight", this_textarea.clientHeight);
this_textarea.overflow = "scroll";
// console.log("this_textarea.scrollHeight", this_textarea.scrollHeight, "this_textarea.scrollTop", this_textarea.scrollTop);
// console.log("this_textarea.clientHeight", this_textarea.clientHeight);
this_textarea.overflow = "hidden";
// console.log("this_textarea.scrollHeight", this_textarea.scrollHeight, "this_textarea.scrollTop", this_textarea.scrollTop);
// console.log("this_textarea.getBoundingClientRect()", this_textarea.getBoundingClientRect());
this_textarea.style.height = this_textarea.scrollHeight + "px";
// }
}, false);
var tmp_id = this_ques_id_text + "_input";
console.log("want focus to ", tmp_id);
console.log("which is ", $("#" +tmp_id));
console.log("the active ", document.hasFocus(), "element is", document.activeElement);
console.log("or maybe it is ", $(":focus"));
$("#" + tmp_id).focus();
document.getElementById(tmp_id).focus();
console.log("the active ", document.hasFocus(), "element is", document.activeElement);
});
}
allow_student_answers();
$('.readingquestion_make_answer.instructor').mousedown(function(e){
console.log(".readingquestion_make_answer instructor", "instId", uname, "pI", pageIdentifier);
if (jQuery.isEmptyObject(reading_answers) || this.classList.contains("reload")) {
rq_data = {"action": "retrieve", "instId": uname, "pw": emanu, "pI": pageIdentifier, "type": "readingquestions"};
// myjson = {"action": "retrieve", "type": "readingquestions", "instId": "100002000", "pI": "beezer-FCLA___FPm"}
$.ajax({
url: "https://aimath.org/cgi-bin/u/highlights.py",
type: "post",
data: JSON.stringify(rq_data),
dataType: "json",
async: false,
success: function(data) {
reading_answers = data;
// console.log("something", data, "back from highlight");
// alert(data);
},
error: function(errMsg) {
console.log("seems to be an error?",errMsg);
alert("Error\n" + errMsg);
}
});
}
// var this_ques_id = this.parentNode.previousSibling.id;
var this_ques_id = this.parentNode.parentNode.id;
console.log("this_ques_id", this_ques_id);
var compiled_answers = "";
var title_of_this_section = $("section > h2 > .title").html();
title_of_this_section = title_of_this_section.replace(/ /g, "%20");
title_of_this_section = title_of_this_section.replace(/\?/g, "");
var number_of_this_rq = 1 + $("#" + this_ques_id).index(".exercise-like");
// console.log("first title_of_this_section", title_of_this_section);
// console.log("first title_of_this_section.html()", title_of_this_section.html());
// console.log("second title_of_this_section[0]", title_of_this_section[0]);
for(var j=0; j < reading_answers.length; ++j) {
var this_answer_all = reading_answers[j];
var this_student_id = this_answer_all[0];
var these_specific_answers = JSON.parse(this_answer_all[1]);
console.log("these_specific_answers", these_specific_answers);
console.log("this_answer_all[2]", this_answer_all[2]);
var this_submitted_time = JSON.parse(this_answer_all[2]);
// var this_submitted_time = this_answer_all[2];
console.log("looking for this answer:", this_ques_id + "_text");
var this_specific_answer = these_specific_answers[this_ques_id + "_text"];
console.log("this_answer_all",this_answer_all);
console.log("j",j,"this_specific_answer", this_specific_answer);
console.log("this_specific_time", this_submitted_time);
this_specific_answer = dollars_to_slashparen(escapeHTML(this_specific_answer))
if (!this_specific_answer) {
this_specific_answer = "no answer submitted";
compiled_answers += '<div class="one_answer noanswer">';
} else {
compiled_answers += '<div class="one_answer">';
}
if (this_student_id.indexOf('@') > -1) {
this_student_id = '<a href="mailto:' + this_student_id + '?Subject=RQ' + number_of_this_rq + '%20of%20' + title_of_this_section +'">' + this_student_id + '</a>';
}
compiled_answers += '<div class="s_id">' + this_student_id + '</div>';
compiled_answers += '<div class="rq_sub_time">' + this_submitted_time + '</div>';
compiled_answers += '<div class="s_ans has_am process-math processme">' + this_specific_answer + '</div>';
compiled_answers += '</div>\n';
console.log(j, "j", these_specific_answers)
}
// if the answers are being reloaded, remove the previous answers
$("#" + this_ques_id + "_ans").remove();
var answers_to_this_question = document.createElement('div');
answers_to_this_question.setAttribute('class', 'compiled_answers');
answers_to_this_question.setAttribute('id', this_ques_id + "_ans");
// this_rq_answer_and_controls.setAttribute('style', 'width:80%; margin-left:auto; margin-right:auto; margin-top:0.5em;');
// this_rq_answer_and_controls.setAttribute('class', 'rq_answer');
answers_to_this_question.innerHTML = compiled_answers;
$('#'+this_ques_id).append(answers_to_this_question);
// this.parentNode.remove();
this.innerHTML = "Reload responses";
$(this).addClass("reload");
// MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
if (mjvers && mjvers < 3) {
MathJax.Hub.Queue(['Typeset', MathJax.Hub]);
} else if (mjvers > 3) {
MathJax.typesetPromise();
}
// this.parentNode.insertAdjacentElement("afterend", this_rq_answe
});
function save_one_reading_question(this_ques_id) {
this_ques_id_text = this_ques_id + "_text";
var this_ans_text = $("#" + this_ques_id_text + "_input");
console.log("the value:", this_ans_text.value);
// var this_ques_id = this_ans.parentNode.previousSibling.previousSibling.id;
// var this_ques_id = this_ans.id;
console.log("this_ques_id", this_ques_id);
// console.log("this_rq_ans", this_rq_ans);
var this_ans_text_value = this_ans_text.val();
console.log("this_ans_text_value", this_ans_text_value);
if ( /[^\x00-\x7F]/.test(this_ans_text_value)) {
this_ans_text_value = this_ans_text_value.replace(/[^\x00-\x7F]/g, "XX");
alert("Illegal characters in answer have been replaced by XX");
}
this_ans_text_value = $.trim(this_ans_text_value); // jQuery trim (some chrome on windows had trouble with trim)
// we have the contents of the answer, so save it to local storage
console.log("saving in local storage at", this_ques_id_text, "the answer", this_ans_text_value);
reading_questions_object[this_ques_id_text] = this_ans_text_value;
localStorage.setObject(reading_questions_object_id, reading_questions_object);
console.log("Object.keys(reading_questions_object)",Object.keys(reading_questions_object));
console.log("reading_questions", reading_questions);
if (Object.keys(reading_questions_object).length >= reading_questions.length && uname != "guest" && role=="student") {
console.log("all reading questions have been answered");
reading_questions_all_answered = true;
make_submit_button();
}
// and save a copy hidden on the page
console.log("looking for", this_ques_id + "_text_hidden");
// when the initial answer box is created, there is no hidden version
if ( !document.getElementById(this_ques_id + "_text_hidden")) {
console.log("making a place to hide the answer");
var hidden_answer_div = document.createElement('div');
hidden_answer_div.setAttribute('id', this_ques_id + '_text_hidden');
hidden_answer_div.setAttribute('class', 'tex2jax_ignore asciimath2jax_ignore');
hidden_answer_div.setAttribute('style', 'display: none');
console.log("this_ans_text", this_ans_text);
document.getElementById(this_ques_id_text + "_input").insertAdjacentElement("beforebegin", hidden_answer_div);
// hidden_answer_div.insertBefore(this_ans_text);
}
console.log("hiding the raw answer in", this_ques_id + "_text_hidden");
document.getElementById(this_ques_id + "_text_hidden").innerHTML = escapeHTML(this_ans_text_value);
//and show it on the page
var this_ans_static = document.createElement('div');
this_ans_static.setAttribute('id', this_ques_id_text);
this_ans_static.setAttribute('class', 'given_answer has_am process-math processme');
console.log("setting this_ans_static.innerHTML to", dollars_to_slashparen(escapeHTML(this_ans_text_value)));
this_ans_static.innerHTML = dollars_to_slashparen(escapeHTML(this_ans_text_value)) + " "
// this_rq_ans.replaceWith(this_ans_static);
console.log("about to replace this_ans_text", this_ans_text);
this_ans_text.replaceWith(this_ans_static);
// MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
if (mjvers && mjvers < 3) {
MathJax.Hub.Queue(['Typeset', MathJax.Hub]);
} else if (mjvers > 3) {
MathJax.typesetPromise();
}
console.log(" this_ans_text", this_ans_text);
console.log("this_ans_static", this_ans_static);
$(this_ans_static).parent().addClass("rq_answer");
$('#' + this_ques_id + "_controls").addClass("hidecontrols");
// $(this_q).parent().parent().addClass("rq_answer");
/*
var edit_button = document.createElement('span');
edit_button.setAttribute('class', "action edit_item rq_edit");
edit_button.innerHTML = "edit";
this.replaceWith(edit_button);
*/
};
/* edit an existing answer */
function edit_one_reading_question(this_ans) {
console.log(".rq_edit", this_ans);
// var this_ques_id = this.parentNode.previousSibling.id;
var this_ques_id = this_ans.parentNode.parentNode.id;
console.log("edditing this_ques_id", this_ques_id);
var this_ques_id_text = this_ques_id + "_text";
// var this_rq_ans = this.parentNode.previousSibling;
var this_rq_ans = this_ans;
// var this_rq_ans_id = this_ans.id;
console.log("now this_ques_id_text", this_ques_id_text);
console.log(".rq_edit", this_ques_id);
console.log("this_rq_ans", this_rq_ans);
var this_rq_text = this_rq_ans.innerHTML;
console.log("looking for", this_ques_id + "_hidden");
var this_rq_text_raw = uNescapeHTML(document.getElementById(this_ques_id + "_text_hidden").innerHTML);
console.log("this_rq_text_raw",this_rq_text_raw);
//this is copied from above. need to eliminate repeated code
var answer_textarea_editable = document.createElement('textarea');
answer_textarea_editable.setAttribute('id', this_ques_id_text + "_input");
answer_textarea_editable.setAttribute('class', 'rq_answer_text');
answer_textarea_editable.setAttribute('rows', '3');
answer_textarea_editable.setAttribute('style', 'width:95%; height: 44px;');
this_rq_ans.replaceWith(answer_textarea_editable);
console.log("this_ans is",this_ans);
console.log("adding editing to the parent of thing with id", this_ques_id_text, "which is the parent of", $('#' + this_ques_id_text));
$('#' + this_ques_id_text + "_input").parent().addClass("editing");
$('#' + this_ques_id + "_controls").removeClass("hidecontrols");
// WHY? $(this).parent().parent().removeClass("rq_answer");
/* var save_button = document.createElement('span');
save_button.setAttribute('class', "action edit_item rq_save");
save_button.innerHTML = "save";
this.replaceWith(save_button);
*/
$('#' + this_ques_id + "_text_input").val(this_rq_text_raw);
answer_textarea_editable.style.height = answer_textarea_editable.scrollHeight + "px";
answer_textarea_editable.addEventListener("keypress", function() {
// if(answer_textarea_editable.scrollTop != 0){
answer_textarea_editable.style.height = answer_textarea_editable.scrollHeight + "px";
// }
}, false);
};
/* handle saving when leaving an answer box, or editing an existing answer
when hovering over an existing answer */
$('body').on('click','.given_answer', function(){
// $(this).children().last().removeClass("hidecontrols");
edit_one_reading_question(this);
$(this).attr('z-index', '2000');
});
$('body').on('mousedown','.rq_save', function(){
// $(this).children().last().addClass("hidecontrols");
// $(this).attr('z-index', '');
// var this_rq = $(this).find(".given_answer");
//ooooooooo var this_ques_id = this.parentNode.id;
var this_ques_id = this.parentNode.parentNode.parentNode.id;
console.log("id of this quesiton", this_ques_id);
// var this_rq = $(this).find(".rq_answer_text");
// console.log("this_rq iiIIIIII", this_rq);
var this_current_answer = document.getElementById(this_ques_id + "_text_input");
console.log("this_current_answer", this_current_answer);
console.log("this_current_answer.value", this_current_answer.value);
// console.log("this_rq IIIIiiii value", this_rq.value);
// save_one_reading_question(this_rq.parentNode.id);
save_one_reading_question(this_ques_id);
console.log("left answer area");
$(this).removeClass("editing");
});
$('body').on('click','.rq_delete', function(){
console.log(".rq_delete");
var this_ques_id = this.parentNode.parentNode.parentNode.id;
console.log(".rq_delete", this_ques_id);
$('#' + this_ques_id + "_controls").removeClass("hidecontrols");
//and now put in controls
var this_answer_link = document.createElement('div');
this_answer_link.innerHTML = rq_answer_label;
console.log("inserting afterend of",reading_question);
// reading_question.insertAdjacentElement("afterend", this_answer_link);
$(this).parent().parent().replaceWith(this_answer_link);
allow_student_answers();
console.log("reading_questions_object", reading_questions_object);
console.log("this_ques_id + _text", this_ques_id + "_text");
delete reading_questions_object[this_ques_id + "_text"];
console.log("now reading_questions_object", reading_questions_object);
// localStorage.removeItem(this_ques_id);
localStorage.setObject(reading_questions_object_id, reading_questions_object);
});
if(reading_questions_all_answered && uname != "guest" && role=="student") {
make_submit_button();
console.log("made submit button");
}
$('body').on('click','#rq_submit', function(){
console.log("submitting rq answers");
$('#rq_submit').addClass('submitted');
document.getElementById('rq_submit').textContent = "Resubmit answers";
save_reading_questions();
});
$('body').on('click','.amhelp', function(){
var amhelpmessage = "Write math formulas as AsciiMath inside `backticks`.\n";
amhelpmessage += "For example:\nThe Pythagorean theorem says `sin^2(x) + cos^2(x) = 1`,\n";
amhelpmessage += "The quadratic formula is `x = (-b +- sqrt(b^2 - 4ac))/(2a)`. \n";
amhelpmessage += "Note the use of parentheses for grouping.\n";
amhelpmessage += "Visit http://asciimath.org for a list of AsciiMath commands.\n\n";
amhelpmessage += "You can also use LaTeX, with either slash-parentheses \\(...\\)\n";
amhelpmessage += "or dollar signs $...$ as delimiters for inline math.";
alert(amhelpmessage)
});
if(reading_questions_all_answered && uname != "guest" && role=="student") {
make_submit_button();
}
}
+2 -2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+282
View File
@@ -0,0 +1,282 @@
window.addEventListener("load", (event2) => {
addKnowls(document);
});
function addKnowls(target) {
const xrefs = target.querySelectorAll("[data-knowl]");
for (const xref of xrefs) {
LinkKnowl.initializeXrefKnowl(xref);
}
const bornHiddens = target.querySelectorAll(".born-hidden-knowl");
for (const bhk of bornHiddens) {
const summary = bhk.querySelector(":scope > summary");
const contents = bhk.querySelector(":scope > summary + *");
new SlideRevealer(summary, contents, bhk);
}
}
class SlideRevealer {
static STATE = Object.freeze({
INACTIVE: 0,
CLOSING: 1,
EXPANDING: 2
});
// triggerElement is the element clicked to open/close
// contentElement is the element that will hide/reveal
// animatedElement is the element that will grow/shrink as contentElement is modified
// may be the same as contentElement or a parent of it
constructor(triggerElement, contentElement, animatedElement) {
this.triggerElement = triggerElement;
this.contentElement = contentElement;
this.animatedElement = animatedElement;
this.animation = null;
this.animationState = SlideRevealer.STATE.INACTIVE;
this.animatedElementInlineStyle = null;
this.triggerElement.addEventListener("click", (e) => this.onClick(e));
}
isBusy() {
return this.animationState !== SlideRevealer.STATE.INACTIVE || this.animatedElementInlineStyle !== null;
}
storeAnimatedElementInlineStyle() {
if (this.animatedElementInlineStyle !== null) return;
this.animatedElementInlineStyle = {
overflow: this.animatedElement.style.overflow,
height: this.animatedElement.style.height,
paddingTop: this.animatedElement.style.paddingTop,
paddingBottom: this.animatedElement.style.paddingBottom
};
}
restoreAnimatedElementInlineStyle() {
if (this.animatedElementInlineStyle === null) return;
this.animatedElement.style.overflow = this.animatedElementInlineStyle.overflow;
this.animatedElement.style.height = this.animatedElementInlineStyle.height;
this.animatedElement.style.paddingTop = this.animatedElementInlineStyle.paddingTop;
this.animatedElement.style.paddingBottom = this.animatedElementInlineStyle.paddingBottom;
this.animatedElementInlineStyle = null;
}
onClick(e) {
if (e) e.preventDefault();
if (this.isBusy()) return;
this.storeAnimatedElementInlineStyle();
this.animatedElement.style.overflow = "hidden";
if (this.animationState === SlideRevealer.STATE.CLOSING || !this.animatedElement.hasAttribute("open")) {
this.animatedElement.setAttribute("open", "");
this.triggerElement.setAttribute("open", "");
this.contentElement.style.display = "";
this.contentElement.style.visibility = "hidden";
let closedHeight = 0;
if (this.animatedElement.contains(this.triggerElement))
closedHeight = this.triggerElement.offsetHeight;
const naturalStyle = window.getComputedStyle(this.animatedElement);
const naturalPaddingTop = naturalStyle.paddingTop;
const naturalPaddingBottom = naturalStyle.paddingBottom;
this.animatedElement.style.height = `${closedHeight}px`;
this.animatedElement.style.paddingTop = "0px";
this.animatedElement.style.paddingBottom = "0px";
const expandingMeasurements = {
fullHeight: this.contentElement === this.animatedElement ? this.contentElement.scrollHeight : closedHeight + this.contentElement.offsetHeight,
paddingTop: naturalPaddingTop,
paddingBottom: naturalPaddingBottom
};
this.contentElement.style.visibility = "";
this.toggle(true, expandingMeasurements);
} else if (this.animationState === SlideRevealer.STATE.EXPANDING || this.animatedElement.hasAttribute("open")) {
this.toggle(false);
}
}
toggle(expanding, expandingMeasurements = null) {
let closedHeight = 0;
if (this.animatedElement.contains(this.triggerElement))
closedHeight = this.triggerElement.offsetHeight;
const computedStyle = window.getComputedStyle(this.animatedElement);
const fullHeight = expandingMeasurements?.fullHeight ?? closedHeight + this.contentElement.offsetHeight;
const startHeight = `${expanding ? closedHeight : this.animatedElement.offsetHeight}px`;
const endHeight = `${expanding ? fullHeight : closedHeight}px`;
const currentPaddingTop = computedStyle.paddingTop;
const currentPaddingBottom = computedStyle.paddingBottom;
const endPaddingTop = expandingMeasurements?.paddingTop ?? currentPaddingTop;
const endPaddingBottom = expandingMeasurements?.paddingBottom ?? currentPaddingBottom;
const startPadTop = expanding ? "0px" : currentPaddingTop;
const endPadTop = expanding ? endPaddingTop : "0px";
const startPadBottom = expanding ? "0px" : currentPaddingBottom;
const endPadBottom = expanding ? endPaddingBottom : "0px";
if (this.animation) {
this.animation.cancel();
}
const animDuration = Math.max(Math.min(Math.abs(closedHeight - fullHeight) / 400 * 1e3, 750), 250);
this.animationState = expanding ? SlideRevealer.STATE.EXPANDING : SlideRevealer.STATE.CLOSING;
this.animation = this.animatedElement.animate({
height: [startHeight, endHeight],
paddingTop: [startPadTop, endPadTop],
paddingBottom: [startPadBottom, endPadBottom]
}, {
duration: animDuration,
easing: "ease-out"
});
this.animation.onfinish = () => {
this.onAnimationFinish(expanding);
};
this.animation.oncancel = () => {
this.animationState = SlideRevealer.STATE.INACTIVE;
this.restoreAnimatedElementInlineStyle();
};
}
onAnimationFinish(isOpen) {
this.animation = null;
this.animationState = SlideRevealer.STATE.INACTIVE;
if (!isOpen) {
this.animatedElement.removeAttribute("open");
this.triggerElement.removeAttribute("open");
}
this.restoreAnimatedElementInlineStyle();
if (!isOpen)
this.contentElement.style.display = "none";
this.contentElement.style.visibility = "";
if (isOpen) {
let hasCallback = this.contentElement.querySelectorAll("[data-knowl-callback]");
hasCallback.forEach((el) => {
window[el.getAttribute("data-knowl-callback")](el, open);
});
}
}
}
class LinkKnowl {
// Used to uniquely identify XrefKnowls
static xrefCount = 0;
// Factory to create an XrefKnowl from a knowl link
// Will avoid duplicate initialization
// This should be used by outside code to create XrefKnowls
static initializeXrefKnowl(knowlLinkElement) {
if (knowlLinkElement.getAttribute("data-knowl-uid") === null) {
return new LinkKnowl(knowlLinkElement);
}
}
// "Private" constructor - should only be called by initializeXrefKnowl
constructor(knowlLinkElement) {
this.linkElement = knowlLinkElement;
this.outputElement = null;
this.slideHandler = null;
this.uid = LinkKnowl.xrefCount++;
knowlLinkElement.setAttribute("data-knowl-uid", this.uid);
knowlLinkElement.setAttribute("role", "button");
knowlLinkElement.setAttribute("data-base-title", knowlLinkElement.getAttribute("title") || this.linkElement.textContent);
knowlLinkElement.classList.add("knowl__link");
this.updateLabels(false);
knowlLinkElement.addEventListener("click", this.handleLinkClick.bind(this));
}
// Set aria-label and title based on visibility of knowl
updateLabels(isVisible) {
const verb = isVisible ? this.linkElement.getAttribute("data-close-label") || "Close" : this.linkElement.getAttribute("data-reveal-label") || "Reveal";
const targetDescript = this.linkElement.getAttribute("data-base-title");
const helpText = verb + " " + targetDescript;
this.linkElement.setAttribute("aria-label", helpText);
this.linkElement.setAttribute("title", helpText);
}
// Toggle the state of the knowl link and output elements
// Assumes output is already created
toggle() {
this.linkElement.classList.toggle("active");
const isActive = this.linkElement.classList.contains("active");
this.updateLabels(isActive);
if (isActive) {
const h = this.outputElement.getBoundingClientRect().height;
if (h > window.innerHeight) {
this.outputElement.scrollIntoView(true);
} else {
if (this.outputElement.getBoundingClientRect().bottom > window.innerHeight)
this.outputElement.scrollIntoView(false);
}
}
}
// Returns element the knowl output should be inserted after
findOutputLocation() {
const invalidParents = "table, mjx-container, div.tabular-box, .runestone > .parsons";
let el = this.linkElement.parentElement;
let problemAncestor = el.closest(invalidParents);
while (problemAncestor && problemAncestor !== el) {
el = problemAncestor;
problemAncestor = el.closest(invalidParents);
}
return el;
}
// Create the knowl output element
createOutputElement() {
const outputId = "knowl-uid-" + this.uid;
const outputContentsId = "knowl-output-" + this.uid;
const linkTarget = this.linkElement.getAttribute("data-knowl");
const placeholderText = `<div class='knowl__content' style='display:none;' id='${outputId}' aria-live='polite' id='${outputContentsId}'>Loading '${linkTarget}'</div>`;
const temp = document.createElement("template");
temp.innerHTML = placeholderText;
this.outputElement = temp.content.children[0];
const insertLoc = this.findOutputLocation(this.linkElement);
insertLoc.after(this.outputElement);
}
// Get content for knowl as dom element. Returns promise that resolves to knowl content
async getContent() {
const contentURL = this.linkElement.getAttribute("data-knowl");
const knowlContent = await fetch(contentURL).then((response) => response.text()).then((data) => {
let knowlDoc = new DOMParser().parseFromString(data, "text/html");
let tempContainer2 = knowlDoc.body;
let scripts = knowlDoc.querySelectorAll("head script");
tempContainer2.append(...scripts);
return tempContainer2;
}).catch((error) => {
const destination = this.linkElement.getAttribute("href");
const text = this.linkElement.textContent;
const err_message = `<div class='knowl-output__error'><div class='para'>Error fetching content. (<em>${error}</em>)</div><div class='para'><a href='${destination}'>Navigate to ${text}</a> instead.</div><div class='para'>If you are viewing this book from your local filesystem, this is expected behavior. To view the book with all features, you must serve the book from a web server. See the <a href="https://pretextbook.org/doc/guide/html/author-faq.html#how-do-i-view-my-book-locally">PreTeXt FAQ</a> for more information.</div></div>`;
return err_message;
});
return knowlContent;
}
// Handle a click on the knowl link
handleLinkClick(event) {
event.preventDefault();
if (this.slideHandler?.isBusy()) {
return;
}
if (this.outputElement !== null) {
this.toggle();
} else {
this.createOutputElement();
this.slideHandler = new SlideRevealer(this.linkElement, this.outputElement, this.outputElement);
this.linkElement.addEventListener("click", this.slideHandler);
let loadingTimeout = setTimeout(() => {
loadingTimeout = null;
this.slideHandler.onClick();
this.toggle();
}, 500);
const content = this.getContent();
content.then((tempContainer) => {
if (loadingTimeout !== null) {
clearTimeout(loadingTimeout);
}
setTimeout(() => {
this.slideHandler.onClick();
this.toggle();
}, 100);
const runestoneElements = tempContainer.querySelectorAll(".ptx-runestone-container");
[...runestoneElements].forEach((e) => {
const rsId = e.querySelector("[data-component]")?.id;
const onPage = document.getElementById(rsId);
if (onPage) {
e.innerHTML = `<div class="para">The interactive that belongs here is already on the page and cannot appear multiple times. <a href="#${rsId}">Scroll to interactive.</a>`;
} else {
window.runestoneComponents.renderOneComponent(e);
}
});
const children = [...tempContainer.children];
this.outputElement.innerHTML = "";
this.outputElement.append(...children);
addKnowls(this.outputElement);
MathJax.typesetPromise([this.outputElement]);
Prism.highlightAllUnder(this.outputElement);
[...this.outputElement.getElementsByTagName("script")].forEach((s) => {
if (s.getAttribute("type") === null || s.getAttribute("type") === "text/javascript") {
eval(s.innerHTML);
}
});
}).catch((data) => {
console.log("Error fetching knowl content: " + data);
});
}
}
}
//# sourceMappingURL=knowl.js.map
+36
View File
@@ -0,0 +1,36 @@
window.addEventListener("message", function(event) {
let edata = event.data;
if (typeof event.data == "string" && event.data.match(/lti\.frameResize/)) {
edata = JSON.parse(event.data);
}
if (edata.subject === "lti.frameResize") {
if ("frame_id" in edata) {
let el = document.getElementById(edata["frame_id"]);
document.getElementById(edata["frame_id"]).style.height = edata.height + "px";
if (edata.wrapheight && document.getElementById(edata["frame_id"] + "wrap")) {
document.getElementById(edata["frame_id"] + "wrap").style.height = edata.wrapheight + "px";
}
} else if ("iframe_resize_id" in edata) {
document.getElementById(edata["iframe_resize_id"]).style.height = edata.height + "px";
} else {
const iFrames = document.getElementsByTagName("iframe");
for (const iFrame of iFrames) {
if (iFrame.contentWindow === event.source) {
if (edata.height) {
iFrame.height = edata.height;
iFrame.style.height = edata.height + "px";
}
if (edata.width) {
iFrame.width = edata.width;
iFrame.style.width = edata.width + "px";
}
break;
}
}
}
}
});
function sendResizeRequest(el) {
el.contentWindow.postMessage("requestResize", "*");
}
//# sourceMappingURL=lti_iframe_resizer.js.map
+119
View File
@@ -0,0 +1,119 @@
let mathJaxOpts = {
"tex": {
"inlineMath": [
[
"\\(",
"\\)"
]
],
"tags": "none",
"tagSide": "right",
"tagIndent": ".8em",
"packages": {
"[+]": [
"amscd",
"color",
"knowl"
]
}
},
"options": {
"ignoreHtmlClass": "tex2jax_ignore|ignore-math",
"processHtmlClass": "process-math"
},
"chtml": {
"scale": 0.98,
"mtextInheritFont": true
},
"loader": {
"load": [
"input/asciimath",
"[tex]/amscd",
"[tex]/color"
]
}
};
function startMathJax(opts) {
if (opts.hasWebworkReps || opts.hasSage) {
mathJaxOpts["renderActions"] = {
"findScript": [
10,
function(doc) {
document.querySelectorAll('script[type^="math/tex"]').forEach(function(node) {
var display = !!node.type.match(/; *mode=display/);
var math = new doc.options.MathItem(node.textContent, doc.inputJax[0], display);
var text = document.createTextNode("");
node.parentNode.replaceChild(text, node);
math.start = { node: text, delim: "", n: 0 };
math.end = { node: text, delim: "", n: 0 };
doc.math.push(math);
});
},
""
]
};
}
if (opts.isReact) {
mathJaxOpts["startup"] = {
typeset: false
};
} else {
mathJaxOpts["startup"] = {
ready() {
const { Configuration } = MathJax._.input.tex.Configuration;
const configuration = Configuration.create("knowl", {
handler: {
macro: ["knowl"]
}
});
const NodeUtil = MathJax._.input.tex.NodeUtil.default;
function GetArgumentMML(parser, name) {
const arg = parser.ParseArg(name);
if (!NodeUtil.isInferred(arg)) {
return arg;
}
const children = NodeUtil.getChildren(arg);
if (children.length === 1) {
return children[0];
}
const mrow = parser.create("node", "mrow");
NodeUtil.copyChildren(arg, mrow);
NodeUtil.copyAttributes(arg, mrow);
return mrow;
}
;
const CommandMap = MathJax._.input.tex.TokenMap.CommandMap;
new CommandMap(
"knowl",
{
knowl(parser, name) {
const url = parser.GetArgument(name);
const arg = GetArgumentMML(parser, name);
const mrow = parser.create("node", "mrow", [arg], { tabindex: "0", "data-knowl": url });
parser.Push(mrow);
}
}
);
MathJax.startup.defaultReady();
},
pageReady() {
return MathJax.startup.defaultPageReady().then(rsMathReady);
}
};
}
if (opts.htmlPresentation) {
mathJaxOpts["options"]["menuOptions"] = {
"settings": {
"zoom": "Click",
"zscale": "300%"
}
};
}
window.MathJax = mathJaxOpts;
const runestoneMathReady = new Promise((resolve) => window.rsMathReady = resolve);
window.runestoneMathReady = runestoneMathReady;
}
export {
startMathJax
};
//# sourceMappingURL=mathjax_startup.js.map
File diff suppressed because it is too large Load Diff
+440
View File
@@ -0,0 +1,440 @@
const timeOutHandler = new Object();
const inputPrefix = "stackapi_input_";
const feedbackPrefix = "stackapi_fb_";
const validationPrefix = "stackapi_val_";
const stackstring = {
"teacheranswershow_mcq": "A correct answer is: {$a->display}",
"api_which_typed": "which can be typed as follows",
"api_valid_all_parts": "Please enter valid answers for all parts of the question.",
"api_out_of": "out of",
"api_marks_sub": "Marks for this submission",
"api_submit": "Submit Answers",
"generalfeedback": "General feedback",
"score": "Score",
"api_response": "Response summary",
"api_correct": "Correct answers"
};
function wrap_math(content) {
content = content.replace(/(?<!\\)(\\\(.*?(?<!\\)\\\))/gs, '<span class="process-math">$1</span>');
return content.replace(/(?<!\\)(\\\[.*?(?<!\\)\\\])/gs, '<span class="process-math">$1</span>');
}
async function collectData(qfile, qname, qprefix) {
let res = "";
await getQuestionFile(qfile, qname).then((response) => {
if (response.questionxml != "<quiz>\nnull\n</quiz>") {
res = {
questionDefinition: response.questionxml,
answers: collectAnswer(qprefix),
seed: response.seed,
renderInputs: qprefix + inputPrefix,
readOnly: false
};
}
;
});
return res;
}
function collectAnswer(qprefix) {
const inputs = document.getElementsByTagName("input");
const textareas = document.getElementsByTagName("textarea");
const selects = document.getElementsByTagName("select");
let res = {};
res = processNodes(res, inputs, qprefix);
res = processNodes(res, textareas, qprefix);
res = processNodes(res, selects, qprefix);
return res;
}
function processNodes(res, nodes, qprefix) {
for (let i = 0; i < nodes.length; i++) {
const element = nodes[i];
if (element.name.indexOf(qprefix + inputPrefix) === 0 && element.name.indexOf("_val") === -1) {
if (element.type === "checkbox" || element.type === "radio") {
if (element.checked) {
res[element.name.slice((qprefix + inputPrefix).length)] = element.value;
}
} else {
res[element.name.slice((qprefix + inputPrefix).length)] = element.value;
}
}
}
return res;
}
function send(qfile, qname, qprefix) {
const http = new XMLHttpRequest();
const url = stack_api_url + "/render";
http.open("POST", url, true);
http.setRequestHeader("Content-Type", "application/json");
http.onreadystatechange = function() {
if (http.readyState == 4) {
try {
const json = JSON.parse(http.responseText);
if (json.message) {
console.log(json);
document.getElementById(`${qprefix + "errors"}`).innerText = json.message;
return;
} else {
document.getElementById(`${qprefix + "errors"}`).innerText = "";
}
renameIframeHolders();
let question = json.questionrender;
const inputs = json.questioninputs;
const seed = json.questionseed;
let correctAnswers = "";
for (const [name, input] of Object.entries(inputs)) {
question = question.replace(`[[input:${name}]]`, input.render);
question = question.replace(`[[validation:${name}]]`, `<span name='${qprefix + validationPrefix + name}'></span>`);
question = question.replace(/javascript:download\(([^,]+?),([^,]+?)\)/, `javascript:download($1,$2, '${qfile}', '${qname}', '${qprefix}', ${seed})`);
question = wrap_math(question);
if (input.samplesolutionrender && name !== "remember") {
correctAnswers += `<p>
${stackstring["teacheranswershow_mcq"]} \\[{${input.samplesolutionrender}}\\],
${stackstring["api_which_typed"]}: `;
for (const [name2, solution] of Object.entries(input.samplesolution)) {
if (name2.indexOf("_val") === -1) {
correctAnswers += `<span class='correct-answer'>${wrap_math(solution)}</span>`;
}
}
correctAnswers += ".</p>";
} else if (name !== "remember") {
for (const solution of Object.values(input.samplesolution)) {
if (input.configuration.options) {
correctAnswers += `<p class='correct-answer'>${input.configuration.options[solution]}</p>`;
}
}
}
}
for (const [name, file] of Object.entries(json.questionassets)) {
const plotUrl = getPlotUrl(file);
question = question.replace(name, plotUrl);
json.questionsamplesolutiontext = json.questionsamplesolutiontext.replace(name, plotUrl);
correctAnswers = correctAnswers.replace(name, plotUrl);
}
question = replaceFeedbackTags(question, qprefix);
qoutput = document.getElementById(`${qprefix + "output"}`);
qoutput.innerHTML = question;
document.getElementById(`${qprefix + "stackapi_qtext"}`).style.display = "block";
document.getElementById(`${qprefix + "stackapi_correct"}`).style.display = "block";
for (const inputName of Object.keys(inputs)) {
const inputElements = document.querySelectorAll(`[name^=${qprefix + inputPrefix + inputName}]`);
for (const inputElement of Object.values(inputElements)) {
inputElement.oninput = (event) => {
const currentTimeout = timeOutHandler[event.target.id];
if (currentTimeout) {
window.clearTimeout(currentTimeout);
}
timeOutHandler[event.target.id] = window.setTimeout(validate.bind(null, event.target, qfile, qname, qprefix), 1e3);
};
}
}
let sampleText = json.questionsamplesolutiontext;
if (sampleText) {
sampleText = replaceFeedbackTags(sampleText, qprefix);
document.getElementById(`${qprefix + "generalfeedback"}`).innerHTML = wrap_math(sampleText);
document.getElementById(`${qprefix + "stackapi_generalfeedback"}`).style.display = "block";
} else {
document.getElementById(`${qprefix + "stackapi_generalfeedback"}`).style.display = "none";
}
document.getElementById(`${qprefix + "stackapi_score"}`).style.display = "none";
document.getElementById(`${qprefix + "stackapi_validity"}`).innerText = "";
const innerFeedback = document.getElementById(`${qprefix + "specificfeedback"}`);
innerFeedback.innerHTML = "";
innerFeedback.classList.remove("feedback");
document.getElementById(`${qprefix + "formatcorrectresponse"}`).innerHTML = correctAnswers;
document.getElementById(`${qprefix + "stackapi_generalfeedback"}`).style.display = "none";
document.getElementById(`${qprefix + "stackapi_correct"}`).style.display = "none";
createIframes(json.iframes);
MathJax.typeset();
} catch (e) {
console.log(e);
document.getElementById(`${qprefix + "errors"}`).innerText = http.responseText;
return;
}
}
};
collectData(qfile, qname, qprefix).then((data) => {
let submitbutton = document.getElementById(`${qprefix + "stackapi_qtext"}`).querySelector('input[type="button"]');
submitbutton.addEventListener("click", function() {
answer(qfile, qname, qprefix, data.seed);
});
http.send(JSON.stringify(data));
let questioncontainer = document.getElementById(`${qprefix + "stack"}`).parentElement;
if (questioncontainer.getBoundingClientRect().top < 0) {
questioncontainer.scrollIntoView({ behavior: "smooth", block: "start" });
}
;
});
}
function validate(element, qfile, qname, qprefix) {
const http = new XMLHttpRequest();
const url = stack_api_url + "/validate";
http.open("POST", url, true);
const answerNamePrefixTrim = (qprefix + inputPrefix).length;
const answerName = element.name.slice(answerNamePrefixTrim).split("_", 1)[0];
http.setRequestHeader("Content-Type", "application/json");
http.onreadystatechange = function() {
if (http.readyState == 4) {
try {
const json = JSON.parse(http.responseText);
if (json.message) {
document.getElementById(`${qprefix + "errors"}`).innerText = json.message;
return;
} else {
document.getElementById(`${qprefix + "errors"}`).innerText = "";
}
renameIframeHolders();
const validationHTML = json.validation;
const element2 = document.getElementsByName(`${qprefix + validationPrefix + answerName}`)[0];
element2.innerHTML = wrap_math(validationHTML);
if (validationHTML) {
element2.classList.add("validation");
} else {
element2.classList.remove("validation");
}
createIframes(json.iframes);
MathJax.typeset();
} catch (e) {
document.getElementById(`${qprefix + "errors"}`).innerText = http.responseText;
return;
}
}
};
collectData(qfile, qname, qprefix).then((data) => {
data.inputName = answerName;
http.send(JSON.stringify(data));
});
}
function answer(qfile, qname, qprefix, seed) {
const http = new XMLHttpRequest();
const url = stack_api_url + "/grade";
http.open("POST", url, true);
if (!document.getElementById(`${qprefix + "output"}`).innerText) {
return;
}
http.setRequestHeader("Content-Type", "application/json");
http.onreadystatechange = function() {
if (http.readyState == 4) {
try {
const json = JSON.parse(http.responseText);
if (json.message) {
document.getElementById(`${qprefix + "errors"}`).innerText = json.message;
return;
} else {
document.getElementById(`${qprefix + "errors"}`).innerText = "";
}
if (!json.isgradable) {
document.getElementById(`${qprefix + "stackapi_validity"}`).innerText = " " + stackstring["api_valid_all_parts"];
return;
}
renameIframeHolders();
document.getElementById(`${qprefix + "score"}`).innerText = (json.score * json.scoreweights.total).toFixed(2) + " " + stackstring["api_out_of"] + " " + json.scoreweights.total;
document.getElementById(`${qprefix + "stackapi_score"}`).style.display = "block";
document.getElementById(`${qprefix + "response_summary"}`).innerText = json.responsesummary;
document.getElementById(`${qprefix + "stackapi_generalfeedback"}`).style.display = "block";
document.getElementById(`${qprefix + "stackapi_summary"}`).style.display = "none";
const feedback = json.prts;
const specificFeedbackElement2 = document.getElementById(`${qprefix + "specificfeedback"}`);
if (json.specificfeedback) {
for (const [name, file] of Object.entries(json.gradingassets)) {
json.specificfeedback = json.specificfeedback.replace(name, getPlotUrl(file));
}
json.specificfeedback = replaceFeedbackTags(json.specificfeedback, qprefix);
specificFeedbackElement2.innerHTML = wrap_math(json.specificfeedback);
specificFeedbackElement2.classList.add("feedback");
} else {
specificFeedbackElement2.classList.remove("feedback");
}
for (let [name, fb] of Object.entries(feedback)) {
for (const [name2, file] of Object.entries(json.gradingassets)) {
fb = fb.replace(name2, getPlotUrl(file));
}
const elements = document.getElementsByName(`${qprefix + feedbackPrefix + name}`);
if (elements.length > 0) {
const element = elements[0];
if (json.scores[name] !== void 0) {
fb = fb + `<div>${stackstring["api_marks_sub"]}:
${(json.scores[name] * json.scoreweights[name] * json.scoreweights.total).toFixed(2)}
/ ${(json.scoreweights[name] * json.scoreweights.total).toFixed(2)}.</div>`;
}
element.innerHTML = wrap_math(fb);
}
}
createIframes(json.iframes);
MathJax.typeset();
} catch (e) {
console.log(e);
document.getElementById(`${qprefix + "errors"}`).innerText = http.responseText;
return;
}
}
};
const specificFeedbackElement = document.getElementById(`${qprefix + "specificfeedback"}`);
specificFeedbackElement.innerHTML = "";
specificFeedbackElement.classList.remove("feedback");
document.getElementById(`${qprefix + "response_summary"}`).innerText = "";
document.getElementById(`${qprefix + "stackapi_summary"}`).style.display = "none";
const inputElements = document.querySelectorAll(`[name^=${qprefix + feedbackPrefix}]`);
for (const inputElement of Object.values(inputElements)) {
inputElement.innerHTML = "";
inputElement.classList.remove("feedback");
}
document.getElementById(`${qprefix + "stackapi_score"}`).style.display = "none";
document.getElementById(`${qprefix + "stackapi_validity"}`).innerText = "";
collectData(qfile, qname, qprefix).then((data) => {
data.seed = seed;
http.send(JSON.stringify(data));
});
}
function download(filename, fileid, qfile, qname, qprefix, seed) {
const http = new XMLHttpRequest();
const url = stack_api_url + "/download";
http.open("POST", url, true);
http.setRequestHeader("Content-Type", "application/json");
http.filename = filename;
http.fileid = fileid;
http.onreadystatechange = function() {
if (http.readyState == 4) {
try {
const blob = new Blob([http.responseText], { type: "application/octet-binary", endings: "native" });
const selector = CSS.escape(`javascript:download('${http.filename}', ${http.fileid}, '${qfile}', '${qname}', '${qprefix}', ${seed})`);
const linkElements = document.querySelectorAll(`a[href^=${selector}]`);
const link = linkElements[0];
link.setAttribute("href", URL.createObjectURL(blob));
link.setAttribute("download", filename);
link.click();
} catch (e) {
document.getElementById("errors").innerText = http.responseText;
return;
}
}
};
collectData(qfile, qname, qprefix).then((data) => {
data.filename = filename;
data.fileid = fileid;
data.seed = seed;
http.send(JSON.stringify(data));
});
}
function saveState(key, value) {
if (typeof Storage !== "undefined") {
localStorage.setItem(key, value);
}
}
function loadState(key) {
if (typeof Storage !== "undefined") {
return localStorage.getItem(key) || "";
}
return "";
}
function renameIframeHolders() {
for (const iframe of document.querySelectorAll(`[id^=stack-iframe-holder]:not([id$=old]`)) {
iframe.id = iframe.id + "_old";
}
}
function createIframes(iframes) {
const corsFragment = "/cors.php?name=";
for (const iframe of iframes) {
create_iframe(
iframe[0],
iframe[1].replaceAll(corsFragment, stack_api_url + corsFragment),
...iframe.slice(2)
);
}
}
function replaceFeedbackTags(text, qprefix) {
let result = text;
const feedbackTags = text.match(/\[\[feedback:.*?\]\]/g);
if (feedbackTags) {
for (const tag of feedbackTags) {
result = result.replace(tag, `<div name='${qprefix + feedbackPrefix + tag.slice(11, -2)}'></div>`);
}
}
return result;
}
async function getQuestionFile(questionURL, questionName) {
let res = "";
if (questionURL) {
await fetch(questionURL).then((result) => result.text()).then((result) => {
res = loadQuestionFromFile(result, questionName);
});
}
return res;
}
function loadQuestionFromFile(fileContents, questionName) {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(fileContents, "text/xml");
let thequestion = null;
let randSeed = "";
for (const question of xmlDoc.getElementsByTagName("question")) {
if (question.getAttribute("type").toLowerCase() === "stack" && (!questionName || question.querySelectorAll("name text")[0].textContent === questionName)) {
thequestion = question.outerHTML;
let seeds = question.querySelectorAll("deployedseed");
if (seeds.length) {
randSeed = parseInt(seeds[Math.floor(Math.random() * seeds.length)].textContent);
}
break;
}
}
return { questionxml: setQuestion(thequestion), seed: randSeed };
}
function setQuestion(question) {
return "<quiz>\n" + question + "\n</quiz>";
}
function createQuestionBlocks() {
questionBlocks = document.getElementsByClassName("que stack");
let i = 0;
for (questionblock of questionBlocks) {
i++;
let questionPrefix = "q" + i.toString() + "_";
var qfile = questionblock.dataset.qfile;
var qname = questionblock.dataset.qname || "";
questionblock.innerHTML = `
<div class="collapsiblecontent" id=${questionPrefix + "stack"}>
<div class="vstack gap-3 ms-3 col-lg-8">
<div id=${questionPrefix + "errors"}></div>
<div id=${questionPrefix + "stackapi_qtext"} class="col-lg-8" style="display: none">
<!--<h2>${stackstring["questiontext"]}:</h2>-->
<div id=${questionPrefix + "output"} class="formulation"></div>
<div id=${questionPrefix + "specificfeedback"}></div>
<br>
<!-- <input type="button" onclick="answer('${qfile}', '${qname}', '${questionPrefix}')" class="btn btn-primary" value=${stackstring["api_submit"]}/>-->
<input type="button" class="btn btn-primary" value=${stackstring["api_submit"]}/>
<span id=${questionPrefix + "stackapi_validity"} style="color:darkred"></span>
</div>
<div id=${questionPrefix + "stackapi_generalfeedback"} class="col-lg-8" style="display: none">
<h2>${stackstring["generalfeedback"]}:</h2>
<div id=${questionPrefix + "generalfeedback"} class="feedback"></div>
</div>
<h2 id=${questionPrefix + "stackapi_score"} style="display: none">${stackstring["score"]}: <span id=${questionPrefix + "score"}></span></h2>
<div id=${questionPrefix + "stackapi_summary"} class="col-lg-10" style="display: none">
<h2>${stackstring["api_response"]}:</h2>
<div id=${questionPrefix + "response_summary"} class="feedback"></div>
</div>
<div id=${questionPrefix + "stackapi_correct"} class="col-lg-10" style="display: none">
<h2>${stackstring["api_correct"]}:</h2>
<div id=${questionPrefix + "formatcorrectresponse"} class="feedback"></div>
</div>
</div>
<div id=${questionPrefix + "newquestionbutton"}>
<input type="button" onclick="send('${qfile}', '${qname}', '${questionPrefix}')" class="btn btn-primary" value="Show new example question"/>
</div>
</div>
`;
}
}
function addCollapsibles() {
var collapsibles = document.querySelectorAll(".level2>h2, .stack>h2");
for (let i = 0; i < collapsibles.length; i++) {
collapsibles[i].addEventListener("click", () => collapseFunc(this));
}
}
function collapseFunc(e) {
e.classList.toggle("collapsed");
}
function stackSetup() {
createQuestionBlocks();
addCollapsibles();
}
function getPlotUrl(file) {
return `${stack_api_url}/plots/${file}`;
}
//# sourceMappingURL=stackapicalls.js.map
+390
View File
@@ -0,0 +1,390 @@
"use strict";
/**
* A javascript module to handle separation of author sourced scripts into
* IFRAMES. All such scripts will have limited access to the actual document
* on the VLE side and this script represents the VLE side endpoint for
* message handling needed to give that access. When porting STACK onto VLEs
* one needs to map this script to do the following:
*
* 1. Ensure that searches for target elements/inputs are limited to questions
* and do not return any elements outside them.
*
* 2. Map any identifiers needed to identify inputs by name.
*
* 3. Any change handling related to input value modifications through this
* logic gets connected to any such handling on the VLE side.
*
*
* This script is intenttionally ordered so that the VLE specific bits should
* be at the top.
*
*
* This script assumes the following:
*
* 1. Each relevant IFRAME has an `id`-attribute that will be told to this
* script.
*
* 2. Each such IFRAME exists within the question itself, so that one can
* traverse up the DOM tree from that IFRAME to find the border of
* the question.
*
* @module qtype_stack/stackjsvle
* @copyright 2023 Aalto University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
let IFRAMES = {};
let INPUTS = {};
let INPUTS_INPUT_EVENT = {};
let DISABLE_CHANGES = false;
function vle_get_element(id) {
let candidate = document.getElementById(id);
let iter = candidate;
while (iter && !iter.classList.contains("formulation")) {
iter = iter.parentElement;
}
if (iter && iter.classList.contains("formulation")) {
return candidate;
}
return null;
}
function vle_get_input_element(name, srciframe) {
let initialcandidate = document.getElementById(srciframe);
let iter = initialcandidate;
while (iter && !iter.classList.contains("formulation")) {
iter = iter.parentElement;
}
if (iter && iter.classList.contains("formulation")) {
let possible2 = iter.querySelector('input[id$="_' + name + '"]');
if (possible2 !== null) {
return possible2;
}
possible2 = iter.querySelector('input[id$="_' + name + '_1"][type=radio]');
if (possible2 !== null) {
return possible2;
}
possible2 = iter.querySelector('select[id$="_' + name + '"]');
if (possible2 !== null) {
return possible2;
}
}
let possible = document.querySelector('.formulation input[id$="_' + name + '"]');
if (possible !== null) {
return possible;
}
possible = document.querySelector('.formulation input[id$="_' + name + '_1"][type=radio]');
if (possible !== null) {
return possible;
}
possible = document.querySelector('.formulation select[id$="_' + name + '"]');
return possible;
}
function vle_update_input(inputelement) {
const c = new Event("change");
inputelement.dispatchEvent(c);
const i = new Event("input");
inputelement.dispatchEvent(i);
}
function vle_update_dom(modifiedsubtreerootelement) {
CustomEvents.notifyFilterContentUpdated(modifiedsubtreerootelement);
}
function vle_html_sanitize(src) {
let parser = new DOMParser();
let doc = parser.parseFromString(src, "text/html");
for (let el of doc.querySelectorAll("script, style")) {
el.remove();
}
for (let el of doc.querySelectorAll("*")) {
for (let { name, value } of el.attributes) {
if (is_evil_attribute(name, value)) {
el.removeAttribute(name);
}
}
}
return doc.body;
}
function is_evil_attribute(name, value) {
const lcname = name.toLowerCase();
if (lcname.startsWith("on")) {
return true;
}
if (lcname === "src" || lcname.endsWith("href")) {
const lcvalue = value.replace(/\s+/g, "").toLowerCase();
if (lcvalue.includes("javascript:") || lcvalue.includes("data:text")) {
return true;
}
}
return false;
}
window.addEventListener("message", (e) => {
if (!(typeof e.data === "string" || e.data instanceof String)) {
return;
}
let msg = null;
try {
msg = JSON.parse(e.data);
} catch (e2) {
return;
}
if (!("version" in msg && msg.version.startsWith("STACK-JS"))) {
return;
}
if (!("src" in msg && "type" in msg && msg.src in IFRAMES)) {
return;
}
let element = null;
let input = null;
let response = {
version: "STACK-JS:1.1.0"
};
switch (msg.type) {
case "register-input-listener":
input = vle_get_input_element(msg.name, msg.src);
if (input === null) {
response.type = "error";
response.msg = 'Failed to connect to input: "' + msg.name + '"';
response.tgt = msg.src;
IFRAMES[msg.src].contentWindow.postMessage(JSON.stringify(response), "*");
return;
}
response.type = "initial-input";
response.name = msg.name;
response.tgt = msg.src;
if (input.nodeName.toLowerCase() === "select") {
response.value = input.value;
response["input-type"] = "select";
response["input-readonly"] = input.hasAttribute("disabled");
} else if (input.type === "checkbox") {
response.value = input.checked;
response["input-type"] = "checkbox";
response["input-readonly"] = input.hasAttribute("disabled");
} else {
response.value = input.value;
response["input-type"] = input.type;
response["input-readonly"] = input.hasAttribute("readonly");
}
if (input.type === "radio") {
response["input-readonly"] = input.hasAttribute("disabled");
response.value = "";
for (let inp of document.querySelectorAll("input[type=radio][name=" + CSS.escape(input.name) + "]")) {
if (inp.checked) {
response.value = inp.value;
}
}
}
if (input.id in INPUTS) {
if (msg.src in INPUTS[input.id]) {
return;
}
if (input.type !== "radio") {
INPUTS[input.id].push(msg.src);
} else {
let radgroup = document.querySelectorAll("input[type=radio][name=" + CSS.escape(input.name) + "]");
for (let inp of radgroup) {
INPUTS[inp.id].push(msg.src);
}
}
} else {
if (input.type !== "radio") {
INPUTS[input.id] = [msg.src];
} else {
let radgroup = document.querySelectorAll("input[type=radio][name=" + CSS.escape(input.name) + "]");
for (let inp of radgroup) {
INPUTS[inp.id] = [msg.src];
}
}
if (input.type !== "radio") {
input.addEventListener("change", () => {
if (DISABLE_CHANGES) {
return;
}
let resp = {
version: "STACK-JS:1.0.0",
type: "changed-input",
name: msg.name
};
if (input.type === "checkbox") {
resp["value"] = input.checked;
} else {
resp["value"] = input.value;
}
for (let tgt of INPUTS[input.id]) {
resp["tgt"] = tgt;
IFRAMES[tgt].contentWindow.postMessage(JSON.stringify(resp), "*");
}
});
} else {
let radgroup = document.querySelectorAll("input[type=radio][name=" + CSS.escape(input.name) + "]");
radgroup.forEach((inp) => {
inp.addEventListener("change", () => {
if (DISABLE_CHANGES) {
return;
}
let resp = {
version: "STACK-JS:1.0.0",
type: "changed-input",
name: msg.name
};
if (inp.checked) {
resp.value = inp.value;
} else {
return;
}
for (let tgt of INPUTS[inp.id]) {
resp["tgt"] = tgt;
IFRAMES[tgt].contentWindow.postMessage(JSON.stringify(resp), "*");
}
});
});
}
}
if ("track-input" in msg && msg["track-input"] && input.type !== "radio") {
if (input.id in INPUTS_INPUT_EVENT) {
if (msg.src in INPUTS_INPUT_EVENT[input.id]) {
return;
}
INPUTS_INPUT_EVENT[input.id].push(msg.src);
} else {
INPUTS_INPUT_EVENT[input.id] = [msg.src];
input.addEventListener("input", () => {
if (DISABLE_CHANGES) {
return;
}
let resp = {
version: "STACK-JS:1.0.0",
type: "changed-input",
name: msg.name
};
if (input.type === "checkbox") {
resp["value"] = input.checked;
} else {
resp["value"] = input.value;
}
for (let tgt of INPUTS_INPUT_EVENT[input.id]) {
resp["tgt"] = tgt;
IFRAMES[tgt].contentWindow.postMessage(JSON.stringify(resp), "*");
}
});
}
}
if (!(msg.src in INPUTS[input.id])) {
IFRAMES[msg.src].contentWindow.postMessage(JSON.stringify(response), "*");
}
break;
case "changed-input":
input = vle_get_input_element(msg.name, msg.src);
if (input === null) {
const ret = {
version: "STACK-JS:1.0.0",
type: "error",
msg: 'Failed to modify input: "' + msg.name + '"',
tgt: msg.src
};
IFRAMES[msg.src].contentWindow.postMessage(JSON.stringify(ret), "*");
return;
}
DISABLE_CHANGES = true;
if (input.type === "checkbox") {
input.checked = msg.value;
} else {
input.value = msg.value;
}
vle_update_input(input);
DISABLE_CHANGES = false;
response.type = "changed-input";
response.name = msg.name;
response.value = msg.value;
for (let tgt of INPUTS[input.id]) {
if (tgt !== msg.src) {
response.tgt = tgt;
IFRAMES[tgt].contentWindow.postMessage(JSON.stringify(response), "*");
}
}
break;
case "toggle-visibility":
element = vle_get_element(msg.target);
if (element === null) {
const ret = {
version: "STACK-JS:1.0.0",
type: "error",
msg: 'Failed to find element: "' + msg.target + '"',
tgt: msg.src
};
IFRAMES[msg.src].contentWindow.postMessage(JSON.stringify(ret), "*");
return;
}
if (msg.set === "show") {
element.style.display = "block";
vle_update_dom(element);
} else if (msg.set === "hide") {
element.style.display = "none";
}
break;
case "change-content":
element = vle_get_element(msg.target);
if (element === null) {
response.type = "error";
response.msg = 'Failed to find element: "' + msg.target + '"';
response.tgt = msg.src;
IFRAMES[msg.src].contentWindow.postMessage(JSON.stringify(response), "*");
return;
}
element.replaceChildren(vle_html_sanitize(msg.content));
vle_update_dom(element);
break;
case "get-content":
element = vle_get_element(msg.target);
response.type = "xfer-content";
response.tgt = msg.src;
response.target = msg.target;
response.content = null;
if (element !== null) {
response.content = element.innerHTML;
}
IFRAMES[msg.src].contentWindow.postMessage(JSON.stringify(response), "*");
break;
case "resize-frame":
element = IFRAMES[msg.src].parentElement;
element.style.width = msg.width;
element.style.height = msg.height;
IFRAMES[msg.src].style.width = "100%";
IFRAMES[msg.src].style.height = "100%";
vle_update_dom(element);
break;
case "ping":
response.type = "ping";
response.tgt = msg.src;
IFRAMES[msg.src].contentWindow.postMessage(JSON.stringify(response), "*");
return;
case "initial-input":
case "error":
break;
default:
response.type = "error";
response.msg = 'Unknown message-type: "' + msg.type + '"';
response.tgt = msg.src;
IFRAMES[msg.src].contentWindow.postMessage(JSON.stringify(response), "*");
}
});
function create_iframe(iframeid, content, targetdivid, title, scrolling, evil) {
const frm = document.createElement("iframe");
frm.id = iframeid;
frm.style.width = "100%";
frm.style.height = "100%";
frm.style.border = 0;
if (scrolling === false) {
frm.scrolling = "no";
frm.style.overflow = "hidden";
} else {
frm.scrolling = "yes";
}
frm.title = title;
frm.referrerpolicy = "no-referrer";
if (!evil) {
frm.sandbox = "allow-scripts allow-downloads";
}
frm.srcdoc = content;
document.getElementById(targetdivid).replaceChildren(frm);
IFRAMES[iframeid] = frm;
}
;
//# sourceMappingURL=stackjsvle.js.map
@@ -0,0 +1,605 @@
function handleWW(ww_id, action) {
const ww_container = document.getElementById(ww_id);
const ww_domain = ww_container.dataset.domain;
const ww_problemSource = ww_container.dataset.problemsource;
const ww_sourceFilePath = ww_container.dataset.sourcefilepath;
const ww_course_id = ww_container.dataset.courseid;
const ww_user_id = ww_container.dataset.userid;
const ww_course_password = ww_container.dataset.coursepassword;
const localize_correct = ww_container.dataset.localizeCorrect ? ww_container.dataset.localizeCorrect : "Correct";
const localize_incorrect = ww_container.dataset.localizeIncorrect ? ww_container.dataset.localizeIncorrect : "Incorrect";
const localize_check_responses = ww_container.dataset.localizeCheckResponses ? ww_container.dataset.localizeCheckResponses : "Check Responses";
const localize_randomize = ww_container.dataset.localizeRandomize ? ww_container.dataset.localizeRandomize : "Randomize";
const localize_reset = ww_container.dataset.localizeReset ? ww_container.dataset.localizeReset : "Reset";
const activate_button = document.getElementById(ww_id + "-button");
if (!action) ww_container.dataset.current_seed = ww_container.dataset.seed;
else if (action == "randomize") ww_container.dataset.current_seed = Number(ww_container.dataset.current_seed) + 100;
let loader = document.createElement("div");
loader.style.position = "absolute";
loader.style.left = 0;
loader.style.top = 0;
loader.style.backgroundColor = "rgba(0.2, 0.2, 0.2, 0.4)";
loader.style.color = "white";
loader.style.width = "100%";
loader.style.height = "100%";
loader.style.display = "flex";
loader.style.alignItems = "center";
loader.style.justifyContent = "center";
loader.style.marginTop = "0";
loader.tabIndex = -1;
const loaderText = document.createElement("span");
loaderText.textContent = "Loading";
loaderText.style.fontSize = "2rem";
loader.appendChild(loaderText);
ww_container.appendChild(loader);
loader.focus();
if (!action) {
ww_container.dataset.hasHint = ww_container.getElementsByClassName("hint").length > 0;
ww_container.dataset.hasSolution = ww_container.getElementsByClassName("solution").length > 0;
ww_container.dataset.hasAnswer = ww_container.getElementsByClassName("answer").length > 0;
ww_container.dataset.hintLabelText = ww_container.dataset.hasHint == "true" ? ww_container.querySelectorAll(".hint-knowl span.type")[0].textContent : "Hint";
ww_container.dataset.solutionLabelText = ww_container.dataset.hasSolution == "true" ? ww_container.querySelectorAll(".solution-knowl span.type")[0].textContent : "Solution";
ww_container.tabIndex = -1;
}
let url;
if (action == "check") {
const iframe2 = ww_container.querySelector(".problem-iframe");
const formData = new FormData(iframe2.contentDocument.getElementById(ww_id + "-form"));
const params = new URLSearchParams(formData);
url = new URL(ww_domain + "/webwork2/html2xml?" + params.toString());
url.searchParams.append("answersSubmitted", "1");
url.searchParams.append("WWsubmit", "1");
} else {
url = new URL(ww_domain + "/webwork2/html2xml");
url.searchParams.append("problemSeed", ww_container.dataset.current_seed);
if (ww_problemSource) url.searchParams.append("problemSource", ww_problemSource);
else if (ww_sourceFilePath) url.searchParams.append("sourceFilePath", ww_sourceFilePath);
url.searchParams.append("answersSubmitted", "0");
url.searchParams.append("displayMode", "MathJax");
url.searchParams.append("courseID", ww_course_id);
url.searchParams.append("userID", ww_user_id);
url.searchParams.append("course_password", ww_course_password);
url.searchParams.append("outputformat", "raw");
url.searchParams.append("showSolutions", ww_container.dataset.hasSolution == "true" ? "1" : "0");
url.searchParams.append("showHints", ww_container.dataset.hasHint == "true" ? "1" : "0");
url.searchParams.append("problemUUID", ww_id);
}
$.getJSON(url.toString(), (data) => {
const form = document.createElement("form");
form.id = ww_id + "-form";
const body_div = document.createElement("div");
body_div.id = ww_id + "-body";
body_div.classList.add("exercise", "exercise-like");
body_div.lang = data.lang;
body_div.dir = data.dir;
body_div.innerHTML = data.rh_result.text;
for (const tag_name of ["h6", "h5", "h4", "h3", "h2", "h1"]) {
const headings = body_div.getElementsByTagName(tag_name);
for (heading of headings) {
const new_heading = document.createElement("h6");
new_heading.innerHTML = heading.innerHTML;
cloneAttributes(new_heading, heading);
new_heading.classList.add("webwork-part");
heading.replaceWith(new_heading);
}
}
adjustSrcHrefs(body_div, ww_domain);
translateHintSol(
ww_id,
body_div,
ww_domain,
ww_container.dataset.hasHint == "true",
ww_container.dataset.hasSolution == "true",
ww_container.dataset.hintLabelText,
ww_container.dataset.solutionLabelText
);
form.appendChild(body_div);
const wwInputs = {
problemSeed: data.inputs_ref.problemSeed,
problemUUID: data.inputs_ref.problemUUID,
psvn: data.inputs_ref.psvn,
courseName: ww_course_id,
courseID: ww_course_id,
userID: ww_user_id,
course_password: ww_course_password,
displayMode: "MathJax",
session_key: data.rh_result.session_key,
outputformat: "raw",
language: data.formLanguage,
showSummary: data.showSummary,
// note ww_container.dataset.hasSolution is a string, possibly 'false' which is true
showSolutions: ww_container.dataset.hasSolution == "true" ? "1" : "0",
showHints: ww_container.dataset.hasHint == "true" ? "1" : "0",
forcePortNumber: data.forcePortNumber
};
if (ww_sourceFilePath) wwInputs.sourceFilePath = ww_sourceFilePath;
else if (ww_problemSource) wwInputs.problemSource = ww_problemSource;
for (const wwInputName of Object.keys(wwInputs)) {
const input = document.createElement("input");
input.type = "hidden";
input.name = wwInputName;
input.value = wwInputs[wwInputName];
form.appendChild(input);
}
const answers = {};
Object.keys(data.rh_result.answers).forEach(function(id) {
answers[id] = {};
}, data.rh_result.answers);
if (ww_container.dataset.hasAnswer == "true") {
Object.keys(data.rh_result.answers).forEach(function(id) {
answers[id] = {
correct_ans: this[id].correct_ans,
correct_ans_latex_string: this[id].correct_ans_latex_string,
correct_choice: this[id].correct_choice
};
}, data.rh_result.answers);
}
let buttonContainer = ww_container.querySelector(".problem-buttons.webwork");
if (!buttonContainer) {
ww_container.querySelector(".problem-buttons").classList.add("hidden-content");
if (activate_button != null) {
activate_button.classList.add("hidden-content");
}
;
buttonContainer = document.createElement("div");
buttonContainer.classList.add("problem-buttons", "webwork");
if (activate_button != null) {
activate_button.after(buttonContainer);
} else {
ww_container.prepend(buttonContainer);
}
const check = document.createElement("button");
check.type = "button";
check.id = ww_id + "-check";
check.style.marginRight = "0.25rem";
check.classList.add("webwork-button");
const answerCount = body_div.querySelectorAll("input:not([type=hidden])").length + body_div.querySelectorAll("select:not([type=hidden])").length;
check.textContent = localize_check_responses;
check.addEventListener("click", () => handleWW(ww_id, "check"));
buttonContainer.appendChild(check);
if (ww_container.dataset.hasAnswer == "true") {
const correct = document.createElement("button");
correct.classList.add("show-correct", "webwork-button");
correct.type = "button";
correct.style.marginRight = "0.25rem";
correct.textContent = answerCount > 1 ? "Show Correct Answers" : "Show Correct Answer";
correct.addEventListener("click", () => WWshowCorrect(ww_id, answers));
buttonContainer.appendChild(correct);
}
const randomize = document.createElement("button");
randomize.type = "button";
randomize.classList.add("webwork-button");
randomize.style.marginRight = "0.25rem";
randomize.textContent = localize_randomize;
randomize.addEventListener("click", () => handleWW(ww_id, "randomize"));
buttonContainer.appendChild(randomize);
const reset = document.createElement("button");
reset.type = "button";
reset.classList.add("webwork-button");
reset.textContent = localize_reset;
reset.addEventListener("click", () => resetWW(ww_id));
buttonContainer.appendChild(reset);
} else {
if (ww_container.dataset.hasAnswer == "true") {
const correct = buttonContainer.querySelector(".show-correct");
const correctNew = correct.cloneNode(true);
correctNew.addEventListener("click", () => WWshowCorrect(ww_id, answers));
correct.replaceWith(correctNew);
}
}
if (action == "check") {
$("body").trigger("runestone_ww_check", data);
const inputs = body_div.querySelectorAll("input:not([type=hidden])");
for (const input of inputs) {
const name = input.name;
if (input.type == "text" && answers[name]) {
const score = data.rh_result.answers[name].score;
let title = "";
if (score == 1) {
title = `<span class="correct">${localize_correct}!</span>`;
} else if (score > 0 && score < 1) {
title = `<span class="partly-correct">${Math.round(score * 100)}% ${localize_correct}.</span>`;
} else if (data.rh_result.answers[name].student_ans == "") {
continue;
} else if (score == 0) {
title = `<span class="incorrect">${localize_incorrect}.</span>`;
}
input.after(createFeedbackButton(`${ww_id}-${name}`, title, data.rh_result.answers[name].ans_message));
}
if (input.type == "radio" && answers[name]) {
if (input.value == data.rh_result.answers[name].student_value) {
const feedbackButton = createFeedbackButton(
`${ww_id}-${name}`,
data.rh_result.answers[name].student_value == data.rh_result.answers[name].correct_choice ? `<span class="correct">${localize_correct}!</span>` : `<span class="incorrect">${localize_incorrect}.</span>`
);
feedbackButton.style.marginRight = "0.25rem";
input.after(feedbackButton);
}
}
}
const hiddenInputs = body_div.querySelectorAll("input[type=hidden]");
for (const input of hiddenInputs) {
const name = input.name;
if (!input.nextElementSibling) continue;
const graphtoolContainer = input.nextElementSibling.nextElementSibling;
if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains("graphtool-container")) {
graphtoolContainer.style.position = "relative";
const score = data.rh_result.answers[name].score;
let title = "";
if (score == 1) {
title = `<span class="correct">${localize_correct}!</span>`;
} else if (score > 0 && score < 1) {
title = `<span class="partly-correct">${Math.round(score * 100)}% ${localize_correct}.</span>`;
} else if (data.rh_result.answers[name].student_ans == "") {
continue;
} else if (score == 0) {
title = `<span class="incorrect">${localize_incorrect}.</span>`;
}
const feedbackButton = createFeedbackButton(`${ww_id}-${name}`, title, data.rh_result.answers[name].ans_message);
feedbackButton.style.position = "absolute";
feedbackButton.style.left = "100%";
feedbackButton.style.top = 0;
feedbackButton.style.marginLeft = "0.5rem";
feedbackButton.dataset.container = "body";
graphtoolContainer.appendChild(feedbackButton);
}
}
const selects = body_div.querySelectorAll("select:not([type=hidden])");
for (const select of selects) {
const name = select.name;
const feedbackButton = createFeedbackButton(
`${ww_id}-${name}`,
data.rh_result.answers[name].score == 1 ? `<span class="correct">${localize_correct}!</span>` : `<span class="incorrect">${localize_incorrect}.</span>`
);
feedbackButton.style.marginRight = "0.25rem";
feedbackButton.style.marginLeft = "0.5rem";
select.after(feedbackButton);
}
}
let iframeContents = '<!DOCTYPE html><head><script src="' + ww_domain + `/webwork2_files/node_modules/jquery/dist/jquery.min.js"><\/script><script>window.MathJax = {
tex: {
inlineMath: [['\\\\(','\\\\)']],
tags: "none",
useLabelIds: true,
tagSide: "right",
tagIndent: ".8em",
packages: {'[+]': ['base', 'extpfeil', 'ams', 'amscd', 'newcommand', 'knowl']}
},
options: {
ignoreHtmlClass: "tex2jax_ignore",
processHtmlClass: "has_am",
renderActions: {
findScript: [10, function (doc) {
document.querySelectorAll('script[type^="math/tex"]').forEach(function(node) {
const display = !!node.type.match(/; *mode=display/);
const math = new doc.options.MathItem(node.textContent, doc.inputJax[0], display);
const text = document.createTextNode('');
node.parentNode.replaceChild(text, node);
math.start = {node: text, delim: '', n: 0};
math.end = {node: text, delim: '', n: 0};
doc.math.push(math);
});
}, '']
},
},
chtml: {
scale: 0.88,
mtextInheritFont: true
},
loader: {
load: ['input/asciimath', '[tex]/extpfeil', '[tex]/amscd', '[tex]/newcommand', '[pretext]/mathjaxknowl3.js'],
paths: {pretext: "https://pretextbook.org/js/lib"},
},
};
<\/script><script src="` + ww_domain + '/webwork2_files/mathjax/es5/tex-chtml.js" id="MathJax-script" defer><\/script><script src="https://pretextbook.org/js/lib/knowl.js" defer><\/script><link rel="stylesheet" href="' + ww_domain + '/webwork2_files/js/vendor/bootstrap/css/bootstrap.css"/><link rel="stylesheet" href="' + ww_domain + '/webwork2_files/themes/math4/math4.css"/><script src="' + ww_domain + '/webwork2_files/js/vendor/bootstrap/js/bootstrap.js" id="MathJax-script" defer><\/script>';
const extra_css_files = [];
const extra_js_files = [];
if (data.rh_result.flags.extra_css_files) data.rh_result.flags.extra_css_files.unshift(...extra_css_files);
else data.rh_result.flags.extra_css_files = extra_css_files;
for (const cssFile of data.rh_result.flags.extra_css_files) {
iframeContents += '<link rel="stylesheet" href="' + (cssFile.external !== "1" ? ww_domain + "/webwork2_files/" : "") + cssFile.file + '"/>';
}
if (data.rh_result.flags.extra_js_files) data.rh_result.flags.extra_js_files.unshift(...extra_js_files);
else data.rh_result.flags.extra_js_files = extra_js_files;
for (const jsFile of data.rh_result.flags.extra_js_files) {
iframeContents += '<script src="' + (jsFile.external !== "1" ? ww_domain + "/webwork2_files/" : "") + jsFile.file + '" ' + Object.keys(jsFile.attributes || {}).reduce((ret, key) => {
ret += key + '="' + jsFile.attributes[key] + '" ';
return ret;
}, "") + "><\/script>";
}
iframeContents += '<link rel="stylesheet" href="https://pretextbook.org/css/0.31/pretext_add_on.css"/><link rel="stylesheet" href="https://pretextbook.org/css/0.31/knowls_default.css"/><script src="' + ww_domain + `/webwork2_files/node_modules/iframe-resizer/js/iframeResizer.contentWindow.min.js"><\/script><style>
html { overflow-y: hidden; }
html body { background:unset; margin: 0; }
body { font-size: initial; line-height: initial; }
.hidden-content { display: none; }
input[type="text"], input[type="radio"], label, select {
height: auto;
width: auto;
max-width: unset;
margin: 0;
font-size: initial;
font-family: sans-serif;
line-height: initial;
}
input[type="text"] {
padding: 1px;
padding-inline: 2px;
border-width: 1px;
border-style: inset;
border-color: rgb(133, 133, 133);
border-radius: 0;
box-shadow: unset;
transition: none;
}
input[type="text"]:focus, input[type="text"]:active {
box-shadow: unset;
border-width: 1px;
border-style: inset;
border-radius: 4px;
border-color: rgb(133, 133, 133);
outline: auto;
}
input[type="radio"] {
box-sizing: border-box;
margin-block: 3px 0;
margin-inline: 5px 3px;
vertical-align: unset;
}
label {
display: inline-block;
}
select {
margin: 0;
border-width: 1px;
border-style: inset;
display: inline-block;
padding-block: 1px;
vertical-align: baseline;
background-color: unset;
padding-inline: 0;
}
.popover-title, .popover-content {
text-align: center;
}
.popover-title.correct {
background-color: #8F8;
}
.accordion-body.expanded {
overflow-y: visible;
overflow-x: clip;
}
.graphtool-answer-container .graphtool-graph {
margin: 0;
width: 300px;
height: 300px;
}
div.PGML img.image-view-elt {
max-width:100%;
}
</style></head><body><main class="pretext-content">` + form.outerHTML + "</main></body></html>";
let iframe2;
if (!action) {
iframe2 = document.createElement("iframe");
iframe2.style.width = "1px";
iframe2.style.minWidth = "100%";
iframe2.classList.add("problem-iframe");
ww_container.querySelector(".problem-contents").classList.add("hidden-content");
if (activate_button != null) {
activate_button.after(iframe2);
} else {
ww_container.prepend(iframe2);
}
iFrameResize({ checkOrigin: false, scrolling: "omit", heightCalculationMethod: "min" }, iframe2);
iframe2.addEventListener("load", () => {
const iframeForm = iframe2.contentDocument.getElementById(ww_id + "-form");
iframeForm.addEventListener("submit", (e) => {
handleWW(ww_id, "check");
e.preventDefault();
});
iframe2.contentDocument.querySelectorAll(".collapse.in").forEach((collapse) => collapse.classList.add("expanded"));
iframe2.contentWindow.jQuery(".collapse").on("shown", function(e) {
if (e.target != this) return;
this.classList.add("expanded");
});
iframe2.contentWindow.jQuery(".collapse").on("hide", function(e) {
if (e.target != this) return;
this.classList.remove("expanded");
});
iframe2.contentDocument.querySelectorAll("button.ww-feedback[data-content]").forEach((button) => {
iframe2.contentWindow.jQuery(button).popover("show");
const content = iframe2.contentDocument.getElementById(button.id.replace("-feedback-button", "-content"));
const popover = content.parentNode.parentNode;
popover.id = button.id.replace("-feedback-button", "-feedback");
button.setAttribute("aria-describedby", popover.id);
if (button.previousElementSibling)
button.previousElementSibling.setAttribute("aria-describedby", popover.id);
popover.querySelector(".arrow").remove();
const title = popover.querySelector(".popover-title");
if (button.dataset.emptyContent) {
title.style.borderBottomWidth = 0;
content.parentNode.remove();
}
if (title.textContent == localize_correct + "!") title.classList.add("correct");
});
iframe2.contentWindow.MathJax.startup.promise.then(() => iframe2.contentWindow.MathJax.typesetPromise([".popover", ".popover-content"]));
});
} else {
iframe2 = ww_container.querySelector(".problem-iframe");
}
iframe2.srcdoc = iframeContents;
iframe2.addEventListener("load", () => {
loader.remove();
}, { once: true });
ww_container.focus();
});
}
function WWshowCorrect(ww_id, answers) {
const ww_container = document.getElementById(ww_id);
const iframe2 = ww_container.querySelector(".problem-iframe");
const body = iframe2.contentDocument.getElementById(ww_id + "-body");
$("body").trigger("runestone_show_correct", {
"ww_id": ww_id,
"answers": answers
});
let inputs = body.querySelectorAll("input:not([type=hidden])");
for (const input of inputs) {
const name = input.name;
const span_id = `${ww_id}-${name}-correct`;
if (input.type == "text" && answers[name] && !iframe2.contentDocument.getElementById(span_id)) {
const feedbackButton = iframe2.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`);
if (feedbackButton) {
feedbackButton.remove();
iframe2.contentWindow.jQuery(feedbackButton).popover("hide");
}
label = iframe2.contentDocument.getElementById(`${span_id}-label`);
if (label) {
label.parentElement.insertBefore(input, label);
label.remove();
}
input.type = "hidden";
const correct_ans_text = iframe2.contentDocument.createElement("div");
correct_ans_text.innerHTML = answers[name].correct_ans;
input.value = correct_ans_text.textContent;
const show_span = iframe2.contentDocument.createElement("span");
show_span.id = span_id;
show_span.appendChild(answers[name].correct_ans_latex_string ? iframe2.contentDocument.createTextNode("\\(" + answers[name].correct_ans_latex_string + "\\)") : iframe2.contentDocument.createTextNode(answers[name].correct_ans));
input.parentElement.insertBefore(show_span, input);
}
if (input.type == "radio" && answers[name]) {
const feedbackButton = iframe2.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`);
if (feedbackButton) {
feedbackButton.remove();
iframe2.contentWindow.jQuery(feedbackButton).popover("hide");
}
correct_value = answers[name].correct_choice;
if (input.value == correct_value) input.checked = true;
}
}
const hiddenInputs = body.querySelectorAll("input[type=hidden]");
for (const input of hiddenInputs) {
const name = input.name;
if (!input.nextElementSibling) continue;
const graphtoolContainer = input.nextElementSibling.nextElementSibling;
if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains("graphtool-container")) {
const feedbackButton = iframe2.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`);
if (feedbackButton) {
feedbackButton.remove();
iframe2.contentWindow.jQuery(feedbackButton).popover("hide");
}
const correct_ans_div = iframe2.contentDocument.createElement("div");
input.parentElement.insertBefore(correct_ans_div, graphtoolContainer);
graphtoolContainer.style.display = "none";
input.value = answers[name].correct_ans;
iframe2.contentWindow.jQuery(correct_ans_div).html(answers[name].correct_ans_latex_string);
const script = iframe2.contentDocument.createElement("script");
script.textContent = correct_ans_div.querySelector("script").textContent.replace('\nwindow.addEventListener("DOMContentLoaded",', "(").replace(/;\n$/, "();");
iframe2.contentDocument.body.appendChild(script);
}
}
let selects = body.querySelectorAll("select:not([type=hidden])");
for (const select of selects) {
const name = select.name;
const span_id = `${ww_id}-${name}-correct`;
if (answers[name] && !iframe2.contentDocument.getElementById(span_id)) {
const feedbackButton = iframe2.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`);
if (feedbackButton) {
feedbackButton.remove();
iframe2.contentWindow.jQuery(feedbackButton).popover("hide");
}
select.style.display = "none";
select.value = answers[name].correct_ans;
const show_span = iframe2.contentDocument.createElement("span");
show_span.id = span_id;
show_span.appendChild(answers[name].correct_ans_latex_string ? iframe2.contentDocument.createTextNode("\\(" + answers[name].correct_ans_latex_string + "\\)") : iframe2.contentDocument.createTextNode(answers[name].correct_ans));
select.parentElement.insertBefore(show_span, select);
}
}
const mathjaxTypesetScript = iframe2.contentDocument.createElement("script");
mathjaxTypesetScript.textContent = "MathJax.startup.promise.then(() => MathJax.typesetPromise([document.body]));";
iframe2.contentDocument.body.appendChild(mathjaxTypesetScript);
}
function resetWW(ww_id) {
const ww_container = document.getElementById(ww_id);
const activate_button = document.getElementById(ww_id + "-button");
ww_container.dataset.current_seed = ww_container.dataset.seed;
iframe = ww_container.querySelector(".problem-iframe");
iframe.remove();
ww_container.querySelector(".problem-contents").classList.remove("hidden-content");
ww_container.querySelector(".problem-buttons.webwork").remove();
ww_container.querySelector(".problem-buttons").classList.remove("hidden-content");
if (activate_button != null) {
activate_button.classList.remove("hidden-content");
}
;
}
function adjustSrcHrefs(container, ww_domain) {
container.querySelectorAll("[href]").forEach((node) => {
const href = node.attributes.href.value;
if (href !== "#" && !href.match(/^[a-z]+:\/\//i)) node.href = ww_domain + "/" + href;
});
container.querySelectorAll("[src]").forEach((node) => {
node.src = ww_domain + "/" + node.attributes.src.value;
});
}
function translateHintSol(ww_id, body_div, ww_domain, b_ptx_has_hint, b_ptx_has_solution, hint_label_text, solution_label_text) {
const ww_container = document.getElementById(ww_id);
const hintsolnodes = document.evaluate("//p[a/b]", body_div, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
if (hintsolnodes) {
let solutionlikewrapper;
for (let i = 0; i < hintsolnodes.snapshotLength; i++) {
const hintsolp = hintsolnodes.snapshotItem(i);
if (!hintsolp) continue;
const hintSolType = hintsolp.textContent.trim().toLowerCase().replace(":", "");
if (hintsolp.previousElementSibling.textContent.trim() != "Hint:" && hintsolp.previousElementSibling.textContent.trim() != "Solution:") {
solutionlikewrapper = document.createElement("div");
solutionlikewrapper.classList.add("webwork", "solutions");
hintsolp.parentNode.insertBefore(solutionlikewrapper, hintsolp);
}
if (hintSolType == "solution" && !b_ptx_has_solution || hintSolType == "hint" && !b_ptx_has_hint) continue;
const knowlDetails = document.createElement("details");
knowlDetails.classList.add(hintSolType);
knowlDetails.classList.add("solution-like");
knowlDetails.classList.add("born-hidden-knowl");
const knowlSummary = document.createElement("summary");
const summaryLabel = document.createElement("span");
summaryLabel.classList.add("type");
summaryLabel.innerHTML = hintSolType == "hint" ? hint_label_text : solution_label_text;
knowlSummary.appendChild(summaryLabel);
knowlDetails.appendChild(knowlSummary);
const knowlContents = document.createElement("div");
knowlContents.classList.add(hintSolType);
knowlContents.classList.add("solution-like");
knowlContents.innerHTML = hintsolp.firstElementChild.dataset.knowlContents;
knowlDetails.appendChild(knowlContents);
adjustSrcHrefs(knowlContents, ww_domain);
solutionlikewrapper.appendChild(knowlDetails);
}
}
for (let i = 0; i < hintsolnodes.snapshotLength; i++) {
hintsolnodes.snapshotItem(i).remove();
}
}
function cloneAttributes(target, source) {
[...source.attributes].forEach((attr) => {
target.setAttribute(attr.nodeName, attr.nodeValue);
});
}
function createFeedbackButton(id, title, content) {
const feedbackButton = document.createElement("button");
feedbackButton.dataset.title = title;
feedbackButton.dataset.content = `<div id="${id}-content">${content || ""}</div>`;
if (!content) feedbackButton.dataset.emptyContent = "1";
const contentSpan = document.createElement("span");
contentSpan.style.fontWeight = 1e3;
contentSpan.textContent = "\u{1F5E9}";
feedbackButton.appendChild(contentSpan);
feedbackButton.type = "button";
feedbackButton.classList.add("ww-feedback");
feedbackButton.style.borderRadius = 0;
feedbackButton.id = `${id}-feedback-button`;
feedbackButton.dataset.html = true;
feedbackButton.dataset.placement = "bottom";
feedbackButton.dataset.trigger = "click";
return feedbackButton;
}
//# sourceMappingURL=pretext-webwork.js.map
@@ -0,0 +1,885 @@
function handleWW(ww_id, action) {
const ww_container = document.getElementById(ww_id);
const ww_domain = ww_container.dataset.domain;
const ww_problemSource = ww_container.dataset.problemsource;
const ww_sourceFilePath = ww_container.dataset.sourcefilepath;
const ww_course_id = ww_container.dataset.courseid;
const ww_user_id = ww_container.dataset.userid;
const ww_course_password = ww_container.dataset.coursepassword || ww_container.dataset.passwd;
const localize_correct = ww_container.dataset.localizeCorrect || "Correct";
const localize_incorrect = ww_container.dataset.localizeIncorrect || "Incorrect";
const localize_blank = ww_container.dataset.localizeBlank || "Blank";
const localize_submit = ww_container.dataset.localizeSubmit || "Submit";
const localize_check_responses = ww_container.dataset.localizeCheckResponses || "Check Responses";
const localize_reveal = ww_container.dataset.localizeReveal || "Reveal";
const localize_randomize = ww_container.dataset.localizeRandomize || "Randomize";
const localize_reset = ww_container.dataset.localizeReset || "Reset";
const runestone_logged_in = typeof eBookConfig !== "undefined" && eBookConfig.username !== "";
const activate_button = document.getElementById(ww_id + "-button");
if (!action) {
ww_container.dataset.current_seed = ww_container.dataset.seed;
if (runestone_logged_in) {
ww_container.dataset.current_seed = webworkSeedHash(eBookConfig.username + ww_container.dataset.current_seed);
}
} else if (action == "randomize") ww_container.dataset.current_seed = Number(ww_container.dataset.current_seed) + 100;
let loader = document.createElement("div");
loader.style.position = "absolute";
loader.style.left = 0;
loader.style.top = 0;
loader.style.backgroundColor = "rgba(0.2, 0.2, 0.2, 0.4)";
loader.style.color = "white";
loader.style.width = "100%";
loader.style.height = "100%";
loader.style.display = "flex";
loader.style.alignItems = "center";
loader.style.justifyContent = "center";
loader.style.marginTop = "0";
loader.tabIndex = -1;
const loaderText = document.createElement("span");
loaderText.textContent = "Loading";
loaderText.style.fontSize = "2rem";
loader.appendChild(loaderText);
ww_container.appendChild(loader);
loader.focus();
if (!action) {
ww_container.dataset.hasHint = ww_container.getElementsByClassName("hint").length > 0;
ww_container.dataset.hasSolution = ww_container.getElementsByClassName("solution").length > 0;
ww_container.dataset.hasAnswer = ww_container.getElementsByClassName("answer").length > 0;
ww_container.dataset.hintLabelText = ww_container.dataset.hasHint == "true" ? ww_container.querySelectorAll(".hint-knowl span.type, details.hint span.type")[0].textContent : "Hint";
ww_container.dataset.solutionLabelText = ww_container.dataset.hasSolution == "true" ? ww_container.querySelectorAll(".solution-knowl span.type, details.solution span.type")[0].textContent : "Solution";
ww_container.tabIndex = -1;
}
let url;
if (action == "check") {
const iframe2 = ww_container.querySelector(".problem-iframe");
const formData = new FormData(iframe2.contentDocument.getElementById(ww_id + "-form"));
const params = new URLSearchParams(formData);
url = new URL(ww_domain + "/webwork2/html2xml?" + params.toString());
url.searchParams.append("answersSubmitted", "1");
url.searchParams.append("WWsubmit", "1");
} else {
url = new URL(ww_domain + "/webwork2/html2xml");
url.searchParams.append("problemSeed", ww_container.dataset.current_seed);
if (ww_problemSource) url.searchParams.append("problemSource", ww_problemSource);
else if (ww_sourceFilePath) url.searchParams.append("sourceFilePath", ww_sourceFilePath);
url.searchParams.append("answersSubmitted", "0");
url.searchParams.append("displayMode", "MathJax");
url.searchParams.append("courseID", ww_course_id);
url.searchParams.append("userID", ww_user_id);
url.searchParams.append("course_password", ww_course_password);
url.searchParams.append("outputformat", "raw");
url.searchParams.append("showSolutions", ww_container.dataset.hasSolution == "true" ? "1" : "0");
url.searchParams.append("showHints", ww_container.dataset.hasHint == "true" ? "1" : "0");
url.searchParams.append("problemUUID", ww_id);
}
$.getJSON(url.toString(), (data) => {
const form = document.createElement("form");
form.id = ww_id + "-form";
const body_div = document.createElement("div");
body_div.id = ww_id + "-body";
body_div.classList.add("exercise", "exercise-like");
body_div.lang = data.lang;
body_div.dir = data.dir;
body_div.innerHTML = data.rh_result.text;
for (const tag_name of ["h6", "h5", "h4", "h3", "h2", "h1"]) {
const headings = body_div.getElementsByTagName(tag_name);
for (heading of headings) {
const new_heading = document.createElement("h6");
new_heading.innerHTML = heading.innerHTML;
cloneAttributes(new_heading, heading);
new_heading.classList.add("webwork-part");
heading.replaceWith(new_heading);
}
}
adjustSrcHrefs(body_div, ww_domain);
translateHintSol(
ww_id,
body_div,
ww_domain,
ww_container.dataset.hasHint == "true",
ww_container.dataset.hasSolution == "true",
ww_container.dataset.hintLabelText,
ww_container.dataset.solutionLabelText
);
if (runestone_logged_in) {
const answersObject = wwList[ww_id.replace(/-ww-rs$/, "")].answers ? wwList[ww_id.replace(/-ww-rs$/, "")].answers : { "answers": [], "mqAnswers": [] };
const mqAnswers = answersObject.mqAnswers;
for (const mqAnswer in mqAnswers) {
const mqInput = body_div.querySelector("input[id=" + mqAnswer + "]");
if (mqInput && mqInput.value == "") {
mqInput.setAttribute("value", mqAnswers[mqAnswer]);
}
}
const answers2 = answersObject.answers;
for (const answer in answers2) {
const input = body_div.querySelector("input[id=" + answer + "]");
if (input && input.value == "") {
input.setAttribute("value", answers2[answer]);
}
if (input && input.type.toUpperCase() == "RADIO") {
const buttons = body_div.querySelectorAll("input[name=" + answer + "]");
for (const button of buttons) {
if (button.value == answers2[answer]) {
button.setAttribute("checked", "checked");
}
}
}
if (input && input.type.toUpperCase() == "CHECKBOX") {
const checkboxes = body_div.querySelectorAll("input[name=" + answer + "]");
for (const checkbox of checkboxes) {
let checkbox_regex = new RegExp("(\\[|, )" + checkbox.value + "(, |\\])");
if (answers2[answer].match(checkbox_regex)) {
checkbox.setAttribute("checked", "checked");
}
}
}
var select = body_div.querySelector("select[id=" + answer + "]");
if (select && answers2[answer]) {
let this_answer = answers2[answer];
if (/^\\text\{.*\}$/.test(this_answer)) {
this_answer = this_answer.match(/^\\text\{(.*)\}$/)[1];
}
;
let quote_escaped_answer = this_answer.replace(/"/g, '\\"');
const option = body_div.querySelector(`select[id="${answer}"] option[value="${quote_escaped_answer}"]`);
if (option) {
option.setAttribute("selected", "selected");
}
;
}
}
}
form.appendChild(body_div);
const wwInputs = {
problemSeed: data.inputs_ref.problemSeed,
problemUUID: data.inputs_ref.problemUUID,
psvn: data.inputs_ref.psvn,
courseName: ww_course_id,
courseID: ww_course_id,
userID: ww_user_id,
course_password: ww_course_password,
displayMode: "MathJax",
session_key: data.rh_result.session_key,
outputformat: "raw",
language: data.formLanguage,
showSummary: data.showSummary,
// note ww_container.dataset.hasSolution is a string, possibly 'false' which is true
showSolutions: ww_container.dataset.hasSolution == "true" ? "1" : "0",
showHints: ww_container.dataset.hasHint == "true" ? "1" : "0",
forcePortNumber: data.forcePortNumber
};
if (ww_sourceFilePath) wwInputs.sourceFilePath = ww_sourceFilePath;
else if (ww_problemSource) wwInputs.problemSource = ww_problemSource;
for (const wwInputName of Object.keys(wwInputs)) {
const input = document.createElement("input");
input.type = "hidden";
input.name = wwInputName;
input.value = wwInputs[wwInputName];
form.appendChild(input);
}
const answers = {};
Object.keys(data.rh_result.answers).forEach(function(id) {
answers[id] = {};
}, data.rh_result.answers);
if (ww_container.dataset.hasAnswer == "true") {
Object.keys(data.rh_result.answers).forEach(function(id) {
answers[id] = {
correct_ans: this[id].correct_ans,
correct_ans_latex_string: this[id].correct_ans_latex_string,
correct_choice: this[id].correct_choice,
correct_choices: this[id].correct_choices
};
}, data.rh_result.answers);
}
let buttonContainer = ww_container.querySelector(".problem-buttons.webwork");
if (!buttonContainer) {
ww_container.querySelector(".problem-buttons").classList.add("hidden-content");
if (activate_button != null) {
activate_button.classList.add("hidden-content");
}
;
buttonContainer = document.createElement("div");
buttonContainer.classList.add("problem-buttons", "webwork");
if (activate_button != null) {
activate_button.after(buttonContainer);
} else {
ww_container.prepend(buttonContainer);
}
const check = document.createElement("button");
check.type = "button";
check.id = ww_id + "-check";
check.style.marginRight = "0.25rem";
check.classList.add("webwork-button");
const answerCount = body_div.querySelectorAll("input:not([type=hidden])").length + body_div.querySelectorAll("select:not([type=hidden])").length;
check.textContent = runestone_logged_in ? localize_submit : localize_check_responses;
check.addEventListener("click", () => handleWW(ww_id, "check"));
buttonContainer.appendChild(check);
if (ww_container.dataset.hasAnswer == "true") {
const correct = document.createElement("button");
correct.classList.add("show-correct", "webwork-button");
correct.type = "button";
correct.style.marginRight = "0.25rem";
correct.textContent = localize_reveal;
correct.addEventListener("click", () => WWshowCorrect(ww_id, answers));
buttonContainer.appendChild(correct);
}
const randomize = document.createElement("button");
randomize.type = "button";
randomize.classList.add("webwork-button");
randomize.style.marginRight = "0.25rem";
randomize.textContent = localize_randomize;
randomize.addEventListener("click", () => handleWW(ww_id, "randomize"));
buttonContainer.appendChild(randomize);
const reset = document.createElement("button");
reset.type = "button";
reset.classList.add("webwork-button");
reset.textContent = localize_reset;
reset.addEventListener("click", () => resetWW(ww_id));
buttonContainer.appendChild(reset);
} else {
if (ww_container.dataset.hasAnswer == "true") {
const correct = buttonContainer.querySelector(".show-correct");
const correctNew = correct.cloneNode(true);
correctNew.addEventListener("click", () => WWshowCorrect(ww_id, answers));
correct.replaceWith(correctNew);
}
}
if (action == "check") {
$("body").trigger("runestone_ww_check", data);
const inputs = body_div.querySelectorAll("input:not([type=hidden])");
for (const input of inputs) {
const name = input.name;
if (input.type == "text" && answers[name]) {
const score = data.rh_result.answers[name].score;
let headline2 = "";
let correctClass2 = "";
if (score >= 1) {
headline2 = localize_correct + "!";
correctClass2 = "correct";
} else if (score > 0 && score < 1) {
headline2 = `${Math.round(score * 100)}% ${localize_correct}.`;
correctClass2 = "partly-correct";
} else if (data.rh_result.answers[name].student_ans == "") {
headline2 = localize_blank + ".";
correctClass2 = "blank";
} else if (score <= 0) {
headline2 = localize_incorrect + ".";
correctClass2 = "incorrect";
}
let title = `<span class="${correctClass2}">${headline2}</span>`;
let message = data.rh_result.answers[name].ans_message ? data.rh_result.answers[name].ans_message : "";
input.classList.add(correctClass2);
feedbackSpan = document.createElement("span");
feedbackSpan.id = `${ww_id}-${name}-feedback`;
feedbackHeadline = document.createElement("span");
feedbackHeadline.id = `${ww_id}-${name}-headline`;
feedbackHeadline.textContent = headline2;
feedbackSpan.appendChild(feedbackHeadline);
feedbackMessage = document.createElement("span");
feedbackMessage.id = `${ww_id}-${name}-message`;
feedbackMessage.textContent = message;
feedbackSpan.appendChild(feedbackMessage);
let nbSpan = document.createElement("span");
nbSpan.classList.add("nobreak");
input.parentNode.insertBefore(nbSpan, input);
nbSpan.appendChild(input);
let srSpan = document.createElement("span");
srSpan.classList.add("visually-hidden");
srSpan.appendChild(feedbackSpan);
nbSpan.appendChild(srSpan);
input.setAttribute("aria-describedby", `${ww_id}-${name}-feedback`);
srSpan.after(createFeedbackButton(`${ww_id}-${name}`, title, message));
}
if (input.type.toUpperCase() == "RADIO" && answers[name]) {
const score = data.rh_result.answers[name].score;
const student_ans = data.rh_result.answers[name].student_value || data.rh_result.answers[name].student_ans;
const correct_ans = data.rh_result.answers[name].correct_choice || data.rh_result.answers[name].correct_ans;
if (input.value == student_ans) {
if (score == 1) {
input.parentNode.classList.add("correct");
} else {
input.parentNode.classList.add("incorrect");
}
const feedbackButton = createFeedbackButton(
`${ww_id}-${name}`,
student_ans == correct_ans ? `<span class="correct">${localize_correct}</span>` : `<span class="incorrect">${localize_incorrect}.</span>`
);
feedbackButton.style.marginRight = "0.25rem";
input.after(feedbackButton);
}
}
if (input.type.toUpperCase() == "CHECKBOX" && answers[name]) {
const score = data.rh_result.answers[name].score;
const student_ans = data.rh_result.answers[name].student_ans;
const correct_ans = data.rh_result.answers[name].correct_ans;
if (input.value == data.rh_result.answers[name].firstElement) {
const checkbox_div = input.parentNode.parentNode;
if (score == 1) {
checkbox_div.classList.add("correct");
} else {
checkbox_div.classList.add("incorrect");
}
const feedbackButton = createFeedbackButton(
`${ww_id}-${name}`,
student_ans == correct_ans ? `<span class="correct">${localize_correct}</span>` : `<span class="incorrect">${localize_incorrect}.</span>`
);
checkbox_div.insertBefore(feedbackButton, checkbox_div.firstChild);
}
}
}
const hiddenInputs = body_div.querySelectorAll("input[type=hidden]");
for (const input of hiddenInputs) {
const name = input.name;
if (!input.nextElementSibling) continue;
const graphtoolContainer = input.nextElementSibling.nextElementSibling;
if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains("graphtool-container")) {
graphtoolContainer.style.position = "relative";
const score = data.rh_result.answers[name].score;
let title = "";
if (score == 1) {
title = `<span class="correct">${localize_correct}</span>`;
} else if (score > 0 && score < 1) {
title = `<span class="partly-correct">${Math.round(score * 100)}% ${localize_correct}.</span>`;
} else if (data.rh_result.answers[name].student_ans == "") {
continue;
} else if (score == 0) {
title = `<span class="incorrect">${localize_incorrect}.</span>`;
}
const feedbackButton = createFeedbackButton(`${ww_id}-${name}`, title, data.rh_result.answers[name].ans_message);
feedbackButton.style.position = "absolute";
feedbackButton.style.left = "100%";
feedbackButton.style.top = 0;
feedbackButton.style.marginLeft = "0.5rem";
feedbackButton.dataset.bsContainer = "body";
graphtoolContainer.appendChild(feedbackButton);
if (score == 1) {
graphtoolContainer.classList.add("correct");
} else {
graphtoolContainer.classList.add("incorrect");
}
}
}
const selects = body_div.querySelectorAll("select:not([type=hidden])");
for (const select2 of selects) {
const name = select2.name;
const score = data.rh_result.answers[name].score;
let title = "";
if (score == 1) {
headline = localize_correct + "!";
correctClass = "correct";
} else {
headline = localize_incorrect + ".";
correctClass = "incorrect";
}
select2.classList.add(correctClass);
title = `<span class="${correctClass}">${headline}</span>`;
feedbackSpan = document.createElement("span");
feedbackSpan.id = `${ww_id}-${name}-feedback`;
feedbackHeadline = document.createElement("span");
feedbackHeadline.id = `${ww_id}-${name}-headline`;
feedbackHeadline.textContent = headline;
feedbackSpan.appendChild(feedbackHeadline);
let nbSpan = document.createElement("span");
nbSpan.classList.add("nobreak");
select2.parentNode.insertBefore(nbSpan, select2);
nbSpan.appendChild(select2);
let psSpan = document.createElement("span");
psSpan.classList.add("select-wrapper", correctClass);
select2.parentNode.insertBefore(psSpan, select2);
psSpan.appendChild(select2);
let srSpan = document.createElement("span");
srSpan.classList.add("visually-hidden");
srSpan.appendChild(feedbackSpan);
nbSpan.appendChild(srSpan);
select2.setAttribute("aria-describedby", `${ww_id}-${name}-feedback`);
srSpan.after(createFeedbackButton(`${ww_id}-${name}`, title));
}
}
let iframeContents = '<!DOCTYPE html><head><script src="' + ww_domain + `/webwork2_files/node_modules/jquery/dist/jquery.min.js"><\/script><script>window.MathJax = {
tex: {
inlineMath: [['\\\\(','\\\\)']],
tags: "none",
useLabelIds: true,
tagSide: "right",
tagIndent: ".8em",
packages: {'[+]': ['base', 'extpfeil', 'ams', 'amscd', 'newcommand', 'knowl']}
},
options: {
ignoreHtmlClass: "tex2jax_ignore",
processHtmlClass: "has_am",
renderActions: {
findScript: [10, function (doc) {
document.querySelectorAll('script[type^="math/tex"]').forEach(function(node) {
const display = !!node.type.match(/; *mode=display/);
const math = new doc.options.MathItem(node.textContent, doc.inputJax[0], display);
const text = document.createTextNode('');
node.parentNode.replaceChild(text, node);
math.start = {node: text, delim: '', n: 0};
math.end = {node: text, delim: '', n: 0};
doc.math.push(math);
});
}, '']
},
},
chtml: {
scale: 0.88,
mtextInheritFont: true
},
loader: {
load: ['input/asciimath', '[tex]/extpfeil', '[tex]/amscd', '[tex]/newcommand', '[pretext]/mathjaxknowl3.js'],
paths: {pretext: "https://pretextbook.org/js/lib"},
},
};
<\/script><script src="` + ww_domain + '/webwork2_files/node_modules/mathjax/es5/tex-chtml.js" id="MathJax-script" defer><\/script><script src="https://pretextbook.org/js/lib/knowl.js" defer><\/script><link rel="stylesheet" href="' + ww_domain + '/webwork2_files/node_modules/bootstrap/dist/css/bootstrap.min.css"/><script src="' + ww_domain + '/webwork2_files/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js" defer><\/script>';
const extra_css_files = [];
const extra_js_files = [];
if (data.extra_css_files) data.extra_css_files.unshift(...extra_css_files);
else data.extra_css_files = extra_css_files;
for (const cssFile of data.extra_css_files) {
if (/knowl/.test(cssFile.file)) continue;
iframeContents += '<link rel="stylesheet" href="' + (cssFile.external !== "1" ? ww_domain : "") + cssFile.file + '"/>';
}
if (data.extra_js_files) data.extra_js_files.unshift(...extra_js_files);
else data.extra_js_files = extra_js_files;
for (const jsFile of data.extra_js_files) {
if (/knowl/.test(jsFile.file)) continue;
iframeContents += '<script src="' + (jsFile.external !== "1" ? ww_domain : "") + jsFile.file + '" ' + Object.keys(jsFile.attributes || {}).reduce((ret, key) => {
ret += key + '="' + jsFile.attributes[key] + '" ';
return ret;
}, "") + "><\/script>";
}
iframeContents += '<link rel="stylesheet" href="https://pretextbook.org/css/0.31/pretext_add_on.css"/><link rel="stylesheet" href="https://pretextbook.org/css/0.31/knowls_default.css"/><script src="' + ww_domain + `/webwork2_files/node_modules/iframe-resizer/js/iframeResizer.contentWindow.min.js"><\/script><style>
html { overflow-y: hidden; }
html body { background:unset; margin: 0; }
body { font-size: initial; line-height: initial; padding:2px; }
.hidden-content { display: none; }
input[type="text"], input[type="radio"], label, select {
height: auto;
width: auto;
max-width: unset;
margin: 0;
font-size: initial;
/*font-family: sans-serif;*/
line-height: initial;
}
input[type="text"] {
padding: 1px;
padding-inline: 2px;
border-width: 1px;
border-style: inset;
border-color: rgb(133, 133, 133);
border-radius: 0;
box-shadow: unset;
transition: none;
}
input[type="text"]:focus, input[type="text"]:active {
box-shadow: unset;
border-width: 1px;
border-style: inset;
border-radius: 4px;
border-color: rgb(133, 133, 133);
outline: auto;
}
input[type="radio"] {
box-sizing: border-box;
margin-block: 3px 0;
margin-inline: 5px 3px;
vertical-align: unset;
}
span.nobreak {
white-space: nowrap;
}
input[type="text"].blank, input[type="text"].correct, input[type="text"].partly-correct, input[type="text"].incorrect, select.correct, select.incorrect {
background-size: auto 100%;
background-position: right;
background-repeat: no-repeat;
padding-right: 29px;
}
input[type="text"].blank {
background-color: #CDF;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='20px' width='20px'><text x='19' y='16' fill='%230049DB' text-anchor='end'>\u26A0</text></svg>");
}
input[type="text"].correct, select.correct, input[type="text"].correct + span.mq-editable-field {
background-color: #8F8;
}
input[type="text"].correct {
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='20px' width='20px'><text x='19' y='16' fill='%23060' text-anchor='end'>\u2713</text></svg>");
}
padding-left: 2rem;
background-repeat: no-repeat;
background-position-y: center;
}
input[type="text"].partly-correct, input[type="text"].partly-correct + span.mq-editable-field {
background-color: #CDF;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='22px' width='30px'><text x='28' y='18' fill='%235C5C00' text-anchor='end'>\u26A0</text></svg>");
}
input[type="text"].incorrect, select.incorrect, input[type="text"].incorrect + span.mq-editable-field {
background-color: #DAA;
}
input[type="text"].incorrect {
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='22px' width='30px'><text x='28' y='18' fill='%23943D3D' text-anchor='end'>\u26A0</text></svg>");
}
input[type="text"].partly-correct, input[type="text"].incorrect {
background-size: auto 70%;
}
label {
padding-left: 1.8em;
}
label.correct::before {
color: #060;
content: '\u2713';
}
label.incorrect::before {
color: #943D3D;
content: '\u26A0';
font-size: small;
}
label.correct::before, label.incorrect::before {
display:inline-block;
width: 0;
direction: rtl;
}
.select-wrapper.correct::before {
color: #060;
content: '\u2713';
margin-right: 2pt;
}
.select-wrapper.incorrect::before {
color: #943D3D;
content: '\u26A0';
margin-right: 2pt;
font-size: small;
}
.checkboxes-container.correct label input, .checkboxes-container.incorrect label input {
border-radius: 2px;
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
width: 20px;
height: 20px;
cursor: pointer;
position: relative;
top: 5px;
background-repeat: no-repeat;
}
.checkboxes-container.correct label input {
background-color: #8F8;
border: #060 solid;
}
.checkboxes-container.correct label input:checked {
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='14px' width='14px'><text x='13' y='13' text-anchor='end'>\u2713</text></svg>");
}
.checkboxes-container.incorrect label input {
background-color: #DAA;
border: #943D3D solid;
}
.checkboxes-container.incorrect label input:checked {
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='14px' width='14px'><text x='13' y='13' text-anchor='end'>\u2718</text></svg>");
}
div.PGML img.image-view-elt {
max-width:100%;
}
label {
display: inline-block;
}
select {
margin: 0;
border-width: 1px;
border-style: inset;
display: inline-block;
padding-block: 1px;
vertical-align: baseline;
background-color: unset;
padding-inline: 0;
}
.result-popover .popover-header, .result-popover .popover-content {
text-align: center;
}
.result-popover .popover-header.correct {
background-color: #8F8;
}
.result-popover.nocontent .popover-header {
border-radius: calc(.3rem - 1px);
}
.result-popover.nocontent .popover-header::before {
border: none;
}
.accordion-body.expanded {
overflow-y: visible;
overflow-x: clip;
}
.graphtool-answer-container .graphtool-graph {
margin: 0;
width: 300px;
height: 300px;
}
.graphtool-answer-container .graphtool-number-line {
height: 57px;
}
.checkboxes-container .ww-feedback {
position: absolute;
left: 2px;
}
.graphtool-container.correct .graphtool-graph {
background-color: #8F8;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='20px' width='20px'><text x='19' y='16' fill='%23060' text-anchor='end'>\u2713</text></svg>");
background-repeat: no-repeat;
}
.graphtool-container.incorrect .graphtool-graph {
background-color: #DAA;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='22px' width='30px'><text x='28' y='18' fill='%235C5C00' text-anchor='end'>\u26A0</text></svg>");
background-repeat: no-repeat;
}
</style></head><body><main class="pretext-content">` + form.outerHTML + "</main></body></html>";
let iframe2;
if (!action) {
iframe2 = document.createElement("iframe");
iframe2.style.width = "1px";
iframe2.style.minWidth = "100%";
iframe2.classList.add("problem-iframe");
ww_container.querySelector(".problem-contents").classList.add("hidden-content");
if (activate_button != null) {
activate_button.after(iframe2);
} else {
ww_container.prepend(iframe2);
}
iFrameResize({ checkOrigin: false, scrolling: "omit", heightCalculationMethod: "min" }, iframe2);
iframe2.addEventListener("load", () => {
const iframeForm = iframe2.contentDocument.getElementById(ww_id + "-form");
iframeForm.addEventListener("submit", (e) => {
handleWW(ww_id, "check");
e.preventDefault();
});
iframe2.contentDocument.querySelectorAll(".collapse.in").forEach((collapse) => collapse.classList.add("expanded"));
iframe2.contentWindow.jQuery(".collapse").on("shown", function(e) {
if (e.target != this) return;
this.classList.add("expanded");
});
iframe2.contentWindow.jQuery(".collapse").on("hide", function(e) {
if (e.target != this) return;
this.classList.remove("expanded");
});
iframe2.contentDocument.querySelectorAll("button.ww-feedback[data-bs-content]").forEach((button) => {
const bsPopover = new iframe2.contentWindow.bootstrap.Popover(button, {
html: true,
placement: "bottom",
fallbackPlacements: [],
trigger: "click",
customClass: "result-popover" + (button.dataset.emptyContent ? " nocontent" : "")
});
bsPopover.show();
const content = iframe2.contentDocument.getElementById(button.id.replace("-feedback-button", "-content"));
const popover = content.parentNode.parentNode;
bsPopover.hide();
button.addEventListener("click", () => bsPopover.show(), { once: true });
popover.querySelector(".popover-arrow").remove();
const title = popover.querySelector(".popover-header");
if (button.dataset.emptyContent) content.parentNode.remove();
if (title.textContent == localize_correct + "!") title.classList.add("correct");
});
iframe2.contentWindow.MathJax.startup.promise.then(() => iframe2.contentWindow.MathJax.typesetPromise([".popover", ".popover-content"]));
});
} else {
iframe2 = ww_container.querySelector(".problem-iframe");
}
iframe2.srcdoc = iframeContents;
iframe2.addEventListener("load", () => {
loader.remove();
}, { once: true });
ww_container.focus();
});
}
function WWshowCorrect(ww_id, answers) {
const ww_container = document.getElementById(ww_id);
const iframe2 = ww_container.querySelector(".problem-iframe");
const body = iframe2.contentDocument.getElementById(ww_id + "-body");
$("body").trigger("runestone_show_correct", {
"ww_id": ww_id,
"answers": answers
});
let inputs = body.querySelectorAll("input:not([type=hidden])");
for (const input of inputs) {
const name = input.name;
const span_id = `${ww_id}-${name}-correct`;
if (input.type == "text" && answers[name] && !iframe2.contentDocument.getElementById(span_id)) {
const input_id = input.id;
const mq_span = iframe2.contentDocument.getElementById(`mq-answer-${input_id}`);
if (mq_span) {
mq_span.style.display = "none";
}
const feedbackButton = iframe2.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`);
if (feedbackButton) {
iframe2.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide();
feedbackButton.remove();
}
label = iframe2.contentDocument.getElementById(`${span_id}-label`);
if (label) {
label.parentElement.insertBefore(input, label);
label.remove();
}
input.type = "hidden";
const correct_ans_text = iframe2.contentDocument.createElement("div");
correct_ans_text.innerHTML = answers[name].correct_ans;
input.value = correct_ans_text.textContent;
const show_span = iframe2.contentDocument.createElement("span");
show_span.id = span_id;
show_span.appendChild(answers[name].correct_ans_latex_string ? iframe2.contentDocument.createTextNode("\\(" + answers[name].correct_ans_latex_string + "\\)") : iframe2.contentDocument.createTextNode(answers[name].correct_ans));
input.parentElement.insertBefore(show_span, input);
}
if (input.type.toUpperCase() == "RADIO" && answers[name]) {
const feedbackButton = iframe2.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`);
if (feedbackButton) {
iframe2.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide();
feedbackButton.remove();
}
const correct_ans = answers[name].correct_choice || answers[name].correct_ans;
if (input.value == correct_ans) {
input.checked = true;
} else {
input.checked = false;
}
}
if (input.type.toUpperCase() == "CHECKBOX" && answers[name]) {
const correct_choices = answers[name].correct_choices;
if (correct_choices.includes(input.value)) {
input.checked = true;
} else {
input.checked = false;
}
}
}
const hiddenInputs = body.querySelectorAll("input[type=hidden]");
for (const input of hiddenInputs) {
const name = input.name;
if (!input.nextElementSibling) continue;
const graphtoolContainer = input.nextElementSibling.nextElementSibling;
if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains("graphtool-container")) {
const feedbackButton = iframe2.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`);
if (feedbackButton) {
iframe2.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide();
feedbackButton.remove();
}
const correct_ans_div = iframe2.contentDocument.createElement("div");
input.parentElement.insertBefore(correct_ans_div, graphtoolContainer);
graphtoolContainer.style.display = "none";
input.value = answers[name].correct_ans;
iframe2.contentWindow.jQuery(correct_ans_div).html(answers[name].correct_ans_latex_string);
const script = iframe2.contentDocument.createElement("script");
script.textContent = correct_ans_div.querySelector("script").textContent.replace('\nwindow.addEventListener("DOMContentLoaded",', "(").replace(/;\n$/, "();");
iframe2.contentDocument.body.appendChild(script);
}
}
let selects = body.querySelectorAll("select:not([type=hidden])");
for (const select of selects) {
const name = select.name;
const span_id = `${ww_id}-${name}-correct`;
if (answers[name] && !iframe2.contentDocument.getElementById(span_id)) {
const feedbackButton = iframe2.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`);
if (feedbackButton) {
iframe2.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide();
feedbackButton.remove();
}
select.style.display = "none";
select.value = answers[name].correct_ans;
const show_span = iframe2.contentDocument.createElement("span");
show_span.id = span_id;
show_span.appendChild(answers[name].correct_ans_latex_string ? iframe2.contentDocument.createTextNode("\\(" + answers[name].correct_ans_latex_string + "\\)") : iframe2.contentDocument.createTextNode(answers[name].correct_ans));
select.parentElement.insertBefore(show_span, select);
}
}
const mathjaxTypesetScript = iframe2.contentDocument.createElement("script");
mathjaxTypesetScript.textContent = "MathJax.startup.promise.then(() => MathJax.typesetPromise([document.body]));";
iframe2.contentDocument.body.appendChild(mathjaxTypesetScript);
}
function resetWW(ww_id) {
const ww_container = document.getElementById(ww_id);
const activate_button = document.getElementById(ww_id + "-button");
ww_container.dataset.current_seed = ww_container.dataset.seed;
iframe = ww_container.querySelector(".problem-iframe");
iframe.remove();
ww_container.querySelector(".problem-contents").classList.remove("hidden-content");
ww_container.querySelector(".problem-buttons.webwork").remove();
ww_container.querySelector(".problem-buttons").classList.remove("hidden-content");
if (activate_button != null) {
activate_button.classList.remove("hidden-content");
}
;
}
function adjustSrcHrefs(container, ww_domain) {
container.querySelectorAll("[href]").forEach((node) => {
const href = node.attributes.href.value;
if (href !== "#" && !href.match(/^[a-z]+:\/\//i)) node.href = ww_domain + "/" + href;
});
container.querySelectorAll("[src]").forEach((node) => {
node.src = new URL(node.attributes.src.value, ww_domain).href;
});
}
function translateHintSol(ww_id, body_div, ww_domain, b_ptx_has_hint, b_ptx_has_solution, hint_label_text, solution_label_text) {
const hintSols = body_div.querySelectorAll('.knowl[data-type="hint"],.knowl[data-type="solution"]');
let solutionlikewrapper;
for (const hintSol of hintSols) {
const hintsolp = hintSol.parentNode;
if (!hintsolp) continue;
const hintsolpp = hintsolp.parentNode;
const hintSolType = hintSol.dataset.type;
if (hintsolpp.querySelectorAll(".webwork.solutions").length == 0) {
solutionlikewrapper = document.createElement("div");
solutionlikewrapper.classList.add("webwork", "solutions");
hintsolpp.insertBefore(solutionlikewrapper, hintsolp);
}
if (hintSolType == "solution" && !b_ptx_has_solution || hintSolType == "hint" && !b_ptx_has_hint)
continue;
const knowlDetails = document.createElement("details");
knowlDetails.classList.add(hintSolType);
knowlDetails.classList.add("solution-like");
knowlDetails.classList.add("born-hidden-knowl");
const knowlSummary = document.createElement("summary");
const summaryLabel = document.createElement("span");
summaryLabel.classList.add("type");
summaryLabel.innerHTML = hintSolType == "hint" ? hint_label_text : solution_label_text;
knowlSummary.appendChild(summaryLabel);
knowlDetails.appendChild(knowlSummary);
const knowlContents = document.createElement("div");
knowlContents.classList.add(hintSolType);
knowlContents.classList.add("solution-like");
knowlContents.innerHTML = hintsolp.firstElementChild.dataset.knowlContents;
knowlDetails.appendChild(knowlContents);
adjustSrcHrefs(knowlContents, ww_domain);
solutionlikewrapper.appendChild(knowlDetails);
}
for (const hintSol of hintSols) {
hintSol.parentNode?.remove();
}
}
function cloneAttributes(target, source) {
[...source.attributes].forEach((attr) => {
target.setAttribute(attr.nodeName, attr.nodeValue);
});
}
function createFeedbackButton(id, title, content) {
const feedbackButton = document.createElement("button");
feedbackButton.dataset.bsTitle = title;
feedbackButton.dataset.bsContent = `<div id="${id}-content">${content || ""}</div>`;
if (!content) feedbackButton.dataset.emptyContent = "1";
const contentSpan = document.createElement("span");
contentSpan.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" fill="none" stroke-width="2" viewBox="0 0 24 24" color="#000000"><path stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M12 11.5v5M12 7.51l.01-.011M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10Z"></path></svg>`;
feedbackButton.appendChild(contentSpan);
feedbackButton.type = "button";
feedbackButton.classList.add("ww-feedback");
feedbackButton.id = `${id}-feedback-button`;
feedbackButton.dataset.bsToggle = "popover";
return feedbackButton;
}
function webworkSeedHash(string) {
var hash = 0, i, chr;
if (string.length === 0) return hash;
for (i = 0; i < string.length; i++) {
chr = string.charCodeAt(i);
hash = (hash << 5) - hash + chr;
hash |= 0;
}
return Math.abs(hash);
}
;
//# sourceMappingURL=pretext-webwork.js.map
@@ -0,0 +1,885 @@
function handleWW(ww_id, action) {
const ww_container = document.getElementById(ww_id);
const ww_domain = ww_container.dataset.domain;
const ww_problemSource = ww_container.dataset.problemsource;
const ww_sourceFilePath = ww_container.dataset.sourcefilepath;
const ww_course_id = ww_container.dataset.courseid;
const ww_user_id = ww_container.dataset.userid;
const ww_course_password = ww_container.dataset.coursepassword;
const localize_correct = ww_container.dataset.localizeCorrect || "Correct";
const localize_incorrect = ww_container.dataset.localizeIncorrect || "Incorrect";
const localize_blank = ww_container.dataset.localizeBlank || "Blank";
const localize_submit = ww_container.dataset.localizeSubmit || "Submit";
const localize_check_responses = ww_container.dataset.localizeCheckResponses || "Check Responses";
const localize_reveal = ww_container.dataset.localizeReveal || "Reveal";
const localize_randomize = ww_container.dataset.localizeRandomize || "Randomize";
const localize_reset = ww_container.dataset.localizeReset || "Reset";
const runestone_logged_in = typeof eBookConfig !== "undefined" && eBookConfig.username !== "";
const activate_button = document.getElementById(ww_id + "-button");
if (!action) {
ww_container.dataset.current_seed = ww_container.dataset.seed;
if (runestone_logged_in) {
ww_container.dataset.current_seed = webworkSeedHash(eBookConfig.username + ww_container.dataset.current_seed);
}
} else if (action == "randomize") ww_container.dataset.current_seed = Number(ww_container.dataset.current_seed) + 100;
let loader = document.createElement("div");
loader.style.position = "absolute";
loader.style.left = 0;
loader.style.top = 0;
loader.style.backgroundColor = "rgba(0.2, 0.2, 0.2, 0.4)";
loader.style.color = "white";
loader.style.width = "100%";
loader.style.height = "100%";
loader.style.display = "flex";
loader.style.alignItems = "center";
loader.style.justifyContent = "center";
loader.style.marginTop = "0";
loader.tabIndex = -1;
const loaderText = document.createElement("span");
loaderText.textContent = "Loading";
loaderText.style.fontSize = "2rem";
loader.appendChild(loaderText);
ww_container.appendChild(loader);
loader.focus();
if (!action) {
ww_container.dataset.hasHint = ww_container.getElementsByClassName("hint").length > 0;
ww_container.dataset.hasSolution = ww_container.getElementsByClassName("solution").length > 0;
ww_container.dataset.hasAnswer = ww_container.getElementsByClassName("answer").length > 0;
ww_container.dataset.hintLabelText = ww_container.dataset.hasHint == "true" ? ww_container.querySelectorAll(".hint-knowl span.type, details.hint span.type")[0].textContent : "Hint";
ww_container.dataset.solutionLabelText = ww_container.dataset.hasSolution == "true" ? ww_container.querySelectorAll(".solution-knowl span.type, details.solution span.type")[0].textContent : "Solution";
ww_container.tabIndex = -1;
}
let url;
if (action == "check") {
const iframe2 = ww_container.querySelector(".problem-iframe");
const formData = new FormData(iframe2.contentDocument.getElementById(ww_id + "-form"));
const params = new URLSearchParams(formData);
url = new URL(ww_domain + "/webwork2/html2xml?" + params.toString());
url.searchParams.append("answersSubmitted", "1");
url.searchParams.append("WWsubmit", "1");
} else {
url = new URL(ww_domain + "/webwork2/html2xml");
url.searchParams.append("problemSeed", ww_container.dataset.current_seed);
if (ww_problemSource) url.searchParams.append("problemSource", ww_problemSource);
else if (ww_sourceFilePath) url.searchParams.append("sourceFilePath", ww_sourceFilePath);
url.searchParams.append("answersSubmitted", "0");
url.searchParams.append("displayMode", "MathJax");
url.searchParams.append("courseID", ww_course_id);
url.searchParams.append("userID", ww_user_id);
url.searchParams.append("course_password", ww_course_password);
url.searchParams.append("outputformat", "raw");
url.searchParams.append("showSolutions", ww_container.dataset.hasSolution == "true" ? "1" : "0");
url.searchParams.append("showHints", ww_container.dataset.hasHint == "true" ? "1" : "0");
url.searchParams.append("problemUUID", ww_id);
}
$.getJSON(url.toString(), (data) => {
const form = document.createElement("form");
form.id = ww_id + "-form";
const body_div = document.createElement("div");
body_div.id = ww_id + "-body";
body_div.classList.add("exercise", "exercise-like");
body_div.lang = data.lang;
body_div.dir = data.dir;
body_div.innerHTML = data.rh_result.text;
for (const tag_name of ["h6", "h5", "h4", "h3", "h2", "h1"]) {
const headings = body_div.getElementsByTagName(tag_name);
for (heading of headings) {
const new_heading = document.createElement("h6");
new_heading.innerHTML = heading.innerHTML;
cloneAttributes(new_heading, heading);
new_heading.classList.add("webwork-part");
heading.replaceWith(new_heading);
}
}
adjustSrcHrefs(body_div, ww_domain);
translateHintSol(
ww_id,
body_div,
ww_domain,
ww_container.dataset.hasHint == "true",
ww_container.dataset.hasSolution == "true",
ww_container.dataset.hintLabelText,
ww_container.dataset.solutionLabelText
);
if (runestone_logged_in) {
const answersObject = wwList[ww_id.replace(/-ww-rs$/, "")].answers ? wwList[ww_id.replace(/-ww-rs$/, "")].answers : { "answers": [], "mqAnswers": [] };
const mqAnswers = answersObject.mqAnswers;
for (const mqAnswer in mqAnswers) {
const mqInput = body_div.querySelector("input[id=" + mqAnswer + "]");
if (mqInput && mqInput.value == "") {
mqInput.setAttribute("value", mqAnswers[mqAnswer]);
}
}
const answers2 = answersObject.answers;
for (const answer in answers2) {
const input = body_div.querySelector("input[id=" + answer + "]");
if (input && input.value == "") {
input.setAttribute("value", answers2[answer]);
}
if (input && input.type.toUpperCase() == "RADIO") {
const buttons = body_div.querySelectorAll("input[name=" + answer + "]");
for (const button of buttons) {
if (button.value == answers2[answer]) {
button.setAttribute("checked", "checked");
}
}
}
if (input && input.type.toUpperCase() == "CHECKBOX") {
const checkboxes = body_div.querySelectorAll("input[name=" + answer + "]");
for (const checkbox of checkboxes) {
let checkbox_regex = new RegExp("(\\[|, )" + checkbox.value + "(, |\\])");
if (answers2[answer].match(checkbox_regex)) {
checkbox.setAttribute("checked", "checked");
}
}
}
var select = body_div.querySelector("select[id=" + answer + "]");
if (select && answers2[answer]) {
let this_answer = answers2[answer];
if (/^\\text\{.*\}$/.test(this_answer)) {
this_answer = this_answer.match(/^\\text\{(.*)\}$/)[1];
}
;
let quote_escaped_answer = this_answer.replace(/"/g, '\\"');
const option = body_div.querySelector(`select[id="${answer}"] option[value="${quote_escaped_answer}"]`);
if (option) {
option.setAttribute("selected", "selected");
}
;
}
}
}
form.appendChild(body_div);
const wwInputs = {
problemSeed: data.inputs_ref.problemSeed,
problemUUID: data.inputs_ref.problemUUID,
psvn: data.inputs_ref.psvn,
courseName: ww_course_id,
courseID: ww_course_id,
userID: ww_user_id,
course_password: ww_course_password,
displayMode: "MathJax",
session_key: data.rh_result.session_key,
outputformat: "raw",
language: data.formLanguage,
showSummary: data.showSummary,
// note ww_container.dataset.hasSolution is a string, possibly 'false' which is true
showSolutions: ww_container.dataset.hasSolution == "true" ? "1" : "0",
showHints: ww_container.dataset.hasHint == "true" ? "1" : "0",
forcePortNumber: data.forcePortNumber
};
if (ww_sourceFilePath) wwInputs.sourceFilePath = ww_sourceFilePath;
else if (ww_problemSource) wwInputs.problemSource = ww_problemSource;
for (const wwInputName of Object.keys(wwInputs)) {
const input = document.createElement("input");
input.type = "hidden";
input.name = wwInputName;
input.value = wwInputs[wwInputName];
form.appendChild(input);
}
const answers = {};
Object.keys(data.rh_result.answers).forEach(function(id) {
answers[id] = {};
}, data.rh_result.answers);
if (ww_container.dataset.hasAnswer == "true") {
Object.keys(data.rh_result.answers).forEach(function(id) {
answers[id] = {
correct_ans: this[id].correct_ans,
correct_ans_latex_string: this[id].correct_ans_latex_string,
correct_choice: this[id].correct_choice,
correct_choices: this[id].correct_choices
};
}, data.rh_result.answers);
}
let buttonContainer = ww_container.querySelector(".problem-buttons.webwork");
if (!buttonContainer) {
ww_container.querySelector(".problem-buttons").classList.add("hidden-content");
if (activate_button != null) {
activate_button.classList.add("hidden-content");
}
;
buttonContainer = document.createElement("div");
buttonContainer.classList.add("problem-buttons", "webwork");
if (activate_button != null) {
activate_button.after(buttonContainer);
} else {
ww_container.prepend(buttonContainer);
}
const check = document.createElement("button");
check.type = "button";
check.id = ww_id + "-check";
check.style.marginRight = "0.25rem";
check.classList.add("webwork-button");
const answerCount = body_div.querySelectorAll("input:not([type=hidden])").length + body_div.querySelectorAll("select:not([type=hidden])").length;
check.textContent = runestone_logged_in ? localize_submit : localize_check_responses;
check.addEventListener("click", () => handleWW(ww_id, "check"));
buttonContainer.appendChild(check);
if (ww_container.dataset.hasAnswer == "true") {
const correct = document.createElement("button");
correct.classList.add("show-correct", "webwork-button");
correct.type = "button";
correct.style.marginRight = "0.25rem";
correct.textContent = localize_reveal;
correct.addEventListener("click", () => WWshowCorrect(ww_id, answers));
buttonContainer.appendChild(correct);
}
const randomize = document.createElement("button");
randomize.type = "button";
randomize.classList.add("webwork-button");
randomize.style.marginRight = "0.25rem";
randomize.textContent = localize_randomize;
randomize.addEventListener("click", () => handleWW(ww_id, "randomize"));
buttonContainer.appendChild(randomize);
const reset = document.createElement("button");
reset.type = "button";
reset.classList.add("webwork-button");
reset.textContent = localize_reset;
reset.addEventListener("click", () => resetWW(ww_id));
buttonContainer.appendChild(reset);
} else {
if (ww_container.dataset.hasAnswer == "true") {
const correct = buttonContainer.querySelector(".show-correct");
const correctNew = correct.cloneNode(true);
correctNew.addEventListener("click", () => WWshowCorrect(ww_id, answers));
correct.replaceWith(correctNew);
}
}
if (action == "check") {
$("body").trigger("runestone_ww_check", data);
const inputs = body_div.querySelectorAll("input:not([type=hidden])");
for (const input of inputs) {
const name = input.name;
if (input.type == "text" && answers[name]) {
const score = data.rh_result.answers[name].score;
let headline2 = "";
let correctClass2 = "";
if (score >= 1) {
headline2 = localize_correct + "!";
correctClass2 = "correct";
} else if (score > 0 && score < 1) {
headline2 = `${Math.round(score * 100)}% ${localize_correct}.`;
correctClass2 = "partly-correct";
} else if (data.rh_result.answers[name].student_ans == "") {
headline2 = localize_blank + ".";
correctClass2 = "blank";
} else if (score <= 0) {
headline2 = localize_incorrect + ".";
correctClass2 = "incorrect";
}
let title = `<span class="${correctClass2}">${headline2}</span>`;
let message = data.rh_result.answers[name].ans_message ? data.rh_result.answers[name].ans_message : "";
input.classList.add(correctClass2);
feedbackSpan = document.createElement("span");
feedbackSpan.id = `${ww_id}-${name}-feedback`;
feedbackHeadline = document.createElement("span");
feedbackHeadline.id = `${ww_id}-${name}-headline`;
feedbackHeadline.textContent = headline2;
feedbackSpan.appendChild(feedbackHeadline);
feedbackMessage = document.createElement("span");
feedbackMessage.id = `${ww_id}-${name}-message`;
feedbackMessage.textContent = message;
feedbackSpan.appendChild(feedbackMessage);
let nbSpan = document.createElement("span");
nbSpan.classList.add("nobreak");
input.parentNode.insertBefore(nbSpan, input);
nbSpan.appendChild(input);
let srSpan = document.createElement("span");
srSpan.classList.add("visually-hidden");
srSpan.appendChild(feedbackSpan);
nbSpan.appendChild(srSpan);
input.setAttribute("aria-describedby", `${ww_id}-${name}-feedback`);
srSpan.after(createFeedbackButton(`${ww_id}-${name}`, title, message));
}
if (input.type.toUpperCase() == "RADIO" && answers[name]) {
const score = data.rh_result.answers[name].score;
const student_ans = data.rh_result.answers[name].student_value || data.rh_result.answers[name].student_ans;
const correct_ans = data.rh_result.answers[name].correct_choice || data.rh_result.answers[name].correct_ans;
if (input.value == student_ans) {
if (score == 1) {
input.parentNode.classList.add("correct");
} else {
input.parentNode.classList.add("incorrect");
}
const feedbackButton = createFeedbackButton(
`${ww_id}-${name}`,
student_ans == correct_ans ? `<span class="correct">${localize_correct}</span>` : `<span class="incorrect">${localize_incorrect}.</span>`
);
feedbackButton.style.marginRight = "0.25rem";
input.after(feedbackButton);
}
}
if (input.type.toUpperCase() == "CHECKBOX" && answers[name]) {
const score = data.rh_result.answers[name].score;
const student_ans = data.rh_result.answers[name].student_ans;
const correct_ans = data.rh_result.answers[name].correct_ans;
if (input.value == data.rh_result.answers[name].firstElement) {
const checkbox_div = input.parentNode.parentNode;
if (score == 1) {
checkbox_div.classList.add("correct");
} else {
checkbox_div.classList.add("incorrect");
}
const feedbackButton = createFeedbackButton(
`${ww_id}-${name}`,
student_ans == correct_ans ? `<span class="correct">${localize_correct}</span>` : `<span class="incorrect">${localize_incorrect}.</span>`
);
checkbox_div.insertBefore(feedbackButton, checkbox_div.firstChild);
}
}
}
const hiddenInputs = body_div.querySelectorAll("input[type=hidden]");
for (const input of hiddenInputs) {
const name = input.name;
if (!input.nextElementSibling) continue;
const graphtoolContainer = input.nextElementSibling.nextElementSibling;
if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains("graphtool-container")) {
graphtoolContainer.style.position = "relative";
const score = data.rh_result.answers[name].score;
let title = "";
if (score == 1) {
title = `<span class="correct">${localize_correct}</span>`;
} else if (score > 0 && score < 1) {
title = `<span class="partly-correct">${Math.round(score * 100)}% ${localize_correct}.</span>`;
} else if (data.rh_result.answers[name].student_ans == "") {
continue;
} else if (score == 0) {
title = `<span class="incorrect">${localize_incorrect}.</span>`;
}
const feedbackButton = createFeedbackButton(`${ww_id}-${name}`, title, data.rh_result.answers[name].ans_message);
feedbackButton.style.position = "absolute";
feedbackButton.style.left = "100%";
feedbackButton.style.top = 0;
feedbackButton.style.marginLeft = "0.5rem";
feedbackButton.dataset.bsContainer = "body";
graphtoolContainer.appendChild(feedbackButton);
if (score == 1) {
graphtoolContainer.classList.add("correct");
} else {
graphtoolContainer.classList.add("incorrect");
}
}
}
const selects = body_div.querySelectorAll("select:not([type=hidden])");
for (const select2 of selects) {
const name = select2.name;
const score = data.rh_result.answers[name].score;
let title = "";
if (score == 1) {
headline = localize_correct + "!";
correctClass = "correct";
} else {
headline = localize_incorrect + ".";
correctClass = "incorrect";
}
select2.classList.add(correctClass);
title = `<span class="${correctClass}">${headline}</span>`;
feedbackSpan = document.createElement("span");
feedbackSpan.id = `${ww_id}-${name}-feedback`;
feedbackHeadline = document.createElement("span");
feedbackHeadline.id = `${ww_id}-${name}-headline`;
feedbackHeadline.textContent = headline;
feedbackSpan.appendChild(feedbackHeadline);
let nbSpan = document.createElement("span");
nbSpan.classList.add("nobreak");
select2.parentNode.insertBefore(nbSpan, select2);
nbSpan.appendChild(select2);
let psSpan = document.createElement("span");
psSpan.classList.add("select-wrapper", correctClass);
select2.parentNode.insertBefore(psSpan, select2);
psSpan.appendChild(select2);
let srSpan = document.createElement("span");
srSpan.classList.add("visually-hidden");
srSpan.appendChild(feedbackSpan);
nbSpan.appendChild(srSpan);
select2.setAttribute("aria-describedby", `${ww_id}-${name}-feedback`);
srSpan.after(createFeedbackButton(`${ww_id}-${name}`, title));
}
}
let iframeContents = '<!DOCTYPE html><head><script src="' + ww_domain + `/webwork2_files/node_modules/jquery/dist/jquery.min.js"><\/script><script>window.MathJax = {
tex: {
inlineMath: [['\\\\(','\\\\)']],
tags: "none",
useLabelIds: true,
tagSide: "right",
tagIndent: ".8em",
packages: {'[+]': ['base', 'extpfeil', 'ams', 'amscd', 'newcommand', 'knowl']}
},
options: {
ignoreHtmlClass: "tex2jax_ignore",
processHtmlClass: "has_am",
renderActions: {
findScript: [10, function (doc) {
document.querySelectorAll('script[type^="math/tex"]').forEach(function(node) {
const display = !!node.type.match(/; *mode=display/);
const math = new doc.options.MathItem(node.textContent, doc.inputJax[0], display);
const text = document.createTextNode('');
node.parentNode.replaceChild(text, node);
math.start = {node: text, delim: '', n: 0};
math.end = {node: text, delim: '', n: 0};
doc.math.push(math);
});
}, '']
},
},
chtml: {
scale: 0.88,
mtextInheritFont: true
},
loader: {
load: ['input/asciimath', '[tex]/extpfeil', '[tex]/amscd', '[tex]/newcommand', '[pretext]/mathjaxknowl3.js'],
paths: {pretext: "https://pretextbook.org/js/lib"},
},
};
<\/script><script src="` + ww_domain + '/webwork2_files/node_modules/mathjax/es5/tex-chtml.js" id="MathJax-script" defer><\/script><script src="https://pretextbook.org/js/lib/knowl.js" defer><\/script><link rel="stylesheet" href="' + ww_domain + '/webwork2_files/node_modules/bootstrap/dist/css/bootstrap.min.css"/><script src="' + ww_domain + '/webwork2_files/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js" defer><\/script>';
const extra_css_files = [];
const extra_js_files = [];
if (data.extra_css_files) data.extra_css_files.unshift(...extra_css_files);
else data.extra_css_files = extra_css_files;
for (const cssFile of data.extra_css_files) {
if (/knowl/.test(cssFile.file)) continue;
iframeContents += '<link rel="stylesheet" href="' + (cssFile.external !== "1" ? ww_domain : "") + cssFile.file + '"/>';
}
if (data.extra_js_files) data.extra_js_files.unshift(...extra_js_files);
else data.extra_js_files = extra_js_files;
for (const jsFile of data.extra_js_files) {
if (/knowl/.test(jsFile.file)) continue;
iframeContents += '<script src="' + (jsFile.external !== "1" ? ww_domain : "") + jsFile.file + '" ' + Object.keys(jsFile.attributes || {}).reduce((ret, key) => {
ret += key + '="' + jsFile.attributes[key] + '" ';
return ret;
}, "") + "><\/script>";
}
iframeContents += '<link rel="stylesheet" href="https://pretextbook.org/css/0.31/pretext_add_on.css"/><link rel="stylesheet" href="https://pretextbook.org/css/0.31/knowls_default.css"/><script src="' + ww_domain + `/webwork2_files/node_modules/iframe-resizer/js/iframeResizer.contentWindow.min.js"><\/script><style>
html { overflow-y: hidden; }
html body { background:unset; margin: 0; }
body { font-size: initial; line-height: initial; padding:2px; }
.hidden-content { display: none; }
input[type="text"], input[type="radio"], label, select {
height: auto;
width: auto;
max-width: unset;
margin: 0;
font-size: initial;
/*font-family: sans-serif;*/
line-height: initial;
}
input[type="text"] {
padding: 1px;
padding-inline: 2px;
border-width: 1px;
border-style: inset;
border-color: rgb(133, 133, 133);
border-radius: 0;
box-shadow: unset;
transition: none;
}
input[type="text"]:focus, input[type="text"]:active {
box-shadow: unset;
border-width: 1px;
border-style: inset;
border-radius: 4px;
border-color: rgb(133, 133, 133);
outline: auto;
}
input[type="radio"] {
box-sizing: border-box;
margin-block: 3px 0;
margin-inline: 5px 3px;
vertical-align: unset;
}
span.nobreak {
white-space: nowrap;
}
input[type="text"].blank, input[type="text"].correct, input[type="text"].partly-correct, input[type="text"].incorrect, select.correct, select.incorrect {
background-size: auto 100%;
background-position: right;
background-repeat: no-repeat;
padding-right: 29px;
}
input[type="text"].blank {
background-color: #CDF;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='20px' width='20px'><text x='19' y='16' fill='%230049DB' text-anchor='end'>\u26A0</text></svg>");
}
input[type="text"].correct, select.correct, input[type="text"].correct + span.mq-editable-field {
background-color: #8F8;
}
input[type="text"].correct {
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='20px' width='20px'><text x='19' y='16' fill='%23060' text-anchor='end'>\u2713</text></svg>");
}
padding-left: 2rem;
background-repeat: no-repeat;
background-position-y: center;
}
input[type="text"].partly-correct, input[type="text"].partly-correct + span.mq-editable-field {
background-color: #CDF;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='22px' width='30px'><text x='28' y='18' fill='%235C5C00' text-anchor='end'>\u26A0</text></svg>");
}
input[type="text"].incorrect, select.incorrect, input[type="text"].incorrect + span.mq-editable-field {
background-color: #DAA;
}
input[type="text"].incorrect {
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='22px' width='30px'><text x='28' y='18' fill='%23943D3D' text-anchor='end'>\u26A0</text></svg>");
}
input[type="text"].partly-correct, input[type="text"].incorrect {
background-size: auto 70%;
}
label {
padding-left: 1.8em;
}
label.correct::before {
color: #060;
content: '\u2713';
}
label.incorrect::before {
color: #943D3D;
content: '\u26A0';
font-size: small;
}
label.correct::before, label.incorrect::before {
display:inline-block;
width: 0;
direction: rtl;
}
.select-wrapper.correct::before {
color: #060;
content: '\u2713';
margin-right: 2pt;
}
.select-wrapper.incorrect::before {
color: #943D3D;
content: '\u26A0';
margin-right: 2pt;
font-size: small;
}
.checkboxes-container.correct label input, .checkboxes-container.incorrect label input {
border-radius: 2px;
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
width: 20px;
height: 20px;
cursor: pointer;
position: relative;
top: 5px;
background-repeat: no-repeat;
}
.checkboxes-container.correct label input {
background-color: #8F8;
border: #060 solid;
}
.checkboxes-container.correct label input:checked {
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='14px' width='14px'><text x='13' y='13' text-anchor='end'>\u2713</text></svg>");
}
.checkboxes-container.incorrect label input {
background-color: #DAA;
border: #943D3D solid;
}
.checkboxes-container.incorrect label input:checked {
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='14px' width='14px'><text x='13' y='13' text-anchor='end'>\u2718</text></svg>");
}
div.PGML img.image-view-elt {
max-width:100%;
}
label {
display: inline-block;
}
select {
margin: 0;
border-width: 1px;
border-style: inset;
display: inline-block;
padding-block: 1px;
vertical-align: baseline;
background-color: unset;
padding-inline: 0;
}
.result-popover .popover-header, .result-popover .popover-content {
text-align: center;
}
.result-popover .popover-header.correct {
background-color: #8F8;
}
.result-popover.nocontent .popover-header {
border-radius: calc(.3rem - 1px);
}
.result-popover.nocontent .popover-header::before {
border: none;
}
.accordion-body.expanded {
overflow-y: visible;
overflow-x: clip;
}
.graphtool-answer-container .graphtool-graph {
margin: 0;
width: 300px;
height: 300px;
}
.graphtool-answer-container .graphtool-number-line {
height: 57px;
}
.checkboxes-container .ww-feedback {
position: absolute;
left: 2px;
}
.graphtool-container.correct .graphtool-graph {
background-color: #8F8;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='20px' width='20px'><text x='19' y='16' fill='%23060' text-anchor='end'>\u2713</text></svg>");
background-repeat: no-repeat;
}
.graphtool-container.incorrect .graphtool-graph {
background-color: #DAA;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='22px' width='30px'><text x='28' y='18' fill='%235C5C00' text-anchor='end'>\u26A0</text></svg>");
background-repeat: no-repeat;
}
</style></head><body><main class="pretext-content">` + form.outerHTML + "</main></body></html>";
let iframe2;
if (!action) {
iframe2 = document.createElement("iframe");
iframe2.style.width = "1px";
iframe2.style.minWidth = "100%";
iframe2.classList.add("problem-iframe");
ww_container.querySelector(".problem-contents").classList.add("hidden-content");
if (activate_button != null) {
activate_button.after(iframe2);
} else {
ww_container.prepend(iframe2);
}
iFrameResize({ checkOrigin: false, scrolling: "omit", heightCalculationMethod: "min" }, iframe2);
iframe2.addEventListener("load", () => {
const iframeForm = iframe2.contentDocument.getElementById(ww_id + "-form");
iframeForm.addEventListener("submit", (e) => {
handleWW(ww_id, "check");
e.preventDefault();
});
iframe2.contentDocument.querySelectorAll(".collapse.in").forEach((collapse) => collapse.classList.add("expanded"));
iframe2.contentWindow.jQuery(".collapse").on("shown", function(e) {
if (e.target != this) return;
this.classList.add("expanded");
});
iframe2.contentWindow.jQuery(".collapse").on("hide", function(e) {
if (e.target != this) return;
this.classList.remove("expanded");
});
iframe2.contentDocument.querySelectorAll("button.ww-feedback[data-bs-content]").forEach((button) => {
const bsPopover = new iframe2.contentWindow.bootstrap.Popover(button, {
html: true,
placement: "bottom",
fallbackPlacements: [],
trigger: "click",
customClass: "result-popover" + (button.dataset.emptyContent ? " nocontent" : "")
});
bsPopover.show();
const content = iframe2.contentDocument.getElementById(button.id.replace("-feedback-button", "-content"));
const popover = content.parentNode.parentNode;
bsPopover.hide();
button.addEventListener("click", () => bsPopover.show(), { once: true });
popover.querySelector(".popover-arrow").remove();
const title = popover.querySelector(".popover-header");
if (button.dataset.emptyContent) content.parentNode.remove();
if (title.textContent == localize_correct + "!") title.classList.add("correct");
});
iframe2.contentWindow.MathJax.startup.promise.then(() => iframe2.contentWindow.MathJax.typesetPromise([".popover", ".popover-content"]));
});
} else {
iframe2 = ww_container.querySelector(".problem-iframe");
}
iframe2.srcdoc = iframeContents;
iframe2.addEventListener("load", () => {
loader.remove();
}, { once: true });
ww_container.focus();
});
}
function WWshowCorrect(ww_id, answers) {
const ww_container = document.getElementById(ww_id);
const iframe2 = ww_container.querySelector(".problem-iframe");
const body = iframe2.contentDocument.getElementById(ww_id + "-body");
$("body").trigger("runestone_show_correct", {
"ww_id": ww_id,
"answers": answers
});
let inputs = body.querySelectorAll("input:not([type=hidden])");
for (const input of inputs) {
const name = input.name;
const span_id = `${ww_id}-${name}-correct`;
if (input.type == "text" && answers[name] && !iframe2.contentDocument.getElementById(span_id)) {
const input_id = input.id;
const mq_span = iframe2.contentDocument.getElementById(`mq-answer-${input_id}`);
if (mq_span) {
mq_span.style.display = "none";
}
const feedbackButton = iframe2.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`);
if (feedbackButton) {
iframe2.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide();
feedbackButton.remove();
}
label = iframe2.contentDocument.getElementById(`${span_id}-label`);
if (label) {
label.parentElement.insertBefore(input, label);
label.remove();
}
input.type = "hidden";
const correct_ans_text = iframe2.contentDocument.createElement("div");
correct_ans_text.innerHTML = answers[name].correct_ans;
input.value = correct_ans_text.textContent;
const show_span = iframe2.contentDocument.createElement("span");
show_span.id = span_id;
show_span.appendChild(answers[name].correct_ans_latex_string ? iframe2.contentDocument.createTextNode("\\(" + answers[name].correct_ans_latex_string + "\\)") : iframe2.contentDocument.createTextNode(answers[name].correct_ans));
input.parentElement.insertBefore(show_span, input);
}
if (input.type.toUpperCase() == "RADIO" && answers[name]) {
const feedbackButton = iframe2.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`);
if (feedbackButton) {
iframe2.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide();
feedbackButton.remove();
}
const correct_ans = answers[name].correct_choice || answers[name].correct_ans;
if (input.value == correct_ans) {
input.checked = true;
} else {
input.checked = false;
}
}
if (input.type.toUpperCase() == "CHECKBOX" && answers[name]) {
const correct_choices = answers[name].correct_choices;
if (correct_choices.includes(input.value)) {
input.checked = true;
} else {
input.checked = false;
}
}
}
const hiddenInputs = body.querySelectorAll("input[type=hidden]");
for (const input of hiddenInputs) {
const name = input.name;
if (!input.nextElementSibling) continue;
const graphtoolContainer = input.nextElementSibling.nextElementSibling;
if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains("graphtool-container")) {
const feedbackButton = iframe2.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`);
if (feedbackButton) {
iframe2.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide();
feedbackButton.remove();
}
const correct_ans_div = iframe2.contentDocument.createElement("div");
input.parentElement.insertBefore(correct_ans_div, graphtoolContainer);
graphtoolContainer.style.display = "none";
input.value = answers[name].correct_ans;
iframe2.contentWindow.jQuery(correct_ans_div).html(answers[name].correct_ans_latex_string);
const script = iframe2.contentDocument.createElement("script");
script.textContent = correct_ans_div.querySelector("script").textContent.replace('\nwindow.addEventListener("DOMContentLoaded",', "(").replace(/;\n$/, "();");
iframe2.contentDocument.body.appendChild(script);
}
}
let selects = body.querySelectorAll("select:not([type=hidden])");
for (const select of selects) {
const name = select.name;
const span_id = `${ww_id}-${name}-correct`;
if (answers[name] && !iframe2.contentDocument.getElementById(span_id)) {
const feedbackButton = iframe2.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`);
if (feedbackButton) {
iframe2.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide();
feedbackButton.remove();
}
select.style.display = "none";
select.value = answers[name].correct_ans;
const show_span = iframe2.contentDocument.createElement("span");
show_span.id = span_id;
show_span.appendChild(answers[name].correct_ans_latex_string ? iframe2.contentDocument.createTextNode("\\(" + answers[name].correct_ans_latex_string + "\\)") : iframe2.contentDocument.createTextNode(answers[name].correct_ans));
select.parentElement.insertBefore(show_span, select);
}
}
const mathjaxTypesetScript = iframe2.contentDocument.createElement("script");
mathjaxTypesetScript.textContent = "MathJax.startup.promise.then(() => MathJax.typesetPromise([document.body]));";
iframe2.contentDocument.body.appendChild(mathjaxTypesetScript);
}
function resetWW(ww_id) {
const ww_container = document.getElementById(ww_id);
const activate_button = document.getElementById(ww_id + "-button");
ww_container.dataset.current_seed = ww_container.dataset.seed;
iframe = ww_container.querySelector(".problem-iframe");
iframe.remove();
ww_container.querySelector(".problem-contents").classList.remove("hidden-content");
ww_container.querySelector(".problem-buttons.webwork").remove();
ww_container.querySelector(".problem-buttons").classList.remove("hidden-content");
if (activate_button != null) {
activate_button.classList.remove("hidden-content");
}
;
}
function adjustSrcHrefs(container, ww_domain) {
container.querySelectorAll("[href]").forEach((node) => {
const href = node.attributes.href.value;
if (href !== "#" && !href.match(/^[a-z]+:\/\//i)) node.href = ww_domain + "/" + href;
});
container.querySelectorAll("[src]").forEach((node) => {
node.src = new URL(node.attributes.src.value, ww_domain).href;
});
}
function translateHintSol(ww_id, body_div, ww_domain, b_ptx_has_hint, b_ptx_has_solution, hint_label_text, solution_label_text) {
const hintSols = body_div.querySelectorAll('.knowl[data-type="hint"],.knowl[data-type="solution"]');
let solutionlikewrapper;
for (const hintSol of hintSols) {
const hintsolp = hintSol.parentNode;
if (!hintsolp) continue;
const hintsolpp = hintsolp.parentNode;
const hintSolType = hintSol.dataset.type;
if (hintsolpp.querySelectorAll(".webwork.solutions").length == 0) {
solutionlikewrapper = document.createElement("div");
solutionlikewrapper.classList.add("webwork", "solutions");
hintsolpp.insertBefore(solutionlikewrapper, hintsolp);
}
if (hintSolType == "solution" && !b_ptx_has_solution || hintSolType == "hint" && !b_ptx_has_hint)
continue;
const knowlDetails = document.createElement("details");
knowlDetails.classList.add(hintSolType);
knowlDetails.classList.add("solution-like");
knowlDetails.classList.add("born-hidden-knowl");
const knowlSummary = document.createElement("summary");
const summaryLabel = document.createElement("span");
summaryLabel.classList.add("type");
summaryLabel.innerHTML = hintSolType == "hint" ? hint_label_text : solution_label_text;
knowlSummary.appendChild(summaryLabel);
knowlDetails.appendChild(knowlSummary);
const knowlContents = document.createElement("div");
knowlContents.classList.add(hintSolType);
knowlContents.classList.add("solution-like");
knowlContents.innerHTML = hintsolp.firstElementChild.dataset.knowlContents;
knowlDetails.appendChild(knowlContents);
adjustSrcHrefs(knowlContents, ww_domain);
solutionlikewrapper.appendChild(knowlDetails);
}
for (const hintSol of hintSols) {
hintSol.parentNode?.remove();
}
}
function cloneAttributes(target, source) {
[...source.attributes].forEach((attr) => {
target.setAttribute(attr.nodeName, attr.nodeValue);
});
}
function createFeedbackButton(id, title, content) {
const feedbackButton = document.createElement("button");
feedbackButton.dataset.bsTitle = title;
feedbackButton.dataset.bsContent = `<div id="${id}-content">${content || ""}</div>`;
if (!content) feedbackButton.dataset.emptyContent = "1";
const contentSpan = document.createElement("span");
contentSpan.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" fill="none" stroke-width="2" viewBox="0 0 24 24" color="#000000"><path stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M12 11.5v5M12 7.51l.01-.011M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10Z"></path></svg>`;
feedbackButton.appendChild(contentSpan);
feedbackButton.type = "button";
feedbackButton.classList.add("ww-feedback");
feedbackButton.id = `${id}-feedback-button`;
feedbackButton.dataset.bsToggle = "popover";
return feedbackButton;
}
function webworkSeedHash(string) {
var hash = 0, i, chr;
if (string.length === 0) return hash;
for (i = 0; i < string.length; i++) {
chr = string.charCodeAt(i);
hash = (hash << 5) - hash + chr;
hash |= 0;
}
return Math.abs(hash);
}
;
//# sourceMappingURL=pretext-webwork.js.map
@@ -0,0 +1,545 @@
async function handleWW(ww_id, action) {
const ww_container = document.getElementById(ww_id);
const ww_domain = ww_container.dataset.domain;
const ww_processing = "webwork2";
const ww_origin = ww_container.dataset.origin;
const ww_problemSource = ww_container.dataset.problemsource;
const ww_sourceFilePath = ww_container.dataset.sourcefilepath;
const ww_course_id = ww_container.dataset.courseid;
const ww_user_id = ww_container.dataset.userid;
const ww_passwd = ww_container.dataset.coursepassword;
const localize_submit = ww_container.dataset.localizeSubmit || "Submit";
const localize_check_responses = ww_container.dataset.localizeCheckResponses || "Check Responses";
const localize_reveal = ww_container.dataset.localizeReveal || "Reveal";
const localize_randomize = ww_container.dataset.localizeRandomize || "Randomize";
const localize_reset = ww_container.dataset.localizeReset || "Reset";
const runestone_logged_in = typeof eBookConfig !== "undefined" && eBookConfig.username !== "";
const activate_button = document.getElementById(ww_id + "-button");
if (!action) {
ww_container.dataset.current_seed = ww_container.dataset.seed;
if (runestone_logged_in) {
ww_container.dataset.current_seed = webworkSeedHash(eBookConfig.username + ww_container.dataset.current_seed);
}
} else if (action == "randomize") ww_container.dataset.current_seed = Number(ww_container.dataset.current_seed) + 100;
let loader = document.createElement("div");
loader.style.position = "absolute";
loader.style.left = 0;
loader.style.top = 0;
loader.style.backgroundColor = "rgba(0.2, 0.2, 0.2, 0.4)";
loader.style.color = "white";
loader.style.width = "100%";
loader.style.height = "100%";
loader.style.display = "flex";
loader.style.alignItems = "center";
loader.style.justifyContent = "center";
loader.style.marginTop = "0";
loader.tabIndex = -1;
const loaderText = document.createElement("span");
loaderText.textContent = "Loading";
loaderText.style.fontSize = "2rem";
loader.appendChild(loaderText);
ww_container.appendChild(loader);
loader.focus();
if (!action) {
ww_container.dataset.hasHint = ww_container.getElementsByClassName("hint").length > 0;
ww_container.dataset.hasSolution = ww_container.getElementsByClassName("solution").length > 0;
ww_container.dataset.hasAnswer = ww_container.getElementsByClassName("answer").length > 0;
ww_container.dataset.hintLabelText = ww_container.dataset.hasHint == "true" ? ww_container.querySelectorAll(".hint-knowl span.type, details.hint span.type")[0].textContent : "Hint";
ww_container.dataset.solutionLabelText = ww_container.dataset.hasSolution == "true" ? ww_container.querySelectorAll(".solution-knowl span.type, details.solution span.type")[0].textContent : "Solution";
ww_container.tabIndex = -1;
}
let url;
if (ww_processing == "webwork2") {
url = new URL(ww_domain + "/webwork2/render_rpc");
}
let formData = new FormData();
let generatedPG = "generated/webwork/pg/";
if (runestone_logged_in) {
generatedPG = `/ns/books/published/${eBookConfig.basecourse}/${generatedPG}`;
}
if (action == "check" || action == "reveal") {
const iframe2 = ww_container.querySelector(".problem-iframe");
formData = new FormData(iframe2.contentDocument.getElementById(ww_id + "-form"));
formData.set("answersSubmitted", "1");
formData.set("WWsubmit", "1");
if (action == "reveal" && ww_container.dataset.hasAnswer == "true") {
formData.set("WWcorrectAnsOnly", "1");
}
if (ww_origin == "generated") {
const rawProblemSource = await fetch(generatedPG + ww_problemSource).then((r) => r.text());
formData.set("rawProblemSource", rawProblemSource);
} else if (ww_origin == "webwork2") formData.set("sourceFilePath", ww_sourceFilePath);
} else {
formData.set("problemSeed", ww_container.dataset.current_seed);
if (ww_origin == "generated") {
const rawProblemSource = await fetch(generatedPG + ww_problemSource).then((r) => r.text());
formData.set("rawProblemSource", rawProblemSource);
} else if (ww_origin == "webwork2") formData.set("sourceFilePath", ww_sourceFilePath);
formData.set("answersSubmitted", "0");
formData.set("displayMode", "MathJax");
formData.set("courseID", ww_course_id);
formData.set("user", ww_user_id);
formData.set("userID", ww_user_id);
formData.set("passwd", ww_passwd);
formData.set("disableCookies", "1");
formData.set("outputformat", "raw");
formData.set("showSolutions", ww_container.dataset.hasSolution == "true" ? "1" : "0");
formData.set("showHints", ww_container.dataset.hasHint == "true" ? "1" : "0");
formData.set("problemUUID", ww_id);
}
let checkboxesString = "";
if (runestone_logged_in && !action) {
const answersObject = wwList[ww_id.replace(/-ww-rs$/, "")].answers ? wwList[ww_id.replace(/-ww-rs$/, "")].answers : { "answers": [], "mqAnswers": [] };
const previousAnswers = answersObject.answers;
if (previousAnswers !== null) {
formData.set("WWsubmit", 1);
}
for (const answer in previousAnswers) {
if (previousAnswers[answer].constructor === Array) {
for (const k in previousAnswers[answer]) {
checkboxesString += "&";
checkboxesString += answer;
checkboxesString += "=";
checkboxesString += previousAnswers[answer][k];
}
} else {
formData.set(answer, previousAnswers[answer]);
}
}
}
const formString = new URLSearchParams(formData).toString();
$.post(url, formString + checkboxesString, (data) => {
const form = document.createElement("form");
form.id = ww_id + "-form";
form.dataset.iframeHeight = 1;
const body_div = document.createElement("div");
body_div.id = ww_id + "-body";
body_div.classList.add("exercise", "exercise-like");
body_div.lang = data.lang;
body_div.dir = data.dir;
body_div.innerHTML = data.rh_result.text;
if ("showPartialCorrectAnswers" in data.rh_result.flags && data.rh_result.flags.showPartialCorrectAnswers == 0) {
if ("score" in data.rh_result.problem_result && data.rh_result.problem_result.score >= 1) {
body_div.querySelectorAll("button.ww-feedback-btn").forEach(
function(button) {
button.classList.remove("btn-info");
button.classList.add("btn-success");
button.setAttribute("aria-label", "Correct");
button.dataset.bsCustomClass = button.dataset.bsCustomClass + " correct";
button.dataset.bsTitle = button.dataset.bsTitle.replace("Answer Preview", "Correct");
button.firstChild.classList.add("correct");
}
);
} else {
body_div.querySelectorAll("button.ww-feedback-btn").forEach(
function(button) {
button.setAttribute("aria-label", "One or more answers incorrect");
button.dataset.bsTitle = button.dataset.bsTitle.replace("Answer Preview", "One or more answers incorrect");
}
);
}
}
for (const tag_name of ["h6", "h5", "h4", "h3", "h2", "h1"]) {
const headings = body_div.getElementsByTagName(tag_name);
for (heading of headings) {
const new_heading = document.createElement("h6");
new_heading.innerHTML = heading.innerHTML;
cloneAttributes(new_heading, heading);
new_heading.classList.add("webwork-part");
heading.replaceWith(new_heading);
}
}
var textareas = body_div.getElementsByTagName("textarea");
for (var i = 0, max = textareas.length; i < max; i++) {
textareas[i].style.display = "none";
textareas[i].className = "";
}
var textareabuttons = body_div.querySelectorAll(".latexentry-preview");
for (var i = 0, max = textareabuttons.length; i < max; i++) {
textareabuttons[i].remove();
}
adjustSrcHrefs(body_div, ww_domain);
translateHintSol(
ww_id,
body_div,
ww_domain,
ww_container.dataset.hasHint == "true",
ww_container.dataset.hasSolution == "true",
ww_container.dataset.hintLabelText,
ww_container.dataset.solutionLabelText
);
if (runestone_logged_in) {
const answersObject = wwList[ww_id.replace(/-ww-rs$/, "")].answers ? wwList[ww_id.replace(/-ww-rs$/, "")].answers : { "answers": [], "mqAnswers": [] };
const mqAnswers = answersObject.mqAnswers;
for (const mqAnswer in mqAnswers) {
const mqInput = body_div.querySelector("input[id=" + mqAnswer + "]");
if (mqInput && mqInput.value == "") {
mqInput.setAttribute("value", mqAnswers[mqAnswer]);
}
}
const answers = answersObject.answers;
for (const answer in answers) {
const input = body_div.querySelector("input[id=" + answer + "]");
if (input && input.value == "") {
input.setAttribute("value", answers[answer]);
}
}
}
form.appendChild(body_div);
const wwInputs = {
problemSeed: data.inputs_ref.problemSeed,
problemUUID: data.inputs_ref.problemUUID,
psvn: data.inputs_ref.psvn,
courseName: ww_course_id,
courseID: ww_course_id,
user: ww_user_id,
passwd: ww_passwd,
displayMode: "MathJax",
session_key: data.rh_result.session_key,
outputformat: "raw",
language: data.formLanguage,
showSummary: data.showSummary,
disableCookies: "1",
// note ww_container.dataset.hasSolution is a string, possibly 'false' which is true
showSolutions: ww_container.dataset.hasSolution == "true" ? "1" : "0",
showHints: ww_container.dataset.hasHint == "true" ? "1" : "0",
forcePortNumber: data.forcePortNumber
};
if (ww_sourceFilePath) wwInputs.sourceFilePath = ww_sourceFilePath;
else if (ww_problemSource && ww_origin == "webwork2") wwInputs.problemSource = ww_problemSource;
for (const wwInputName of Object.keys(wwInputs)) {
const input = document.createElement("input");
input.type = "hidden";
input.name = wwInputName;
input.value = wwInputs[wwInputName];
form.appendChild(input);
}
let buttonContainer = ww_container.querySelector(".problem-buttons.webwork");
if (!buttonContainer) {
ww_container.querySelector(".problem-buttons").classList.add("hidden-content", "hidden");
buttonContainer = document.createElement("div");
buttonContainer.classList.add("problem-buttons", "webwork");
if (activate_button != null) {
activate_button.after(buttonContainer);
} else {
ww_container.prepend(buttonContainer);
}
const check = document.createElement("button");
check.type = "button";
check.id = ww_id + "-check";
check.style.marginRight = "0.25rem";
check.classList.add("webwork-button");
check.textContent = runestone_logged_in ? localize_submit : localize_check_responses;
check.addEventListener("click", () => handleWW(ww_id, "check"));
buttonContainer.appendChild(check);
if (ww_container.dataset.hasAnswer == "true") {
const correct = document.createElement("button");
correct.classList.add("show-correct", "webwork-button");
correct.type = "button";
correct.style.marginRight = "0.25rem";
correct.textContent = localize_reveal;
correct.addEventListener("click", () => handleWW(ww_id, "reveal"));
buttonContainer.appendChild(correct);
}
const randomize = document.createElement("button");
randomize.type = "button";
randomize.classList.add("webwork-button");
randomize.style.marginRight = "0.25rem";
randomize.textContent = localize_randomize;
randomize.addEventListener("click", () => handleWW(ww_id, "randomize"));
buttonContainer.appendChild(randomize);
const reset = document.createElement("button");
reset.type = "button";
reset.classList.add("webwork-button");
reset.textContent = localize_reset;
reset.addEventListener("click", () => resetWW(ww_id));
buttonContainer.appendChild(reset);
}
if (runestone_logged_in && action == "check") {
$("body").trigger("runestone_ww_check", data);
}
let courseUrlBase = "";
if (runestone_logged_in) {
courseUrlBase = "/ns/books/published/" + eBookConfig.basecourse + "/";
}
let iframeContents = '<!DOCTYPE html><head><script src="' + ww_domain + `/webwork2_files/node_modules/jquery/dist/jquery.min.js"><\/script><script>
window.MathJax = {
tex: { packages: { '[+]': ['noerrors'] } },
loader: { load: ['input/asciimath', '[tex]/noerrors'] },
startup: {
ready() {
const AM = MathJax.InputJax.AsciiMath.AM;
// Modify existing AsciiMath triggers.
AM.symbols[AM.names.indexOf('**')] = {
input: '**',
tag: 'msup',
output: '^',
tex: null,
ttype: AM.TOKEN.INFIX
};
const i = AM.names.indexOf('infty');
AM.names[i] = 'infinity';
AM.symbols[i] = { input: 'infinity', tag: 'mo', output: '\u221E', tex: 'infty', ttype: AM.TOKEN.CONST };
return MathJax.startup.defaultReady();
}
},
options: {
processHtmlClass: "process-math",
renderActions: {
findScript: [
10,
(doc) => {
for (const node of document.querySelectorAll('script[type^="math/tex"]')) {
const math = new doc.options.MathItem(
node.textContent,
doc.inputJax[0],
!!node.type.match(/; *mode=display/)
);
const text = document.createTextNode('');
node.parentNode.replaceChild(text, node);
math.start = { node: text, delim: '', n: 0 };
math.end = { node: text, delim: '', n: 0 };
doc.math.push(math);
}
},
''
]
},
ignoreHtmlClass: 'tex2jax_ignore'
}
};
<\/script><script src="` + ww_domain + `/webwork2_files/node_modules/mathjax/es5/tex-chtml.js" id="MathJax-script" defer><\/script><script src="${courseUrlBase}_static/pretext/js/dist/knowl.js" defer><\/script><link rel="stylesheet" href="` + ww_domain + '/webwork2_files/node_modules/bootstrap/dist/css/bootstrap.min.css"/><script src="' + ww_domain + '/webwork2_files/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js" defer><\/script>';
const extra_css_files = [];
const extra_js_files = [];
if (data.extra_css_files) data.extra_css_files.unshift(...extra_css_files);
else data.extra_css_files = extra_css_files;
for (const cssFile of data.extra_css_files) {
iframeContents += '<link rel="stylesheet" href="' + (cssFile.external !== "1" ? ww_domain : "") + cssFile.file + '"/>';
}
if (data.extra_js_files) data.extra_js_files.unshift(...extra_js_files);
else data.extra_js_files = extra_js_files;
for (const jsFile of data.extra_js_files) {
iframeContents += '<script src="' + (jsFile.external !== "1" ? ww_domain : "") + jsFile.file + '" ' + Object.keys(jsFile.attributes || {}).reduce((ret, key) => {
ret += key + '="' + jsFile.attributes[key] + '" ';
return ret;
}, "") + "><\/script>";
}
iframeContents += `<link rel="stylesheet" href="${courseUrlBase}_static/pretext/css/theme.css"/><script src="` + ww_domain + `/webwork2_files/node_modules/iframe-resizer/js/iframeResizer.contentWindow.min.js"><\/script><style>
html { overflow-y: hidden; }
html body { background:unset; margin: 0; }
body { font-size: initial; line-height: initial; padding:2px; }
.hidden-content { display: none; }
span.nobreak { white-space: nowrap; }
div.PGML img.image-view-elt { max-width:100%; }
.graphtool-answer-container .graphtool-graph { margin: 0; width: 300px; height: 300px; }
.graphtool-answer-container .graphtool-number-line { height: 57px; }
.quill-toolbar { scrollbar-width: thin; overflow-x: hidden; }
</style></head><body><main class="pretext-content problem-content" data-iframe-height="1">` + form.outerHTML + "</main></body></html>";
let iframe2;
if (!action) {
iframe2 = document.createElement("iframe");
iframe2.style.width = "1px";
iframe2.style.minWidth = "100%";
iframe2.classList.add("problem-iframe");
ww_container.querySelector(".problem-contents").classList.add("hidden-content", "hidden");
if (activate_button != null) {
activate_button.after(iframe2);
} else {
ww_container.prepend(iframe2);
}
iFrameResize({ checkOrigin: false, scrolling: "omit", heightCalculationMethod: "taggedElement" }, iframe2);
iframe2.addEventListener("load", () => {
const iframeForm = iframe2.contentDocument.getElementById(ww_id + "-form");
iframeForm.addEventListener("submit", (e) => {
handleWW(ww_id, "check");
e.preventDefault();
});
iframe2.contentDocument.querySelectorAll(".collapse.in").forEach((collapse) => collapse.classList.add("expanded"));
iframe2.contentWindow.jQuery(".collapse").on("shown", function(e) {
if (e.target != this) return;
this.classList.add("expanded");
});
iframe2.contentWindow.jQuery(".collapse").on("hide", function(e) {
if (e.target != this) return;
this.classList.remove("expanded");
});
iframe2.contentWindow.MathJax.startup.promise.then(() => iframe2.contentWindow.MathJax.typesetPromise([".popover", ".popover-content"]));
});
} else {
iframe2 = ww_container.querySelector(".problem-iframe");
}
iframe2.srcdoc = iframeContents;
iframe2.addEventListener("load", () => {
loader.remove();
}, { once: true });
ww_container.focus();
}, "json");
}
function WWshowCorrect(ww_id, answers) {
const ww_container = document.getElementById(ww_id);
const iframe2 = ww_container.querySelector(".problem-iframe");
const body = iframe2.contentDocument.getElementById(ww_id + "-body");
$("body").trigger("runestone_show_correct", {
"ww_id": ww_id,
"answers": answers
});
let inputs = body.querySelectorAll("input:not([type=hidden])");
for (const input of inputs) {
const name = input.name;
const span_id = `${ww_id}-${name}-correct`;
if (input.type == "text" && answers[name] && !iframe2.contentDocument.getElementById(span_id)) {
const input_id = input.id;
const mq_span = iframe2.contentDocument.getElementById(`mq-answer-${input_id}`);
if (mq_span) {
mq_span.style.display = "none";
}
label = iframe2.contentDocument.getElementById(`${span_id}-label`);
if (label) {
label.parentElement.insertBefore(input, label);
label.remove();
}
input.type = "hidden";
const correct_ans_text = iframe2.contentDocument.createElement("div");
correct_ans_text.innerHTML = answers[name].correct_ans;
input.value = correct_ans_text.textContent;
const show_span = iframe2.contentDocument.createElement("span");
show_span.id = span_id;
show_span.appendChild(answers[name].correct_ans_latex_string ? iframe2.contentDocument.createTextNode("\\(" + answers[name].correct_ans_latex_string + "\\)") : iframe2.contentDocument.createTextNode(answers[name].correct_ans));
input.parentElement.insertBefore(show_span, input);
}
if (input.type.toUpperCase() == "RADIO" && answers[name]) {
const correct_ans = answers[name].correct_choice || answers[name].correct_ans;
if (input.value == correct_ans) {
input.checked = true;
} else {
input.checked = false;
}
}
if (input.type.toUpperCase() == "CHECKBOX" && answers[name]) {
const correct_choices = answers[name].correct_choices;
if (correct_choices.includes(input.value)) {
input.checked = true;
} else {
input.checked = false;
}
}
}
const hiddenInputs = body.querySelectorAll("input[type=hidden]");
for (const input of hiddenInputs) {
const name = input.name;
if (!input.nextElementSibling) continue;
const graphtoolContainer = input.nextElementSibling.nextElementSibling;
if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains("graphtool-container")) {
const correct_ans_div = iframe2.contentDocument.createElement("div");
input.parentElement.insertBefore(correct_ans_div, graphtoolContainer);
graphtoolContainer.style.display = "none";
input.value = answers[name].correct_ans;
iframe2.contentWindow.jQuery(correct_ans_div).html(answers[name].correct_ans_latex_string);
const script = iframe2.contentDocument.createElement("script");
script.textContent = correct_ans_div.querySelector("script").textContent.replace('\nwindow.addEventListener("DOMContentLoaded",', "(").replace(/;\n$/, "();");
iframe2.contentDocument.body.appendChild(script);
}
}
let selects = body.querySelectorAll("select:not([type=hidden])");
for (const select of selects) {
const name = select.name;
const span_id = `${ww_id}-${name}-correct`;
if (answers[name] && !iframe2.contentDocument.getElementById(span_id)) {
select.style.display = "none";
select.value = answers[name].correct_ans;
const show_span = iframe2.contentDocument.createElement("span");
show_span.id = span_id;
show_span.appendChild(answers[name].correct_ans_latex_string ? iframe2.contentDocument.createTextNode("\\(" + answers[name].correct_ans_latex_string + "\\)") : iframe2.contentDocument.createTextNode(answers[name].correct_ans));
select.parentElement.insertBefore(show_span, select);
}
}
const mathjaxTypesetScript = iframe2.contentDocument.createElement("script");
mathjaxTypesetScript.textContent = "MathJax.startup.promise.then(() => MathJax.typesetPromise([document.body]));";
iframe2.contentDocument.body.appendChild(mathjaxTypesetScript);
}
function resetWW(ww_id) {
const ww_container = document.getElementById(ww_id);
const activate_button = document.getElementById(ww_id + "-button");
ww_container.dataset.current_seed = ww_container.dataset.seed;
iframe = ww_container.querySelector(".problem-iframe");
iframe.remove();
ww_container.querySelector(".problem-contents").classList.remove("hidden-content", "hidden");
ww_container.querySelector(".problem-buttons.webwork").remove();
ww_container.querySelector(".problem-buttons").classList.remove("hidden-content", "hidden");
if (activate_button != null) {
activate_button.classList.remove("hidden-content", "hidden");
}
;
}
function adjustSrcHrefs(container, ww_domain) {
container.querySelectorAll("[href]").forEach((node) => {
const href = node.attributes.href.value;
if (href !== "#" && !href.match(/^[a-z]+:\/\//i)) node.href = ww_domain + "/" + href;
});
container.querySelectorAll("[data-knowl-url]").forEach((node) => {
const dku = node.dataset.knowlUrl;
if (dku !== "#" && !dku.match(/^[a-z]+:\/\//i)) node.dataset.knowlUrl = ww_domain + dku;
});
container.querySelectorAll("[src]").forEach((node) => {
node.src = new URL(node.attributes.src.value, ww_domain).href;
});
}
function translateHintSol(ww_id, body_div, ww_domain, b_ptx_has_hint, b_ptx_has_solution, hint_label_text, solution_label_text) {
const hintSols = body_div.querySelectorAll(".accordion.hint,.accordion.solution");
if (hintSols.length == 0) {
return;
}
;
for (const hintSol of hintSols) {
const parent = hintSol.parentNode;
solutionlikewrapper = document.createElement("div");
solutionlikewrapper.classList.add("solutions");
parent.insertBefore(solutionlikewrapper, hintSol);
const hintSolType = hintSol.classList.contains("hint") ? "hint" : "solution";
if (hintSolType == "solution" && !b_ptx_has_solution || hintSolType == "hint" && !b_ptx_has_hint)
continue;
const knowlDetails = hintSol.getElementsByTagName("details")[0];
knowlDetails.className = "";
knowlDetails.classList.add(hintSolType, "solution-like", "born-hidden-knowl");
solutionlikewrapper.appendChild(knowlDetails);
const knowlSummary = knowlDetails.getElementsByTagName("summary")[0];
knowlSummary.className = "";
knowlSummary.classList.add("knowl__link");
const summaryLabel = knowlSummary.children[0];
summaryLabel.remove();
const newLabelSpan = document.createElement("span");
newLabelSpan.innerHTML = hintSolType == "hint" ? hint_label_text : solution_label_text;
knowlSummary.appendChild(newLabelSpan);
const newLabelPeriod = document.createElement("span");
newLabelPeriod.innerHTML = ".";
knowlSummary.appendChild(newLabelPeriod);
adjustSrcHrefs(knowlDetails, ww_domain);
hintSol.remove();
const originalDetailsContent = knowlDetails.getElementsByTagName("div")[0];
const newDetailsContent = originalDetailsContent.getElementsByTagName("div")[0];
newDetailsContent.className = "";
newDetailsContent.classList.add(hintSolType, "solution-like", "knowl__content");
knowlDetails.appendChild(newDetailsContent);
originalDetailsContent.remove();
}
}
function cloneAttributes(target, source) {
[...source.attributes].forEach((attr) => {
target.setAttribute(attr.nodeName, attr.nodeValue);
});
}
function webworkSeedHash(string) {
var hash = 0, i, chr;
if (string.length === 0) return hash;
for (i = 0; i < string.length; i++) {
chr = string.charCodeAt(i);
hash = (hash << 5) - hash + chr;
hash |= 0;
}
return Math.abs(hash);
}
;
//# sourceMappingURL=pretext-webwork.js.map
@@ -0,0 +1,551 @@
async function handleWW(ww_id, action) {
const ww_container = document.getElementById(ww_id);
const ww_domain = ww_container.dataset.domain;
const ww_processing = "webwork2";
const ww_origin = ww_container.dataset.origin;
const ww_problemSource = ww_container.dataset.problemsource;
let ww_baseCourse = ww_container.dataset.documentid;
const ww_sourceFilePath = ww_container.dataset.sourcefilepath;
const ww_course_id = ww_container.dataset.courseid;
const ww_user_id = ww_container.dataset.userid;
const ww_passwd = ww_container.dataset.coursepassword;
const localize_submit = ww_container.dataset.localizeSubmit || "Submit";
const localize_check_responses = ww_container.dataset.localizeCheckResponses || "Check Responses";
const localize_reveal = ww_container.dataset.localizeReveal || "Reveal";
const localize_randomize = ww_container.dataset.localizeRandomize || "Randomize";
const localize_reset = ww_container.dataset.localizeReset || "Reset";
const runestone_logged_in = typeof eBookConfig !== "undefined" && eBookConfig.username !== "";
const activate_button = document.getElementById(ww_id + "-button");
if (!action) {
ww_container.dataset.current_seed = ww_container.dataset.seed;
if (runestone_logged_in) {
ww_container.dataset.current_seed = webworkSeedHash(eBookConfig.username + ww_container.dataset.current_seed);
}
} else if (action == "randomize") ww_container.dataset.current_seed = Number(ww_container.dataset.current_seed) + 100;
let loader = document.createElement("div");
loader.style.position = "absolute";
loader.style.left = 0;
loader.style.top = 0;
loader.style.backgroundColor = "rgba(0.2, 0.2, 0.2, 0.4)";
loader.style.color = "white";
loader.style.width = "100%";
loader.style.height = "100%";
loader.style.display = "flex";
loader.style.alignItems = "center";
loader.style.justifyContent = "center";
loader.style.marginTop = "0";
loader.tabIndex = -1;
const loaderText = document.createElement("span");
loaderText.textContent = "Loading";
loaderText.style.fontSize = "2rem";
loader.appendChild(loaderText);
ww_container.appendChild(loader);
loader.focus();
if (!action) {
ww_container.dataset.hasHint = ww_container.getElementsByClassName("hint").length > 0;
ww_container.dataset.hasSolution = ww_container.getElementsByClassName("solution").length > 0;
ww_container.dataset.hasAnswer = ww_container.getElementsByClassName("answer").length > 0;
ww_container.dataset.hintLabelText = ww_container.dataset.hasHint == "true" ? ww_container.querySelectorAll(".hint-knowl span.type, details.hint span.type")[0].textContent : "Hint";
ww_container.dataset.solutionLabelText = ww_container.dataset.hasSolution == "true" ? ww_container.querySelectorAll(".solution-knowl span.type, details.solution span.type")[0].textContent : "Solution";
ww_container.tabIndex = -1;
}
let url;
if (ww_processing == "webwork2") {
url = new URL(ww_domain + "/webwork2/render_rpc");
}
let formData = new FormData();
let generatedPG = "generated/webwork/pg/";
if (runestone_logged_in) {
if (ww_problemSource && !ww_baseCourse) {
const parts = ww_id.split("_");
ww_baseCourse = parts.length > 1 ? parts[0] : eBookConfig.basecourse;
console.log("using the base course " + ww_baseCourse);
}
generatedPG = `/ns/books/published/${ww_baseCourse}/${generatedPG}`;
}
if (action == "check" || action == "reveal") {
const iframe2 = ww_container.querySelector(".problem-iframe");
formData = new FormData(iframe2.contentDocument.getElementById(ww_id + "-form"));
formData.set("answersSubmitted", "1");
formData.set("WWsubmit", "1");
if (action == "reveal" && ww_container.dataset.hasAnswer == "true") {
formData.set("WWcorrectAnsOnly", "1");
}
if (ww_origin == "generated") {
const rawProblemSource = await fetch(generatedPG + ww_problemSource).then((r) => r.text());
formData.set("rawProblemSource", rawProblemSource);
} else if (ww_origin == "webwork2") formData.set("sourceFilePath", ww_sourceFilePath);
} else {
formData.set("problemSeed", ww_container.dataset.current_seed);
if (ww_origin == "generated") {
const rawProblemSource = await fetch(generatedPG + ww_problemSource).then((r) => r.text());
formData.set("rawProblemSource", rawProblemSource);
} else if (ww_origin == "webwork2") formData.set("sourceFilePath", ww_sourceFilePath);
formData.set("answersSubmitted", "0");
formData.set("displayMode", "MathJax");
formData.set("courseID", ww_course_id);
formData.set("user", ww_user_id);
formData.set("userID", ww_user_id);
formData.set("passwd", ww_passwd);
formData.set("disableCookies", "1");
formData.set("outputformat", "raw");
formData.set("showSolutions", ww_container.dataset.hasSolution == "true" ? "1" : "0");
formData.set("showHints", ww_container.dataset.hasHint == "true" ? "1" : "0");
formData.set("problemUUID", ww_id);
}
let checkboxesString = "";
if (runestone_logged_in && !action) {
const answersObject = wwList[ww_id.replace(/-ww-rs$/, "")].answers ? wwList[ww_id.replace(/-ww-rs$/, "")].answers : { "answers": [], "mqAnswers": [] };
const previousAnswers = answersObject.answers;
if (previousAnswers !== null) {
formData.set("WWsubmit", 1);
}
for (const answer in previousAnswers) {
if (previousAnswers[answer].constructor === Array) {
for (const k in previousAnswers[answer]) {
checkboxesString += "&";
checkboxesString += answer;
checkboxesString += "=";
checkboxesString += previousAnswers[answer][k];
}
} else {
formData.set(answer, previousAnswers[answer]);
}
}
}
const formString = new URLSearchParams(formData).toString();
$.post(url, formString + checkboxesString, (data) => {
const form = document.createElement("form");
form.id = ww_id + "-form";
form.dataset.iframeHeight = 1;
const body_div = document.createElement("div");
body_div.id = ww_id + "-body";
body_div.classList.add("exercise", "exercise-like");
body_div.lang = data.lang;
body_div.dir = data.dir;
body_div.innerHTML = data.rh_result.text;
if ("showPartialCorrectAnswers" in data.rh_result.flags && data.rh_result.flags.showPartialCorrectAnswers == 0) {
if ("score" in data.rh_result.problem_result && data.rh_result.problem_result.score >= 1) {
body_div.querySelectorAll("button.ww-feedback-btn").forEach(
function(button) {
button.classList.remove("btn-info");
button.classList.add("btn-success");
button.setAttribute("aria-label", "Correct");
button.dataset.bsCustomClass = button.dataset.bsCustomClass + " correct";
button.dataset.bsTitle = button.dataset.bsTitle.replace("Answer Preview", "Correct");
button.firstChild.classList.add("correct");
}
);
} else {
body_div.querySelectorAll("button.ww-feedback-btn").forEach(
function(button) {
button.setAttribute("aria-label", "One or more answers incorrect");
button.dataset.bsTitle = button.dataset.bsTitle.replace("Answer Preview", "One or more answers incorrect");
}
);
}
}
for (const tag_name of ["h6", "h5", "h4", "h3", "h2", "h1"]) {
const headings = body_div.getElementsByTagName(tag_name);
for (heading of headings) {
const new_heading = document.createElement("h6");
new_heading.innerHTML = heading.innerHTML;
cloneAttributes(new_heading, heading);
new_heading.classList.add("webwork-part");
heading.replaceWith(new_heading);
}
}
var textareas = body_div.getElementsByTagName("textarea");
for (var i = 0, max = textareas.length; i < max; i++) {
textareas[i].style.display = "none";
textareas[i].className = "";
}
var textareabuttons = body_div.querySelectorAll(".latexentry-preview");
for (var i = 0, max = textareabuttons.length; i < max; i++) {
textareabuttons[i].remove();
}
adjustSrcHrefs(body_div, ww_domain);
translateHintSol(
ww_id,
body_div,
ww_domain,
ww_container.dataset.hasHint == "true",
ww_container.dataset.hasSolution == "true",
ww_container.dataset.hintLabelText,
ww_container.dataset.solutionLabelText
);
if (runestone_logged_in) {
const answersObject = wwList[ww_id.replace(/-ww-rs$/, "")].answers ? wwList[ww_id.replace(/-ww-rs$/, "")].answers : { "answers": [], "mqAnswers": [] };
const mqAnswers = answersObject.mqAnswers;
for (const mqAnswer in mqAnswers) {
const mqInput = body_div.querySelector("input[id=" + mqAnswer + "]");
if (mqInput && mqInput.value == "") {
mqInput.setAttribute("value", mqAnswers[mqAnswer]);
}
}
const answers = answersObject.answers;
for (const answer in answers) {
const input = body_div.querySelector("input[id=" + answer + "]");
if (input && input.value == "") {
input.setAttribute("value", answers[answer]);
}
}
}
form.appendChild(body_div);
const wwInputs = {
problemSeed: data.inputs_ref.problemSeed,
problemUUID: data.inputs_ref.problemUUID,
psvn: data.inputs_ref.psvn,
courseName: ww_course_id,
courseID: ww_course_id,
user: ww_user_id,
passwd: ww_passwd,
displayMode: "MathJax",
session_key: data.rh_result.session_key,
outputformat: "raw",
language: data.formLanguage,
showSummary: data.showSummary,
disableCookies: "1",
// note ww_container.dataset.hasSolution is a string, possibly 'false' which is true
showSolutions: ww_container.dataset.hasSolution == "true" ? "1" : "0",
showHints: ww_container.dataset.hasHint == "true" ? "1" : "0",
forcePortNumber: data.forcePortNumber
};
if (ww_sourceFilePath) wwInputs.sourceFilePath = ww_sourceFilePath;
else if (ww_problemSource && ww_origin == "webwork2") wwInputs.problemSource = ww_problemSource;
for (const wwInputName of Object.keys(wwInputs)) {
const input = document.createElement("input");
input.type = "hidden";
input.name = wwInputName;
input.value = wwInputs[wwInputName];
form.appendChild(input);
}
let buttonContainer = ww_container.querySelector(".problem-buttons.webwork");
if (!buttonContainer) {
ww_container.querySelector(".problem-buttons").classList.add("hidden-content", "hidden");
buttonContainer = document.createElement("div");
buttonContainer.classList.add("problem-buttons", "webwork");
if (activate_button != null) {
activate_button.after(buttonContainer);
} else {
ww_container.prepend(buttonContainer);
}
const check = document.createElement("button");
check.type = "button";
check.id = ww_id + "-check";
check.style.marginRight = "0.25rem";
check.classList.add("webwork-button");
check.textContent = runestone_logged_in ? localize_submit : localize_check_responses;
check.addEventListener("click", () => handleWW(ww_id, "check"));
buttonContainer.appendChild(check);
if (ww_container.dataset.hasAnswer == "true") {
const correct = document.createElement("button");
correct.classList.add("show-correct", "webwork-button");
correct.type = "button";
correct.style.marginRight = "0.25rem";
correct.textContent = localize_reveal;
correct.addEventListener("click", () => handleWW(ww_id, "reveal"));
buttonContainer.appendChild(correct);
}
const randomize = document.createElement("button");
randomize.type = "button";
randomize.classList.add("webwork-button");
randomize.style.marginRight = "0.25rem";
randomize.textContent = localize_randomize;
randomize.addEventListener("click", () => handleWW(ww_id, "randomize"));
buttonContainer.appendChild(randomize);
const reset = document.createElement("button");
reset.type = "button";
reset.classList.add("webwork-button");
reset.textContent = localize_reset;
reset.addEventListener("click", () => resetWW(ww_id));
buttonContainer.appendChild(reset);
}
if (runestone_logged_in && action == "check") {
$("body").trigger("runestone_ww_check", data);
}
let courseUrlBase = "";
if (runestone_logged_in) {
courseUrlBase = "/ns/books/published/" + eBookConfig.basecourse + "/";
}
let iframeContents = '<!DOCTYPE html><head><script src="' + ww_domain + `/webwork2_files/node_modules/jquery/dist/jquery.min.js"><\/script><script>
window.MathJax = {
tex: { packages: { '[+]': ['noerrors'] } },
loader: { load: ['input/asciimath', '[tex]/noerrors'] },
startup: {
ready() {
const AM = MathJax.InputJax.AsciiMath.AM;
// Modify existing AsciiMath triggers.
AM.symbols[AM.names.indexOf('**')] = {
input: '**',
tag: 'msup',
output: '^',
tex: null,
ttype: AM.TOKEN.INFIX
};
const i = AM.names.indexOf('infty');
AM.names[i] = 'infinity';
AM.symbols[i] = { input: 'infinity', tag: 'mo', output: '\u221E', tex: 'infty', ttype: AM.TOKEN.CONST };
return MathJax.startup.defaultReady();
}
},
options: {
processHtmlClass: "process-math",
renderActions: {
findScript: [
10,
(doc) => {
for (const node of document.querySelectorAll('script[type^="math/tex"]')) {
const math = new doc.options.MathItem(
node.textContent,
doc.inputJax[0],
!!node.type.match(/; *mode=display/)
);
const text = document.createTextNode('');
node.parentNode.replaceChild(text, node);
math.start = { node: text, delim: '', n: 0 };
math.end = { node: text, delim: '', n: 0 };
doc.math.push(math);
}
},
''
]
},
ignoreHtmlClass: 'tex2jax_ignore'
}
};
<\/script><script src="` + ww_domain + `/webwork2_files/node_modules/mathjax/es5/tex-chtml.js" id="MathJax-script" defer><\/script><script src="${courseUrlBase}_static/pretext/js/dist/knowl.js" defer><\/script><link rel="stylesheet" href="` + ww_domain + '/webwork2_files/node_modules/bootstrap/dist/css/bootstrap.min.css"/><script src="' + ww_domain + '/webwork2_files/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js" defer><\/script>';
const extra_css_files = [];
const extra_js_files = [];
if (data.extra_css_files) data.extra_css_files.unshift(...extra_css_files);
else data.extra_css_files = extra_css_files;
for (const cssFile of data.extra_css_files) {
iframeContents += '<link rel="stylesheet" href="' + (cssFile.external !== "1" ? ww_domain : "") + cssFile.file + '"/>';
}
if (data.extra_js_files) data.extra_js_files.unshift(...extra_js_files);
else data.extra_js_files = extra_js_files;
for (const jsFile of data.extra_js_files) {
iframeContents += '<script src="' + (jsFile.external !== "1" ? ww_domain : "") + jsFile.file + '" ' + Object.keys(jsFile.attributes || {}).reduce((ret, key) => {
ret += key + '="' + jsFile.attributes[key] + '" ';
return ret;
}, "") + "><\/script>";
}
iframeContents += `<link rel="stylesheet" href="${courseUrlBase}_static/pretext/css/theme.css"/><script src="` + ww_domain + `/webwork2_files/node_modules/iframe-resizer/js/iframeResizer.contentWindow.min.js"><\/script><style>
html { overflow-y: hidden; }
html body { background:unset; margin: 0; }
body { font-size: initial; line-height: initial; padding:2px; }
.hidden-content { display: none; }
span.nobreak { white-space: nowrap; }
div.PGML img.image-view-elt { max-width:100%; }
.graphtool-answer-container .graphtool-graph { margin: 0; width: 300px; height: 300px; }
.graphtool-answer-container .graphtool-number-line { height: 57px; }
.quill-toolbar { scrollbar-width: thin; overflow-x: hidden; }
</style></head><body><main class="pretext-content problem-content" data-iframe-height="1">` + form.outerHTML + "</main></body></html>";
let iframe2;
if (!action) {
iframe2 = document.createElement("iframe");
iframe2.style.width = "1px";
iframe2.style.minWidth = "100%";
iframe2.classList.add("problem-iframe");
ww_container.querySelector(".problem-contents").classList.add("hidden-content", "hidden");
if (activate_button != null) {
activate_button.after(iframe2);
} else {
ww_container.prepend(iframe2);
}
iFrameResize({ checkOrigin: false, scrolling: "omit", heightCalculationMethod: "taggedElement" }, iframe2);
iframe2.addEventListener("load", () => {
const iframeForm = iframe2.contentDocument.getElementById(ww_id + "-form");
iframeForm.addEventListener("submit", (e) => {
handleWW(ww_id, "check");
e.preventDefault();
});
iframe2.contentDocument.querySelectorAll(".collapse.in").forEach((collapse) => collapse.classList.add("expanded"));
iframe2.contentWindow.jQuery(".collapse").on("shown", function(e) {
if (e.target != this) return;
this.classList.add("expanded");
});
iframe2.contentWindow.jQuery(".collapse").on("hide", function(e) {
if (e.target != this) return;
this.classList.remove("expanded");
});
iframe2.contentWindow.MathJax.startup.promise.then(() => iframe2.contentWindow.MathJax.typesetPromise([".popover", ".popover-content"]));
});
} else {
iframe2 = ww_container.querySelector(".problem-iframe");
}
iframe2.srcdoc = iframeContents;
iframe2.addEventListener("load", () => {
loader.remove();
}, { once: true });
ww_container.focus();
}, "json");
}
function WWshowCorrect(ww_id, answers) {
const ww_container = document.getElementById(ww_id);
const iframe2 = ww_container.querySelector(".problem-iframe");
const body = iframe2.contentDocument.getElementById(ww_id + "-body");
$("body").trigger("runestone_show_correct", {
"ww_id": ww_id,
"answers": answers
});
let inputs = body.querySelectorAll("input:not([type=hidden])");
for (const input of inputs) {
const name = input.name;
const span_id = `${ww_id}-${name}-correct`;
if (input.type == "text" && answers[name] && !iframe2.contentDocument.getElementById(span_id)) {
const input_id = input.id;
const mq_span = iframe2.contentDocument.getElementById(`mq-answer-${input_id}`);
if (mq_span) {
mq_span.style.display = "none";
}
label = iframe2.contentDocument.getElementById(`${span_id}-label`);
if (label) {
label.parentElement.insertBefore(input, label);
label.remove();
}
input.type = "hidden";
const correct_ans_text = iframe2.contentDocument.createElement("div");
correct_ans_text.innerHTML = answers[name].correct_ans;
input.value = correct_ans_text.textContent;
const show_span = iframe2.contentDocument.createElement("span");
show_span.id = span_id;
show_span.appendChild(answers[name].correct_ans_latex_string ? iframe2.contentDocument.createTextNode("\\(" + answers[name].correct_ans_latex_string + "\\)") : iframe2.contentDocument.createTextNode(answers[name].correct_ans));
input.parentElement.insertBefore(show_span, input);
}
if (input.type.toUpperCase() == "RADIO" && answers[name]) {
const correct_ans = answers[name].correct_choice || answers[name].correct_ans;
if (input.value == correct_ans) {
input.checked = true;
} else {
input.checked = false;
}
}
if (input.type.toUpperCase() == "CHECKBOX" && answers[name]) {
const correct_choices = answers[name].correct_choices;
if (correct_choices.includes(input.value)) {
input.checked = true;
} else {
input.checked = false;
}
}
}
const hiddenInputs = body.querySelectorAll("input[type=hidden]");
for (const input of hiddenInputs) {
const name = input.name;
if (!input.nextElementSibling) continue;
const graphtoolContainer = input.nextElementSibling.nextElementSibling;
if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains("graphtool-container")) {
const correct_ans_div = iframe2.contentDocument.createElement("div");
input.parentElement.insertBefore(correct_ans_div, graphtoolContainer);
graphtoolContainer.style.display = "none";
input.value = answers[name].correct_ans;
iframe2.contentWindow.jQuery(correct_ans_div).html(answers[name].correct_ans_latex_string);
const script = iframe2.contentDocument.createElement("script");
script.textContent = correct_ans_div.querySelector("script").textContent.replace('\nwindow.addEventListener("DOMContentLoaded",', "(").replace(/;\n$/, "();");
iframe2.contentDocument.body.appendChild(script);
}
}
let selects = body.querySelectorAll("select:not([type=hidden])");
for (const select of selects) {
const name = select.name;
const span_id = `${ww_id}-${name}-correct`;
if (answers[name] && !iframe2.contentDocument.getElementById(span_id)) {
select.style.display = "none";
select.value = answers[name].correct_ans;
const show_span = iframe2.contentDocument.createElement("span");
show_span.id = span_id;
show_span.appendChild(answers[name].correct_ans_latex_string ? iframe2.contentDocument.createTextNode("\\(" + answers[name].correct_ans_latex_string + "\\)") : iframe2.contentDocument.createTextNode(answers[name].correct_ans));
select.parentElement.insertBefore(show_span, select);
}
}
const mathjaxTypesetScript = iframe2.contentDocument.createElement("script");
mathjaxTypesetScript.textContent = "MathJax.startup.promise.then(() => MathJax.typesetPromise([document.body]));";
iframe2.contentDocument.body.appendChild(mathjaxTypesetScript);
}
function resetWW(ww_id) {
const ww_container = document.getElementById(ww_id);
const activate_button = document.getElementById(ww_id + "-button");
ww_container.dataset.current_seed = ww_container.dataset.seed;
iframe = ww_container.querySelector(".problem-iframe");
iframe.remove();
ww_container.querySelector(".problem-contents").classList.remove("hidden-content", "hidden");
ww_container.querySelector(".problem-buttons.webwork").remove();
ww_container.querySelector(".problem-buttons").classList.remove("hidden-content", "hidden");
if (activate_button != null) {
activate_button.classList.remove("hidden-content", "hidden");
}
;
}
function adjustSrcHrefs(container, ww_domain) {
container.querySelectorAll("[href]").forEach((node) => {
const href = node.attributes.href.value;
if (href !== "#" && !href.match(/^[a-z]+:\/\//i)) node.href = ww_domain + "/" + href;
});
container.querySelectorAll("[data-knowl-url]").forEach((node) => {
const dku = node.dataset.knowlUrl;
if (dku !== "#" && !dku.match(/^[a-z]+:\/\//i)) node.dataset.knowlUrl = ww_domain + dku;
});
container.querySelectorAll("[src]").forEach((node) => {
node.src = new URL(node.attributes.src.value, ww_domain).href;
});
}
function translateHintSol(ww_id, body_div, ww_domain, b_ptx_has_hint, b_ptx_has_solution, hint_label_text, solution_label_text) {
const hintSols = body_div.querySelectorAll(".accordion.hint,.accordion.solution");
if (hintSols.length == 0) {
return;
}
;
for (const hintSol of hintSols) {
const parent = hintSol.parentNode;
solutionlikewrapper = document.createElement("div");
solutionlikewrapper.classList.add("solutions");
parent.insertBefore(solutionlikewrapper, hintSol);
const hintSolType = hintSol.classList.contains("hint") ? "hint" : "solution";
if (hintSolType == "solution" && !b_ptx_has_solution || hintSolType == "hint" && !b_ptx_has_hint)
continue;
const knowlDetails = hintSol.getElementsByTagName("details")[0];
knowlDetails.className = "";
knowlDetails.classList.add(hintSolType, "solution-like", "born-hidden-knowl");
solutionlikewrapper.appendChild(knowlDetails);
const knowlSummary = knowlDetails.getElementsByTagName("summary")[0];
knowlSummary.className = "";
knowlSummary.classList.add("knowl__link");
const summaryLabel = knowlSummary.children[0];
summaryLabel.remove();
const newLabelSpan = document.createElement("span");
newLabelSpan.innerHTML = hintSolType == "hint" ? hint_label_text : solution_label_text;
knowlSummary.appendChild(newLabelSpan);
const newLabelPeriod = document.createElement("span");
newLabelPeriod.innerHTML = ".";
knowlSummary.appendChild(newLabelPeriod);
adjustSrcHrefs(knowlDetails, ww_domain);
hintSol.remove();
const originalDetailsContent = knowlDetails.getElementsByTagName("div")[0];
const newDetailsContent = originalDetailsContent.getElementsByTagName("div")[0];
newDetailsContent.className = "";
newDetailsContent.classList.add(hintSolType, "solution-like", "knowl__content");
knowlDetails.appendChild(newDetailsContent);
originalDetailsContent.remove();
}
}
function cloneAttributes(target, source) {
[...source.attributes].forEach((attr) => {
target.setAttribute(attr.nodeName, attr.nodeValue);
});
}
function webworkSeedHash(string) {
var hash = 0, i, chr;
if (string.length === 0) return hash;
for (i = 0; i < string.length; i++) {
chr = string.charCodeAt(i);
hash = (hash << 5) - hash + chr;
hash |= 0;
}
return Math.abs(hash);
}
;
//# sourceMappingURL=pretext-webwork.js.map
+232
View File
@@ -0,0 +1,232 @@
window.i18next = window.i18next || {
t(key, params = {}) {
for (const param in params) {
key = key.replace(`{{${param}}}`, params[param]);
}
return key;
}
};
function doSearch() {
const terms = document.getElementById("ptx-search-terms").value;
localStorage.setItem("last-search-terms", JSON.stringify({ terms, time: Date.now() }));
let resultArea = document.getElementById("ptx-search-results");
resultArea.innerHTML = "";
const searchTerms = terms.toLowerCase().trim();
let pageResult = [];
if (searchTerms != "") {
pageResult = ptx_lunr_idx.query((q) => {
for (let term of searchTerms.split(" ")) {
q.term(term, { fields: ["title"], boost: 20 });
q.term(term, { wildcard: lunr.Query.wildcard.TRAILING, fields: ["title"], boost: 10 });
q.term(term, { fields: ["body"], boost: 5 });
q.term(term, { wildcard: lunr.Query.wildcard.TRAILING, fields: ["body"] });
}
});
}
snum = 0;
for (let doc of ptx_lunr_docs) {
doc.snum = snum;
snum += 1;
}
const MAX_RESULTS = 100;
let numUnshown = pageResult.length > MAX_RESULTS ? pageResult.length - MAX_RESULTS : 0;
pageResult.slice(0, MAX_RESULTS);
augmentResults(pageResult, ptx_lunr_docs);
pageResult.sort(comparePosition);
addResultToPage(terms, pageResult, ptx_lunr_docs, numUnshown, resultArea);
MathJax.typeset();
}
function findEntry(resultId, db) {
for (const page of db) {
if (page.id === resultId) {
return page;
}
}
return resultId;
}
function augmentResults(result, docs) {
for (let res of result) {
let info = findEntry(res.ref, docs);
res.number = info.number;
res.type = info.type;
res.title = info.title;
res.url = info.url;
res.level = info.level;
res.snum = info.snum;
res.score = parseFloat(res.score);
const LEVEL_WEIGHTS = [3, 2, 1.5];
if (res.level < 2)
res.score *= LEVEL_WEIGHTS[res.level];
res.body = "";
const REVEAL_WINDOW = 30;
let titleMarked = false;
for (const hit in res.matchData.metadata) {
if (res.matchData.metadata[hit].title) {
if (!titleMarked) {
if (!res.matchData.metadata[hit].title.position)
continue;
let positionData = res.matchData.metadata[hit].title.position[0];
const startClipInd = positionData[0];
const endClipInd = positionData[0] + positionData[1];
res.title = res.title.substring(0, endClipInd) + "</span>" + res.title.substring(endClipInd);
res.title = res.title.substring(0, startClipInd) + '<span class="ptx-search-result-clip-highlight">' + res.title.substring(startClipInd);
titleMarked = true;
}
} else if (res.matchData.metadata[hit].body) {
if (!res.matchData.metadata[hit].body.position)
continue;
const bodyContent = info.body;
let positionData = res.matchData.metadata[hit].body.position[0];
const startInd = positionData[0] - REVEAL_WINDOW;
const endInd = positionData[0] + positionData[1] + REVEAL_WINDOW;
const startClipInd = positionData[0];
const endClipInd = positionData[0] + positionData[1];
let resultSnippet = (startInd > 0 ? "..." : "") + bodyContent.substring(startInd, startClipInd);
resultSnippet += '<span class="ptx-search-result-clip-highlight">' + bodyContent.substring(startClipInd, endClipInd) + "</span>";
resultSnippet += bodyContent.substring(endClipInd, endInd) + (endInd < bodyContent.length ? "..." : "") + "<br/>";
res.body += resultSnippet;
}
}
}
}
function rearrangedArray(arry) {
let newarry = [];
let startind = 0;
let numtograb = 0;
let ct = 1;
while (arry.length > 0 && ct < 500) {
++ct;
const locofmax = maxLocation(arry);
let segmentstart = locofmax;
let segmentlength = 1;
while (arry[segmentstart].level == "2") {
--segmentstart;
}
while (segmentstart + segmentlength < arry.length && arry[segmentstart + segmentlength].level == "2") {
++segmentlength;
}
newarry.push(...arry.splice(segmentstart, segmentlength));
}
return newarry;
}
function maxLocation(arry) {
let maxloc = 0;
let maxvalsofar = -1;
for (let index = 0; index < arry.length; ++index) {
if (arry[index].score > maxvalsofar) {
maxloc = index;
maxvalsofar = arry[index].score;
}
}
return maxloc;
}
function comparePosition(a, b) {
if (a.snum < b.snum) {
return -1;
}
if (a.snum > b.snum) {
return 1;
}
return 0;
}
function compareScoreDesc(a, b) {
if (a.score < b.score) {
return 1;
}
if (a.score > b.score) {
return -1;
}
return 0;
}
function addResultToPage(searchterms, result, docs, numUnshown, resultArea) {
let len = result.length;
const searchStatus = document.getElementById("ptx-search-status");
if (len == 0) {
document.getElementById("ptx-search-empty").style.display = "block";
document.getElementById("ptx-search-dialog").style.display = null;
searchStatus.innerHTML = window.i18next.t('No results found for "{{terms}}".', { terms: searchterms });
return;
}
document.getElementById("ptx-search-empty").style.display = "none";
searchStatus.innerHTML = window.i18next.t("{{count}} results found.", { count: len });
let allScores = result.map(function(r) {
return r.score;
});
allScores.sort((a, b) => a - b);
allScores.reverse();
let high = allScores[Math.floor(len * 0.2)];
let med = allScores[Math.floor(len * 0.4)];
let low = allScores[Math.floor(len * 0.75)];
if (ptx_lunr_search_style == "reference") {
result = rearrangedArray(result);
}
let indent = "1";
let currIndent = indent;
let origResult = resultArea;
for (const res of result) {
let link = document.createElement("a");
if (res.score >= high) {
link.classList.add("ptx-search-result-high");
} else if (res.score >= med) {
link.classList.add("ptx-search-result-medium");
} else if (res.score >= low) {
link.classList.add("ptx-search-result-low");
} else {
link.classList.add("ptx-search-result-none");
}
currIndent = res.level;
if (currIndent > indent) {
indent = currIndent;
let ilist = document.createElement("ul");
ilist.classList.add("ptx-search-detailed-result");
resultArea.appendChild(ilist);
resultArea = ilist;
} else if (currIndent < indent) {
resultArea = origResult;
indent = currIndent;
}
link.href = `${res.url}`;
link.innerHTML = `${res.type} ${res.number} ${res.title}`;
let clip = document.createElement("div");
clip.classList.add("ptx-search-result-clip");
clip.innerHTML = `${res.body}`;
let bullet = document.createElement("li");
bullet.classList.add("ptx-search-result-bullet");
bullet.appendChild(link);
bullet.appendChild(clip);
let p = document.createElement("text");
p.classList.add("ptx-search-result-score");
p.innerHTML = ` (${res.score.toFixed(2)})`;
bullet.appendChild(p);
resultArea.appendChild(bullet);
}
const resultsDialog = document.getElementById("ptx-search-dialog");
resultArea.querySelectorAll("a").forEach((link) => {
link.addEventListener("click", (e) => {
resultsDialog.close();
});
});
document.getElementById("ptx-search-dialog").style.display = null;
MathJax.typesetPromise();
}
window.addEventListener("load", function(event) {
const searchDialogElement = document.getElementById("ptx-search-dialog");
const searchButtonElement = document.getElementById("ptx-search-button");
const closeBtn = document.getElementById("ptx-search-close");
const searchDialog = new PTXDialog(searchDialogElement, searchButtonElement, {
closeButton: closeBtn
});
searchButtonElement.addEventListener("click", (e) => {
const lastSearch = localStorage.getItem("last-search-terms");
let searchInput = document.getElementById("ptx-search-terms");
searchInput.value = lastSearch ? JSON.parse(lastSearch)?.terms || "" : "";
searchInput.select();
if (searchInput.value) {
doSearch();
}
});
document.getElementById("ptx-search-terms").addEventListener("input", (e) => {
doSearch();
});
});
//# sourceMappingURL=pretext_search.js.map
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-480
View File
@@ -1,480 +0,0 @@
/*
highlight_css = document.createElement('style');
highlight_css.type = "text/css";
highlight_css.id = "highlight_css";
document.head.appendChild(highlight_css);
var css_for_hl = 'span.hl { background: yellow; }\n';
css_for_hl += '#hlmenu { position: absolute; top: 300px; left: 200px;}\n';
css_for_hl += '#hlmenu { padding: 8px; background: #FFF; }\n';
css_for_hl += '#hlmenu { box-shadow: 8px 10px 5px #888; border: 1px solid #aaa;}\n';
css_for_hl += '#hlmenu .hldelete { background: #fdd; }';
css_for_hl += '#hlmenu .hldelete:hover { background: #fbb; }';
css_for_hl += '#hlmenu .hlcopy { background: #ddf; }';
css_for_hl += '#hlmenu .hlcopy:hover { background: #bbf; }';
css_for_hl += '#hlmenu .dismiss:hover { background: #ff9; }';
css_for_hl += '#hlmenu > div { padding: 4px; font-size: 90%}';
highlight_css.innerHTML = css_for_hl;
*/
hlmenu = document.createElement('div');
hlmenu.id = "hlmenu";
hlmenu.style.display = "none";
hlmenu_contents = '<div class="hlcopy" data-hlid="">copy to clipboard</div>\n';
hlmenu_contents += '<div class="hldelete" data-hlid="">delete highlight</div>\n';
hlmenu_contents += '<div class="dismiss">dismiss menu</div>';
hlmenu.innerHTML = hlmenu_contents;
document.body.appendChild(hlmenu);
var all_highlights = localStorage.getObject("all_highlights");
if (!all_highlights) {
console.log("no highlights on this page",all_highlights);
all_highlights= {};
} else {
console.log("highlights already",all_highlights);
}
console.log("all_highlights.keys()", Object.keys(all_highlights));
console.log("waiting for MathJax");
MathJax.Hub.Register.StartupHook("End",function () {
// need to wait for MathJax, because MathJax changes the number of nodes in a paragraph
console.log("MathJax is done");
display_all_highlights(all_highlights, Object.keys(all_highlights), 0);
});
/*
for (var key in all_highlights) {
var this_key = key;
var these_highlights = all_highlights[key];
console.log("adding highlights to", key);
// await display_highlights_on(key, all_highlights[key], 0)
for (var i=0; i< these_highlights.length; ++i) {
hl = these_highlights[i];
console.log("inserting highlight",i,"which is",hl, "on", key);
// display_one_highlight(key, hl);
//fails because
setTimeout(function() { display_one_highlight(this_key, hl)}, i*2000);
}
}
*/
function index_of_child(child) {
var i = 0;
while( (child = child.previousSibling) != null )
{i++ }
return i
}
function increment_id(list_of_things_with_ids) { // assume ids are of the form xxxxNN with N a digit and x a non-digit
console.log("incrementing id on", list_of_things_with_ids);
if (list_of_things_with_ids.length == 0) {
return 0
}
id_start = list_of_things_with_ids[0]["id"].replace(/^(.*?)[0-9]+$/, "$1");
current_endings = [];
for (var i=0; i < list_of_things_with_ids.length; ++i) {
current_endings.push(list_of_things_with_ids[i]["id"].replace(/^.*?([0-9]+)$/, "$1"));
}
console.log("existing id endings", current_endings);
current_max = Math.max(...current_endings);
return id_start + (parseInt(current_max) + 1)
}
function enclosing_p_or_li(obj) {
console.log("obj.tagName", obj.tagName, "ggg",obj);
if (!obj) {
console.log("problem with previous object");
return null
}
else if (obj.tagName == 'P' || obj.tagName == 'LI') {
return obj
}
return enclosing_p_or_li(obj.parentNode)
}
// If you just loop ofer the highlights to add them,
// you end up with a race condition because each highlight changes
// the structure of the paragraph.
async function display_one_highlight(parent_id, hl) {
var the_parent = document.getElementById(parent_id);
if (!the_parent) {
console.log("this id not on this page:", parent_id);
return
}
console.log("setting", hl, "on", parent_id);
var st_node_ind = hl['start_nn'];
var st_offset = hl['start_offset'];
var end_node_ind = hl['end_nn'];
var end_offset = hl['end_offset'];
console.log("st_node_ind", st_node_ind, "st_offset", st_offset, "end_node_ind", end_node_ind, "end_offset", end_offset);
// other error checks: same parent
if (st_offset < 0 || st_node_ind > the_parent.childNodes.length || end_node_ind > the_parent.childNodes.length || st_offset > the_parent.childNodes[st_node_ind].textContent.length || end_offset < 0 || end_offset > the_parent.childNodes[end_node_ind].textContent.length) {
console.log("highlight data inconsistent with paragraph structure that contains", the_parent.childNodes.length, "nodes");
return
}
let this_range = document.createRange();
console.log("setting this_range.setStart", the_parent.childNodes[st_node_ind], "with offset", st_offset, "out of", the_parent.childNodes[st_node_ind].textContent.length);
this_range.setStart(the_parent.childNodes[st_node_ind], st_offset);
this_range.setEnd(the_parent.childNodes[end_node_ind], end_offset);
var inside_part = document.createElement("span")
inside_part.classList.add("hl");
inside_part.id = hl['id'];
console.log("inside_part", inside_part, "going inside this_range",this_range);
console.log("in the_parent",the_parent);
this_range.surroundContents(inside_part);
return;
}
//var display_highlights_on = function(parent_id, highlights_on, ind) {
async function display_highlights_on(parent_id, highlights_on, ind) {
console.log("display_highlights_on", parent_id, "xxxx", ind, "out of", highlights_on.length);
if (ind < highlights_on.length) {
console.log("about to display one highlight, number", ind, "on", parent_id);
await display_one_highlight(parent_id, highlights_on[ind]);
++ind;
await display_highlights_on(parent_id, highlights_on, ind)
}
return;
}
async function display_all_highlights(every_highlight, hl_p_keys, i) {
console.log("making hl on", hl_p_keys[i]);
if (i < hl_p_keys.length) {
await display_highlights_on(hl_p_keys[i], every_highlight[hl_p_keys[i]], 0);
++i;
await display_all_highlights(every_highlight, hl_p_keys, i)
}
return;
}
var DELAY = 500, clicks = 0, timer = null;
$("p[id], li[id]").on("click", function(e) {
clicks++; //count clicks
if(clicks === 1) {
if (e.target.classList.contains("hl")) {
modifyhighlight(e);
clicks = 0;
} else {
timer = setTimeout(function() {
newhighlight()
clicks = 0; //after action performed, reset counter
}, DELAY);
}
} else if (clicks === 2) {
timer2 = setTimeout(function() {
clearTimeout(timer);
// alert("Double Click");
clicks = 0;
}, DELAY/2);
} else {
clearTimeout(timer); //prevent single-click action
clearTimeout(timer2); //prevent double-click action
// alert("Triple Click"); //perform triple-click action
clicks = 0; //after action performed, reset counter
}
});
function save_highlights() {
hl_data = {"action": "save", "user": uname, "pw": emanu, "bookID": bodyID, "type": "highlights", "hl": JSON.stringify(all_highlights)}
$.ajax({
url: "https://aimath.org/cgi-bin/u/highlights.py",
type: "post",
data: JSON.stringify(hl_data),
dataType: "json",
success: function(data) {
console.log("something", data, "back from highlight");
// alert(data);
},
error: function(errMsg) {
console.log("seems to be an error?",errMsg);
// alert("Error\n" + errMsg);
}
});
console.log("just ajax sent", JSON.stringify(reading_questions_object));
}
function newhighlight() {
var this_selection = window.getSelection();
console.log("UUUUUUUUUUUUUUUUUUUUUUUUUUU");
console.log("selection", this_selection, "as string", this_selection.toString(), "length", this_selection.toString().length);
if(this_selection.toString().length >= 3) {
console.log("this_selection", this_selection);
console.log("this_selection parentNode", this_selection.parentNode);
num_selected_ranges = this_selection.rangeCount;
console.log("this_selection.rangeCount", this_selection.rangeCount);
console.log("this_selection.getRangeAt(0)", this_selection.getRangeAt(0));
console.log("this_selection.getRangeAt(num_selected_ranges)", this_selection.getRangeAt(num_selected_ranges-1));
console.log("this_selection.getRangeAt(0).startContainer", this_selection.getRangeAt(0).startContainer);
console.log("this_selection.getRangeAt(0).endContainer", this_selection.getRangeAt(0).endContainer);
console.log("this_selection.getRangeAt(-1).startContainer", this_selection.getRangeAt(num_selected_ranges-1).startContainer);
console.log("this_selection.getRangeAt(-1).endContainer", this_selection.getRangeAt(num_selected_ranges-1).endContainer);
console.log("start equals end", this_selection.getRangeAt(0).startContainer == this_selection.getRangeAt(0).endContainer);
console.log("this_selection.getRangeAt(0).startContainer.parent", this_selection.getRangeAt(0).startContainer.parentNode);
console.log("this_selection.getRangeAt(0).endContainer.parent", this_selection.getRangeAt(0).endContainer.parentNode);
starting_parent = enclosing_p_or_li(this_selection.getRangeAt(0).startContainer);
starting_parent_id = starting_parent.id;
ending_parent_id = enclosing_p_or_li(this_selection.getRangeAt(num_selected_ranges-1).endContainer).id;
console.log("starting parent", enclosing_p_or_li(this_selection.getRangeAt(0).startContainer));
console.log("starting_parent.childNodes", starting_parent.childNodes);
console.log("starting_parent.childNodes.length", starting_parent.childNodes.length);
console.log("ending parent", enclosing_p_or_li(this_selection.getRangeAt(0).endContainer));
console.log("index of starting node", index_of_child(this_selection.getRangeAt(0).startContainer));
console.log("which is", starting_parent.childNodes[index_of_child(this_selection.getRangeAt(0).startContainer)]);
console.log("index of ending node", index_of_child(this_selection.getRangeAt(num_selected_ranges-1).endContainer));
console.log("which is", starting_parent.childNodes[index_of_child(this_selection.getRangeAt(num_selected_ranges-1).endContainer)]);
console.log("XXXXXXXXXXXXXXXXXXXXXXX");
starting_node = this_selection.getRangeAt(0);
starting_node_container = starting_node.startContainer;
starting_node_number = index_of_child(starting_node_container);
starting_node_offset = starting_node.startOffset;
console.log("starting_node", starting_node, "starting_node_container", starting_node_container);
console.log("starting_parent.childNodes[0]", starting_parent.childNodes[0]);
ending_node = this_selection.getRangeAt(num_selected_ranges-1);
ending_node_container = ending_node.endContainer;
console.log("ending_node", ending_node, "ending_node_container", ending_node_container);
ending_node_number = index_of_child(ending_node_container);
ending_node_offset = ending_node.endOffset;
console.log("selection starts at character number", starting_node.startOffset, "in node number", index_of_child(starting_node), "of node", starting_node.startContainer, "within", starting_node.startContainer.parentNode,"which is", starting_node);
console.log("selection ends at character number", ending_node.endOffset, "in node number", index_of_child(ending_node_container), "of node", ending_node.endContainer, "within", ending_node.endContainer.parentNode, "which is", ending_node);
// let this_range = document.createRange();
// this_range.setStart(starting_parent.childNodes[starting_node_number], starting_node_offset);
// this_range.setEnd(document.getElementById(starting_parent_id).childNodes[ending_node_number], ending_node_offset);
// test_inside_part=document.createElement("span")
// test_inside_part.classList.add("gggg");
// this_range.surroundContents(test_inside_part);
console.log("num_selected_ranges", num_selected_ranges, "starting_parent_id", starting_parent_id, "ending_parent_id", ending_parent_id);
this_selection.empty();
console.log("starting_parent_id", starting_parent_id, "ending_parent_id", ending_parent_id);
if (starting_parent_id != ending_parent_id) {
alert("Highlights must be within\none paragraph or list item.");
return ""
}
if (starting_parent != starting_node_container.parentNode) {
starting_node_number = index_of_child(starting_node_container.parentNode) - 1;
console.log("new starting node",starting_node_number, " which is", starting_parent.childNodes[starting_node_number]);
starting_node_offset = starting_parent.childNodes[starting_node_number].length;
}
if (starting_parent != ending_node_container.parentNode) {
ending_node_number = index_of_child(ending_node_container.parentNode) + 1;
console.log("new edngin node",ending_node_number, " which is", starting_parent.childNodes[ending_node_number]);
ending_node_offset = 0;
}
console.log("starting_parent eq st cont parent", starting_parent == starting_node_container.parentNode, "starting_parent eq end cont parent", starting_parent == ending_node_container.parentNode);
// display_one_highlight(starting_parent_id, starting_node_number, starting_node_offset, ending_node_number, ending_node_offset);
// console.log("this_range", this_range);
this_highlight = {"start_nn": starting_node_number,
"start_offset": starting_node_offset,
"end_nn": ending_node_number,
"end_offset": ending_node_offset};
if (starting_parent_id in all_highlights) {
console.log("the starting_parent_id", starting_parent_id, "is already in all_highlights", all_highlights);
new_id = increment_id(all_highlights[starting_parent_id]);
this_highlight['id'] = new_id;
all_highlights[starting_parent_id].push(this_highlight)
} else {
this_highlight['id'] = starting_parent_id + "-" + "hl" + 1;
all_highlights[starting_parent_id] = [this_highlight]
}
display_one_highlight(starting_parent_id, this_highlight);
localStorage.setObject("all_highlights", all_highlights);
if (uname != "guest" && role=="student") {
console.log("saving highlights");
save_highlights();
}
console.log("all_highlights", all_highlights);
return "";
}
}
function modifyhighlight(e) {
this_hl = e.target;
console.log("clicked hl", this_hl.id);
var x = e.clientX, y = e.clientY;
console.log("x", x, "y", y);
document.getElementById("hlmenu").style.top = (y - 10 + $(window).scrollTop()) + 'px';
document.getElementById("hlmenu").style.left = (x + 20) + 'px';
document.getElementById("hlmenu").style.display = 'block';
document.getElementsByClassName("hlcopy")[0].setAttribute("data-hlid", this_hl.id);
document.getElementsByClassName("hldelete")[0].setAttribute("data-hlid", this_hl.id);
// tooltipSpan.style.left = (x + 20) + 'px';
var parent_id = this_hl.id.replace(/^(.*)-[^\-]*$/, "$1");
var number_of_this_highlight = this_hl.id.slice(-1);
var these_highlights = all_highlights[parent_id];
var this_highlight = these_highlights[number_of_this_highlight-1];
console.log("parent id", parent_id, "nunber",number_of_this_highlight);
var num_child_nodes = this_hl.childNodes.length;
console.log("which has", this_hl.childNodes.length, "child nodes");
for (var i=0; i < num_child_nodes; ++i) {
console.log(i, "node is", this_hl.childNodes[i])
}
console.log("highlights on this item",all_highlights[parent_id], "of which we clicked", these_highlights[number_of_this_highlight - 1],
"number", number_of_this_highlight, "out of", these_highlights.length );
for (var i=0; i<these_highlights.length; ++i) {
console.log("highilght", i, "is", these_highlights[i])
}
console.log("this one starts at", this_highlight["start_nn"], "and ends at", this_highlight["end_nn"]);
console.log("$(this_hl)", $(this_hl));
console.log("$(this_hl).contents", $(this_hl).text());
console.log("this_hl.innerHTML", this_hl.innerHTML);
if (this_hl.childNodes.length == 1) { //highlight contains only text
if (this_hl.previousSibling.nodeType == 3 && this_hl.nextSibling.nodeType == 3) { // prev and next are also text
console.log("this_hl", this_hl, "this_hl.previousSibling", this_hl.previousSibling, "type", this_hl.previousSibling.nodeType);
}
}
}
//from https://techoverflow.net/2018/03/30/copying-strings-to-the-clipboard-using-pure-javascript/
function copyStringToClipboard (str) {
// Create new element
var el = document.createElement('textarea');
// Set value (string to be copied)
el.value = str;
// Set non-editable to avoid focus and move outside of view
el.setAttribute('readonly', '');
el.style = {position: 'absolute', left: '-9999px'};
document.body.appendChild(el);
// Select text inside element
el.select();
// Copy text to clipboard
document.execCommand('copy');
// Remove temporary element
document.body.removeChild(el);
}
$('body').on('click','.hlcopy', function(e){
var hl_to_copy_id = this.getAttribute("data-hlid");
console.log("copying highlight", hl_to_copy_id);
hl_to_copy = document.getElementById(hl_to_copy_id);
the_highlight = hl_to_copy.innerHTML;
copyStringToClipboard(the_highlight);
// console.log("hl_to_copy",hl_to_copy,"the_highlight", the_highlight);
// hl_to_copy.focus();
// document.execCommand('copy');
document.getElementById("hlmenu").style.display = 'none';
});
$('body').on('click','.hldelete', function(e){
var hl_to_del_id = this.getAttribute("data-hlid");
console.log("going to delete", hl_to_del_id);
hl_to_delete = document.getElementById(hl_to_del_id);
var parent_id = hl_to_del_id.replace(/^(.*)-[^\-]*$/, "$1");
var the_parent = document.getElementById(parent_id);
//and then update the stored highlights
//this is complicated, because highlights are described in terms of the structure
//of the paragrpah, which depends on which highlights already xist
//So first we determine the order in which the highlights appear
//(and find out how many nodes are being deleted)
var sibling_nodes = the_parent.childNodes;
var node_counter = {};
var additional_offset, node_increment;
var node_ct = 0;
for (var i=0; i < sibling_nodes.length; ++i) {
if (sibling_nodes[i].id) {
node_counter[sibling_nodes[i].id] = node_ct;
node_ct += 1;
}
if (sibling_nodes[i].id == hl_to_del_id) {
node_increment = sibling_nodes[i].childNodes.length - 1
}
}
console.log("node_increment", node_increment);
console.log("node_counter", node_counter);
console.log("deleting node", node_counter[hl_to_del_id]);
//find out the offset of the node to be deleted
console.log("all_highlights[parent_id]", all_highlights[parent_id]);
for (var i=0; i < all_highlights[parent_id].length; ++i) {
var one_highlight = all_highlights[parent_id][i];
console.log("checking one_highlight", one_highlight);
if (node_counter[one_highlight['id']] == node_counter[hl_to_del_id]) {
additional_offset = one_highlight['end_offset'];
the_hl_to_delete = all_highlights[parent_id][i];
}
}
console.log("the_hl_to_delete", the_hl_to_delete);
console.log("additional_offset", additional_offset);
//then we re-make the list of highlights of this parent
//go through the highlights, adjusting those which we added after the
//lh being deleted, and whcich also occurs later in the text.
//Note: this is only for the case if pure text paragraphs
var new_highlights = [];
var previous_highlight;
var past_this_hl = false;
for (var i=0; i < all_highlights[parent_id].length; ++i) {
var one_highlight = all_highlights[parent_id][i];
console.log("again checking one_highlight", one_highlight);
console.log("x",one_highlight["start_nn"], "y", the_hl_to_delete["end_nn"], "z", the_hl_to_delete["end_nn"] + additional_offset);
if (!past_this_hl) {
if (one_highlight["id"] == hl_to_del_id) {
console.log("found it", one_highlight);
past_this_hl = true
} else {
new_highlights.push(one_highlight)
}
} else { // hl occurs after, but maybe not physically after
console.log('the_hl_to_delete["start_nn"]', the_hl_to_delete["start_nn"], 'one_highlight["start_nn"]', one_highlight["start_nn"]);
if (node_counter[one_highlight["id"]] < node_counter[hl_to_del_id]) {
console.log("case 0");
new_highlights.push(one_highlight)
} else if (node_counter[one_highlight["id"]] == node_counter[hl_to_del_id]) {
console.log("ERROR, we shoudl be past this point!");
continue
} else if (one_highlight["start_nn"] == the_hl_to_delete["start_nn"] + 2){
console.log("offset match with", one_highlight);
previous_highlight = one_highlight;
previous_highlight['start_nn'] += -2 + node_increment;
previous_highlight['end_nn'] += -2 + node_increment;
previous_highlight['start_offset'] += additional_offset;
previous_highlight['end_offset'] += additional_offset;
new_highlights.push(previous_highlight);
} else {
console.log("apparently a far away match", one_highlight);
previous_highlight = one_highlight;
previous_highlight['start_nn'] += -2 + node_increment;
previous_highlight['end_nn'] += -2 + node_increment;
new_highlights.push(previous_highlight);
}
}
}
console.log("the new_highlights", new_highlights, "parent_id", parent_id);
if (new_highlights.length > 0) {
all_highlights[parent_id] = new_highlights
} else {
delete all_highlights[parent_id]
}
localStorage.setObject("all_highlights", all_highlights);
// hide the current highlight
$(hl_to_delete).replaceWith(hl_to_delete.innerHTML);
document.getElementById(parent_id).normalize(); // because a previous step creates adjacent text nodes
document.getElementById("hlmenu").style.display = 'none';
});
$('body').on('click','.dismiss', function(e){
console.log(".dismiss of",this);
the_parent = this.closest("[id]");
the_parent.style.display = 'none';
});
-131
View File
@@ -1,131 +0,0 @@
var icon = {
"warning": "⚠️ ",
"commentary": "&#9749;",
"media": "&#9654;",
"tip": "&#128227;",
"worksheet": "&#128196;",
"assesment": "A<sup>+</sup>",
"slides": "&#128251;",
"outcomes": "&#10003;"
};
var html_words_of = {
"warning": "Common pitfall",
"commentary": "Alert",
"media": "An amusing demo",
"tip": "Tip: discussion point",
"worksheet": "Worksheet:",
"assesment": "Sample exam:",
"slides": "Slides:",
"outcomes": "Learning outcomes"
}
// <br>&nbsp;&nbsp;&nbsp;<a href='word'>Word</a> <a href='word'>PTX</a> <a href='word'>PDF</a></span>",
var type_name = {
"warning": "warning",
"commentary": "commentary",
"media": "media",
"tip": "tip",
"worksheet": "worksheet",
"assesment": "assesment",
"slides": "slides",
"outcomes": "outcomes"
};
console.log("in instructor.js", role, "role", logged_in, "logged_in");
if (role=="instructor") {
console.log(" loading instructor resources");
var instructor_resources = document.querySelectorAll(".instructor");
console.log('instructor_resources.length', instructor_resources.length);
var icons_on_this_page = [];
for (var j=0; j < instructor_resources.length; ++j) {
var instructor_resource = instructor_resources[j];
var instructor_resource_parent_id = instructor_resource.parentNode.id;
console.log(" XXXXXXXXXXXX instructor_resource.parentNode", instructor_resource.parentNode);
console.log("instructor_resource_parent_id", instructor_resource_parent_id);
if(instructor_resource_parent_id) {
} else {
instructor_resource_parent_id = instructor_resource.parentNode.parentNode.id;
}
this_type = instructor_resource.getAttribute("data-resource");
this_icon = icon[this_type];
var id_of_this_icon = "resourceid" + j;
icons_on_this_page.push([type_name[this_type], this_icon, id_of_this_icon]);
console.log("this_type", this_type, "this_icon", this_icon, "html_words_of[this_type]", html_words_of[this_type]);
this_item_with_resource = document.getElementById(instructor_resource_parent_id);
console.log("instructor_resource_parent_id", instructor_resource_parent_id, "this_item_with_resource", this_item_with_resource);
var this_margin_resource = document.createElement('div');
this_margin_resource.setAttribute('class', 'marginresource');
this_margin_resource.setAttribute('id', id_of_this_icon);
this_margin_resource.innerHTML = "<span class='icon " + this_type + "'>" + this_icon + "</span>";
if(!(this_title = instructor_resource.getAttribute("title"))) {
this_title = html_words_of[this_type]
}
if(instructor_resource.hasAttribute("data-content")) {
this_content = instructor_resource.getAttribute("data-content");
this_title = '<a href="" data-knowl="' + this_content + '">' + this_title + '</a>';
}
var links_html = ""
if(instructor_resource.hasAttribute("data-links")) {
var this_links = instructor_resource.getAttribute("data-links");
these_links = this_links.split(";");
links_html = '<span class="resource_links">'
console.log(" OOOOOOOOOOOOOO these_links", these_links, "these_links.length", these_links.length);
if(these_links.length > 0) {
console.log("these_links[0]", these_links[0]);
}
tmpJ = these_links.length;
console.log("tmpJ", tmpJ);
for(var jj=0; jj < these_links.length; ++jj) {
console.log( " UUUUUUUUUUUUUu these_links[jj]", these_links[jj]);
this_type_and_link = these_links[jj].split(",");
if(jj>0) { links_html += ", " }
links_html += '<a href="' + this_type_and_link[1] + '">' + this_type_and_link[0] + '</a>';
}
links_html += 'XX</span>';
console.log("done adding links to title");
}
this_title = '<span class="resource_description">' + this_title
if(links_html) {
this_title += links_html
}
this_title += '</span>';
this_margin_resource.innerHTML += this_title;
// this_item_with_resource.insertBefore(this_margin_resource);
this_item_with_resource.insertBefore(this_margin_resource, this_item_with_resource.firstChild);
console.log("appended to ",this_item_with_resource);
}
icons_on_this_page.sort();
var icon_legend_for_this_page = document.createElement('div');
var prev_icon = "";
var icon_list = '<span class="icongroup">' + "<span class='icon_name'>" + icons_on_this_page[0][0] + ":</span> ";
for (var j=0; j < icons_on_this_page.length; ++j) {
next_icon = icons_on_this_page[j][1];
if(j > 0 && next_icon !== prev_icon) {
icon_list += '</span>' + "<br>";
icon_list += '<span class="icongroup">' + "<span class='icon_name'>" + icons_on_this_page[j][0] + ":</span> ";
icon_list += '<a href="#' + icons_on_this_page[j][2] + '">' + next_icon + '</a>';
} else {
icon_list += '<a href="#' + icons_on_this_page[j][2] + '">' + next_icon + '</a>';
}
prev_icon = next_icon;
}
icon_list += '</span>';
icon_legend_for_this_page.setAttribute('class', 'iconlegend');
icon_legend_for_this_page.innerHTML = icon_list
document.body.appendChild(icon_legend_for_this_page);
} // if logged in as instructor
Vendored Executable → Regular
View File
Executable → Regular
+88 -21
View File
@@ -39,15 +39,44 @@ class SlideRevealer {
// mid animation state tracking
this.animation = null;
this.animationState = SlideRevealer.STATE.INACTIVE;
this.animatedElementInlineStyle = null;
this.triggerElement.addEventListener('click', (e) => this.onClick(e));
}
isBusy() {
return this.animationState !== SlideRevealer.STATE.INACTIVE || this.animatedElementInlineStyle !== null;
}
storeAnimatedElementInlineStyle() {
if (this.animatedElementInlineStyle !== null) return;
this.animatedElementInlineStyle = {
overflow: this.animatedElement.style.overflow,
height: this.animatedElement.style.height,
paddingTop: this.animatedElement.style.paddingTop,
paddingBottom: this.animatedElement.style.paddingBottom
};
}
restoreAnimatedElementInlineStyle() {
if (this.animatedElementInlineStyle === null) return;
this.animatedElement.style.overflow = this.animatedElementInlineStyle.overflow;
this.animatedElement.style.height = this.animatedElementInlineStyle.height;
this.animatedElement.style.paddingTop = this.animatedElementInlineStyle.paddingTop;
this.animatedElement.style.paddingBottom = this.animatedElementInlineStyle.paddingBottom;
this.animatedElementInlineStyle = null;
}
onClick(e) {
// Stop default behavior from the browser
if (e) e.preventDefault();
if (this.isBusy()) return;
// Add an overflow on the <details> to avoid content overflowing
this.storeAnimatedElementInlineStyle();
this.animatedElement.style.overflow = 'hidden';
// Check if the element is being closed or is already closed
@@ -56,29 +85,57 @@ class SlideRevealer {
this.animatedElement.setAttribute("open","");
this.triggerElement.setAttribute("open","");
this.contentElement.style.display = '';
// Trigger the animation to expand or collapse the knowl.
// Delay the MathJax typesetting until the knowl is visible to ensure proper measurements
// are taken, but before the unrolling begins. This helps avoid layout shifts and ensures
// smooth animation with correctly sized content.
MathJax.typesetPromise().then(() => window.requestAnimationFrame(() => this.toggle(true)));
this.contentElement.style.visibility = 'hidden';
let closedHeight = 0;
if (this.animatedElement.contains(this.triggerElement))
closedHeight = this.triggerElement.offsetHeight;
const naturalStyle = window.getComputedStyle(this.animatedElement);
const naturalPaddingTop = naturalStyle.paddingTop;
const naturalPaddingBottom = naturalStyle.paddingBottom;
this.animatedElement.style.height = `${closedHeight}px`;
this.animatedElement.style.paddingTop = '0px';
this.animatedElement.style.paddingBottom = '0px';
// Trigger the animation to expand or collapse the knowl
// We assume content is already rendered and size is accurate.
// If not, there may be a jump at the end of the animation when styles are cleared
const expandingMeasurements = {
fullHeight: this.contentElement === this.animatedElement
? this.contentElement.scrollHeight
: closedHeight + this.contentElement.offsetHeight,
paddingTop: naturalPaddingTop,
paddingBottom: naturalPaddingBottom
};
this.contentElement.style.visibility = '';
this.toggle(true, expandingMeasurements);
} else if (this.animationState === SlideRevealer.STATE.EXPANDING || this.animatedElement.hasAttribute("open")) {
this.toggle(false);
}
}
toggle(expanding) {
toggle(expanding, expandingMeasurements = null) {
let closedHeight = 0;
if (this.animatedElement.contains(this.triggerElement))
closedHeight = this.triggerElement.offsetHeight;
const fullHeight = closedHeight + this.contentElement.offsetHeight;
const startHeight = `${expanding ? closedHeight : fullHeight}px`;
const computedStyle = window.getComputedStyle(this.animatedElement);
const fullHeight = expandingMeasurements?.fullHeight ?? closedHeight + this.contentElement.offsetHeight;
const startHeight = `${expanding ? closedHeight : this.animatedElement.offsetHeight}px`;
const endHeight = `${expanding ? fullHeight : closedHeight}px`;
// Need to animate padding to avoid extra height for xref knowls
const padding = this.animatedElement.offsetHeight - this.animatedElement.clientHeight;
const startPad = `${expanding ? 0 : padding}px`;
const endPad = `${expanding ? padding : 0}px`;
const currentPaddingTop = computedStyle.paddingTop;
const currentPaddingBottom = computedStyle.paddingBottom;
const endPaddingTop = expandingMeasurements?.paddingTop ?? currentPaddingTop;
const endPaddingBottom = expandingMeasurements?.paddingBottom ?? currentPaddingBottom;
const startPadTop = expanding ? '0px' : currentPaddingTop;
const endPadTop = expanding ? endPaddingTop : '0px';
const startPadBottom = expanding ? '0px' : currentPaddingBottom;
const endPadBottom = expanding ? endPaddingBottom : '0px';
// Cancel any existing animation
if (this.animation) {
@@ -92,15 +149,18 @@ class SlideRevealer {
this.animationState = expanding ? SlideRevealer.STATE.EXPANDING : SlideRevealer.STATE.CLOSING;
this.animation = this.animatedElement.animate({
height: [startHeight, endHeight],
paddingTop: [startPad, endPad],
paddingBottom: [startPad, endPad]
paddingTop: [startPadTop, endPadTop],
paddingBottom: [startPadBottom, endPadBottom]
}, {
duration: animDuration,
easing: 'ease'
easing: 'ease-out'
});
this.animation.onfinish = () => { this.onAnimationFinish(expanding); };
this.animation.oncancel = () => { this.animationState = SlideRevealer.STATE.INACTIVE; };
this.animation.oncancel = () => {
this.animationState = SlideRevealer.STATE.INACTIVE;
this.restoreAnimatedElementInlineStyle();
};
}
onAnimationFinish(isOpen) {
@@ -115,9 +175,10 @@ class SlideRevealer {
}
// Clear styles used in animation
this.animatedElement.style.overflow = '';
this.restoreAnimatedElementInlineStyle();
if (!isOpen)
this.contentElement.style.display = 'none';
this.contentElement.style.visibility = '';
if (isOpen) {
let hasCallback = this.contentElement.querySelectorAll("[data-knowl-callback]");
@@ -148,6 +209,7 @@ class LinkKnowl {
constructor(knowlLinkElement) {
this.linkElement = knowlLinkElement;
this.outputElement = null;
this.slideHandler = null;
this.uid = LinkKnowl.xrefCount++;
knowlLinkElement.setAttribute("data-knowl-uid", this.uid);
@@ -262,21 +324,25 @@ class LinkKnowl {
// prevent navigation
event.preventDefault();
if (this.slideHandler?.isBusy()) {
return;
}
if (this.outputElement !== null) {
// output already created, toggle visibility
this.toggle();
} else {
this.createOutputElement();
const slideHandler = new SlideRevealer(this.linkElement, this.outputElement, this.outputElement);
this.slideHandler = new SlideRevealer(this.linkElement, this.outputElement, this.outputElement);
//slideHandler is now responsible for handling clicks to this element
this.linkElement.addEventListener('click', slideHandler);
this.linkElement.addEventListener('click', this.slideHandler);
// Wait up to a half second in hopes of avoiding double content change
// then render to show loading message
let loadingTimeout = setTimeout(() => {
loadingTimeout = null;
slideHandler.onClick(); //fake initial click
this.slideHandler.onClick(); //fake initial click
this.toggle();
}, 500);
@@ -291,7 +357,7 @@ class LinkKnowl {
}
// Now give code that follows .1 seconds to render before making visible
setTimeout(() => {
slideHandler.onClick(); //fake initial click
this.slideHandler.onClick(); //fake initial click
this.toggle();
}, 100);
@@ -316,6 +382,7 @@ class LinkKnowl {
// render any knowls and mathjax in the knowl
addKnowls(this.outputElement);
MathJax.typesetPromise([this.outputElement]);
// try prism highlighting
Prism.highlightAllUnder(this.outputElement);
-195
View File
@@ -1,195 +0,0 @@
(function(b, u, v) {
function x(b, f, e) {
b = (b + "").match(/^(-?[0-9]+)(%)?$/);
if (!b) return !1;
var c = parseInt(b[1], 10);
b[2] && (c *= f / 100);
return 0 > c ? f + c + (e || 0) : c
}
function y(k, f) {
function e() {
function b() {
g = +new Date;
f.apply(e, t);
c && (c = clearTimeout(c))
}
var e = this,
q = +new Date - g,
t = arguments;
c && (c = clearTimeout(c));
q > k ? b() : c = setTimeout(b, k - q)
}
var c, g = 0;
b.guid && (e.guid = f.guid = f.guid || b.guid++);
return e
}
b.Espy = function(k, f, e) {
function c(a, d) {
b.isPlainObject(a) && (d = a, a = null);
b.extend(t.prototype, d);
null !== a && (w = a)
}
function g(a) {
if (a =
q(a)) {
var d = a.$el.offset()[a.settings.horizontal ? "left" : "top"] - p.offset[a.settings.horizontal ? "left" : "top"],
h = a.$el[a.settings.horizontal ? "outerWidth" : "outerHeight"]();
b.extend(a, {
start: d,
elSize: h,
end: d + h
})
}
}
function r(a) {
// console.log("r(a) with a = ", a);
if (a === v) b.each(m, r);
else if (a = q(a)) {
var d = p[a.settings.horizontal ? "width" : "height"],
h = x(a.settings.size, d),
d = p[a.settings.horizontal ? "left" : "top"] + x(a.settings.offset, d, -h),
c = d + h,
h = a.settings.contain ? d <= a.start && c >= a.end ? "inside" : d + h / 2 > a.start + a.elSize / 2 ? a.settings.horizontal ? "left" :
"up" : a.settings.horizontal ? "right" : "down" : d > a.start && d < a.end || c > a.start && c < a.end || d <= a.start && c >= a.start || d <= a.end && c >= a.end ? "inside" : d > a.end ? a.settings.horizontal ? "left" : "up" : a.settings.horizontal ? "right" : "down";
a.state !== h && (a.state = h, "function" === typeof w && w.call(a.el, "inside" === h, h), "function" === typeof a.callback && a.callback.call(a.el, "inside" === h, h))
}
}
function s(a) {
if (m.hasOwnProperty(a)) return a;
if (b.isPlainObject(a) && m.hasOwnProperty(a.id)) return a.id;
a = b(a)[0];
var d = !1;
b.each(m, function(b,
c) {
c.el === a && (d = b)
});
return d
}
// console.log("nothing yet");
function q(a) {
return (a = s(a)) ? m[a] : !1
}
"function" !== typeof f && (e = f, f = 0);
// console.log("k was", k);
var t = function(a) {
b.extend(this, a)
},
u = function(a, d, c, e) {
this.id = a;
this.el = d;
this.$el = b(d);
this.callback = c;
this.settings = new t(e);
this.configure = function(a, d) {
b.isPlainObject(a) && (d = a, a = null);
b.extend(this.settings, d);
null !== a && (this.callback = a)
}
},
n = this,
l = b(k);
// console.log("l", l, "l==window", l[0]==window);
k = b.fn.espy.defaults;
// console.log("k is", k);
var w, m = {},
z = 0;
// because window.offset() is not defined in jQuery 3 (why?!?!?!),
// we have to treat that as a special case (DF 1/28/19)
if (l[0]==window) { offSET = { top: 0, left: 0 } }
else { offSET = l.offset() }
var p = {
top: l.scrollTop(),
left: l.scrollLeft(),
width: l.innerWidth(),
height: l.innerHeight(),
offset: offSET
/*
offset: l.offset() || {
top: 0,
left: 0
}
*/
};
// console.log("p", p);
c(f, b.extend({}, k, e));
n.add = function(a, d, c) {
b.isPlainObject(d) && (c = d, d = 0);
b(a).each(function(a, b) {
var e = s(b) || "s" + z++;
m[e] = new u(e, b, d, c);
g(e);
r(e)
})
};
n.configure = function(a, d, e) {
"function" === typeof a ? (d = a, a = null, b.isPlainObject(d) && (e = d, d = null)) : b.isPlainObject(a) ? (e = a, d = a = null) : b.isPlainObject(d) && (e = d, d = null);
null === a ? (c(d, e), b.each(m, function(a, b) {
g(b)
})) : b(a).each(function(a, b) {
var c = q(b);
c && (c.configure(d, e), g(b))
})
};
n.reload = function(a) {
a === v ? b.each(m, function() {
g(this.id)
}) :
b(a).each(function(a, b) {
var c = s(b);
c && (g(c), r(c))
})
};
n.remove = function(a) {
b(a).each(function(a, b) {
var c = s(b);
c && delete m[c]
})
};
n.destroy = function() {
l.off(".espy");
m = {};
n = v
};
n.resize = function() {
b.each(m, function() {
this.reloadOnResize && g(this)
});
p.width = l.innerWidth();
p.height = l.innerHeight();
r()
};
l.on("scroll.espy", y(k.delay, function() {
p.top = l.scrollTop();
p.left = l.scrollLeft();
r()
}));
l.on("resize.espy", y(k.delay, function() {
n.resize()
}))
};
b.fn.espy = function(k, f) {
var e, c;
e = f && f.context || u;
var g = b.data(e,
"espy") || b.data(e, "espy", new b.Espy(e));
"string" !== typeof k ? g.add(this, k, f) : (e = k, c = Array.prototype.slice.call(arguments), c[0] = this, "function" === typeof g[e] && g[e].apply(g, c));
return this
};
b.fn.espy.defaults = {
delay: 100,
context: window,
horizontal: 0,
offset: 0,
size: "100%",
contain: 0,
reloadOnResize: !0
}
})(jQuery, window);
File diff suppressed because one or more lines are too long
-161
View File
@@ -1,161 +0,0 @@
// Sticky Plugin v1.0.0 for jQuery
// =============
// Author: Anthony Garand
// Improvements by German M. Bravo (Kronuz) and Ruud Kamphuis (ruudk)
// Improvements by Leonardo C. Daronco (daronco)
// Created: 2/14/2011
// Date: 2/12/2012
// Website: http://labs.anthonygarand.com/sticky
// Description: Makes an element on the page stick on the screen as you scroll
// It will only set the 'top' and 'position' of your element, you
// might need to adjust the width in some cases.
(function($) {
var defaults = {
topSpacing: 0,
bottomSpacing: 0,
className: 'is-sticky',
wrapperClassName: 'sticky-wrapper',
center: false,
getWidthFrom: ''
},
$window = $(window),
$document = $(document),
sticked = [],
windowHeight = $window.height(),
scroller = function() {
var scrollTop = $window.scrollTop(),
documentHeight = $document.height(),
dwh = documentHeight - windowHeight,
extra = (scrollTop > dwh) ? dwh - scrollTop : 0;
for (var i = 0; i < sticked.length; i++) {
var s = sticked[i],
elementTop = s.stickyWrapper.offset().top,
etse = elementTop - s.topSpacing - extra;
if (scrollTop <= etse) {
if (s.currentTop !== null) {
s.stickyElement
.css('position', '')
.css('top', '');
s.stickyElement.parent().removeClass(s.className);
s.currentTop = null;
}
}
else {
var newTop = documentHeight - s.stickyElement.outerHeight()
- s.topSpacing - s.bottomSpacing - scrollTop - extra;
if (newTop < 0) {
newTop = newTop + s.topSpacing;
} else {
newTop = s.topSpacing;
}
if (s.currentTop != newTop) {
s.stickyElement
.css('position', 'fixed')
.css('top', newTop);
if (typeof s.getWidthFrom !== 'undefined') {
s.stickyElement.css('width', $(s.getWidthFrom).width());
}
s.stickyElement.parent().addClass(s.className);
s.currentTop = newTop;
}
}
}
},
resizer = function() {
windowHeight = $window.height();
},
methods = {
init: function(options) {
var o = $.extend({}, defaults, options);
return this.each(function() {
var stickyElement = $(this);
var stickyId = stickyElement.attr('id');
var wrapperId = stickyId ? stickyId + '-' + defaults.wrapperClassName : defaults.wrapperClassName
var wrapper = $('<div></div>')
.attr('id', stickyId + '-sticky-wrapper')
.addClass(o.wrapperClassName);
stickyElement.wrapAll(wrapper);
if (o.center) {
stickyElement.parent().css({width:stickyElement.outerWidth(),marginLeft:"auto",marginRight:"auto"});
}
if (stickyElement.css("float") == "right") {
stickyElement.css({"float":"none"}).parent().css({"float":"right"});
}
var stickyWrapper = stickyElement.parent();
stickyWrapper.css('height', stickyElement.outerHeight());
sticked.push({
topSpacing: o.topSpacing,
bottomSpacing: o.bottomSpacing,
stickyElement: stickyElement,
currentTop: null,
stickyWrapper: stickyWrapper,
className: o.className,
getWidthFrom: o.getWidthFrom
});
});
},
update: scroller,
unstick: function(options) {
return this.each(function() {
var unstickyElement = $(this);
var removeIdx = -1;
for (var i = 0; i < sticked.length; i++)
{
if (sticked[i].stickyElement.get(0) == unstickyElement.get(0))
{
removeIdx = i;
}
}
if(removeIdx != -1)
{
sticked.splice(removeIdx,1);
unstickyElement.unwrap();
unstickyElement.removeAttr('style');
}
});
}
};
// should be more efficient than using $window.scroll(scroller) and $window.resize(resizer):
if (window.addEventListener) {
window.addEventListener('scroll', scroller, false);
window.addEventListener('resize', resizer, false);
} else if (window.attachEvent) {
window.attachEvent('onscroll', scroller);
window.attachEvent('onresize', resizer);
}
$.fn.sticky = function(method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || !method ) {
return methods.init.apply( this, arguments );
} else {
$.error('Method ' + method + ' does not exist on jQuery.sticky');
}
};
$.fn.unstick = function(method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || !method ) {
return methods.unstick.apply( this, arguments );
} else {
$.error('Method ' + method + ' does not exist on jQuery.sticky');
}
};
$(function() {
setTimeout(scroller, 0);
});
})(jQuery);
-338
View File
@@ -1,338 +0,0 @@
// Code controlling behavior of xref knowls and born hidden knowls
// Assumes this file is loaded as part of initial page
window.addEventListener("load", (event) => {
addKnowls(document);
});
function addKnowls(target) {
const xrefs = target.querySelectorAll("[data-knowl]");
for (const xref of xrefs) {
LinkKnowl.initializeXrefKnowl(xref);
}
const bornHiddens = target.querySelectorAll(".born-hidden-knowl");
for (const bhk of bornHiddens) {
const summary = bhk.querySelector(":scope > summary");
const contents = bhk.querySelector(":scope > summary + *");
new SlideRevealer(summary, contents, bhk);
}
}
// Used to animate both types of knowls
class SlideRevealer {
static STATE = Object.freeze({
INACTIVE: 0,
CLOSING: 1,
EXPANDING: 2
});
// triggerElement is the element clicked to open/close
// contentElement is the element that will hide/reveal
// animatedElement is the element that will grow/shrink as contentElement is modified
// may be the same as contentElement or a parent of it
constructor(triggerElement, contentElement, animatedElement) {
this.triggerElement = triggerElement;
this.contentElement = contentElement;
this.animatedElement = animatedElement;
// mid animation state tracking
this.animation = null;
this.animationState = SlideRevealer.STATE.INACTIVE;
this.triggerElement.addEventListener('click', (e) => this.onClick(e));
}
onClick(e) {
// Stop default behavior from the browser
if (e) e.preventDefault();
// Add an overflow on the <details> to avoid content overflowing
this.animatedElement.style.overflow = 'hidden';
// Check if the element is being closed or is already closed
if (this.animationState === SlideRevealer.STATE.CLOSING || !this.animatedElement.hasAttribute("open")) {
// Force the [open] attributes - allow for similar targetting of xref and born-hidden knowls
this.animatedElement.setAttribute("open","");
this.triggerElement.setAttribute("open","");
this.contentElement.style.display = '';
// Trigger the animation to expand or collapse the knowl.
// Delay the MathJax typesetting until the knowl is visible to ensure proper measurements
// are taken, but before the unrolling begins. This helps avoid layout shifts and ensures
// smooth animation with correctly sized content.
MathJax.typesetPromise().then(() => window.requestAnimationFrame(() => this.toggle(true)));
} else if (this.animationState === SlideRevealer.STATE.EXPANDING || this.animatedElement.hasAttribute("open")) {
this.toggle(false);
}
}
toggle(expanding) {
let closedHeight = 0;
if (this.animatedElement.contains(this.triggerElement))
closedHeight = this.triggerElement.offsetHeight;
const fullHeight = closedHeight + this.contentElement.offsetHeight;
const startHeight = `${expanding ? closedHeight : fullHeight}px`;
const endHeight = `${expanding ? fullHeight : closedHeight}px`;
// Need to animate padding to avoid extra height for xref knowls
const padding = this.animatedElement.offsetHeight - this.animatedElement.clientHeight;
const startPad = `${expanding ? 0 : padding}px`;
const endPad = `${expanding ? padding : 0}px`;
// Cancel any existing animation
if (this.animation) {
this.animation.cancel();
}
// Animate ~400 pixels per second with max of 0.75 second and min of 0.25
const animDuration = Math.max( Math.min( (Math.abs(closedHeight - fullHeight) / 400 * 1000), 750), 250);
// Start animation
this.animationState = expanding ? SlideRevealer.STATE.EXPANDING : SlideRevealer.STATE.CLOSING;
this.animation = this.animatedElement.animate({
height: [startHeight, endHeight],
paddingTop: [startPad, endPad],
paddingBottom: [startPad, endPad]
}, {
duration: animDuration,
easing: 'ease'
});
this.animation.onfinish = () => { this.onAnimationFinish(expanding); };
this.animation.oncancel = () => { this.animationState = SlideRevealer.STATE.INACTIVE; };
}
onAnimationFinish(isOpen) {
// Clear animation state
this.animation = null;
this.animationState = SlideRevealer.STATE.INACTIVE;
// Make sure animated element has open (needed for details)
if(!isOpen) {
this.animatedElement.removeAttribute("open");
this.triggerElement.removeAttribute("open");
}
// Clear styles used in animation
this.animatedElement.style.overflow = '';
if (!isOpen)
this.contentElement.style.display = 'none';
if (isOpen) {
let hasCallback = this.contentElement.querySelectorAll("[data-knowl-callback]");
hasCallback.forEach((el) => {
window[el.getAttribute("data-knowl-callback")](el, open);
});
}
}
}
// A LinkKnowl manages a single link based knowl
class LinkKnowl {
// Used to uniquely identify XrefKnowls
static xrefCount = 0;
// Factory to create an XrefKnowl from a knowl link
// Will avoid duplicate initialization
// This should be used by outside code to create XrefKnowls
static initializeXrefKnowl(knowlLinkElement) {
if (knowlLinkElement.getAttribute("data-knowl-uid") === null) {
return new LinkKnowl(knowlLinkElement);
}
}
// "Private" constructor - should only be called by initializeXrefKnowl
constructor(knowlLinkElement) {
this.linkElement = knowlLinkElement;
this.outputElement = null;
this.uid = LinkKnowl.xrefCount++;
knowlLinkElement.setAttribute("data-knowl-uid", this.uid);
// Xref's behavior is that of a button
knowlLinkElement.setAttribute("role", "button");
// Stash a copy of the original title for use in aria-label
// If no title, use textContent
knowlLinkElement.setAttribute("data-base-title", knowlLinkElement.getAttribute("title") || this.linkElement.textContent);
knowlLinkElement.classList.add("knowl__link");
this.updateLabels(false);
// Bind required to force "this" of event handler to be this object
knowlLinkElement.addEventListener("click", this.handleLinkClick.bind(this));
}
// Set aria-label and title based on visibility of knowl
updateLabels(isVisible) {
const verb = isVisible
? this.linkElement.getAttribute("data-close-label") || "Close"
: this.linkElement.getAttribute("data-reveal-label") || "Reveal";
const targetDescript = this.linkElement.getAttribute("data-base-title");
const helpText = verb + " " + targetDescript;
this.linkElement.setAttribute("aria-label", helpText);
this.linkElement.setAttribute("title", helpText);
}
// Toggle the state of the knowl link and output elements
// Assumes output is already created
toggle() {
this.linkElement.classList.toggle("active");
const isActive = this.linkElement.classList.contains("active");
this.updateLabels(isActive);
// Scroll to reveal if needed
if (isActive) {
const h = this.outputElement.getBoundingClientRect().height;
if (h > window.innerHeight) {
// knowl is taller than window, scroll to top of knowl
this.outputElement.scrollIntoView(true);
} else {
// reveal full knowl
if (this.outputElement.getBoundingClientRect().bottom > window.innerHeight)
this.outputElement.scrollIntoView(false);
}
}
}
// Returns element the knowl output should be inserted after
findOutputLocation() {
const invalidParents = "table, mjx-container, div.tabular-box, .runestone > .parsons";
// Start with the link's parent, move up as long as there are invalid parents
let el = this.linkElement.parentElement;
let problemAncestor = el.closest(invalidParents);
while (problemAncestor && problemAncestor !== el) {
el = problemAncestor;
problemAncestor = el.closest(invalidParents);
}
return el;
}
// Create the knowl output element
createOutputElement() {
const outputId = "knowl-uid-" + this.uid;
const outputContentsId = "knowl-output-" + this.uid;
const linkTarget = this.linkElement.getAttribute("data-knowl");
const placeholderText = `<div class='knowl__content' style='display:none;' id='${outputId}' aria-live='polite' id='${outputContentsId}'>`
+ `Loading '${linkTarget}'`
+ `</div>`;
const temp = document.createElement("template");
temp.innerHTML = placeholderText;
this.outputElement = temp.content.children[0];
const insertLoc = this.findOutputLocation(this.linkElement);
insertLoc.after(this.outputElement);
}
// Get content for knowl as dom element. Returns promise that resolves to knowl content
async getContent() {
const contentURL = this.linkElement.getAttribute("data-knowl");
const knowlContent = await fetch(contentURL)
.then((response) => response.text())
.then((data) => {
// knowls are full HTML pages, need to just extract body
let knowlDoc = (new DOMParser()).parseFromString(data, "text/html");
let tempContainer = knowlDoc.body;
// grab any scripts from head of knowl doc and add them to the output
let scripts = knowlDoc.querySelectorAll("head script");
tempContainer.append(...scripts);
return tempContainer;
})
.catch((error) => {
const destination = this.linkElement.getAttribute("href");
const text = this.linkElement.textContent;
const err_message = `<div class='knowl-output__error'>`
+ `<div class='para'>Error fetching content. (<em>${error}</em>)</div>`
+ `<div class='para'><a href='${destination}'>Navigate to ${text}</a> instead.</div>`
+ `<div class='para'>If you are viewing this book from your local filesystem, this is expected behavior. To view the book with all features, you must serve the book from a web server. See the <a href="https://pretextbook.org/doc/guide/html/author-faq.html#how-do-i-view-my-book-locally">PreTeXt FAQ</a> for more information.</div>`
+ `</div>`;
return err_message;
});
return knowlContent;
}
// Handle a click on the knowl link
handleLinkClick(event) {
// prevent navigation
event.preventDefault();
if (this.outputElement !== null) {
// output already created, toggle visibility
this.toggle();
} else {
this.createOutputElement();
const slideHandler = new SlideRevealer(this.linkElement, this.outputElement, this.outputElement);
//slideHandler is now responsible for handling clicks to this element
this.linkElement.addEventListener('click', slideHandler);
// Wait up to a half second in hopes of avoiding double content change
// then render to show loading message
let loadingTimeout = setTimeout(() => {
loadingTimeout = null;
slideHandler.onClick(); //fake initial click
this.toggle();
}, 500);
const content = this.getContent();
// Content is a promise at this point, insert when resolved
content
.then((tempContainer) => {
// if timeout still active, cancel it and render
if (loadingTimeout !== null) {
clearTimeout(loadingTimeout);
}
// Now give code that follows .1 seconds to render before making visible
setTimeout(() => {
slideHandler.onClick(); //fake initial click
this.toggle();
}, 100);
// check embedded runestone interactives by loading content into a temp container
// we want to not render any that already are on page. Dupe IDs probably bad
const runestoneElements = tempContainer.querySelectorAll(".ptx-runestone-container");
[...runestoneElements].forEach((e) => {
const rsId = e.querySelector("[data-component]")?.id;
const onPage = document.getElementById(rsId);
if (onPage) {
e.innerHTML = `<div class="para">The interactive that belongs here is already on the page and cannot appear multiple times. <a href="#${rsId}">Scroll to interactive.</a>`;
} else {
// let runestone start rendering it
window.runestoneComponents.renderOneComponent(e);
}
});
// now move all contents to the real output element
const children = [...tempContainer.children];
this.outputElement.innerHTML = "";
this.outputElement.append(...children);
// render any knowls and mathjax in the knowl
addKnowls(this.outputElement);
// try prism highlighting
Prism.highlightAllUnder(this.outputElement);
// force any scripts (e.g. sagecell) to execute by evaling them
[...this.outputElement.getElementsByTagName("script")].forEach((s) => {
if (
s.getAttribute("type") === null ||
s.getAttribute("type") === "text/javascript"
) {
eval(s.innerHTML);
}
});
})
.catch((data) => {
console.log("Error fetching knowl content: " + data);
});
}
}
}
-109
View File
@@ -1,109 +0,0 @@
/****************************************************
*
* mathjaxknowl.js
*
* Implements \knowl{url}{math} macro for MathJax. Knowls are
* described at
*
* http://www.aimath.org/knowlepedia/
*
* Be sure to change the loadComplete() address to the URL
* of the location of this file on your server.
*
* You can load it via the config=file parameter on the script
* tag that loads MathJax.js, or by including it in the extensions
* array in your configuration.
*
* Based on an approach developed by Tom Leathrum. See
*
* http://groups.google.com/group/mathjax-users/browse_thread/thread/d8a8d081b8e63242
*
* for details.
*/
MathJax.Extension.Knowl = {
version: "1.0",
//
// Reveal or hide a MathJax knowl
//
Show: function (url,id) {
var oid = "MathJax-knowl-output-"+id,
uid = "MathJax-kuid-"+id;
if ($("#"+oid).length) {
$("#"+uid).slideToggle("fast");
} else {
var the_content = "<div class='knowl-output' id='"+uid+"'>" +
"<div class='knowl'>" +
"<div class='knowl-content' id='"+oid+"'>" +
"loading '"+url+"'" +
"</div>" +
"</div>" +
"</div>";
var the_parent = $("#MathJax-knowl-"+id).closest("p, article, .displaymath");
if(the_parent.length == 0) {
the_parent = $("#MathJax-knowl-"+id).closest(".MJXc-display").next();
if(the_parent.length == 0) {
the_parent = $("#MathJax-knowl-"+id).parent().parent().parent().parent();
}
}
the_parent.after(the_content);
$("#"+oid).load(url,function () {
MathJax.Hub.Queue(["Typeset",MathJax.Hub,uid]);
});
$("#"+uid).slideDown("slow");
}
},
//
// Get a unique ID for the knowl
//
id: 0,
GetID: function () {return this.id++}
};
MathJax.Callback.Queue(
MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
var TEX = MathJax.InputJax.TeX,
TEXDEF = TEX.Definitions,
KNOWL = MathJax.Extension.Knowl;
TEXDEF.macros.knowl = "Knowl";
TEX.Parse.Augment({
//
// Implements \knowl{url}{math}
//
Knowl: function (name) {
var url = this.GetArgument(name), math = this.ParseArg(name);
if (math.inferred && math.data.length == 1)
{math = math.data[0]} else {delete math.inferred}
var id = KNOWL.GetID();
this.Push(math.With({
"class": "MathJax_knowl",
href: "javascript:MathJax.Extension.Knowl.Show('"+url+"','"+id+"')",
id: "MathJax-knowl-"+id,
// border and padding will only work properly if given explicitly on the element
style: "color:blue; border-bottom: 1px dotted #00A; padding-bottom: 1px"
}));
}
});
}),
MathJax.Hub.Register.StartupHook("onLoad",function () {
MathJax.Ajax.Styles({
".MathJax_knowl:hover": {"background-color": "#DDF"}
});
}),
["Post",MathJax.Hub.Startup.signal,"TeX knowl ready"]
);
MathJax.Ajax.loadComplete("http://pretextbook.org/js/lib/mathjaxknowl.js");
MathJax.Ajax.loadComplete("https://pretextbook.org/js/lib/mathjaxknowl.js");
-48
View File
@@ -1,48 +0,0 @@
const { Configuration } = MathJax._.input.tex.Configuration;
const { CommandMap } = MathJax._.input.tex.SymbolMap;
const NodeUtil = MathJax._.input.tex.NodeUtil.default;
var GetArgumentMML = function (parser, name) {
var arg = parser.ParseArg(name);
if (!NodeUtil.isInferred(arg)) {
return arg;
}
var children = NodeUtil.getChildren(arg);
if (children.length === 1) {
return children[0];
}
var mrow = parser.create("node", "mrow");
NodeUtil.copyChildren(arg, mrow);
NodeUtil.copyAttributes(arg, mrow);
return mrow;
};
let mathjaxKnowl = {};
/**
* Implements \knowl{url}{math}
* @param {TexParser} parser The calling parser.
* @param {string} name The TeX string
*/
mathjaxKnowl.Knowl = function (parser, name) {
var url = parser.GetArgument(name);
var arg = GetArgumentMML(parser, name);
var mrow = parser.create("node", "mrow", [arg], {
tabindex: '0',
"data-knowl": url
});
parser.Push(mrow);
};
new CommandMap(
"knowl",
{
knowl: ["Knowl"]
},
mathjaxKnowl
);
const configuration = Configuration.create("knowl", {
handler: {
macro: ["knowl"]
}
});
-322
View File
@@ -1,322 +0,0 @@
console.log("enabling login");
function createCookie(name,value,days) {
if (days) {
var date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
var expires = "; expires="+date.toGMTString();
}
else var expires = "";
/* document.cookie = name+"="+value+expires+"; path=/; domain=aimath.org";
*/
document.cookie = name + "=" + value + expires + "; path=/; " + "SameSite=None; Secure";
console.log("created cookie " + name);
}
function readCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1,c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
}
return null;
}
function eraseCookie(name) {
createCookie(name,"",-1);
}
/* next two also in edit.js. Need to clean up */
Storage.prototype.setObject = function(key, value) {
// this.setItem(key, JSON.stringify(value));
this.setItem(key, JSON.stringify(value, function(key, val) {
// console.log("key", key, "value", value, "val", val);
return val.toFixed ? Number(val.toFixed(3)) : val;
}));
}
Storage.prototype.getObject = function(key) {
var value = this.getItem(key);
return value && JSON.parse(value);
}
function hash_of_string(str) {
str = str.toString();
var the_len = str.length;
the_len = Math.min(12,the_len);
var the_hash = 123456;
var hash_lis = [510149, 120151, 230157, 411063, 320167, 631973, 410179, 321081, 231091, 111093, 121097, 230199];
for (var j=0; j < the_len; ++ j) {
var this_char = str.charCodeAt(j);
this_char = parseInt(this_char);
var this_add = this_char * hash_lis[j];
the_hash += this_add;
the_hash = the_hash % 1000000;
}
return the_hash.toString()
}
function login_form(mode="login") {
var the_form = "";
/* the_form += '<div id="theloginform" class="modal login">';
*/
if (mode == 'logout') {
the_form += '<form name="logoutform" class="modal-content animate" onSubmit="return removeLogin();" action="">';
the_form += '<div class="container">\n';
the_form += '<button type="submit">Yes, really logout</button>';
the_form += '<div id="dontlogout" class=dontlogout">Stay logged in</div>'
the_form += '</div>\n';
the_form += '</form>\n';
}
else{
the_form += '<form name="loginform" class="modal-content animate" onSubmit="return validateLogin();" action="">';
if ((typeof guest_access !== 'undefined') && guest_access) {
the_form += '<p class="instructions" >You can log in as "guest" with password "guest" for 3 hours of access.</p>'; }
the_form += '<div class="container">\n';
the_form += '<label><b>Id</b></label>\n<input type="text" placeholder="Enter Username" name="uname" required>';
the_form += '<label><b>Password</b></label>\n<input type="password" placeholder="Enter Password" name="psw" required>';
the_form += '<button type="submit">Login</button>';
the_form += '</div>\n';
the_form += '<div class="container" style="background-color:#f1f1f1">';
the_form += '<span class="psw">Login trouble? Email <a href="mailto:help@aimath.org">help@aimath.org</a></span>';
the_form += '</div>\n';
the_form += '</form>\n';
}
theform = document.createElement('div');
theform.id = "the" + mode + "form";
theform.className = "modal login";
document.body.appendChild(theform);
theform.innerHTML = the_form;
}
function survey_form(surveylink) {
var the_form = "";
/* the_form += '<div id="theloginform" class="modal login">';
*/
the_form += '<div class="modal-content">';
the_form += '<p class="instructions">Can you take a brief survey<br>about your use of this book?</p>';
the_form += '<button class="surveyresponse" id="takesurvey">Yes, I can take the survey<br>(opens new window)</button>';
the_form += '<button class="surveyresponse" id="remindlater">Not now. Please remind me later.</button>';
the_form += '</div>\n';
theform = document.createElement('div');
theform.id = "the" + "survey" + "form";
theform.className = "modal survey";
document.body.appendChild(theform);
theform.innerHTML = the_form;
}
function loadScript(script) {
if (typeof js_version === 'undefined') { js_version = '0.12' }
var newscript = document.createElement('script');
newscript.type = 'text/javascript';
newscript.async = true;
newscript.src = 'https://pretextbook.org/js/' + js_version + '/' + script + '.js';
var allscripts = document.getElementsByTagName('script');
var s = allscripts[allscripts.length - 1];
console.log('s',s);
console.log("adding a script", newscript);
s.parentNode.insertBefore(newscript, s.nextSibling);
}
function removeLogin() {
eraseCookie("ut_cookie");
}
var uname = "";
var emanu = "";
function check_role() {
console.log("check_role for", uname, "uname");
if(uname=="instructor") { return "instructor" }
var_role_data = {"action": "check", "user": uname, "pw": emanu, "type": "instructor", "instId": uname}
var role_key = "";
$.ajax({
url: "https://aimath.org/cgi-bin/u/highlights.py",
type: "post",
data: JSON.stringify(var_role_data),
dataType: "json",
async: false,
success: function(data) {
console.log("something", data, "back from highlight");
role_key = data
},
error: function(errMsg) {
console.log("seems to be an error?",errMsg);
// alert("Error X2\n" + errMsg);
}
});
console.log("done checking role", role_key);
return role_key
}
function validateLogin() {
var logged_in = false;
var un = document.loginform.uname.value;
// un = un.toLowerCase();
uname = un;
var pw = document.loginform.psw.value;
emanu = pw;
console.log("xuname", uname, "yemanu", emanu);
var guest_name = "guest";
var the_password_guest = "guest";
var editor_name = "editor";
var the_password_editor = "editor";
var instructor_name = "instructor";
var the_password_instructor = "instructor";
var the_un_enc = hash_of_string(un);
console.log("un", un, "the_un_enc", "y"+the_un_enc+"y", "pw", "x"+pw+"x", "pw == the_un_enc", pw == the_un_enc);
var the_url_enc = hash_of_string(window.location.hostname);
console.log('window.location.hostname ' + window.location.hostname);
if ((typeof guest_access !== 'undefined') && guest_access && ( ((un == guest_name) && (pw == the_password_guest)) || ((un == editor_name) && (pw == the_password_editor))) ) {
console.log("setting the guest ut_cookie");
createCookie('ut_cookie',un,0.25);
logged_in = true;
console.log("logged in as guest", logged_in);
}
else if ((typeof guest_access !== 'undefined') && guest_access && (un == instructor_name) && (pw == the_password_instructor)) {
console.log("setting the instructor ut_cookie");
createCookie('ut_cookie',un,0.25);
logged_in = true;
console.log("logged in as instructor", logged_in);
role = "instructor";
}
else if (pw == the_un_enc) {
console.log("setting the ut_cookie");
createCookie('ut_cookie',un,150);
logged_in = true;
}
else if (pw == the_url_enc) {
console.log("setting the url ut_cookie");
createCookie('ut_cookie',un,0.25);
logged_in = true;
}
else {
// alert ('Login was unsuccessful, please check id: "' + uname + '" and password"' + emanu +'". Also checking "' + pw + '" and "' + the_un_enc + '".');
alert ('Login was unsuccessful, please check id: "' + uname + '".');
console.log("failed to set the ut_cookie");
logged_in = false;
}
console.log("logged_in", logged_in);
if (logged_in) {
$("#theloginform").hide();
document.getElementById('loginlogout').innerHTML = 'logout' + ' ' + un;
if(uname != "editor") {
loadScript('answer');
loadScript('highlight')
if ((typeof trails !== 'undefined') && trails) {
loadScript('trails');
}
}
}
var role_key = check_role();
console.log("role_key", role_key);
if (role_key) {
console.log("another instructor", role_key);
role = 'instructor'
}
// if (logged_in && /^\d+$/.test(uname) && ( (uname.length == 5 || uname.length == 8 && /^\d+00$/.test(uname)) )) {
// console.log("an instructor");
// role = 'instructor'
// }
console.log("role", role);
return logged_in
}
var aa_id = readCookie('aa_cookie');
if (aa_id) {
console.log("found cookie");
console.log(aa_id);
var date = new Date();
var date_now = date.getTime();
var date_str = date_now.toString();
var date_str_trimmed = date_str.slice(5,13);
console.log(date_str)
console.log(date_str_trimmed)
}
else {
console.log("no cookie found")
var date = new Date();
var date_now = date.getTime();
var date_str = date_now.toString();
var date_str_trimmed = date_str.slice(5,13);
aa_id = date_str_trimmed;
createCookie('aa_cookie',aa_id,150);
}
/* dataurlbase = dataurlbase.concat("per").concat("=").concat(aa_id).concat("&");
*/
var ut_id = readCookie('ut_cookie');
uname = ut_id || "";
if(uname == "instructor") { role="instructor"}
console.log("uname", uname);
var pageIdentifier = "";
window.addEventListener('load', function(){
console.log("checking login", ut_id);
bodyID = document.getElementsByTagName('body')[0].id;
console.log("bodyID", bodyID);
if (bodyID) {
var secID = document.getElementsByTagName('section')[0].id;
if (secID) {
pageIdentifier = bodyID + "___" + secID
}
}
if (pageIdentifier) {
if (ut_id) {
console.log("found "+ut_id);
$("#theloginform").hide();
// if (/^\d+$/.test(uname) && ( (uname.length == 5 || uname.length == 8 && /^\d+00$/.test(uname)) )) {
var role_key = check_role();
console.log("the role_key", role_key);
if (role_key) {
console.log("another instructor", role_key);
role = 'instructor'
}
document.getElementById('loginlogout').className = 'logout';
document.getElementById('loginlogout').innerHTML = 'logout' + ' ' + ut_id;
console.log("done hiding "+ut_id);
loadScript('answer');
if(uname != "editor") {loadScript('highlight')}
if ((typeof trails !== 'undefined') && trails) {
loadScript('trails');
}
}
else if (typeof login_required !== 'undefined' && login_required) {
login_form();
}
$("#loginlogout.logout").click(function(){
login_form('logout');
$("#dontlogout").click(function() {
$("#thelogoutform").remove()
});
});
$("#loginlogout.login").click(function(){
login_form('login');
});
} else {
console.log("login not enabled because document not identified")
}
// console.log("the role", role);
});
+8 -2
View File
@@ -33,8 +33,14 @@ window.addEventListener('message', function (event) {
const iFrames = document.getElementsByTagName('iframe');
for(const iFrame of iFrames) {
if(iFrame.contentWindow === event.source) {
if (edata.height) iFrame.height = edata.height;
if (edata.width) iFrame.width = edata.width;
if (edata.height) {
iFrame.height = edata.height;
iFrame.style.height = edata.height + 'px';
}
if (edata.width) {
iFrame.width = edata.width;
iFrame.style.width = edata.width + 'px';
}
break;
}
}
+132
View File
@@ -0,0 +1,132 @@
/***************************************************************
* Implements startup of MathJax v4
***************************************************************/
// Base config options. Will be supplemented by optional parts later
let mathJaxOpts = {
"tex": {
"inlineMath": [
[
"\\(",
"\\)"
]
],
"tags": "none",
"tagSide": "right",
"tagIndent": ".8em",
"packages": {
"[+]": [
"amscd",
"color",
"knowl"
]
}
},
"options": {
"ignoreHtmlClass": "tex2jax_ignore|ignore-math",
"processHtmlClass": "process-math",
},
"chtml": {
"scale": 0.98,
"mtextInheritFont": true
},
"loader": {
"load": [
"input/asciimath",
"[tex]/amscd",
"[tex]/color",
]
}
};
export function startMathJax(opts) {
if(opts.hasWebworkReps || opts.hasSage) {
mathJaxOpts['renderActions'] = {
"findScript": [
10,
function (doc) {
document.querySelectorAll('script[type^="math/tex"]').forEach(function (node) {
var display = !!node.type.match(/; *mode=display/);
var math = new doc.options.MathItem(node.textContent, doc.inputJax[0], display);
var text = document.createTextNode('');
node.parentNode.replaceChild(text, node);
math.start = { node: text, delim: '', n: 0 };
math.end = { node: text, delim: '', n: 0 };
doc.math.push(math);
});
},
""
]
}
}
if(opts.isReact) {
mathJaxOpts['startup'] = {
typeset: false,
}
} else {
mathJaxOpts['startup'] = {
ready() {
const { Configuration } = MathJax._.input.tex.Configuration;
const configuration = Configuration.create("knowl", {
handler: {
macro: ["knowl"]
}
});
const NodeUtil = MathJax._.input.tex.NodeUtil.default;
function GetArgumentMML(parser, name) {
const arg = parser.ParseArg(name);
if (!NodeUtil.isInferred(arg)) {
return arg;
}
const children = NodeUtil.getChildren(arg);
if (children.length === 1) {
return children[0];
}
const mrow = parser.create("node", "mrow");
NodeUtil.copyChildren(arg, mrow);
NodeUtil.copyAttributes(arg, mrow);
return mrow;
};
const CommandMap = MathJax._.input.tex.TokenMap.CommandMap;
new CommandMap(
"knowl",
{
knowl(parser, name) {
const url = parser.GetArgument(name);
const arg = GetArgumentMML(parser, name);
const mrow = parser.create("node", "mrow", [arg], { tabindex: '0', "data-knowl": url });
parser.Push(mrow);
}
}
);
MathJax.startup.defaultReady();
},
pageReady() {
return MathJax.startup.defaultPageReady().then(rsMathReady);
},
}
}
if(opts.htmlPresentation) {
mathJaxOpts['options']['menuOptions'] = {
"settings": {
"zoom": "Click",
"zscale": "300%"
}
}
}
// Apply the options
window.MathJax = mathJaxOpts;
// Lets Runestone know that MathJax is ready
const runestoneMathReady = new Promise((resolve) => window.rsMathReady = resolve);
window.runestoneMathReady = runestoneMathReady;
}
-48
View File
@@ -1,48 +0,0 @@
const { Configuration } = MathJax._.input.tex.Configuration;
const { CommandMap } = MathJax._.input.tex.SymbolMap;
const NodeUtil = MathJax._.input.tex.NodeUtil.default;
var GetArgumentMML = function (parser, name) {
var arg = parser.ParseArg(name);
if (!NodeUtil.isInferred(arg)) {
return arg;
}
var children = NodeUtil.getChildren(arg);
if (children.length === 1) {
return children[0];
}
var mrow = parser.create("node", "mrow");
NodeUtil.copyChildren(arg, mrow);
NodeUtil.copyAttributes(arg, mrow);
return mrow;
};
let mathjaxKnowl = {};
/**
* Implements \knowl{url}{math}
* @param {TexParser} parser The calling parser.
* @param {string} name The TeX string
*/
mathjaxKnowl.Knowl = function (parser, name) {
var url = parser.GetArgument(name);
var arg = GetArgumentMML(parser, name);
var mrow = parser.create("node", "mrow", [arg], {
tabindex: '0',
"data-knowl": url
});
parser.Push(mrow);
};
new CommandMap(
"knowl",
{
knowl: ["Knowl"]
},
mathjaxKnowl
);
const configuration = Configuration.create("knowl", {
handler: {
macro: ["knowl"]
}
});
+2 -2
View File
@@ -21,8 +21,8 @@ const stackstring = {
function wrap_math(content) {
// Wrap instances of \[ ... \] and \( ... \) into the tags configured to be processed by MathJax
// Here we make sure that the backslashes are not escaped like \\[
content = content.replace(/(?<!\\)(\\\(.*?(?<!\\)\\\))/g, "<span class=\"process-math\">$1</span>");
return content.replace(/(?<!\\)(\\\[.*?(?<!\\)\\\])/g, "<span class=\"process-math\">$1</span>");
content = content.replace(/(?<!\\)(\\\(.*?(?<!\\)\\\))/gs, "<span class=\"process-math\">$1</span>");
return content.replace(/(?<!\\)(\\\[.*?(?<!\\)\\\])/gs, "<span class=\"process-math\">$1</span>");
}
// Create data for call to API.
View File
@@ -1,753 +0,0 @@
//Critical:
// TODO: Think about what should be done for an essay question.
//Accessibility:
//Enhancement:
// TODO: In a scaffold problem, adjust the green bar for when one part is correct.
// TODO: Have randomize check that new seed is actually producing new HTML.
// TODO: Don't offer randomize or button unless we know a new version can be produced after trying a few seeds.
// TODO: In a staged problem, Make Interactive button is adjacent to solution knowls; move it.
// TODO: MathQuill
// TODO: Image pop up.
// TODO: Deal with singleResult MultiAnswer problems.
//Styling:
// TODO: Review all styling in all scenarios (staged/not, correct/partly-correct/incorrect/blank, single/multiple)
// TODO: Sean: I think I'd like to see a slightly larger font size on the buttons
function handleWW(ww_id, action) {
const ww_container = document.getElementById(ww_id);
const ww_domain = ww_container.dataset.domain;
const ww_problemSource = ww_container.dataset.problemsource;
const ww_sourceFilePath = ww_container.dataset.sourcefilepath;
const ww_course_id = ww_container.dataset.courseid;
const ww_user_id = ww_container.dataset.userid;
const ww_course_password = ww_container.dataset.coursepassword;
const localize_correct = (ww_container.dataset.localizeCorrect ? ww_container.dataset.localizeCorrect : "Correct");
const localize_incorrect = (ww_container.dataset.localizeIncorrect ? ww_container.dataset.localizeIncorrect : "Incorrect");
const localize_check_responses = (ww_container.dataset.localizeCheckResponses ? ww_container.dataset.localizeCheckResponses : "Check Responses");
const localize_randomize = (ww_container.dataset.localizeRandomize ? ww_container.dataset.localizeRandomize : "Randomize");
const localize_reset = (ww_container.dataset.localizeReset ? ww_container.dataset.localizeReset : "Reset");
// will be null on pages generated prior to late December 2022
const activate_button = document.getElementById(ww_id + '-button')
// Set the current seed
if (!action) ww_container.dataset.current_seed = ww_container.dataset.seed;
else if (action == 'randomize') ww_container.dataset.current_seed = Number(ww_container.dataset.current_seed) + 100;
let loader = document.createElement('div');
loader.style.position = 'absolute';
loader.style.left = 0;
loader.style.top = 0;
loader.style.backgroundColor = 'rgba(0.2, 0.2, 0.2, 0.4)';
loader.style.color = 'white';
loader.style.width = '100%';
loader.style.height = '100%';
loader.style.display = 'flex';
loader.style.alignItems = 'center';
loader.style.justifyContent = 'center';
loader.style.marginTop = '0';
loader.tabIndex = -1;
const loaderText = document.createElement('span');
loaderText.textContent = 'Loading';
loaderText.style.fontSize = '2rem';
loader.appendChild(loaderText);
ww_container.appendChild(loader);
loader.focus();
if (!action) {
// Determine if static version shows hints, solutions, or answers and save that information in the container dataset for later runs.
ww_container.dataset.hasHint = ww_container.getElementsByClassName('hint').length > 0;
ww_container.dataset.hasSolution = ww_container.getElementsByClassName('solution').length > 0;
ww_container.dataset.hasAnswer = ww_container.getElementsByClassName('answer').length > 0;
// Get (possibly localized) label text for hints and solutions.
ww_container.dataset.hintLabelText = ww_container.dataset.hasHint == 'true'
? ww_container.querySelectorAll('.hint-knowl span.type')[0].textContent : 'Hint';
ww_container.dataset.solutionLabelText = ww_container.dataset.hasSolution == 'true'
? ww_container.querySelectorAll('.solution-knowl span.type')[0].textContent : 'Solution';
ww_container.tabIndex = -1;
}
let url;
if (action == 'check') {
const iframe = ww_container.querySelector('.problem-iframe');
const formData = new FormData(iframe.contentDocument.getElementById(ww_id + "-form"));
const params = new URLSearchParams(formData);
url = new URL(ww_domain + '/webwork2/html2xml?' + params.toString())
url.searchParams.append("answersSubmitted", '1');
url.searchParams.append('WWsubmit', "1");
} else {
url = new URL(ww_domain + '/webwork2/html2xml');
url.searchParams.append("problemSeed", ww_container.dataset.current_seed);
if (ww_problemSource) url.searchParams.append("problemSource", ww_problemSource);
else if (ww_sourceFilePath) url.searchParams.append("sourceFilePath", ww_sourceFilePath);
url.searchParams.append("answersSubmitted", '0');
url.searchParams.append("displayMode", "MathJax");
url.searchParams.append("courseID", ww_course_id);
url.searchParams.append("userID", ww_user_id);
url.searchParams.append("course_password", ww_course_password);
url.searchParams.append("outputformat", "raw");
// note ww_container.dataset.hasSolution is a string, possibly 'false' which is true
url.searchParams.append("showSolutions", ww_container.dataset.hasSolution == 'true' ? '1' : '0');
url.searchParams.append("showHints", ww_container.dataset.hasHint == 'true' ? '1' : '0');
url.searchParams.append("problemUUID",ww_id);
}
// get the json and do stuff with what we get
$.getJSON(url.toString(), (data) => {
// Create the form that will contain the text and input fields of the interactive problem.
const form = document.createElement("form");
form.id = ww_id + "-form";
// Create a div for the problem text.
const body_div = document.createElement("div");
body_div.id = ww_id + "-body";
body_div.classList.add("exercise", "exercise-like");
body_div.lang = data.lang;
body_div.dir = data.dir;
// Dump the problem text, answer blanks, etc.
body_div.innerHTML = data.rh_result.text;
// Replace all hn headings with h6 headings.
for (const tag_name of ['h6', 'h5', 'h4', 'h3', 'h2', 'h1']) {
const headings = body_div.getElementsByTagName(tag_name);
for (heading of headings) {
const new_heading = document.createElement("h6");
new_heading.innerHTML = heading.innerHTML;
cloneAttributes(new_heading, heading);
new_heading.classList.add('webwork-part');
heading.replaceWith(new_heading);
}
}
adjustSrcHrefs(body_div, ww_domain);
translateHintSol(ww_id, body_div, ww_domain,
ww_container.dataset.hasHint == 'true', ww_container.dataset.hasSolution == 'true',
ww_container.dataset.hintLabelText, ww_container.dataset.solutionLabelText)
// insert our cleaned up problem text
form.appendChild(body_div);
// Set up hidden input fields that the form uses
const wwInputs = {
problemSeed: data.inputs_ref.problemSeed,
problemUUID: data.inputs_ref.problemUUID,
psvn: data.inputs_ref.psvn,
courseName: ww_course_id,
courseID: ww_course_id,
userID: ww_user_id,
course_password: ww_course_password,
displayMode: "MathJax",
session_key: data.rh_result.session_key,
outputformat: "raw",
language: data.formLanguage,
showSummary: data.showSummary,
// note ww_container.dataset.hasSolution is a string, possibly 'false' which is true
showSolutions: ww_container.dataset.hasSolution == 'true' ? '1' : '0',
showHints: ww_container.dataset.hasHint == 'true' ? '1' : '0',
forcePortNumber: data.forcePortNumber
};
if (ww_sourceFilePath) wwInputs.sourceFilePath = ww_sourceFilePath;
else if (ww_problemSource) wwInputs.problemSource = ww_problemSource;
for (const wwInputName of Object.keys(wwInputs)) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = wwInputName;
input.value = wwInputs[wwInputName];
form.appendChild(input);
}
// Prepare answers object
const answers = {};
// id the answers even if we won't populate them
Object.keys(data.rh_result.answers).forEach(function(id) {
answers[id] = {};
}, data.rh_result.answers);
if (ww_container.dataset.hasAnswer == 'true') {
// Update answer data
Object.keys(data.rh_result.answers).forEach(function(id) {
answers[id] = {
correct_ans: this[id].correct_ans,
correct_ans_latex_string: this[id].correct_ans_latex_string,
correct_choice: this[id].correct_choice
};
}, data.rh_result.answers);
}
let buttonContainer = ww_container.querySelector('.problem-buttons.webwork');
// Create the submission buttons if they have not yet been created.
if (!buttonContainer) {
// Hide the original div that contains the old make active button.
ww_container.querySelector('.problem-buttons').classList.add('hidden-content');
// And the newer activate button if it is there
if (activate_button != null) {activate_button.classList.add('hidden-content');};
// Create a new div for the webwork buttons.
buttonContainer = document.createElement('div');
buttonContainer.classList.add('problem-buttons', 'webwork');
if (activate_button != null) {
// Make sure the button container follows the activate button in the DOM
activate_button.after(buttonContainer);
} else {
ww_container.prepend(buttonContainer);
}
// Check button
const check = document.createElement("button");
check.type = "button";
check.id = ww_id + '-check';
check.style.marginRight = "0.25rem";
check.classList.add('webwork-button');
// Adjust if more than one answer to check
const answerCount = body_div.querySelectorAll("input:not([type=hidden])").length +
body_div.querySelectorAll("select:not([type=hidden])").length;
check.textContent = localize_check_responses;
check.addEventListener('click', () => handleWW(ww_id, "check"));
buttonContainer.appendChild(check);
// Show correct answers button if original PTX has answer knowl.
if (ww_container.dataset.hasAnswer == 'true') {
const correct = document.createElement("button");
correct.classList.add("show-correct", 'webwork-button');
correct.type = "button";
correct.style.marginRight = "0.25rem";
correct.textContent = answerCount > 1 ? "Show Correct Answers" : "Show Correct Answer";
correct.addEventListener('click', () => WWshowCorrect(ww_id, answers));
buttonContainer.appendChild(correct);
}
// randomize button
const randomize = document.createElement("button")
randomize.type = "button";
randomize.classList.add('webwork-button');
randomize.style.marginRight = "0.25rem";
randomize.textContent = localize_randomize;
randomize.addEventListener('click', () => handleWW(ww_id, 'randomize'));
buttonContainer.appendChild(randomize)
// reset button
const reset = document.createElement("button")
reset.type = "button"
reset.classList.add('webwork-button');
reset.textContent = localize_reset;
reset.addEventListener('click', () => resetWW(ww_id));
buttonContainer.appendChild(reset)
} else {
// Update the click handler for the show correct button.
if (ww_container.dataset.hasAnswer == 'true') {
const correct = buttonContainer.querySelector('.show-correct');
const correctNew = correct.cloneNode(true);
correctNew.addEventListener('click', () => WWshowCorrect(ww_id, answers));
correct.replaceWith(correctNew);
}
}
if (action == 'check') {
// Runestone trigger
// TODO: chondition on platform=Runestone
$("body").trigger('runestone_ww_check', data)
const inputs = body_div.querySelectorAll("input:not([type=hidden])");
for (const input of inputs) {
const name = input.name;
if (input.type == 'text' && answers[name]) {
const score = data.rh_result.answers[name].score;
let title = '';
if (score == 1) {
title = `<span class="correct">${localize_correct}!</span>`;
} else if (score > 0 && score < 1) {
title = `<span class="partly-correct">${Math.round(score * 100)}% ${localize_correct}.</span>`;
} else if (data.rh_result.answers[name].student_ans == '') {
// do nothing if the submitted answer is blank and the problem has not already been scored as correct
continue;
} else if (score == 0) {
title = `<span class="incorrect">${localize_incorrect}.</span>`;
}
input.after(createFeedbackButton(`${ww_id}-${name}`, title, data.rh_result.answers[name].ans_message));
}
if (input.type == 'radio' && answers[name]) {
if (input.value == data.rh_result.answers[name].student_value) {
const feedbackButton = createFeedbackButton(`${ww_id}-${name}`,
data.rh_result.answers[name].student_value == data.rh_result.answers[name].correct_choice
? `<span class="correct">${localize_correct}!</span>` : `<span class="incorrect">${localize_incorrect}.</span>`)
feedbackButton.style.marginRight = '0.25rem';
input.after(feedbackButton);
}
}
}
const hiddenInputs = body_div.querySelectorAll("input[type=hidden]");
for (const input of hiddenInputs) {
const name = input.name;
if (!input.nextElementSibling) continue;
const graphtoolContainer = input.nextElementSibling.nextElementSibling;
if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains('graphtool-container')) {
graphtoolContainer.style.position = 'relative';
const score = data.rh_result.answers[name].score;
let title = '';
if (score == 1) {
title = `<span class="correct">${localize_correct}!</span>`;
} else if (score > 0 && score < 1) {
title = `<span class="partly-correct">${Math.round(score * 100)}% ${localize_correct}.</span>`;
} else if (data.rh_result.answers[name].student_ans == '') {
// do nothing if the submitted answer is blank and the problem has not already been scored as correct
continue;
} else if (score == 0) {
title = `<span class="incorrect">${localize_incorrect}.</span>`;
}
const feedbackButton = createFeedbackButton(`${ww_id}-${name}`, title, data.rh_result.answers[name].ans_message);
feedbackButton.style.position = 'absolute';
feedbackButton.style.left = '100%';
feedbackButton.style.top = 0;
feedbackButton.style.marginLeft = '0.5rem';
feedbackButton.dataset.container = 'body';
graphtoolContainer.appendChild(feedbackButton);
}
}
const selects = body_div.querySelectorAll("select:not([type=hidden])");
for (const select of selects) {
const name = select.name;
const feedbackButton = createFeedbackButton(`${ww_id}-${name}`,
data.rh_result.answers[name].score == 1 ? `<span class="correct">${localize_correct}!</span>`
: `<span class="incorrect">${localize_incorrect}.</span>`)
feedbackButton.style.marginRight = '0.25rem';
feedbackButton.style.marginLeft = '0.5rem';
select.after(feedbackButton);
}
}
let iframeContents = '<!DOCTYPE html><head>' +
'<script src="' + ww_domain + '/webwork2_files/node_modules/jquery/dist/jquery.min.js"></script>' +
`<script>window.MathJax = {
tex: {
inlineMath: [['\\\\(','\\\\)']],
tags: "none",
useLabelIds: true,
tagSide: "right",
tagIndent: ".8em",
packages: {'[+]': ['base', 'extpfeil', 'ams', 'amscd', 'newcommand', 'knowl']}
},
options: {
ignoreHtmlClass: "tex2jax_ignore",
processHtmlClass: "has_am",
renderActions: {
findScript: [10, function (doc) {
document.querySelectorAll('script[type^="math/tex"]').forEach(function(node) {
const display = !!node.type.match(/; *mode=display/);
const math = new doc.options.MathItem(node.textContent, doc.inputJax[0], display);
const text = document.createTextNode('');
node.parentNode.replaceChild(text, node);
math.start = {node: text, delim: '', n: 0};
math.end = {node: text, delim: '', n: 0};
doc.math.push(math);
});
}, '']
},
},
chtml: {
scale: 0.88,
mtextInheritFont: true
},
loader: {
load: ['input/asciimath', '[tex]/extpfeil', '[tex]/amscd', '[tex]/newcommand', '[pretext]/mathjaxknowl3.js'],
paths: {pretext: "https://pretextbook.org/js/lib"},
},
};
</script>` +
'<script src="' + ww_domain + '/webwork2_files/mathjax/es5/tex-chtml.js" id="MathJax-script" defer></script>' +
'<script src="https://pretextbook.org/js/lib/knowl.js" defer></script>' +
'<link rel="stylesheet" href="' + ww_domain + '/webwork2_files/js/vendor/bootstrap/css/bootstrap.css"/>' +
'<link rel="stylesheet" href="' + ww_domain + '/webwork2_files/themes/math4/math4.css"/>' +
'<script src="' + ww_domain + '/webwork2_files/js/vendor/bootstrap/js/bootstrap.js" id="MathJax-script" defer></script>';
// Determine javascript and css dependencies
const extra_css_files = [];
const extra_js_files = [];
//if (data.rh_result.flags.extra_js_files) {
// for (const jsFile of data.rh_result.flags.extra_js_files) {
// if (jsFile.file == "js/apps/GraphTool/graphtool.min.js" || jsFile.file == "js/apps/Scaffold/scaffold.js") {
// extra_css_files.push({ file: 'js/vendor/bootstrap/css/bootstrap.css', external: '0' });
// extra_css_files.push({ file: 'themes/math4/math4.css', external: '0' });
// extra_js_files.push({ file: 'js/vendor/bootstrap/js/bootstrap.js', external: '0' });
// }
// }
//}
if (data.rh_result.flags.extra_css_files) data.rh_result.flags.extra_css_files.unshift(...extra_css_files);
else data.rh_result.flags.extra_css_files = extra_css_files;
for (const cssFile of data.rh_result.flags.extra_css_files) {
iframeContents += '<link rel="stylesheet" href="' + (cssFile.external !== '1' ? (ww_domain + '/webwork2_files/') : '') + cssFile.file + '"/>';
}
if (data.rh_result.flags.extra_js_files) data.rh_result.flags.extra_js_files.unshift(...extra_js_files);
else data.rh_result.flags.extra_js_files = extra_js_files;
for (const jsFile of data.rh_result.flags.extra_js_files) {
iframeContents += '<script src="' + (jsFile.external !== '1' ? (ww_domain + '/webwork2_files/') : '') + jsFile.file + '" ' +
Object.keys(jsFile.attributes || {}).reduce((ret, key) => {
ret += key + '="' + jsFile.attributes[key] + '" '; return ret;
}, "") + '></script>';
}
iframeContents +=
'<link rel="stylesheet" href="https://pretextbook.org/css/0.31/pretext_add_on.css"/>' +
'<link rel="stylesheet" href="https://pretextbook.org/css/0.31/knowls_default.css"/>' +
'<script src="' + ww_domain + '/webwork2_files/node_modules/iframe-resizer/js/iframeResizer.contentWindow.min.js"></script>' +
`<style>
html { overflow-y: hidden; }
html body { background:unset; margin: 0; }
body { font-size: initial; line-height: initial; }
.hidden-content { display: none; }
input[type="text"], input[type="radio"], label, select {
height: auto;
width: auto;
max-width: unset;
margin: 0;
font-size: initial;
font-family: sans-serif;
line-height: initial;
}
input[type="text"] {
padding: 1px;
padding-inline: 2px;
border-width: 1px;
border-style: inset;
border-color: rgb(133, 133, 133);
border-radius: 0;
box-shadow: unset;
transition: none;
}
input[type="text"]:focus, input[type="text"]:active {
box-shadow: unset;
border-width: 1px;
border-style: inset;
border-radius: 4px;
border-color: rgb(133, 133, 133);
outline: auto;
}
input[type="radio"] {
box-sizing: border-box;
margin-block: 3px 0;
margin-inline: 5px 3px;
vertical-align: unset;
}
label {
display: inline-block;
}
select {
margin: 0;
border-width: 1px;
border-style: inset;
display: inline-block;
padding-block: 1px;
vertical-align: baseline;
background-color: unset;
padding-inline: 0;
}
.popover-title, .popover-content {
text-align: center;
}
.popover-title.correct {
background-color: #8F8;
}
.accordion-body.expanded {
overflow-y: visible;
overflow-x: clip;
}
.graphtool-answer-container .graphtool-graph {
margin: 0;
width: 300px;
height: 300px;
}
div.PGML img.image-view-elt {
max-width:100%;
}
</style>` +
'</head><body><main class="pretext-content">' + form.outerHTML + '</main></body>' +
'</html>';
let iframe;
// If there is no action this is the initialization call.
if (!action) {
// Create the iframe.
iframe = document.createElement('iframe');
iframe.style.width = '1px';
iframe.style.minWidth = '100%';
iframe.classList.add('problem-iframe');
// Hide the static problem
ww_container.querySelector('.problem-contents').classList.add('hidden-content');
if (activate_button != null) {
// Make sure the iframe follows the activate button in the DOM
activate_button.after(iframe);
} else {
ww_container.prepend(iframe);
}
iFrameResize({ checkOrigin: false, scrolling: 'omit', heightCalculationMethod: 'min' }, iframe);
iframe.addEventListener('load', () => {
// Set up form submission from inside the iframe.
const iframeForm = iframe.contentDocument.getElementById(ww_id + '-form');
iframeForm.addEventListener('submit', (e) => {
handleWW(ww_id, "check");
e.preventDefault();
});
iframe.contentDocument.querySelectorAll('.collapse.in').forEach(collapse => collapse.classList.add('expanded'));
iframe.contentWindow.jQuery('.collapse').on('shown', function(e) { if (e.target != this) return; this.classList.add('expanded'); });
iframe.contentWindow.jQuery('.collapse').on('hide', function(e) { if (e.target != this) return; this.classList.remove('expanded'); });
iframe.contentDocument.querySelectorAll("button.ww-feedback[data-content]").forEach(button => {
iframe.contentWindow.jQuery(button).popover("show");
const content = iframe.contentDocument.getElementById(button.id.replace("-feedback-button", "-content"));
const popover = content.parentNode.parentNode;
popover.id = button.id.replace("-feedback-button", "-feedback")
button.setAttribute('aria-describedby', popover.id);
if (button.previousElementSibling)
button.previousElementSibling.setAttribute('aria-describedby', popover.id);
popover.querySelector('.arrow').remove();
const title = popover.querySelector('.popover-title');
if (button.dataset.emptyContent) {
title.style.borderBottomWidth = 0;
content.parentNode.remove();
}
if (title.textContent == localize_correct + '!') title.classList.add('correct');
});
iframe.contentWindow.MathJax.startup.promise.then(() => iframe.contentWindow.MathJax.typesetPromise(['.popover', '.popover-content']));
});
} else {
iframe = ww_container.querySelector('.problem-iframe');
}
iframe.srcdoc = iframeContents;
iframe.addEventListener('load', () => {
// Remove the loader overlay
loader.remove();
}, { once: true })
// Place focus on the problem.
ww_container.focus()
});
}
function WWshowCorrect(ww_id, answers) {
const ww_container = document.getElementById(ww_id);
const iframe = ww_container.querySelector('.problem-iframe');
const body = iframe.contentDocument.getElementById(ww_id + '-body')
$("body").trigger("runestone_show_correct", {
"ww_id": ww_id,
"answers": answers
});
let inputs = body.querySelectorAll("input:not([type=hidden])");
for (const input of inputs) {
const name = input.name;
const span_id = `${ww_id}-${name}-correct`;
if (input.type == 'text' && answers[name] && !(iframe.contentDocument.getElementById(span_id))) {
const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`);
if (feedbackButton) {
feedbackButton.remove();
iframe.contentWindow.jQuery(feedbackButton).popover("hide");
}
label = iframe.contentDocument.getElementById(`${span_id}-label`);
if (label) {
label.parentElement.insertBefore(input, label);
label.remove();
}
input.type = "hidden";
// we need to convert things like &lt; in answers to <
const correct_ans_text = iframe.contentDocument.createElement('div');
correct_ans_text.innerHTML = answers[name].correct_ans;
input.value = correct_ans_text.textContent;
const show_span = iframe.contentDocument.createElement('span');
show_span.id = span_id;
show_span.appendChild(answers[name].correct_ans_latex_string
? iframe.contentDocument.createTextNode('\\(' + answers[name].correct_ans_latex_string + '\\)')
: iframe.contentDocument.createTextNode(answers[name].correct_ans));
input.parentElement.insertBefore(show_span, input);
}
if (input.type == 'radio' && answers[name]) {
const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`);
if (feedbackButton) {
feedbackButton.remove();
iframe.contentWindow.jQuery(feedbackButton).popover("hide");
}
correct_value = answers[name].correct_choice;
if (input.value == correct_value) input.checked = true;
}
}
const hiddenInputs = body.querySelectorAll("input[type=hidden]");
for (const input of hiddenInputs) {
const name = input.name;
if (!input.nextElementSibling) continue;
const graphtoolContainer = input.nextElementSibling.nextElementSibling;
if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains('graphtool-container')) {
const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`);
if (feedbackButton) {
feedbackButton.remove();
iframe.contentWindow.jQuery(feedbackButton).popover("hide");
}
const correct_ans_div = iframe.contentDocument.createElement('div');
input.parentElement.insertBefore(correct_ans_div, graphtoolContainer);
graphtoolContainer.style.display = 'none';
input.value = answers[name].correct_ans;
iframe.contentWindow.jQuery(correct_ans_div).html(answers[name].correct_ans_latex_string);
const script = iframe.contentDocument.createElement('script');
script.textContent = correct_ans_div.querySelector('script').textContent
.replace('\nwindow.addEventListener("DOMContentLoaded",', '(')
.replace(/;\n$/, '();');
iframe.contentDocument.body.appendChild(script);
}
}
let selects = body.querySelectorAll("select:not([type=hidden])");
for (const select of selects) {
const name = select.name;
const span_id = `${ww_id}-${name}-correct`;
if (answers[name] && !iframe.contentDocument.getElementById(span_id)) {
const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`);
if (feedbackButton) {
feedbackButton.remove();
iframe.contentWindow.jQuery(feedbackButton).popover("hide");
}
select.style.display = "none";
select.value = answers[name].correct_ans;
const show_span = iframe.contentDocument.createElement('span');
show_span.id = span_id;
show_span.appendChild(answers[name].correct_ans_latex_string
? iframe.contentDocument.createTextNode('\\(' + answers[name].correct_ans_latex_string + '\\)')
: iframe.contentDocument.createTextNode(answers[name].correct_ans));
select.parentElement.insertBefore(show_span, select);
}
}
// run MathJax on our new rendering
// FIXME: We only need to typeset the added elements, not the entire body.
const mathjaxTypesetScript = iframe.contentDocument.createElement('script');
mathjaxTypesetScript.textContent = 'MathJax.startup.promise.then(() => MathJax.typesetPromise([document.body]));';
iframe.contentDocument.body.appendChild(mathjaxTypesetScript);
}
function resetWW(ww_id) {
const ww_container = document.getElementById(ww_id);
const activate_button = document.getElementById(ww_id + '-button');
ww_container.dataset.current_seed = ww_container.dataset.seed;
iframe = ww_container.querySelector('.problem-iframe');
iframe.remove();
ww_container.querySelector('.problem-contents').classList.remove('hidden-content');
ww_container.querySelector('.problem-buttons.webwork').remove();
ww_container.querySelector('.problem-buttons').classList.remove('hidden-content');
// if the newer activate button is there (but hidden) bring it back too
if (activate_button != null) {activate_button.classList.remove('hidden-content');};
}
function adjustSrcHrefs(container,ww_domain) {
container.querySelectorAll('[href]').forEach((node) => {
const href = node.attributes.href.value;
if (href !== '#' && !href.match(/^[a-z]+:\/\//i)) node.href = ww_domain + '/' + href;
});
container.querySelectorAll('[src]').forEach((node) => {
node.src = ww_domain + '/' + node.attributes.src.value;
});
}
function translateHintSol(ww_id, body_div, ww_domain, b_ptx_has_hint, b_ptx_has_solution, hint_label_text, solution_label_text) {
// the problem text may come with "hint"s and "solution"s
// each one is an "a" with content "Hint" or "Solution", and an attribute with base64-encoded HTML content
// the WeBWorK knowl js would normally handle this, but we want PreTeXt knowl js to handle it
// so we replace the "a" with the content that should be there for PTX knowl js
// also if hint/sol were missing from the static version, we want these removed here
const ww_container = document.getElementById(ww_id);
const hintsolnodes = document.evaluate("//p[a/b]", body_div, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
if (hintsolnodes) {
let solutionlikewrapper;
for (let i = 0; i < hintsolnodes.snapshotLength; i++) {
const hintsolp = hintsolnodes.snapshotItem(i);
if (!hintsolp) continue;
const hintSolType = hintsolp.textContent.trim().toLowerCase().replace(':', '');
if (hintsolp.previousElementSibling.textContent.trim() != 'Hint:' && hintsolp.previousElementSibling.textContent.trim() != 'Solution:') {
solutionlikewrapper = document.createElement('div');
solutionlikewrapper.classList.add('webwork', 'solutions');
hintsolp.parentNode.insertBefore(solutionlikewrapper, hintsolp);
}
if ((hintSolType == 'solution' && !b_ptx_has_solution) || (hintSolType == 'hint' && !b_ptx_has_hint)) continue;
const knowlDetails = document.createElement('details');
knowlDetails.classList.add(hintSolType);
knowlDetails.classList.add('solution-like');
knowlDetails.classList.add('born-hidden-knowl');
const knowlSummary = document.createElement('summary');
const summaryLabel = document.createElement('span');
summaryLabel.classList.add('type');
summaryLabel.innerHTML = hintSolType == 'hint' ? hint_label_text : solution_label_text;
knowlSummary.appendChild(summaryLabel);
knowlDetails.appendChild(knowlSummary);
const knowlContents = document.createElement('div');
knowlContents.classList.add(hintSolType);
knowlContents.classList.add('solution-like');
knowlContents.innerHTML = hintsolp.firstElementChild.dataset.knowlContents;
knowlDetails.appendChild(knowlContents);
adjustSrcHrefs(knowlContents, ww_domain)
solutionlikewrapper.appendChild(knowlDetails);
}
}
for (let i = 0; i < hintsolnodes.snapshotLength; i++) {
hintsolnodes.snapshotItem(i).remove();
}
}
function cloneAttributes(target, source) {
[...source.attributes].forEach( attr => { target.setAttribute(attr.nodeName ,attr.nodeValue) });
}
function createFeedbackButton(id, title, content) {
const feedbackButton = document.createElement('button');
feedbackButton.dataset.title = title;
feedbackButton.dataset.content = `<div id="${id}-content">${content || ''}</div>`;
if (!content) feedbackButton.dataset.emptyContent = '1';
const contentSpan = document.createElement('span');
contentSpan.style.fontWeight = 1000;
contentSpan.textContent = '\uD83D\uDDE9'
feedbackButton.appendChild(contentSpan);
feedbackButton.type = 'button';
feedbackButton.classList.add('ww-feedback');
feedbackButton.style.borderRadius = 0;
feedbackButton.id = `${id}-feedback-button`;
feedbackButton.dataset.html = true;
feedbackButton.dataset.placement = 'bottom';
feedbackButton.dataset.trigger = 'click';
return feedbackButton;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -384,7 +384,7 @@ async function handleWW(ww_id, action) {
};
</script>` +
'<script src="' + ww_domain + '/webwork2_files/node_modules/mathjax/es5/tex-chtml.js" id="MathJax-script" defer></script>' +
`<script src="${courseUrlBase}_static/pretext/js/lib/knowl.js" defer></script>` +
`<script src="${courseUrlBase}_static/pretext/js/dist/knowl.js" defer></script>` +
'<link rel="stylesheet" href="' + ww_domain + '/webwork2_files/node_modules/bootstrap/dist/css/bootstrap.min.css"/>' +
'<script src="' + ww_domain + '/webwork2_files/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js" defer></script>';
+1 -1
View File
@@ -393,7 +393,7 @@ async function handleWW(ww_id, action) {
};
</script>` +
'<script src="' + ww_domain + '/webwork2_files/node_modules/mathjax/es5/tex-chtml.js" id="MathJax-script" defer></script>' +
`<script src="${courseUrlBase}_static/pretext/js/lib/knowl.js" defer></script>` +
`<script src="${courseUrlBase}_static/pretext/js/dist/knowl.js" defer></script>` +
'<link rel="stylesheet" href="' + ww_domain + '/webwork2_files/node_modules/bootstrap/dist/css/bootstrap.min.css"/>' +
'<script src="' + ww_domain + '/webwork2_files/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js" defer></script>';
Executable → Regular
+81 -37
View File
@@ -60,16 +60,29 @@ function scrollTocToActive() {
}
function toggletoc() {
thesidebar = document.getElementById("ptx-sidebar");
if (thesidebar.classList.contains("hidden") || thesidebar.classList.contains("visible")) {
thesidebar.classList.toggle("hidden");
thesidebar.classList.toggle("visible");
} else if (thesidebar.offsetParent === null) { /* not currently visible */
thesidebar.classList.toggle("visible");
} else {
thesidebar.classList.toggle("hidden");
}
scrollTocToActive();
let ptxSidebar = document.getElementById("ptx-sidebar");
let sideBarIsHidden = ptxSidebar.classList.contains("hidden") || (!ptxSidebar.classList.contains("visible") && ptxSidebar.offsetParent === null);
if (sideBarIsHidden) {
ptxSidebar.classList.add("visible");
ptxSidebar.classList.remove("hidden");
} else {
ptxSidebar.classList.remove("visible");
ptxSidebar.classList.add("hidden");
}
sideBarIsHidden = !sideBarIsHidden; //toggled value for aria-expanded
let ptxTocButton = document.getElementById("ptx-toc-toggle");
ptxTocButton.setAttribute("aria-expanded", !sideBarIsHidden);
if (!sideBarIsHidden) {
scrollTocToActive();
// Focus the TOC for accessibility
document.querySelector("#ptx-toc").focus();
} else {
// Focus the TOC toggle button for accessibility
ptxTocButton.focus();
}
}
function samePageLink(a) {
@@ -93,31 +106,35 @@ function samePageLink(a) {
window.addEventListener("DOMContentLoaded",function(event) {
thetocbutton = document.getElementsByClassName("toc-toggle")[0];
thetocbutton.addEventListener("click", (e) => {
let tocButton = document.getElementById("ptx-toc-toggle");
tocButton.addEventListener("click", (e) => {
toggletoc();
e.stopPropagation(); // keep global click handler from immediately toggling it back
});
// determine if toc starts off hidden or not, use that to set aria-expanded
let ptxSidebar = document.getElementById("ptx-sidebar");
let sideBarIsHidden = ptxSidebar.classList.contains("hidden") || (!ptxSidebar.classList.contains("visible") && ptxSidebar.offsetParent === null);
tocButton.setAttribute("aria-expanded", !sideBarIsHidden);
// For themes that want it, install click handlers to auto close the toc
// when the reader clicks anywhere outside it or selects a subsection.
// (Selecting other sections or chapters navigates away from the page so
// effectively closes the TOC.)
if (getComputedStyle(document.documentElement).getPropertyValue('--auto-collapse-toc') == "yes") {
const sidebar = document.getElementById("ptx-sidebar");
const autoCollapseToc = getComputedStyle(document.documentElement).getPropertyValue('--auto-collapse-toc') == "yes";
if (autoCollapseToc) {
// Handle all clicks outside the sidebar
window.addEventListener("click", function(event) {
if (sidebar.classList.contains("visible")) {
if (!event.composedPath().includes(sidebar)) {
if (ptxSidebar.classList.contains("visible")) {
if (!event.composedPath().includes(ptxSidebar)) {
toggletoc();
}
}
});
// Handle clicks inside the sidebar but on link within a subsection.
sidebar.addEventListener("click", function (event) {
ptxSidebar.addEventListener("click", function (event) {
if (samePageLink(event.target.closest('a'))) {
toggletoc();
}
@@ -126,11 +143,28 @@ window.addEventListener("DOMContentLoaded",function(event) {
// Handle persistent sidebar if the page is restored from cache on back/forward buttons.
window.addEventListener('pageshow', (e) => {
if (e.persisted) {
sidebar.classList.remove('visible');
sidebar.classList.add('hidden');
ptxSidebar.classList.remove('visible');
ptxSidebar.classList.add('hidden');
tocButton.setAttribute("aria-expanded", "false");
}
});
}
// Handle Escape key to close the sidebar when it is presented as a mobile overlay
// or at any size if autoCollapseToc is enabled
window.addEventListener("keydown", function(event) {
if (
event.key === "Escape"
&& ptxSidebar.classList.contains("visible")
&& (
getComputedStyle(ptxSidebar).position === "fixed"
|| autoCollapseToc
)
) {
toggletoc();
}
});
});
@@ -139,17 +173,20 @@ window.addEventListener("DOMContentLoaded",function(event) {
//-----------------------------------------------------------------------------
//item is assumed to be expander in toc-item
function toggleTOCItem(expander) {
function toggleTOCItem(expander, event = null) {
let listItem = expander.closest(".toc-item");
listItem.classList.toggle("expanded");
let expanded = listItem.classList.contains("expanded");
let itemType = getTOCItemType(listItem);
let groupName = listItem.querySelector(".toc-title-box").innerText;
if(expanded) {
expander.title = "Close" + (itemType !== "" ? " " + itemType : "");
expander.title = "Close " + groupName;
expander.setAttribute("aria-expanded", "true");
} else {
expander.title = "Expand" + (itemType !== "" ? " " + itemType : "");
expander.title = "Expand " + groupName;
expander.setAttribute("aria-expanded", "false");
}
expander.setAttribute("aria-label", expander.title);
//should be one of each... for/of for safety and built in null avoidance
for (const childUL of listItem.querySelectorAll(":scope > ul.toc-item-list")) {
@@ -163,16 +200,17 @@ function toggleTOCItem(expander) {
}
}
}
}
//finds item type from classes or empty string on failure
function getTOCItemType(item) {
//Type should be class that looks like toc-X where X is not item. Find it and return X
for(let className of item.classList) {
if(className !== "toc-item" && className.length > 3 && className.slice(0,4) === "toc-")
return className.slice(4);
// if opened by keyboard, focus on the first child item, if any
if (expanded && expander === document.activeElement && event && event instanceof KeyboardEvent) {
const firstChildItem = listItem.querySelector(":scope > ul.toc-item-list > li.toc-item");
if (firstChildItem) {
const firstChildLink = firstChildItem.querySelector("a");
if (firstChildLink) {
firstChildLink.focus();
}
}
}
return "";
}
//finds depth of toc-item as defined by number .toc-item-lists it is in
@@ -208,23 +246,29 @@ window.addEventListener("DOMContentLoaded", function(event) {
if(hasChildren && depth < maxDepth) {
let expander = document.createElement("button");
expander.type = "button";
expander.classList.add('toc-expander');
expander.classList.add('toc-chevron-surround');
expander.title = 'toc-expander';
// content of span is set by CSS :before rule.
expander.innerHTML = '<span class="icon material-symbols-outlined" aria-hidden="true"></span>';
const subList = tocItem.querySelector('.toc-item-list');
expander.controlledGroup = subList.id;
expander.setAttribute('aria-controls', subList.id);
expander.setAttribute("aria-expanded", "false");
tocItem.querySelector(".toc-title-box").append(expander);
expander.addEventListener('click', () => {
toggleTOCItem(expander);
expander.addEventListener('click', (e) => {
toggleTOCItem(expander, e);
});
let isActive = tocItem.classList.contains("contains-active") || tocItem.classList.contains("active");
let preExpanded = isActive || depth < preexpandedLevels;
let itemType = getTOCItemType(tocItem);
if(preExpanded) {
toggleTOCItem(expander);
} else {
expander.title = "Expand" + (itemType !== "" ? " " + itemType : "");
let groupName = tocItem.querySelector(".toc-title-box").innerText;
expander.title = "Expand " + groupName;
expander.setAttribute("aria-label", expander.title);
}
}
}
+162 -217
View File
@@ -11,35 +11,17 @@
*******************************************************************************
*/
/*
console.log("thisbrowser.userAgent", window.navigator.userAgent);
*/
/* scrollbar width from https://stackoverflow.com/questions/13382516/getting-scroll-bar-width-using-javascript */
function getScrollbarWidth() {
var outer = document.createElement("div");
outer.style.visibility = "hidden";
outer.style.width = "100px";
outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps
document.body.appendChild(outer);
var widthNoScroll = outer.offsetWidth;
// force scrollbars
outer.style.overflow = "scroll";
// add innerdiv
var inner = document.createElement("div");
inner.style.width = "100%";
outer.appendChild(inner);
var widthWithScroll = inner.offsetWidth;
// remove divs
outer.parentNode.removeChild(outer);
return widthNoScroll - widthWithScroll;
}
// stub for i18next to future-proof the code. We don't actually use it for
// anything right now, but it will be needed if we want to localize the
// accessibility search status messages.
window.i18next = window.i18next || {
t(key, params = {}) {
for (const param in params) {
key = key.replace(`{{${param}}}`, params[param]);
}
return key;
}
};
/*
copy permalink address to clipboard
@@ -122,15 +104,33 @@ window.addEventListener("load",function(event) {
/* click an image to magnify */
$('body').on('click','.image-box > img:not(.draw_on_me):not(.mag_popup), .sbspanel > img:not(.draw_on_me):not(.mag_popup), figure > img:not(.draw_on_me):not(.mag_popup), figure > div > img:not(.draw_on_me):not(.mag_popup)', function(){
var img_big = document.createElement('div');
img_big.setAttribute('style', 'background:#fff;');
const content_element = document.getElementById('ptx-content');
img_big.setAttribute('class', 'mag_popup_container');
img_big.innerHTML = '<img src="' + $(this).attr("src") + '" style="width:100%" class="mag_popup"/>';
img_big.innerHTML = `<img src="${$(this).attr("src")}" style="width:100%;" class="mag_popup"/>`;
// place_to_put_big_img = $(this).parents(".sbsrow, figure, li").last();
place_to_put_big_img = $(this).parents(".image-box, .sbsrow, figure, li, .cols2 article:nth-of-type(2n)").last();
// for .cols2, the even ones have to go inside the previous odd one
if (place_to_put_big_img.prop("tagName") == "ARTICLE") {
place_to_put_big_img = place_to_put_big_img.prev().children().first();
}
// find ancestor so that place_to_put_big_img's position is relative to that ancestor
var img_big_parent = place_to_put_big_img[0].parentElement;
while (img_big_parent.id !== "ptx-content") {
const computed_position = getComputedStyle(img_big_parent).position;
if (computed_position !== "static") {
break;
}
img_big_parent = img_big_parent.parentElement;
}
const content_element_computed_style = getComputedStyle(content_element);
const content_padding_left = parseFloat(content_element_computed_style.paddingLeft );
const content_padding_right = parseFloat(content_element_computed_style.paddingRight);
const img_big_offset = content_element.getBoundingClientRect().left - img_big_parent.getBoundingClientRect().left + content_padding_left;
const doc_width = content_element.offsetWidth - content_padding_left - content_padding_right;
img_big.setAttribute('style', `width:${doc_width.toString()}px; left:${img_big_offset.toString()}px;`);
$(img_big).insertBefore(place_to_put_big_img);
});
@@ -286,61 +286,67 @@ function process_workspace() {
console.log("processing workspace");
MathJax.typesetPromise();
}
/* for the GeoGebra calculator */
function pretext_geogebra_calculator_onload() {
$("#calculator-toggle").focus();
var inputfield = $("input.gwt-SuggestBox.TextField")[0];
console.log("inputfield", inputfield);
inputfield.focus();
}
window.addEventListener("load",function(event) {
const calcDialogElement = document.getElementById('ptx-calculator-container');
const calcButtonElement = document.getElementById('ptx-calculator-toggle');
if (!calcDialogElement || !calcButtonElement) {
return;
}
const calcDialog = new PTXDialog(calcDialogElement, calcButtonElement, {"kind": "non-modal"});
/* scrolling on GG plot should scale, not move browser body */
// var scrollWidth = 15; //currently correct for FF, Ch, and Saf, but would be better to calculate
var scrollWidth = getScrollbarWidth();
if ( (navigator.userAgent.match(/Mozilla/i) != null) ) {
// scrollWidth += 0.5
}
console.log("scrollWidth", scrollWidth);
calcoffsetR = 5;
calcoffsetB = 5;
$('body').on('mouseover','#geogebra-calculator canvas', function(){
$('body').css('overflow', 'hidden');
$('html').css('margin-right', '15px');
$('#calculator-container').css('right', (calcoffsetR+scrollWidth).toString() + 'px');
$('#calculator-container').css('bottom', (calcoffsetB+scrollWidth).toString() + 'px');
});
const focusCalcInput = function() {
const inputField = document.querySelector("#ptx-geogebra-calculator input.gwt-SuggestBox.TextField");
if (inputField) {
inputField.focus();
}
}
function initGeogebra() {
// Some paramaters are fixed here, others are set by publisher options in the HTML source
// and stored in ggbParams. Merge those here.
const fixedParams = {
showToolBar: true,
showAlgebraInput: true,
perspective: "G/A",
algebraInputPosition: "bottom",
appletOnLoad: focusCalcInput,
scaleContainerClass: "ptx-calculator-container",
allowUpscale: false,
autoHeight: false,
}
const generatedParams = (typeof ggbParams === "object" && ggbParams) ? ggbParams : {};
const params = {...generatedParams, ...fixedParams};
let applet = new GGBApplet(params, true);
applet.inject('ptx-geogebra-calculator');
return applet;
}
$('body').on('mouseout','#geogebra-calculator canvas', function(){
$('body').css('overflow', 'scroll')
$('html').css('margin-right', '0');
$('#calculator-container').css('right', calcoffsetR.toString() + 'px');
$('#calculator-container').css('bottom', calcoffsetB.toString() + 'px');
});
let applet;
calcButtonElement.addEventListener('click', function() {
if (calcDialog.dialog.open) {
let initialized = calcDialogElement.dataset.initialized || false;
if (!initialized) {
applet = initGeogebra();
calcDialogElement.dataset.initialized = true;
} else {
focusCalcInput();
}
}
});
$('body').on('click', '#calculator-toggle', function() {
if ($('#calculator-container').css('display') == 'none') {
$('#calculator-container').css('display', 'block');
$('#calculator-toggle').addClass('open');
$('#calculator-toggle').attr('title', 'Hide calculator');
$('#calculator-toggle').attr('aria-expanded', 'true');
create_calc_script = document.getElementById("create_ggb_calc");
if (!create_calc_script) {
var ggbscript = document.createElement("script");
ggbscript.id = "create_ggb_calc";
ggbscript.innerHTML = "ggbApp.inject('geogebra-calculator')";
document.body.appendChild(ggbscript);
} else {
pretext_geogebra_calculator_onload();
}
} else {
$('#calculator-container').css('display', 'none');
$('#calculator-toggle').removeClass('open');
$('#calculator-toggle').attr('title', 'Show calculator');
$('#calculator-toggle').attr('aria-expanded', 'false');
}
});
//add resize observer for dialog
const resizeObserver = new ResizeObserver(entries => {
for (let entry of entries) {
if (entry.target === calcDialogElement && applet && applet.getAppletObject()) {
const width = entry.contentRect.width;
const height = entry.contentRect.height;
const topBarHeight = calcDialogElement.querySelector('.ptx-dialog-topbar').clientHeight || 0;
applet.getAppletObject().setSize(width, height - topBarHeight);
applet.getAppletObject().recalculateEnvironments();
}
}
});
resizeObserver.observe(calcDialogElement);
});
@@ -410,6 +416,42 @@ window.addEventListener("load",function(event) {
// The new method for creating pages and adjusting workspace //
// Unwrap section.paragraphs containers so their children flow directly
// into the parent, enabling CSS page breaks between the inner elements.
function flattenParagraphsSections(printout) {
const paragraphsSections = printout.querySelectorAll('section.paragraphs');
paragraphsSections.forEach(section => {
const parent = section.parentNode;
// Move all children out of the section wrapper and into the parent
while (section.firstChild) {
parent.insertBefore(section.firstChild, section);
}
// Remove the now-empty section wrapper
parent.removeChild(section);
});
}
// Wait for all images inside a container to finish loading.
// Returns a promise that resolves when every <img> has loaded (or on timeout).
function waitForImages(container, timeoutMs = 5000) {
const images = container.querySelectorAll('img');
const promises = [];
for (const img of images) {
if (!img.complete) {
promises.push(new Promise(resolve => {
img.addEventListener('load', resolve, { once: true });
img.addEventListener('error', resolve, { once: true });
}));
}
}
if (promises.length === 0) return Promise.resolve();
// Race all image loads against a timeout so broken images don't block forever
return Promise.race([
Promise.all(promises),
new Promise(resolve => setTimeout(resolve, timeoutMs))
]);
}
// This is used multiple places to set height of workspace divs to their author-provided heights
function setInitialWorkspaceHeights() {
const workspaces = document.querySelectorAll('.workspace');
@@ -482,7 +524,18 @@ function createPrintoutPages(margins) {
} else if (child.querySelector('.task')) {
// Keep the child as a block, but put each task after the first one as its own row:
rows.push(child);
const tasks = child.querySelectorAll('.task');
const tasks = child.querySelectorAll('.task, .conclusion');
//Determine how many levels of nesting each task has. If parent is an .exercise, leave alone. If parent is a .task, add .subtask class. If grandparent is .task, add .subsubtask to it so it can be indented by css:
for (let i = 0; i < tasks.length; i++) {
let parent = tasks[i].parentElement;
let grandparent = parent.parentElement;
if (grandparent.classList.contains('task')) {
tasks[i].classList.add('subsubtask');
} else if (parent.classList.contains('task')) {
tasks[i].classList.add('subtask');
}
}
for (let i = tasks.length-1; i > 0; i--) {
// Move the task out of the original child and place it directly after it in the printout. We do this in reverse order so when every task is moved, they return to the original order. They will then be added to the rows list as their own blocks.
printout.insertBefore(tasks[i], child.nextSibling);
@@ -740,10 +793,10 @@ function getElemWorkspaceHeight(elem) {
// Functions for finding the optimal page breaks
function findPageBreaks(rows, pageHeight) {
console.log("*** Finding page breaks for", rows.length, "rows with page height:", pageHeight);
// An array for the page breaks. The nth element will be the index of the last row on page n.
// An array for the page breaks. The nth element will be the index of the first row on page n+1.
let pageBreaks = [];
// An array for the minimum cost possible for rows i to the end.
let minCost = Array(rows.length).fill(Infinity);
let minCost = Array(rows.length + 1).fill(Infinity);
minCost[rows.length] = 0; // No cost for no rows
// An array to keep track of the next row to start a new page after i in minCost.
let nextPageBreak = Array(rows.length).fill(-1);
@@ -1059,6 +1112,17 @@ window.addEventListener("DOMContentLoaded", async function(event) {
// Finally, with everything set up, we create or adjust the printout pages as needed.
// Flatten paragraphs sections so page breaks can occur inside them.
const printoutSection = document.querySelector('section.worksheet, section.handout');
if (printoutSection) {
flattenParagraphsSections(printoutSection);
}
// Wait for all images to load so height measurements are accurate.
if (printoutSection) {
await waitForImages(printoutSection);
}
// If the printout has authored pages, there will be at least one .onepage element.
if (document.querySelector('.onepage')) {
adjustPrintoutPages();
@@ -1119,144 +1183,16 @@ window.addEventListener("DOMContentLoaded", async function(event) {
}
});
//-----------------------------------------------------------------
// Dark/Light mode swiching
function isDarkMode() {
if (document.documentElement.dataset.darkmode === 'disabled')
return false;
const currentTheme = localStorage.getItem("theme");
if (currentTheme === "dark")
return true;
else if (currentTheme === "light")
return false;
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
}
function setDarkMode(isDark) {
if(document.documentElement.dataset.darkmode === 'disabled')
return;
const parentHtml = document.documentElement;
const iframes = document.querySelectorAll("iframe[data-dark-mode-enabled]");
// Update the parent document
if (isDark) {
parentHtml.classList.add("dark-mode");
} else {
parentHtml.classList.remove("dark-mode");
}
// Sync each iframe's <html> class with the parent
for (const iframe of iframes) {
try {
const iframeHtml = iframe.contentWindow.document.documentElement;
if (isDark) {
iframeHtml.classList.add("dark-mode")
} else {
iframeHtml.classList.remove("dark-mode")
}
} catch (err) {
console.warn("Dark mode sync to iframe failed:", err);
}
}
const modeButton = document.getElementById("light-dark-button");
if (modeButton) {
modeButton.querySelector('.icon').innerText = isDark ? "light_mode" : "dark_mode";
modeButton.querySelector('.name').innerText = isDark ? "Light Mode" : "Dark Mode";
}
}
// Run this as soon as possible to avoid flicker
setDarkMode(isDarkMode());
// Rest of dark mode setup logic waits until after load
window.addEventListener("DOMContentLoaded", function(event) {
// Rerun setDarkMode now that it can update buttons
const isDark = isDarkMode();
setDarkMode(isDark);
const modeButton = document.getElementById("light-dark-button");
modeButton.addEventListener("click", function() {
const wasDark = isDarkMode();
setDarkMode(!wasDark);
localStorage.setItem("theme", wasDark ? "light" : "dark");
});
});
// Share button and embed in LMS code
window.addEventListener("DOMContentLoaded", function(event) {
const shareButton = document.getElementById("embed-button");
if (shareButton) {
const sharePopup = document.getElementById("embed-popup");
const embedCode = "<iframe src='" + window.location.href + "?embed' width='100%' height='1000px' frameborder='0'></iframe>";
const embedTextbox = document.getElementById("embed-code-textbox");
if (embedTextbox) {
embedTextbox.value = embedCode;
}
shareButton.addEventListener("click", function() {
sharePopup.classList.toggle("hidden");
});
const copyButton = document.getElementById("copy-embed-button");
if (copyButton) {
copyButton.addEventListener("click", function() {
const embedTextbox = document.getElementById("embed-code-textbox");
if (embedTextbox) {
navigator.clipboard.writeText(embedCode).then(() => {
console.log("Embed code copied to clipboard!");
}).catch(err => {
console.error("Failed to copy embed code: ", err);
});
//copyButton.innerHTML = "✓✓";
// show confirmation for 2 seconds:
copyButton.querySelector('.icon').innerText = "library_add_check";
setTimeout(function() {
copyButton.querySelector('.icon').innerText = "content_copy";
sharePopup.classList.add("hidden");
}, 450);
}
});
}
}
});
// Hide everything except the content when the URL has "embed" in it
window.addEventListener("DOMContentLoaded", function(event) {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has("embed")) {
// Set dark mode based on value of param
if (urlParams.get("embed") === "dark") {
setDarkMode(true);
} else {
setDarkMode(false);
}
const elemsToHide = [
"ptx-navbar",
"ptx-masthead",
"ptx-page-footer",
"ptx-sidebar",
"ptx-content-footer"
];
for (let id of elemsToHide) {
const elem = document.getElementById(id);
if (elem) {
elem.classList.add("hidden");
}
}
}
});
// START Support for code-copy button functionality
document.addEventListener("click", (ev) => {
const codeBox = ev.target.closest(".clipboardable");
if (!navigator.clipboard || !codeBox) return;
const button = ev.target.closest(".code-copy");
const preContent = codeBox.querySelector("pre").textContent;
// Copy a clone with "unselectable" content removed (e.g. a console prompt),
// so the copied text matches what a manual selection would capture.
const pre = codeBox.querySelector("pre").cloneNode(true);
pre.querySelectorAll(".unselectable").forEach((el) => el.remove());
const preContent = pre.textContent;
navigator.clipboard.writeText(preContent);
button.classList.toggle("copied")
setTimeout(() => button.classList.toggle("copied"), 1000);
@@ -1279,3 +1215,12 @@ document.addEventListener("DOMContentLoaded", () => {
}
});
// END Support for code-copy button functionality
window.addEventListener("DOMContentLoaded", () => {
const userDropdownButton = document.getElementById("ptx-user-dropdown-button");
const userDropdownContent = document.getElementById("ptx-user-dropdown-content");
if (userDropdownButton && userDropdownContent) {
new PTXDropdown(userDropdownContent, userDropdownButton);
}
});
+62 -93
View File
@@ -5,35 +5,34 @@
// or
// var ptx_lunr_search_style = "reference";
// since there is only one search box now, this can be simplified
function doSearch(searchlocation="A") {
// Get the search terms from the input text box
var terms;
if(searchlocation == "A") {
terms = document.getElementById("ptxsearch").value;
} else {
terms = document.getElementById("ptxsearchB").value;
}
localStorage.setItem('last-search-terms', JSON.stringify({terms: terms, time: Date.now()}));
// Where do we want to put the results?
let resultArea = document.getElementById("searchresults")
resultArea.innerHTML = ""; // clear out any previous results
// assume AND for multiple words
var searchterms = terms;
if(searchlocation == "B") {
document.getElementById("ptxsearch").value = searchterms
console.log("ptxsearch value", document.getElementById("ptxsearch").value);
} else {
searchterms = terms;
// stub for i18next to future-proof the code. We don't actually use it for
// anything right now, but it will be needed if we want to localize the
// accessibility search status messages.
window.i18next = window.i18next || {
t(key, params = {}) {
for (const param in params) {
key = key.replace(`{{${param}}}`, params[param]);
}
return key;
}
};
searchterms = searchterms.toLowerCase().trim();
function doSearch() {
// Get the search terms from the input text box
const terms = document.getElementById("ptx-search-terms").value;
localStorage.setItem('last-search-terms', JSON.stringify({terms: terms, time: Date.now()}));
// Where do we want to put the results?
let resultArea = document.getElementById("ptx-search-results")
resultArea.innerHTML = ""; // clear out any previous results
// assume AND for multiple words
const searchTerms = terms.toLowerCase().trim();
let pageResult = [];
if(searchterms != "") {
if(searchTerms != "") {
pageResult = ptx_lunr_idx.query((q) => {
for(let term of searchterms.split(' ')) {
for(let term of searchTerms.split(' ')) {
q.term(term, { fields: ["title"], boost: 20 }); //exact title match with 20x weight
q.term(term, { wildcard: lunr.Query.wildcard.TRAILING, fields: ["title"], boost: 10 }); //inexact title 10x weight
q.term(term, { fields: ["body"], boost: 5 }); //exact body 5x weight
@@ -102,7 +101,7 @@ function augmentResults(result, docs) {
const startClipInd = positionData[0];
const endClipInd = positionData[0] + positionData[1];
res.title = res.title.substring(0, endClipInd) + '</span>' + res.title.substring(endClipInd);
res.title = res.title.substring(0, startClipInd) + '<span class="search-result-clip-highlight">' + res.title.substring(startClipInd);
res.title = res.title.substring(0, startClipInd) + '<span class="ptx-search-result-clip-highlight">' + res.title.substring(startClipInd);
titleMarked = true;
}
} else if (res.matchData.metadata[hit].body) {
@@ -115,7 +114,7 @@ function augmentResults(result, docs) {
const startClipInd = positionData[0];
const endClipInd = positionData[0] + positionData[1];
let resultSnippet = (startInd > 0 ? '...' : '' ) + bodyContent.substring(startInd, startClipInd);
resultSnippet += '<span class="search-result-clip-highlight">' + bodyContent.substring(startClipInd, endClipInd) + '</span>';
resultSnippet += '<span class="ptx-search-result-clip-highlight">' + bodyContent.substring(startClipInd, endClipInd) + '</span>';
resultSnippet += bodyContent.substring(endClipInd, endInd) + (endInd < bodyContent.length ? '...' : '' ) + '<br/>';
res.body += resultSnippet;
}
@@ -180,36 +179,23 @@ function compareScoreDesc(a, b) {
}
function addResultToPage(searchterms, result, docs, numUnshown, resultArea) {
/* backward compatibility for old html */
if (document.getElementById("searchempty")) {
document.getElementById("searchempty").style.display = "none";
}
let len = result.length;
console.log("first result", result[0]);
const searchStatus = document.getElementById("ptx-search-status");
if (len == 0) {
if (document.getElementById("searchempty")) {
document.getElementById("searchempty").style.display = "block";
} else {
let noresults = document.createElement("div");
noresults.classList.add("noresults");
search_no_results_string = "No results were found"
noresults.innerHTML = search_no_results_string + ".";
// console.log("the new variable", search_results_heading_string);
resultArea.appendChild(noresults);
}
document.getElementById("searchresultsplaceholder").style.display = null;
return
document.getElementById("ptx-search-empty").style.display = "block";
document.getElementById("ptx-search-dialog").style.display = null;
searchStatus.innerHTML = window.i18next.t('No results found for "{{terms}}".', { terms: searchterms });
return;
}
// console.log("result",result);
document.getElementById("ptx-search-empty").style.display = "none";
searchStatus.innerHTML = window.i18next.t('{{count}} results found.', { count: len });
let allScores = result.map(function (r) { return r.score });
// console.log(typeof allScores[0], "allScores",allScores);
allScores.sort((a,b) => (a - b));
allScores.reverse();
// console.log("allScores, sorted",allScores);
// let high = result[Math.floor(len*0.25)].score;
// let med = result[Math.floor(len*0.5)].score;
// let low = result[Math.floor(len*0.75)].score;
// sort the results by their position in the book, not their score
let high = allScores[Math.floor(len*0.20)];
let med = allScores[Math.floor(len*0.40)];
@@ -226,19 +212,19 @@ function addResultToPage(searchterms, result, docs, numUnshown, resultArea) {
// add a class so we can colorize the results based on their rank in terms
// of search score.
if (res.score >= high) {
link.classList.add("high_result")
link.classList.add("ptx-search-result-high")
} else if (res.score >= med) {
link.classList.add("medium_result")
link.classList.add("ptx-search-result-medium")
} else if (res.score >= low) {
link.classList.add("low_result")
link.classList.add("ptx-search-result-low")
} else {
link.classList.add("no_result")
link.classList.add("ptx-search-result-none")
}
currIndent = res.level;
if (currIndent > indent) {
indent = currIndent;
let ilist = document.createElement("ul")
ilist.classList.add("detailed_result");
ilist.classList.add("ptx-search-detailed-result");
resultArea.appendChild(ilist);
resultArea = ilist;
} else if (currIndent < indent) {
@@ -248,14 +234,14 @@ function addResultToPage(searchterms, result, docs, numUnshown, resultArea) {
link.href = `${res.url}`;
link.innerHTML = `${res.type} ${res.number} ${res.title}`;
let clip = document.createElement("div");
clip.classList.add("search-result-clip");
clip.classList.add("ptx-search-result-clip");
clip.innerHTML = `${res.body}`;
let bullet = document.createElement("li");
bullet.classList.add('search-result-bullet');
bullet.classList.add('ptx-search-result-bullet');
bullet.appendChild(link);
bullet.appendChild(clip);
let p = document.createElement("text");
p.classList.add('search-result-score');
p.classList.add('ptx-search-result-score');
p.innerHTML = ` (${res.score.toFixed(2)})`;
bullet.appendChild(p);
resultArea.appendChild(bullet);
@@ -263,53 +249,36 @@ function addResultToPage(searchterms, result, docs, numUnshown, resultArea) {
// Auto-close search results when a result is clicked in case result is on
// the same page search started from
const resultsDiv = document.getElementById('searchresultsplaceholder');
const backDiv = document.querySelector('.searchresultsbackground');
const resultsDialog = document.getElementById('ptx-search-dialog');
resultArea.querySelectorAll("a").forEach((link) => {
link.addEventListener('click', (e) => {
backDiv.style.display = 'none';
resultsDiv.style.display = 'none';
resultsDialog.close()
});
});
//Could print message about how many results are not shown. No way to localize it though...
// if(numUnshown > 0) {
// let bullet = document.createElement("li");
// bullet.classList.add('search-results-bullet');
// bullet.classList.add('search-results-unshown-count');
// let p = document.createElement("text");
// p.innerHTML = `${parseInt(numUnshown)} unshown results...`;
// bullet.appendChild(p);
// resultArea.appendChild(bullet);
// }
document.getElementById("searchresultsplaceholder").style.display = null;
document.getElementById("ptx-search-dialog").style.display = null;
MathJax.typesetPromise();
}
window.addEventListener("load", function (event) {
const resultsDiv = document.getElementById('searchresultsplaceholder');
const searchDialogElement = document.getElementById('ptx-search-dialog');
const searchButtonElement = document.getElementById('ptx-search-button');
const closeBtn = document.getElementById("ptx-search-close");
const searchDialog = new PTXDialog(searchDialogElement, searchButtonElement, {
closeButton: closeBtn,
});
//insert a div to be backgroud behind searchresultsplaceholder
const backDiv = document.createElement("div");
backDiv.classList.add("searchresultsbackground");
backDiv.style.display = 'none';
resultsDiv.parentNode.appendChild(backDiv);
document.getElementById("searchbutton").addEventListener('click', (e) => {
resultsDiv.style.display = null;
backDiv.style.display = null;
let searchInput = document.getElementById("ptxsearch");
searchInput.value = JSON.parse(localStorage.getItem("last-search-terms")).terms;
searchButtonElement.addEventListener('click', (e) => {
// Attempt to restore last search
const lastSearch = localStorage.getItem("last-search-terms");
let searchInput = document.getElementById("ptx-search-terms");
searchInput.value = lastSearch ? (JSON.parse(lastSearch)?.terms || "") : "";
searchInput.select();
doSearch();
if(searchInput.value) {
doSearch();
}
});
document.getElementById("ptxsearch").addEventListener('input', (e) => {
document.getElementById("ptx-search-terms").addEventListener('input', (e) => {
doSearch();
});
document.getElementById("closesearchresults").addEventListener('click', (e) => {
resultsDiv.style.display = 'none';
backDiv.style.display = 'none';
document.getElementById('searchbutton').focus();
});
});
File diff suppressed because it is too large Load Diff
-112
View File
@@ -1,112 +0,0 @@
function doSearch() {
// Get the search terms from the input text box
let terms = document.getElementById("ptxsearch").value;
// Where do we want to put the results?
let resultArea = document.getElementById("searchresults")
resultArea.innerHTML = ""; // clear out any previous results
// do the search using the provided index
let pageResult = ptx_lunr_idx.search(terms);
// Number the documents from first to last so we can order the results by their
// position in the book.
snum = 0;
for (let doc of ptx_lunr_docs) {
doc.snum = snum;
snum += 1;
}
// Transfer meta data from the document to the results to make it easy to add
// our lists later.
augmentResults(pageResult, ptx_lunr_docs);
addResultToPage(pageResult, ptx_lunr_docs, resultArea);
MathJax.typeset();
}
// Find the entry for a search result in the original document index
function findEntry(resultId, db) {
for (const page of db) {
if (page.id === resultId) {
return page;
}
}
return resultId;
}
function augmentResults(result, docs) {
for (let res of result) {
let info = findEntry(res.ref, docs);
res.number = info.number;
res.type = info.type;
res.title = info.title;
res.url = info.url;
res.level = info.level;
res.snum = info.snum;
}
}
function comparePosition(a, b) {
if (a.snum < b.snum) {
return -1;
}
if (a.snum > b.snum) {
return 1;
}
return 0;
}
function addResultToPage(result, docs, resultArea) {
let len = result.length
let high = result[Math.floor(len*0.25)].score;
let med = result[Math.floor(len*0.5)].score;
let low = result[Math.floor(len*0.75)].score;
// sort the results by their position in the book, not their score
result = result.sort(comparePosition)
let indent = "1";
let currIndent = indent;
let origResult = resultArea;
// Create list entries indenting as needed.
for (const res of result) {
let link = document.createElement("a")
// add a class so we can colorize the results based on their rank in terms
// of search score.
if (res.score >= high) {
link.classList.add("high_result")
} else if (res.score >= med) {
link.classList.add("medium_result")
} else if (res.score >= low) {
link.classList.add("low_result")
}
currIndent = res.level;
if (currIndent > indent) {
indent = currIndent;
let ilist = document.createElement("ul")
ilist.classList.add("detailed_result");
resultArea.appendChild(ilist);
resultArea = ilist;
} else if (currIndent < indent) {
resultArea = origResult;
indent = currIndent;
}
let bullet = document.createElement("li")
bullet.style.marginTop = "5px";
link.href = `${res.url}`;
link.innerHTML = `${res.type} ${res.number} ${res.title}`;
bullet.appendChild(link)
let p = document.createElement("text");
p.innerHTML = ` (${res.score.toFixed(2)})`;
bullet.appendChild(p);
resultArea.appendChild(bullet);
}
}
function showHelp() {
let state = document.getElementById("helpme").style.display;
if (state == "none") {
document.getElementById("helpme").style.display = "block";
document.getElementById("helpbutt").innerHTML = "Hide Help"
} else {
document.getElementById("helpme").style.display = "none";
document.getElementById("helpbutt").innerHTML = "Show Help"
}
}
+22
View File
@@ -0,0 +1,22 @@
/*******************************************************************************
* pretext-core.js entry point for the core PreTeXt bundle
*******************************************************************************
* This file is NOT shipped directly. jsbuilder.mjs reads it as the entry point
* for esbuild, which bundles these imports into js/dist/pretext-core.js.
*
* Load order matters: pretext-dialog.js defines PTXDialog (used by
* pretext_search.js), and knowl.js hooks into the DOM at "load" time alongside
* pretext_add_on.js, so they need to share the same event-listener order they
* had when loaded as separate <script> elements.
*
* When adding new always-loaded scripts, import them here rather than adding
* additional <script> tags to the XSL.
******************************************************************************/
import './pretext-dialog.js';
import './pretext-dropdown.js';
import './readability-options.js';
import '../pretext.js';
import '../pretext_add_on.js';
import './pretext-embed.js';
import '../knowl.js';
+246
View File
@@ -0,0 +1,246 @@
// Standard dialog widget for PreTeXt UI
// Builds on native dialog with accessibility enhancements and fallback for
// browsers with limited support
class PTXDialog {
static hasNativeCommandInvokers() {
return 'commandForElement' in HTMLButtonElement.prototype;
}
// dialogElement: should be a <dialog> element
// openButton: is an optional element that triggers the dialog to open and will receive focus again when the dialog closes
// if provided, will automatically have an event listener added to open the dialog on click
// options can include:
// - kind: whether the dialog is "modal" (the default), "light-close" or "non-modal"
// - "modal" traps focus and must be dismissed with the close button or escape
// - "light-close" are model, but close if the user clicks outside the dialog
// - "non-modal" do not trap focus and can be interacted with while open
// - closeButton: button element that should close the dialog when clicked
// If not provided for a modal dialog, one will be added.
constructor(dialogElement, openButton = null, options = {}) {
this.dialog = dialogElement;
this.controlElement = openButton;
this.kind = options.kind || "modal";
this.isModal = this.kind === "modal" || this.kind === "light-close";
// verify we have a dialog and set some basic attributes on the dialog
if (!this.dialog) {
console.log("PTXDialog: No dialog element provided.");
return;
}
this.dialog.setAttribute("aria-modal", this.isModal ? "true" : "false");
if (PTXDialog.hasNativeCommandInvokers()) {
if (this.isModal) {
this.dialog.closedBy = (this.kind !== "light-close") ? "closerequest" : "any";
} else {
// non-modal dialogs don't have a native closedBy behavior
// but make explicit
this.dialog.closedBy = "none";
}
}
// set up the control element if provided
if (this.controlElement ) {
this.controlElement.setAttribute('aria-expanded', "false");
this.controlElement.setAttribute('aria-controls', this.dialog.id);
if(PTXDialog.hasNativeCommandInvokers()) {
this.controlElement.commandFor = this.dialog.id;
}
if (this.isModal) {
this.controlElement.addEventListener("click", () => this.open());
} else {
this.controlElement.addEventListener("click", () => this.toggle());
}
}
this.closeButton = options.closeButton;
// add a close button to modals unless the dialog already has one as identified in options
if (!this.closeButton && this.isModal) {
const topBar = document.createElement("div");
topBar.classList.add("ptx-dialog-topbar");
this.dialog.prepend(topBar);
this.closeButton = document.createElement("button");
this.closeButton.classList.add("button", "ptx-dialog-close-button");
this.closeButton.setAttribute("aria-label", "Close dialog");
this.closeButton.innerHTML = `<span class="material-symbols-outlined">close</span>`;
topBar.appendChild(this.closeButton);
}
if (this.closeButton) {
this.closeButton.addEventListener("click", () => this.close());
}
if (!this.isModal) {
// For non-modal dialogs, make a top bar as a grab area for dragging
const topBar = document.createElement("div");
topBar.classList.add("ptx-dialog-topbar");
this.topBar = topBar;
this.dialog.prepend(topBar);
}
if (PTXDialog.hasNativeCommandInvokers()) {
// If the browser supports command invokers, we can just use the native dialog element and its showModal and close methods.
this.open = () => {
if (this.isModal) {
this.dialog.showModal();
} else {
this.dialog.show();
}
if (this.controlElement) {
this.setExpanded(true);
}
};
this.close = () => {
this.dialog.close();
if (this.controlElement) {
this.controlElement.focus();
this.setExpanded(false);
}
};
this.toggle = () => {
if (this.dialog.open) {
this.close();
} else {
this.open();
}
};
} else {
// Otherwise, we use the fallback functions defined above to manage the dialog state.
this.open = () => this.openDialogFallback();
this.close = () => this.closeDialogFallback();
this.toggle = () => this.toggleDialogFallback();
}
if (!PTXDialog.hasNativeCommandInvokers() && this.kind === "light-close") {
// Add event listener to close the dialog if the user clicks outside of it
this.dialog.addEventListener("click", (event) => {
if (event.target === this.dialog) {
// need to ask for bounding rext and do manual check
// to include border and padding area of the dialog
const rect = this.dialog.getBoundingClientRect();
const isInDialog = (
rect.top <= event.clientY &&
event.clientY <= rect.top + rect.height &&
rect.left <= event.clientX &&
event.clientX <= rect.left + rect.width
);
if (!isInDialog) {
this.close();
}
}
});
}
// Should be handled natively, but Sagecells currently break native esc handling
if (this.isModal) {
this.dialog.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
this.close();
}
});
}
// make non-modal dialogs draggable by their top bar
if (!this.isModal) {
const topBar = this.dialog.querySelector(".ptx-dialog-topbar");
let isDragging = false;
let offsetX = 0;
let offsetY = 0;
topBar.addEventListener("pointerover", (e) => {
topBar.style.cursor = "move";
});
// Trigger when the user presses down on the element
topBar.addEventListener("pointerdown", (e) => {
isDragging = true;
const dialogRect = this.dialog.getBoundingClientRect();
// Track the pointer offset within the dialog so movement stays smooth.
offsetX = e.clientX - dialogRect.left;
offsetY = e.clientY - dialogRect.top;
// Lock pointer to capture movement even outside the element boundaries
topBar.setPointerCapture(e.pointerId);
});
// Trigger as the user moves the pointer
topBar.addEventListener("pointermove", (e) => {
if (!isDragging) return;
// Calculate new coordinates from the current pointer position.
const newX = e.clientX - offsetX;
const newY = e.clientY - offsetY;
// Apply styles to move the element.
this.dialog.style.left = `${newX}px`;
this.dialog.style.top = `${newY}px`;
this.dialog.style.bottom = "auto"; // Reset bottom to auto to allow top positioning
this.dialog.style.right = "auto"; // Reset right to auto to allow left positioning
});
// Trigger when the user releases the pointer
topBar.addEventListener("pointerup", (e) => {
isDragging = false;
topBar.releasePointerCapture(e.pointerId);
});
// Make sure we stay in view during resizes
window.addEventListener('resize', (event) => {
this.dialog.style.left = '';
this.dialog.style.right = '';
// make sure top is in viewport
if (this.dialog.getBoundingClientRect().top > window.innerHeight) {
this.dialog.style.top = '20px';
}
});
}
}
setExpanded(expanded) {
if (this.controlElement) {
this.controlElement.setAttribute('aria-expanded', expanded ? 'true' : 'false');
if (expanded) {
this.controlElement.classList.add('open');
} else {
this.controlElement.classList.remove('open');
}
}
}
openDialogFallback() {
if (this.dialog && typeof this.dialog.showModal === "function" && !this.dialog.open) {
if(this.isModal) {
this.dialog.showModal();
} else {
this.dialog.show();
}
this.setExpanded(true);
}
}
closeDialogFallback() {
if (this.dialog && typeof this.dialog.close === "function" && this.dialog.open) {
this.dialog.close();
this.setExpanded(false);
}
if (this.controlElement) {
this.controlElement.focus();
}
}
toggleDialogFallback() {
if (!this.dialog) {
return;
}
if (this.dialog.open) {
this.closeDialogFallback();
} else {
this.openDialogFallback();
}
}
}
// PTXDialog is used by pretext_search.js, which is a separately-loaded script.
// When this file is bundled into pretext-core.js (IIFE format), it is scoped
// to the bundle. Assigning to window makes it reachable globally.
window.PTXDialog = PTXDialog;
+155
View File
@@ -0,0 +1,155 @@
// Standard dropdown widget for PreTeXt UI.
// Uses ordinary DOM state and ARIA attributes; intentionally does not use the
// popover API so it works with existing positioned navbar menus. Potentially
// revisit that once popover support is more widespread and stable.
class PTXDropdown {
// dropdownElement: menu element to show and hide
// openButton: element that toggles the dropdown and receives focus on close
constructor(dropdownElement, openButton = null, options = {}) {
this.dropdown = dropdownElement;
this.controlElement = openButton;
this.closeOnSelect = options.closeOnSelect !== false;
if (!this.dropdown) {
console.warn("PTXDropdown: No dropdown element provided.");
return;
}
this.dropdown.hidden = true;
if (this.controlElement) {
this.controlElement.setAttribute("aria-expanded", "false");
this.controlElement.setAttribute("aria-controls", this.dropdown.id);
this.controlElement.addEventListener("click", (event) => {
event.preventDefault();
this.toggle();
});
this.controlElement.addEventListener("keydown", (event) => {
if (event.key === "ArrowDown" || event.key === "Enter" || event.key === " ") {
event.preventDefault();
this.open({ focusMenu: true });
}
});
// Close on escape even if opened via click and focus is on button
this.controlElement.addEventListener("keydown", (event) => {
if (event.key === "Escape" && this.isOpen()) {
event.preventDefault();
this.close();
}
});
}
this.dropdown.addEventListener("keydown", (event) => this.handleKeydown(event));
this.dropdown.addEventListener("click", (event) => {
if (this.closeOnSelect && event.target.closest('[role="menuitem"], a, button')) {
this.close({ restoreFocus: false });
}
});
document.addEventListener("click", (event) => {
if (!this.isOpen()) return;
if (this.dropdown.contains(event.target) || this.controlElement?.contains(event.target)) {
return;
}
this.close({ restoreFocus: false });
});
}
isOpen() {
return !this.dropdown.hidden;
}
setExpanded(expanded) {
this.dropdown.hidden = !expanded;
this.dropdown.classList.toggle("open", expanded);
if (this.controlElement) {
this.controlElement.setAttribute("aria-expanded", expanded ? "true" : "false");
this.controlElement.classList.toggle("open", expanded);
}
}
open(options = {}) {
// All links should not be tabbable, navigation is via arrow keys
// Links may have been added post-initialization, so set tabindex here rather than on initialization
this.dropdown.querySelectorAll("a").forEach((link) => {
link.setAttribute("tabindex", "-1");
});
this.setExpanded(true);
if (options.focusMenu) {
this.focusFirstItem();
}
}
close(options = {}) {
this.setExpanded(false);
if (options.restoreFocus !== false && this.controlElement) {
this.controlElement.focus();
}
}
toggle() {
if (this.isOpen()) {
this.close();
} else {
this.open();
}
}
menuItems() {
return Array.from(this.dropdown.querySelectorAll('[role="menuitem"], a, button'))
.filter((item) => !item.hasAttribute("disabled") && item.getAttribute("aria-disabled") !== "true");
}
focusFirstItem() {
this.menuItems()[0]?.focus();
}
focusLastItem() {
const items = this.menuItems();
items[items.length - 1]?.focus();
}
focusNextItem(currentItem, direction) {
const items = this.menuItems();
if (!items.length) return;
const currentIndex = items.indexOf(currentItem);
const nextIndex = currentIndex === -1
? 0
: (currentIndex + direction + items.length) % items.length;
items[nextIndex].focus();
}
handleKeydown(event) {
switch (event.key) {
case "Escape":
event.preventDefault();
this.close();
break;
case "ArrowDown":
event.preventDefault();
this.focusNextItem(document.activeElement, 1);
break;
case "ArrowUp":
event.preventDefault();
this.focusNextItem(document.activeElement, -1);
break;
case "Home":
event.preventDefault();
this.focusFirstItem();
break;
case "End":
event.preventDefault();
this.focusLastItem();
break;
case "Tab":
this.close({ restoreFocus: false });
break;
}
}
}
window.PTXDropdown = PTXDropdown;
+80
View File
@@ -0,0 +1,80 @@
// Share button and embed in LMS code
window.addEventListener("DOMContentLoaded", function(event) {
const shareButton = document.getElementById("ptx-embed-button");
const sharePopupElement = document.getElementById("ptx-embed-popup");
if (!shareButton || !sharePopupElement) {
return;
}
const closeBtn = document.getElementById("ptx-embed-close-button");
const sharePopup = new PTXDialog(
sharePopupElement,
shareButton,
{
kind: "light-close",
closeButton: closeBtn
}
);
const embedCode = "<iframe src='" + window.location.href + "?embed' width='100%' height='1000px' frameborder='0'></iframe>";
const embedTextbox = document.getElementById("ptx-embed-code-textbox");
if (embedTextbox) {
embedTextbox.value = embedCode;
}
const copyButton = document.getElementById("ptx-embed-copy-button");
if (copyButton) {
if (navigator.clipboard) {
copyButton.addEventListener("click", function() {
const embedTextbox = document.getElementById("ptx-embed-code-textbox");
if (embedTextbox) {
if (navigator.clipboard) {
navigator.clipboard.writeText(embedCode).then(() => {
console.log("Embed code copied to clipboard!");
copyButton.querySelector('.icon').innerText = "library_add_check";
setTimeout(function() {
copyButton.querySelector('.icon').innerText = "content_copy";
sharePopup.close();
shareButton.focus();
}, 450);
}).catch(err => {
console.error("Failed to copy embed code: ", err);
});
} else {
console.warn("Clipboard API not supported, falling back to manual copy.");
}
}
});
} else {
// If clipboard API is not supported, hide the copy button and
// rely on users to manually copy from the textbox
copyButton.style.display = "none";
}
}
});
// Hide everything except the content when the URL has "embed" in it
window.addEventListener("DOMContentLoaded", function(event) {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has("embed")) {
// Set dark mode based on value of param
if (urlParams.get("embed") === "dark") {
setDarkMode(true);
} else {
setDarkMode(false);
}
const elemsToHide = [
"ptx-navbar",
"ptx-masthead",
"ptx-page-footer",
"ptx-sidebar",
"ptx-content-footer"
];
for (let id of elemsToHide) {
const elem = document.getElementById(id);
if (elem) {
elem.classList.add("hidden");
}
}
}
});
@@ -0,0 +1,325 @@
// Handle controls for readability dialog / options
// This script needs to be run before initial render as it makes significant
// visual changes
//-----------------------------------------------------------------
// Dark/Light mode switching
function getSavedTheme() {
const savedTheme = localStorage.getItem("theme");
if (savedTheme === "light" || savedTheme === "dark") {
return savedTheme;
}
return "system";
}
function setSavedTheme(theme) {
if (theme === "system") {
localStorage.removeItem("theme");
} else {
localStorage.setItem("theme", theme);
}
}
function applyThemeChoice(theme) {
if (theme === "system") {
setDarkMode(isDarkMode());
} else {
setDarkMode(theme === "dark");
}
}
function isDarkMode() {
if (document.documentElement.dataset.darkmode === 'disabled')
return false;
const currentTheme = localStorage.getItem("theme");
if (currentTheme === "dark")
return true;
else if (currentTheme === "light")
return false;
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
}
function setDarkMode(isDark) {
if(document.documentElement.dataset.darkmode === 'disabled')
return;
const parentHtml = document.documentElement;
const iframes = document.querySelectorAll("iframe[data-dark-mode-enabled]");
// Update the parent document
if (isDark) {
parentHtml.classList.add("dark-mode");
} else {
parentHtml.classList.remove("dark-mode");
}
// Sync each iframe's <html> class with the parent
for (const iframe of iframes) {
try {
const iframeHtml = iframe.contentWindow.document.documentElement;
if (isDark) {
iframeHtml.classList.add("dark-mode")
} else {
iframeHtml.classList.remove("dark-mode")
}
} catch (err) {
console.warn("Dark mode sync to iframe failed:", err);
}
}
}
//-----------------------------------------------------------------
// Line height controls
function getSavedLineHeight() {
const savedLineHeight = localStorage.getItem("lineHeight");
if (isValidLineHeight(savedLineHeight)) {
return savedLineHeight;
}
return null;
}
function isValidLineHeight(value) {
return value !== null && !isNaN(value) && Number(value) > 0 && Number(value) < 5;
}
function formatLineHeight(value) {
return Number(value).toFixed(2);
}
function applyLineHeight(lineHeight) {
if (isValidLineHeight(lineHeight)) {
document.documentElement.style.setProperty("--ptx-content-line-height", lineHeight);
}
}
function updateLineHeightOutput(output, lineHeight) {
if (output) {
output.value = formatLineHeight(lineHeight);
}
}
//-----------------------------------------------------------------
// Font size controls
function getSavedFontSize() {
const savedFontSize = localStorage.getItem("fontSize");
if (isValidFontSize(savedFontSize)) {
return savedFontSize;
}
return null;
}
function isValidFontSize(value) {
return value !== null && !isNaN(value) && Number(value) > 0 && Number(value) < 5;
}
function formatFontSize(value) {
return `${Math.round(Number(value) * 100)}%`;
}
function applyFontSize(fontSize) {
if (isValidFontSize(fontSize)) {
document.documentElement.style.setProperty("--ptx-content-font-size", formatFontSize(fontSize));
}
}
function updateFontSizeOutput(output, fontSize) {
if (output) {
output.value = formatFontSize(fontSize);
}
}
//-----------------------------------------------------------------
// Permalink accessibility controls
function getSavedAccessiblePermalinks() {
return localStorage.getItem("accessiblePermalinks") === "true";
}
function setSavedAccessiblePermalinks(accessiblePermalinks) {
if (accessiblePermalinks) {
localStorage.setItem("accessiblePermalinks", "true");
} else {
localStorage.removeItem("accessiblePermalinks");
}
}
function setAutopermalinksAccessible(accessible) {
const autopermalinks = document.querySelectorAll('.autopermalink');
autopermalinks.forEach(permalink => {
const link = permalink.querySelector('a');
if (!link) {
return;
}
if (accessible) {
permalink.removeAttribute('aria-hidden');
link.setAttribute('tabindex', '0');
} else {
permalink.setAttribute('aria-hidden', 'true');
link.setAttribute('tabindex', '-1');
}
});
}
//-----------------------------------------------------------------
// Core functionality
function resetReadabilityOptions(options) {
localStorage.removeItem("theme");
localStorage.removeItem("lineHeight");
localStorage.removeItem("fontSize");
localStorage.removeItem("accessiblePermalinks");
const systemThemeInput = document.getElementById("ptx-readability-theme-system");
if (systemThemeInput) {
systemThemeInput.checked = true;
}
applyThemeChoice("system");
if (options.lineHeightInput) {
options.lineHeightInput.value = options.defaultLineHeight;
updateLineHeightOutput(options.lineHeightOutput, options.defaultLineHeight);
document.documentElement.style.removeProperty("--ptx-content-line-height");
}
if (options.fontSizeInput) {
options.fontSizeInput.value = options.defaultFontSize;
updateFontSizeOutput(options.fontSizeOutput, options.defaultFontSize);
applyFontSize(options.defaultFontSize);
}
if (options.accessiblePermalinksInput) {
options.accessiblePermalinksInput.checked = false;
setAutopermalinksAccessible(false);
}
}
window.addEventListener("DOMContentLoaded", function() {
const readabilityButton = document.getElementById("ptx-readability-options-button");
const readabilityPopupElement = document.getElementById("ptx-readability-options-popup");
if (!readabilityButton || !readabilityPopupElement || !window.PTXDialog) {
return;
}
const closeButton = document.getElementById("ptx-readability-options-close-button");
new window.PTXDialog(
readabilityPopupElement,
readabilityButton,
{
closeButton: closeButton
}
);
const themeInputs = readabilityPopupElement.querySelectorAll('input[name="ptx-readability-theme"]');
const savedTheme = getSavedTheme();
for (const input of themeInputs) {
input.checked = input.value === savedTheme;
input.addEventListener("change", function() {
if (!this.checked) {
return;
}
setSavedTheme(this.value);
applyThemeChoice(this.value);
});
}
// Listen for system theme changes and update if user has "system" selected
if (window.matchMedia) {
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function() {
if (getSavedTheme() === "system") {
applyThemeChoice("system");
}
});
}
// apply again once DOM is loaded to make sure iframes are in sync
setDarkMode(isDarkMode());
const lineHeightInput = document.getElementById("ptx-readability-line-height");
const lineHeightOutput = document.getElementById("ptx-readability-line-height-value");
const defaultLineHeight = lineHeightInput ? lineHeightInput.defaultValue : null;
const savedLineHeight = getSavedLineHeight();
if (lineHeightInput) {
if (savedLineHeight) {
lineHeightInput.value = savedLineHeight;
applyLineHeight(savedLineHeight);
}
updateLineHeightOutput(lineHeightOutput, lineHeightInput.value);
lineHeightInput.addEventListener("input", function() {
updateLineHeightOutput(lineHeightOutput, this.value);
if (!isValidLineHeight(this.value)) {
return;
}
localStorage.setItem("lineHeight", this.value);
applyLineHeight(this.value);
});
}
const fontSizeInput = document.getElementById("ptx-readability-font-size");
const fontSizeOutput = document.getElementById("ptx-readability-font-size-value");
const defaultFontSize = fontSizeInput ? fontSizeInput.defaultValue : null;
const savedFontSize = getSavedFontSize();
if (fontSizeInput) {
if (savedFontSize) {
fontSizeInput.value = savedFontSize;
applyFontSize(savedFontSize);
}
updateFontSizeOutput(fontSizeOutput, fontSizeInput.value);
fontSizeInput.addEventListener("input", function() {
updateFontSizeOutput(fontSizeOutput, this.value);
if (!isValidFontSize(this.value)) {
return;
}
localStorage.setItem("fontSize", this.value);
applyFontSize(this.value);
});
}
const accessiblePermalinksInput = document.getElementById("ptx-readability-accessible-permalinks");
if (accessiblePermalinksInput) {
accessiblePermalinksInput.checked = getSavedAccessiblePermalinks();
accessiblePermalinksInput.addEventListener("change", function() {
setSavedAccessiblePermalinks(this.checked);
setAutopermalinksAccessible(this.checked);
});
}
setAutopermalinksAccessible(getSavedAccessiblePermalinks());
const resetButton = document.getElementById("ptx-readability-reset-button");
if (resetButton) {
resetButton.addEventListener("click", function() {
resetReadabilityOptions({
fontSizeInput: fontSizeInput,
fontSizeOutput: fontSizeOutput,
defaultFontSize: defaultFontSize,
accessiblePermalinksInput: accessiblePermalinksInput,
defaultLineHeight: defaultLineHeight,
lineHeightInput: lineHeightInput,
lineHeightOutput: lineHeightOutput
});
});
}
});
// Run these as soon as possible to avoid flicker - don't wait for DOMContentLoaded
// They may be re-applied on DOMContentLoaded, but this way we minimize the chance
// of a flash of unstyled content for users who have changed defaults from the system settings
setDarkMode(isDarkMode());
applyLineHeight(getSavedLineHeight());
applyFontSize(getSavedFontSize());
// isDarkMode and setDarkMode are called from XSL-generated inline <script> blocks
// and from other modules (e.g. mermaid, embed code). Expose them globally.
window.isDarkMode = isDarkMode;
window.setDarkMode = setDarkMode;
-510
View File
@@ -1,510 +0,0 @@
editorLog = console.log;
// editorLog = function(){};
debugLog = function(){};
// debugLog = console.log;
//parseLog = function(){};
parseLog = console.log;
errorLog = console.log;
/* the structure of each object, and its realization as PreTeXt source or in HTML,
is recorded in the objectStructure dictionary.
{"xml:id": new_id, "sourcetag": new_tag, "parent": parent_description, "title": ""}
In pretext, pieces are of the form ["piecename", "tag"], while
in source, pieces are of the form ["piecename", "required_component"], while
*/
editing_mode = 0;
current_page = location.host+location.pathname;
debugLog("current_page", current_page);
chosen_edit_option_key = "edit_option".concat(current_page);
/*
chosen_edit_option = readCookie(chosen_edit_option_key) || "";
editing_mode = chosen_edit_option;
*/
chosen_edit_option = "PLACEHOLDER";
debugLog("chosen_edit_option", chosen_edit_option, "chosen_edit_option", chosen_edit_option > 0);
var font_families = {
'RS': "'Roboto Serif', serif;",
'OS': "'Open Sans', sans-serif;"
}
var font_vals = {
// 'face': 'serif',
'size': [12, 8, 20],
'height': [135, 80, 200],
'wspace': [0, -10,20],
'lspace': [0, -20,20],
'wdth': [100, 50, 150],
'wght': [400, 100, 1000]
}
function fontcss(fvals) {
console.log("in fontcss",fvals);
var csskeys = Object.keys(fvals);
var this_style = "";
for (var j=0; j < csskeys.length; ++j) {
this_key = csskeys[j];
document.getElementById("the" + this_key).innerHTML = fvals[this_key][0];
console.log("in fontcss", this_key, "with value", fvals[this_key][0], "/10", fvals[this_key][0]/10.0);
if (this_key == 'size') {
this_style += "font-size: " + fvals[this_key][0].toString() + "pt; "
} else if (this_key == 'height') {
this_style += "line-height: " + (fvals[this_key][0]/100.0).toString() + "; "
} else if (this_key == 'lspace') {
this_style += "letter-spacing: " + (fvals[this_key][0]/200.0).toString() + "rem; "
} else if (this_key == 'wspace') {
this_style += "word-spacing: " + (fvals[this_key][0]/50.0).toString() + "rem; "
}
}
this_style += "font-variation-settings: ";
for (var j=0; j < csskeys.length; ++j) {
this_key = csskeys[j];
if (this_key == 'wdth') {
this_style += "'wdth' " + fvals[this_key][0].toString() + ","
} else if (this_key == 'wght') {
this_style += "'wght' " + fvals[this_key][0].toString() + ","
}
}
this_style = this_style.slice(0, -1);
this_style += ";";
console.log("returning this_style", this_style);
return this_style
}
function choice_options(this_choice) {
console.log("choice_options of", this_choice);
val_dict = prefs_menu_vals[this_choice];
console.log("val_dict", val_dict);
var these_keys = Object.keys(val_dict);
these_keys.sort();
var these_choices = "";
for (var j=0; j < these_keys.length; ++j) {
this_key = these_keys[j];
these_choices += '<li id="' + this_key + '">';
these_choices += val_dict[this_key];
these_choices += '</li>';
}
return these_choices
}
// we have to keep track of multiple consecutive carriage returns
this_char = "";
prev_char = "";
prev_prev_char = "";
// sometimes we have to prevent Tab from changing focus
this_focused_element = "";
prev_focused_element = "";
prev_prev_focused_element = "";
var menu_neutral_background = "#ddb";
var menu_active_background = "#fdd";
var recent_editing_actions = []; // we unshift to this, so most recent edit is first.
// currently just a human-readable list
var ongoing_editing_actions = [];
var old_content = {}; // to hold old versions of changed materials
// what will happen with internationalization?
var keyletters = ["KeyA", "KeyB", "KeyC", "KeyD", "KeyE", "KeyF", "KeyG", "KeyH", "KeyI", "KeyJ", "KeyK", "KeyL", "KeyM", "KeyN", "KeyO", "KeyP", "KeyQ", "KeyR", "KeyS", "KeyT", "KeyU", "KeyV", "KeyW", "KeyX", "KeyY", "KeyZ"];
var movement_location_options = [];
var movement_location = 0;
var first_move = true; // used when starting to move, because object no longer occupies its original location
Storage.prototype.setObject = function(key, value) {
// this.setItem(key, JSON.stringify(value));
this.setItem(key, JSON.stringify(value, function(key, val) {
// console.log("key", key, "value", value, "val", val);
return val.toFixed ? Number(val.toFixed(3)) : val;
}));
}
Storage.prototype.getObject = function(key) {
var value = this.getItem(key);
return value && JSON.parse(value);
}
function randomstring(len) {
if (!len) { len = 10 }
return "tMP" + (Math.random() + 1).toString(36).substring(2,len)
}
function removeItemFromList(lis, value) {
var index = lis.indexOf(value);
if (index > -1) {
lis.splice(index, 1);
}
return lis;
}
function textNodesUnder(node){
var all = [];
for (node=node.firstChild;node;node=node.nextSibling){
if (node.nodeType==3) { all.push([3, node]) }
else if (node.nodeType==1) {
all.push([1, node])
var thistag = node.cloneNode().outerHTML;
console.log("thistag", thistag);
[thisopen, thisclose] = thistag.split("><");
thisopen += ">"; thisclose += "<";
thisinsides = textNodesUnder(node.cloneNode().innerHTML);
// all.push([0, thisopen]);
for (var j=0; j < thisinsides.length; ++j) {
// all.push(thisinsides[j])
}
// all.push([0, thisclose]);
}
// probably we only want direct children
// else all = all.concat(textNodesUnder(node));
}
return all;
}
function wordsAllWrapped(node) {
var these_text_nodes = textNodesUnder(node);
console.log("node", node);
console.log("these_text_nodes", these_text_nodes);
for (var j=0; j < these_text_nodes.length; ++j) {
var this_text_node = these_text_nodes[j];
var thistype = this_text_node[0];
var thisnode = this_text_node[1];
if (thistype == 3) {
// var these_node_words_and_spaces = these_text_nodes[j].nodeValue.split(/(\s+)/);
var these_node_words_and_spaces = thisnode.nodeValue.split(/(\s+)/);
console.log("these_node_words_and_spaces", these_node_words_and_spaces);
var spanned_words = "";
for (var k=0; k < these_node_words_and_spaces.length; ++k) {
spanned_words += '<span class="oneword">' + these_node_words_and_spaces[k] + "</span>"
}
var wass_text = document.createElement('div');
wass_text.setAttribute('class', 'wastext');
wass_text.innerHTML = spanned_words;
// these_text_nodes[j].nodeValue = spanned_words
thisnode.replaceWith(wass_text);
wass_text.outerHTML = wass_text.innerHTML
} else if (thistype == 1) {
// // leave it there, but make sure we know about it
// thisnode.classList.add("oneelement")
if (thisnode.classList.contains("process-math") ||
thisnode.classList.contains("autopermalink") ||
thisnode.classList.contains("latex-logo") ||
thisnode.classList.contains("heading") ||
// should we instead check for tags that *can* contain line breaks?
["A", "CODE", "PRE"].includes(thisnode.tagName)) {
//wrap it in a span.oneword
var word_wrapper = document.createElement('span');
word_wrapper.setAttribute('class', 'oneword');
thisnode.parentNode.insertBefore(word_wrapper, thisnode);
word_wrapper.appendChild(thisnode);
} else {
wordsAllWrapped(thisnode)
}
}
}
}
function linesAllWrapped() {
// var testID = "p-446";
// var testNode = document.getElementById(testID);
// var all_para = document.querySelectorAll(".para");
// var all_para = document.querySelectorAll("SECTION P");
var all_para = document.querySelectorAll("SECTION .para:not(.logical)");
for (var pj = 0; pj < all_para.length; ++pj) {
testNode = all_para[pj];
wordsAllWrapped(testNode);
console.log(" wordsAllWrapped", pj, "of", testNode);
var these_words = document.querySelectorAll(".oneword, .oneelement");
var this_line = [];
var all_lines = "";
var current_height = these_words[0].getBoundingClientRect().bottom;
var word_depth = 0;
for (var j=0; j < these_words.length; ++j) {
// next line is an error if the paragraphs starts with an element
var this_word = these_words[j];
var this_parent = this_word.parentElement;
if (this_word.classList.contains("oneword")) {
this_height = this_word.getBoundingClientRect().bottom;
if ( (Math.abs(this_height - current_height) < 10) && j > 0) {
if (this_parent == testNode && word_depth == 0) {
this_line.push(this_word.innerHTML)
} else {
console.log(word_depth, ":",this_parent == testNode, "this_parent", this_parent.tagName, "ccc", this_parent.ClassList, "xxx", this_word.innerHTML);
if (word_depth == 0) {
console.log("opening the tag", this_parent.tagName,"parent", this_parent,"of",this_word.innerHTML);
var parent_classlist = this_parent.classList;
console.log("parent_classlist", parent_classlist, "first", parent_classlist[0], "length", parent_classlist.length);
// did not work. why? var parent_classes = parent_classlist.join(" ");
this_line.push('<' + this_parent.tagName + ' class="' + parent_classlist[0] + '">');
this_line.push(this_word.innerHTML);
word_depth += 1
} else {
this_line.push(this_word.innerHTML);
}
if (this_word.nextElementSibling == null) {
console.log("closing the tag", this_parent.tagName);
// need to close the wrapping element
this_line.push("</" + this_parent.tagName + ">");
word_depth -= 1;
if (word_depth > 0 && this_word.parentElement.nextElementSibling == null) {
this_line.push("</" + this_parent.parentElement.tagName + ">");
word_depth -= 1;
}
}
}
} else {
html_line_contents = "";
for (var k=0; k < this_line.length; ++k) {
html_line_contents += this_line[k]
}
// hack which only handles up to depth 2
if (word_depth > 0) {
html_line_contents += "</" + this_parent.tagName + ">"
}
if (word_depth > 1) {
html_line_contents += "</" + this_parent.parentElement.tagName + ">"
}
console.log("made", html_line_contents);
all_lines += '<div class="onelineX">' + html_line_contents + '</div>';
current_height = this_height;
this_line = [this_word.innerHTML];
if (word_depth > 0) {
this_line.unshift('<' + this_parent.tagName + ' class="' + this_parent.classList[0] + '">')
}
if (word_depth > 1) {
this_line.unshift('<' + this_parent.tagName + ' class="' + this_parent.parentElement.classList[0] + '">')
}
}
} else { // we have an html node
this_line.push(this_word.outerHTML)
}
}
// partial last line may be dangling
// bug: need to fix closing tags at the end? maybe rely on automatic closure
if (this_line.length > 0) {
html_line_contents = "";
for (var k=0; k < this_line.length; ++k) {
html_line_contents += this_line[k]
}
all_lines += '<div class="onelineX">' + html_line_contents + '</div>'
}
testNode.innerHTML = all_lines
}
}
editorLog("adding tab listener");
document.addEventListener('keydown', logKeyDown);
function nextsibligntabbable(startingplace, where) {
var candidate = startingplace.nextElementSibling;
if (where == "previous" ) { candidate = startingplace.previousElementSibling }
while (candidate) {
console.log("candidate A", candidate);
if (candidate.hasAttribute("tabindex")) { return candidate }
console.log("candidate B", candidate);
if (where == "next" ) { candidate = candidate.nextElementSibling }
else { candidate = candidate.previousElementSibling }
}
}
function logKeyDown(e) {
if (e.code == "ShiftLeft" || e.code == "ShiftRight" || e.code == "Shift") { return }
prev_prev_char = prev_char;
prev_char = this_char;
this_char = e;
debugLog("logKey",e,"XXX",e.code);
var input_region = document.activeElement;
debugLog("input_region", input_region);
if (input_region.id == "user-preferences-button") {
if (e.code == "Enter") {
document.getElementById("preferences_menu_holder").classList.toggle("hidden")
} else if (e.code == "Tab") {
console.log("tabbing to main pref menu", document.getElementById("preferences_menu_holder").firstElementChild);
console.log("tabbing to main pref menu", document.getElementById("preferences_menu_holder").firstElementChild.firstElementChild);
e.preventDefault();
document.getElementById("preferences_menu_holder").firstElementChild.firstElementChild.focus()
}
} else if (input_region.hasAttribute("data-env")) {
var this_submenu = input_region.getElementsByTagName("ol")[0];
console.log("this_submenu C", this_submenu);
if ((e.code == "Enter") || (e.code == "ArrowRight")) {
console.log("selecting",input_region, "which has", input_region.getElementsByTagName("ol"), "first",input_region.getElementsByTagName("ol")[0]);
this_submenu.classList.toggle("hidden")
input_region.classList.toggle("selected")
input_region.parentElement.classList.toggle("active")
} else if (e.code == "Tab" && e.shiftKey) {
e.preventDefault();
if (input_region.classList.contains("selected")) {
this_submenu.classList.loggle("hidden");
input_region.classList.loggle("selected");
} else { // cycle up through the elements
// previous_sibling = input_region.previousElementSibling;
previous_sibling = nextsibligntabbable(input_region, "previous");
if (previous_sibling) { previous_sibling.focus() }
else {
document.getElementById("preferences_menu_holder").classList.toggle("hidden");
document.getElementById("user-preferences-button").focus();
}
}
} else if (e.code == "Tab") {
e.preventDefault();
if (input_region.classList.contains("selected")) {
var firstchild = this_submenu.firstElementChild;
if (firstchild.hasAttribute("tabindex")) {
firstchild.focus()
} else {
nextsibligntabbable(firstchild, "next").focus()
}
} else { // cycle through the elements
// next_sibling = input_region.nextElementSibling;
next_sibling = nextsibligntabbable(input_region, "next");
if (next_sibling) { next_sibling.focus() }
else {
document.getElementById("preferences_menu_holder").classList.toggle("hidden");
document.getElementById("user-preferences-button").focus();
}
}
}
} else if (input_region.hasAttribute("data-val")) {
console.log("input_region", input_region);
console.log("input_regionparentElement", input_region.parentElement);
if ((e.code == "Enter") || (e.code == "ArrowRight")) {
console.log("font_vals is", font_vals);
var dataval = input_region.getAttribute("data-val");
var datachange = input_region.getAttribute("data-change");
console.log("dataval", dataval, "datachange",datachange);
if (input_region.parentElement.classList.contains("fonts")) {
font_vals[dataval][0] += parseFloat(datachange);
if (font_vals[dataval][0] < font_vals[dataval][1]) { font_vals[dataval][0] = font_vals[dataval][1] }
else if (font_vals[dataval][0] > font_vals[dataval][2]) { font_vals[dataval][0] = font_vals[dataval][2] }
console.log("font_vals are", font_vals);
console.log("css font_vals", fontcss(font_vals));
var new_style = fontcss(font_vals);
var paras = document.getElementsByClassName('para');
for (i = 0; i < paras.length; i++) {
paras[i].setAttribute('style', new_style);
}
} else if (input_region.parentElement.classList.contains("fontfamily")) {
document.body.setAttribute("data-font", datachange);
var checks = document.getElementsByClassName('ffcheck');
for (i = 0; i < checks.length; i++) {
checks[i].innerHTML = '';
}
document.getElementById("the" + datachange).innerHTML = "✔️";
} else if (input_region.parentElement.classList.contains("avatar")) {
var checks = document.getElementsByClassName('avatarcheck');
for (i = 0; i < checks.length; i++) {
checks[i].innerHTML = '';
}
document.getElementById("theavatarbutton").innerHTML = dataval;
document.getElementById("the" + dataval).innerHTML = "✔️";
} else if (input_region.parentElement.classList.contains("atmosphere")) {
document.body.setAttribute("data-atmosphere", dataval);
var checks = document.getElementsByClassName('atmospherecheck');
for (i = 0; i < checks.length; i++) {
checks[i].innerHTML = '';
}
document.getElementById("the" + dataval).innerHTML = "✔️";
} else if (input_region.parentElement.classList.contains("ruler")) {
// could be motion or actual ruler
if (["mouse", "arrow", "eye"].includes(dataval)) {
document.body.setAttribute("data-motion", dataval);
var checks = document.getElementsByClassName('motioncheck');
for (i = 0; i < checks.length; i++) {
checks[i].innerHTML = '';
}
document.getElementById("the" + dataval).innerHTML = "✔️";
} else {
if (!document.body.hasAttribute("data-ruler")) {
linesAllWrapped()
}
document.body.setAttribute("data-ruler", dataval);
var checks = document.getElementsByClassName('rulercheck');
for (i = 0; i < checks.length; i++) {
checks[i].innerHTML = '';
}
document.getElementById("the" + dataval).innerHTML = "✔️";
}
}
} else if (e.code == "Tab" && e.shiftKey) {
e.preventDefault();
// previous_sibling = input_region.previousElementSibling;
previous_sibling = nextsibligntabbable(input_region, "previous");
console.log("previous_sibling",previous_sibling);
if (previous_sibling) { previous_sibling.focus() }
else {
input_region.parentElement.parentElement.parentElement.classList.toggle("active");
input_region.parentElement.parentElement.classList.toggle("selected");
input_region.parentElement.parentElement.focus();
input_region.parentElement.classList.toggle("hidden");
}
} else if (e.code == "Tab") {
e.preventDefault();
// next_sibling = input_region.nextElementSibling;
next_sibling = nextsibligntabbable(input_region, "next");
if (next_sibling) { next_sibling.focus() }
else {
input_region.parentElement.parentElement.parentElement.classList.toggle("active");
input_region.parentElement.parentElement.classList.toggle("selected");
input_region.parentElement.parentElement.focus();
input_region.parentElement.classList.toggle("hidden");
}
}
}
return;
editorLog("input_region", input_region);
// if we are writing something, keystrokes usually are just text input
if (document.getElementById('actively_editing')) {
editorLog(" we are actively editing");
if (e.code == "Tab" && !document.getElementById('local_menu_holder')) {
// disabled for now
e.preventDefault();
return ""
create_local_menu()
} else if (document.getElementById('local_menu_holder')) {
main_menu_navigator(e);
} else {
local_editing_action(e)
}
} else if (document.getElementById('phantomobject')) {
var the_phantomobject = document.getElementById('phantomobject');
if (the_phantomobject.classList.contains('move')) {
move_object(e)
} else {
alert("do not know what to do with that")
}
} else {
console.log("calling main_menu_navigator");
main_menu_navigator(e);
}
}