Latest build deployed.
This commit is contained in:
Executable → Regular
+1
-1
File diff suppressed because one or more lines are too long
Executable → Regular
+3
-3
File diff suppressed because one or more lines are too long
Executable → Regular
+1
-1
File diff suppressed because one or more lines are too long
Executable → Regular
+3
-3
File diff suppressed because one or more lines are too long
@@ -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(/< /g, "<");
|
||||
the_ans = the_ans.replace(/</g, "<");
|
||||
the_ans = the_ans.replace(/>/g, ">");
|
||||
the_ans = the_ans.replace(/&/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→';
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
Executable → Regular
+2
-2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Vendored
+282
@@ -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
@@ -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
@@ -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
|
||||
+2106
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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
@@ -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
|
||||
+1122
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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';
|
||||
});
|
||||
@@ -1,131 +0,0 @@
|
||||
|
||||
var icon = {
|
||||
"warning": "⚠️ ",
|
||||
"commentary": "☕",
|
||||
"media": "▶",
|
||||
"tip": "📣",
|
||||
"worksheet": "📄",
|
||||
"assesment": "A<sup>+</sup>",
|
||||
"slides": "📻",
|
||||
"outcomes": "✓"
|
||||
};
|
||||
|
||||
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> <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
|
||||
Executable → Regular
+88
-21
@@ -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
@@ -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);
|
||||
|
||||
|
||||
Vendored
-2
File diff suppressed because one or more lines are too long
@@ -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);
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
Executable → Regular
+8
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
});
|
||||
Executable → Regular
+2
-2
@@ -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.
|
||||
|
||||
Executable → Regular
@@ -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 < 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
Executable → Regular
+1
-1
@@ -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>';
|
||||
|
||||
|
||||
Executable → Regular
+1
-1
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Executable → Regular
+162
-217
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
Executable → Regular
+62
-93
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user