Latest build deployed.
This commit is contained in:
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Executable
+3
File diff suppressed because one or more lines are too long
Executable
+5
@@ -0,0 +1,5 @@
|
||||
/*! Hammer.JS - v2.0.8 - 2016-04-23
|
||||
* http://hammerjs.github.io/
|
||||
*
|
||||
* Copyright (c) 2016 Jorik Tangelder;
|
||||
* Licensed under the MIT license */
|
||||
Executable
BIN
Binary file not shown.
Executable
+1
File diff suppressed because one or more lines are too long
Executable
BIN
Binary file not shown.
Executable
+3
File diff suppressed because one or more lines are too long
Executable
+5
@@ -0,0 +1,5 @@
|
||||
/*! Hammer.JS - v2.0.8 - 2016-04-23
|
||||
* http://hammerjs.github.io/
|
||||
*
|
||||
* Copyright (c) 2016 Jorik Tangelder;
|
||||
* Licensed under the MIT license */
|
||||
Executable
BIN
Binary file not shown.
Executable
+1
File diff suppressed because one or more lines are too long
Executable
BIN
Binary file not shown.
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" ?>
|
||||
<all>
|
||||
<js type="list">
|
||||
<item type="str">prefix-runtime.fc0e080e84d54281.bundle.js</item>
|
||||
<item type="str">prefix-runtime.0d5f811896ddeac1.bundle.js</item>
|
||||
<item type="str">prefix-723.3e6434f80549315a.bundle.js</item>
|
||||
<item type="str">prefix-runestone.e2abc79debce15fd.bundle.js</item>
|
||||
</js>
|
||||
@@ -10,5 +10,5 @@
|
||||
<item type="str">prefix-runestone.6ec5960970d8b364.css</item>
|
||||
</css>
|
||||
<cdn-url type="str">https://runestone.academy/cdn/runestone/</cdn-url>
|
||||
<version type="str">7.11.11</version>
|
||||
<version type="str">7.11.13</version>
|
||||
</all>
|
||||
|
||||
+2
File diff suppressed because one or more lines are too long
BIN
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
File diff suppressed because one or more lines are too long
Executable
+338
@@ -0,0 +1,338 @@
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
|
||||
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"]
|
||||
}
|
||||
});
|
||||
@@ -17,6 +17,14 @@ const stackstring = {
|
||||
"api_correct":"Correct answers"
|
||||
};
|
||||
|
||||
|
||||
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>");
|
||||
}
|
||||
|
||||
// Create data for call to API.
|
||||
async function collectData(qfile, qname, qprefix) {
|
||||
let res = "";
|
||||
@@ -95,6 +103,7 @@ function send(qfile, qname, qprefix) {
|
||||
// This is a bit of a hack. The question render returns an <a href="..."> calling the download function with
|
||||
// two arguments. We add the additional arguments that we need for context (question definition) here.
|
||||
question = question.replace(/javascript:download\(([^,]+?),([^,]+?)\)/, `javascript:download($1,$2, '${qfile}', '${qname}', '${qprefix}', ${seed})`);
|
||||
question = wrap_math(question);
|
||||
if (input.samplesolutionrender && name !== 'remember') {
|
||||
// Display render of answer and matching user input to produce the answer.
|
||||
correctAnswers += `<p>
|
||||
@@ -102,7 +111,7 @@ function send(qfile, qname, qprefix) {
|
||||
${stackstring['api_which_typed']}: `;
|
||||
for (const [name, solution] of Object.entries(input.samplesolution)) {
|
||||
if (name.indexOf('_val') === -1) {
|
||||
correctAnswers += `<span class='correct-answer'>${solution}</span>`;
|
||||
correctAnswers += `<span class='correct-answer'>${wrap_math(solution)}</span>`;
|
||||
}
|
||||
}
|
||||
correctAnswers += '.</p>';
|
||||
@@ -147,7 +156,7 @@ function send(qfile, qname, qprefix) {
|
||||
let sampleText = json.questionsamplesolutiontext;
|
||||
if (sampleText) {
|
||||
sampleText = replaceFeedbackTags(sampleText,qprefix);
|
||||
document.getElementById(`${qprefix+'generalfeedback'}`).innerHTML = sampleText;
|
||||
document.getElementById(`${qprefix+'generalfeedback'}`).innerHTML = wrap_math(sampleText);
|
||||
document.getElementById(`${qprefix+'stackapi_generalfeedback'}`).style.display = 'block';
|
||||
} else {
|
||||
// If the question is updated, there may no longer be general feedback.
|
||||
@@ -165,7 +174,7 @@ function send(qfile, qname, qprefix) {
|
||||
document.getElementById(`${qprefix+'stackapi_correct'}`).style.display = 'none';
|
||||
|
||||
createIframes(json.iframes);
|
||||
MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
|
||||
MathJax.typeset();
|
||||
}
|
||||
catch(e) {
|
||||
console.log(e);
|
||||
@@ -207,14 +216,14 @@ function validate(element, qfile, qname, qprefix) {
|
||||
renameIframeHolders();
|
||||
const validationHTML = json.validation;
|
||||
const element = document.getElementsByName(`${qprefix+validationPrefix + answerName}`)[0];
|
||||
element.innerHTML = validationHTML;
|
||||
element.innerHTML = wrap_math(validationHTML);
|
||||
if (validationHTML) {
|
||||
element.classList.add('validation');
|
||||
} else {
|
||||
element.classList.remove('validation');
|
||||
}
|
||||
createIframes(json.iframes);
|
||||
MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
|
||||
MathJax.typeset();
|
||||
}
|
||||
catch(e) {
|
||||
document.getElementById(`${qprefix+'errors'}`).innerText = http.responseText;
|
||||
@@ -274,7 +283,7 @@ function answer(qfile, qname, qprefix, seed) {
|
||||
json.specificfeedback = json.specificfeedback.replace(name, getPlotUrl(file));
|
||||
}
|
||||
json.specificfeedback = replaceFeedbackTags(json.specificfeedback,qprefix);
|
||||
specificFeedbackElement.innerHTML = json.specificfeedback;
|
||||
specificFeedbackElement.innerHTML = wrap_math(json.specificfeedback);
|
||||
specificFeedbackElement.classList.add('feedback');
|
||||
} else {
|
||||
specificFeedbackElement.classList.remove('feedback');
|
||||
@@ -292,7 +301,7 @@ function answer(qfile, qname, qprefix, seed) {
|
||||
${(json.scores[name] * json.scoreweights[name] * json.scoreweights.total).toFixed(2)}
|
||||
/ ${(json.scoreweights[name] * json.scoreweights.total).toFixed(2)}.</div>`;
|
||||
}
|
||||
element.innerHTML = fb;
|
||||
element.innerHTML = wrap_math(fb);
|
||||
// if (fb) {
|
||||
// element.classList.add('feedback');
|
||||
// } else {
|
||||
@@ -301,7 +310,7 @@ function answer(qfile, qname, qprefix, seed) {
|
||||
}
|
||||
}
|
||||
createIframes(json.iframes);
|
||||
MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
|
||||
MathJax.typeset();
|
||||
}
|
||||
catch(e) {
|
||||
console.log(e);
|
||||
|
||||
@@ -133,25 +133,6 @@ window.addEventListener("DOMContentLoaded",function(event) {
|
||||
}
|
||||
});
|
||||
|
||||
/* jump to next page if reader tries to scroll past the bottom */
|
||||
// var hitbottom = false;
|
||||
// window.onscroll = function(ev) {
|
||||
// if ((window.innerHeight + window.scrollY) >= document.body.scrollHeight) {
|
||||
// // you're at the bottom of the page
|
||||
// console.log("Bottom of page");
|
||||
// if (hitbottom) {
|
||||
// console.log("hit bottom again");
|
||||
// thenextbutton = document.getElementsByClassName("next-button")[0];
|
||||
// thenextbutton.click();
|
||||
// } else {
|
||||
// hitbottom = true;
|
||||
// /* only jump to next page if hard scroll in quick succession */
|
||||
// window.scrollBy(0, -20);
|
||||
// setTimeout(function (){ hitbottom = false }, 1000);
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Dynamic TOC logic
|
||||
@@ -246,7 +227,30 @@ window.addEventListener("DOMContentLoaded", function(event) {
|
||||
expander.title = "Expand" + (itemType !== "" ? " " + itemType : "");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Do we have a hash in the URL? If so, we need to identify up to make sure
|
||||
// all parents of that item are expanded
|
||||
if(window.location.hash) {
|
||||
let hash = window.location.hash;
|
||||
// find the link in the TOC that has an href ending in this hash
|
||||
let hashLink = document.querySelector(`.ptx-toc a[href$="${hash}"]`);
|
||||
if(hashLink) {
|
||||
let parentTocItem = hashLink.closest(".toc-item");
|
||||
while(parentTocItem && !parentTocItem.classList.contains("contains-active")) {
|
||||
parentTocItem.classList.add("contains-active");
|
||||
let expander = parentTocItem.querySelector(".toc-expander");
|
||||
if(expander) {
|
||||
//make sure it is expanded
|
||||
if(!parentTocItem.classList.contains("expanded")) {
|
||||
toggleTOCItem(expander);
|
||||
}
|
||||
}
|
||||
parentTocItem = parentTocItem.parentElement.closest(".toc-item");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// This needs to be after the TOC's geometry is settled
|
||||
|
||||
@@ -15,13 +15,6 @@
|
||||
console.log("thisbrowser.userAgent", window.navigator.userAgent);
|
||||
*/
|
||||
|
||||
var minivers = "0";
|
||||
if (typeof miniversion !== 'undefined') {
|
||||
console.log("typeof miniversion", typeof miniversion, "dddd", typeof miniversion == 'undefined');
|
||||
minivers = miniversion.toString();
|
||||
}
|
||||
console.log(" minivers", minivers);
|
||||
|
||||
/* scrollbar width from https://stackoverflow.com/questions/13382516/getting-scroll-bar-width-using-javascript */
|
||||
function getScrollbarWidth() {
|
||||
var outer = document.createElement("div");
|
||||
@@ -289,22 +282,8 @@ function updateURLParameter(url, param, paramVal){
|
||||
return baseURL + "?" + newAdditionalURL + rows_txt;
|
||||
}
|
||||
|
||||
function WWiframeReseed(iframe, seed) {
|
||||
var this_problem = document.getElementsByName(iframe)[0];
|
||||
var this_problem_url = this_problem.src;
|
||||
if (seed === undefined){seed = Number(this_problem.getAttribute('data-seed')) + 80 + 84 + 88;}
|
||||
this_problem.setAttribute('data-seed', seed);
|
||||
this_problem_url = updateURLParameter(this_problem_url, "problemSeed", seed);
|
||||
this_problem.src = this_problem_url;
|
||||
}
|
||||
|
||||
function process_workspace() {
|
||||
console.log("processing workspace");
|
||||
// next does not work, because the cursor does back to the beginning
|
||||
// so: need to handle the cursor
|
||||
// the_text = document.activeElement.innerHTML;
|
||||
// the_text = the_text.replace(/(^|\s)\$([^\$]+)\$(\s|$|[.,!?;:])/g, "\1\\(\2\\)\3")
|
||||
// document.activeElement.innerHTML = the_text
|
||||
MathJax.typesetPromise();
|
||||
}
|
||||
/* for the GeoGebra calculator */
|
||||
@@ -352,17 +331,8 @@ window.addEventListener("load",function(event) {
|
||||
ggbscript.id = "create_ggb_calc";
|
||||
ggbscript.innerHTML = "ggbApp.inject('geogebra-calculator')";
|
||||
document.body.appendChild(ggbscript);
|
||||
// setTimeout( function() {
|
||||
// $("#calculator-toggle").focus();
|
||||
// var inputfield = $("input.gwt-SuggestBox.TextField")[0];
|
||||
// console.log("inputfield", inputfield);
|
||||
// inputfield.focus();
|
||||
// }, 4000);
|
||||
} else {
|
||||
pretext_geogebra_calculator_onload();
|
||||
// var inputfield = $("input.gwt-SuggestBox.TextField")[0];
|
||||
// console.log("inputfield", inputfield);
|
||||
// inputfield.focus();
|
||||
}
|
||||
} else {
|
||||
$('#calculator-container').css('display', 'none');
|
||||
@@ -374,16 +344,6 @@ window.addEventListener("load",function(event) {
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
window.addEventListener("load",function(event) {
|
||||
// setTimeout( function() {
|
||||
console.log("changein play color");
|
||||
$('figure > div.onclick > svg > path').attr('fill', '#0000aa');
|
||||
$('path').attr('fill', '#0000aa')
|
||||
// }, 5000)
|
||||
});
|
||||
*/
|
||||
|
||||
window.addEventListener("load",function(event) {
|
||||
document.onkeyup = function(event)
|
||||
{
|
||||
@@ -402,14 +362,6 @@ window.addEventListener("load",function(event) {
|
||||
console.log("staying in the sage cell", parent_sage_cell, document.activeElement)
|
||||
just_hit_escape = true;
|
||||
setTimeout(function(){ just_hit_escape = false }, 1000);
|
||||
// console.log("parent_sage_cell", parent_sage_cell);
|
||||
// if ($(parent_sage_cell).hasClass('sagecell_editor')) {
|
||||
// console.log("I am trapped in a sage cell", $(document.activeElement).closest(".sagecell_editor"));
|
||||
// console.log($(document.activeElement));
|
||||
// var this_sage_cell = $(document.activeElement).closest(".sagecell_editor");
|
||||
// this_sage_cell.next().focus;
|
||||
// }
|
||||
// else
|
||||
} else
|
||||
if(knowl_focus_stack.length > 0 ) {
|
||||
most_recently_opened = knowl_focus_stack.pop();
|
||||
@@ -426,144 +378,6 @@ window.addEventListener("load",function(event) {
|
||||
},
|
||||
false);
|
||||
|
||||
// a hack for hosted tracking
|
||||
|
||||
window.addEventListener("load",function(event) {
|
||||
if($('body').attr('id') == "judson-AATA") {
|
||||
console.log(" found AATA");
|
||||
console.log(" looking for id");
|
||||
if (typeof eBookConfig !== 'undefined') {
|
||||
if(eBookConfig['username']) {
|
||||
aa_id = "run" + eBookConfig['username'];
|
||||
ut_id = eBookConfig['username'];
|
||||
console.log(" done looking for id", ut_id);
|
||||
var newscript = document.createElement('script');
|
||||
newscript.type = 'text/javascript';
|
||||
newscript.async = true;
|
||||
newscript.src = 'https://pretextbook.org/js/' + '0.13' + '/' + 'trails' + '.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);
|
||||
trail = true;
|
||||
console.log(" done adding script");
|
||||
} else {
|
||||
console.log(" did not find username");
|
||||
}
|
||||
} else {
|
||||
console.log(" did not find eBookConfig")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function loadResource(type, file) {
|
||||
/* type should be js or css */
|
||||
if (typeof js_version === 'undefined') { js_version = '0.2' }
|
||||
if (typeof css_version === 'undefined') { css_version = '0.6' }
|
||||
var newresource, allresources, s;
|
||||
var linktype = "script";
|
||||
if (type == "css") { linktype = "link" }
|
||||
newresource = document.createElement(linktype);
|
||||
|
||||
if (type == "css") {
|
||||
newresource.type = 'text/css';
|
||||
newresource.rel = 'stylesheet';
|
||||
newresource.href = 'https://pretextbook.org/css/' + css_version + '/' + file + '.css';
|
||||
newresource.href += '?minivers=' + minivers;
|
||||
} else if (type == "js") {
|
||||
newresource.type = 'text/javascript';
|
||||
// newscript.async = true;
|
||||
newresource.src = 'https://pretextbook.org/js/' + js_version + '/' + file + '.js';
|
||||
newresource.src += '?minivers=' + minivers;
|
||||
} else {
|
||||
console.log("unknown resource type", type, "for", file);
|
||||
return
|
||||
}
|
||||
|
||||
allresources = document.getElementsByTagName(linktype);
|
||||
s = allresources[allresources.length - 1];
|
||||
console.log('s',s);
|
||||
console.log("adding a resource", newresource);
|
||||
s.parentNode.insertBefore(newresource, s.nextSibling);
|
||||
}
|
||||
|
||||
|
||||
window.addEventListener("load",function(event) {
|
||||
if(false && $('body').attr('id') == "pretext-SA") {
|
||||
console.log(" found DMOI");
|
||||
if (typeof uname === "undefined") { uname = "" }
|
||||
console.log("aaaa", uname, " uname");
|
||||
if(uname == "editor") {
|
||||
loadResource('js', 'edit');
|
||||
} else {
|
||||
console.log("not enabling editing")
|
||||
}
|
||||
/* } else if ($('body').attr('id') == "pugetsound-SW") { */
|
||||
} else if (false && window.location.href.includes("soundwriting.pugetsound")) {
|
||||
/* a bunch of temporary exploration for a Sound Writing survey */
|
||||
console.log("please take our survey");
|
||||
console.log(window.location.href);
|
||||
console.log(window.location.href.includes("soundwriting.pugetsound"));
|
||||
|
||||
loadResource("js", "login");
|
||||
loadResource("css", "features");
|
||||
setTimeout( loadResource("js", "survey"), 1000); /* I know: sloppy */
|
||||
|
||||
// } else if ((typeof online_editable !== 'undefined') && online_editable) {
|
||||
} else if (false && $('body').attr('id') == "pretext-SA") {
|
||||
loadResource('css', 'features');
|
||||
loadResource('js', 'login')
|
||||
loadResource('js', 'edit');
|
||||
} else {
|
||||
var this_source_txt;
|
||||
var source_url = window.location.href;
|
||||
source_url = source_url.replace(/(#|\?).*/, "");
|
||||
source_url = source_url.replace(/html$/, "ptx");
|
||||
if (typeof sourceeditable !== 'undefined') {
|
||||
fetch(source_url).then(
|
||||
function(u){ return u.text();}
|
||||
).then(
|
||||
function(text){
|
||||
this_source_txt = text;
|
||||
if (this_source_txt.includes("404 Not")) {
|
||||
console.log("Editing not enabled: source unavailable")
|
||||
} else {
|
||||
loadResource('css', 'features');
|
||||
loadResource('css', 'edit');
|
||||
loadResource('js', 'login')
|
||||
loadResource('js', 'edit');
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.log("Source file unavailable: editing not possible")
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// this is to open every knowl on a page
|
||||
// (this code is not actually used anywhere)
|
||||
window.addEventListener("load",function(event) {
|
||||
if($('body').hasClass("braillesample")) {
|
||||
var knowl_id_counterX = 0;
|
||||
console.log(" found braillesample");
|
||||
var all_knowls = $('[data-knowl]');
|
||||
console.log("found", all_knowls.length, "knowls");
|
||||
console.log("which are", all_knowls);
|
||||
for (var j=1; j < all_knowls.length; ++j) {
|
||||
console.log(j, "un-knowling", all_knowls[j]);
|
||||
console.log("attr", $(all_knowls[j]).attr("data-knowl"));
|
||||
$knowl = $(all_knowls[j]);
|
||||
if(!$knowl.attr("data-knowl-uid")) {
|
||||
$knowl.attr("data-knowl-uid", knowl_id_counterX);
|
||||
knowl_id_counterX++;
|
||||
}
|
||||
knowl_click_handler($knowl);
|
||||
// knowl_click_handler($(all_knowls[j]))
|
||||
}
|
||||
}});
|
||||
|
||||
// when the anchor is a knowl, open it
|
||||
window.addEventListener("load",function(event) {
|
||||
@@ -594,13 +408,6 @@ window.addEventListener("load",function(event) {
|
||||
});
|
||||
|
||||
|
||||
// What purpose does this serve?
|
||||
function urlattribute() {
|
||||
var this_urlstub = window.location.hostname;
|
||||
document.body.setAttribute("data-urlstub", this_urlstub);
|
||||
}
|
||||
|
||||
|
||||
// The new method for creating pages and adjusting workspace //
|
||||
|
||||
// This is used multiple places to set height of workspace divs to their author-provided heights
|
||||
@@ -614,6 +421,7 @@ function setInitialWorkspaceHeights() {
|
||||
|
||||
// If a printout (worksheet or handout) includes authored pages, we only need to put content before the first page and after the last page into the first and last pages, respectively.
|
||||
function adjustPrintoutPages() {
|
||||
console.log("*** Adjusting printout pages.");
|
||||
const printout = document.querySelector('section.worksheet, section.handout');
|
||||
if (!printout) {
|
||||
console.warn("No printout found, exiting adjustPrintoutPages.");
|
||||
@@ -647,6 +455,7 @@ function adjustPrintoutPages() {
|
||||
|
||||
// This is the main function we will call then a printout does not come from the XSL with pages already defined (for now, the XSL will keep the <page> behavior as an option).
|
||||
function createPrintoutPages(margins) {
|
||||
console.log("*** Creating printout pages with margins:", margins);
|
||||
|
||||
// Assumptions: needs to work for both letter (8.5in x 11in) and a4 (210mm x 297mm) paper sizes. We will work in pixels (96/in): those are 816px x 1056px and 794px x 1122.5px respectively (1 inch = 96 px, 1 cm = 37.8 px). We assume that the printing interface of the browser will do the right thing with these.
|
||||
|
||||
@@ -679,16 +488,6 @@ function createPrintoutPages(margins) {
|
||||
printout.insertBefore(tasks[i], child.nextSibling);
|
||||
}
|
||||
// Skipping separate treatment of exercisegroups for now.
|
||||
//} else if (child.classList.contains('exercisegroup')) {
|
||||
// for (const subChild of child.children) {
|
||||
// if (subChild.classList.contains('exercisegroup-exercises')){
|
||||
// for (const row of subChild.children){
|
||||
// rows.push(row);
|
||||
// }
|
||||
// } else {
|
||||
// rows.push(child);
|
||||
// }
|
||||
// }
|
||||
} else {
|
||||
rows.push(child);
|
||||
}
|
||||
@@ -743,10 +542,90 @@ function createPrintoutPages(margins) {
|
||||
}
|
||||
}
|
||||
|
||||
// Add headers and footers to all pages in a printout. Start with this set to be hidden by default; a toggle later will show/hide them.
|
||||
function addHeadersAndFootersToPrintout() {
|
||||
const printout = document.querySelector('section.worksheet, section.handout');
|
||||
if (!printout) {
|
||||
console.warn("No printout found, exiting addHeadersAndFootersToPrintout.");
|
||||
return;
|
||||
}
|
||||
const pages = printout.querySelectorAll('.onepage');
|
||||
// Loop through pages and add header and footer divs
|
||||
pages.forEach((page, index) => {
|
||||
const isFirstPage = index === 0;
|
||||
// Add header
|
||||
const headerDiv = document.createElement('div');
|
||||
headerDiv.classList.add(isFirstPage ? 'first-page-header' : 'running-header', 'hidden');
|
||||
headerDiv.innerHTML = `<div class="header-left" contenteditable="true"></div><div class="header-center" contenteditable="true"></div><div class="header-right" contenteditable="true"></div>`;
|
||||
page.insertBefore(headerDiv, page.firstChild);
|
||||
// Add footer
|
||||
const footerDiv = document.createElement('div');
|
||||
footerDiv.classList.add(isFirstPage ? 'first-page-footer' : 'running-footer', 'hidden');
|
||||
footerDiv.innerHTML = `<div class="footer-left" contenteditable="true"></div><div class="footer-center" contenteditable="true"></div><div class="footer-right" contenteditable="true"></div>`;
|
||||
page.appendChild(footerDiv);
|
||||
});
|
||||
// Add content based on local storage if available, otherwise from data-attributes on the printout
|
||||
const headerFooterKeys = ['header-first-left', 'header-first-center', 'header-first-right', 'footer-first-left', 'footer-first-center', 'footer-first-right', 'header-running-left', 'header-running-center', 'header-running-right', 'footer-running-left', 'footer-running-center', 'footer-running-right'];
|
||||
const headerFooterContent = {};
|
||||
headerFooterKeys.forEach(key => {
|
||||
headerFooterContent[key] = localStorage.getItem(key) || printout.getAttribute(`data-${key}`) || '';
|
||||
});
|
||||
// First page header and footer
|
||||
document.querySelector('.first-page-header').querySelector('.header-left').innerHTML = headerFooterContent['header-first-left'];
|
||||
document.querySelector('.first-page-header').querySelector('.header-center').innerHTML = headerFooterContent['header-first-center'];
|
||||
document.querySelector('.first-page-header').querySelector('.header-right').innerHTML = headerFooterContent['header-first-right'];
|
||||
document.querySelector('.first-page-footer').querySelector('.footer-left').innerHTML = headerFooterContent['footer-first-left'];
|
||||
document.querySelector('.first-page-footer').querySelector('.footer-center').innerHTML = headerFooterContent['footer-first-center'];
|
||||
document.querySelector('.first-page-footer').querySelector('.footer-right').innerHTML = headerFooterContent['footer-first-right'];
|
||||
// Running headers and footers
|
||||
document.querySelectorAll('.running-header').forEach(headerDiv => {
|
||||
headerDiv.querySelector('.header-left').innerHTML = headerFooterContent['header-running-left'];
|
||||
headerDiv.querySelector('.header-center').innerHTML = headerFooterContent['header-running-center'];
|
||||
headerDiv.querySelector('.header-right').innerHTML = headerFooterContent['header-running-right'];
|
||||
});
|
||||
document.querySelectorAll('.running-footer').forEach(footerDiv => {
|
||||
footerDiv.querySelector('.footer-left').innerHTML = headerFooterContent['footer-running-left'];
|
||||
footerDiv.querySelector('.footer-center').innerHTML = headerFooterContent['footer-running-center'];
|
||||
footerDiv.querySelector('.footer-right').innerHTML = headerFooterContent['footer-running-right'];
|
||||
});
|
||||
// Add event listeners to update local storage when content is edited
|
||||
headerFooterKeys.forEach(key => {
|
||||
const selectorMap = {
|
||||
'header-first-left': '.first-page-header .header-left',
|
||||
'header-first-center': '.first-page-header .header-center',
|
||||
'header-first-right': '.first-page-header .header-right',
|
||||
'footer-first-left': '.first-page-footer .footer-left',
|
||||
'footer-first-center': '.first-page-footer .footer-center',
|
||||
'footer-first-right': '.first-page-footer .footer-right',
|
||||
'header-running-left': '.running-header .header-left',
|
||||
'header-running-center': '.running-header .header-center',
|
||||
'header-running-right': '.running-header .header-right',
|
||||
'footer-running-left': '.running-footer .footer-left',
|
||||
'footer-running-center': '.running-footer .footer-center',
|
||||
'footer-running-right': '.running-footer .footer-right'
|
||||
};
|
||||
const elements = document.querySelectorAll(selectorMap[key]);
|
||||
elements.forEach(elem => {
|
||||
elem.addEventListener('input', () => {
|
||||
localStorage.setItem(key, elem.innerHTML);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// We look at each page and adjust the heights of the workspaces to fit it nicely into the page.
|
||||
// The width and height of the page will now depend on the letter or a4 setting.
|
||||
|
||||
// We look at each page and adjust the heights of the workspaces to fit it nicely into the page.
|
||||
// The width and height of the page will now depend on the letter or a4 setting.
|
||||
function adjustWorkspaceToFitPage({paperSize, margins}) {
|
||||
console.log("*** Adjusting workspace to fit page size:", paperSize, "with margins:", margins);
|
||||
|
||||
// Toggle off workspace highlight if it is on, so it doesn't interfere with resizing
|
||||
const highlightWorkspaceCheckbox = document.getElementById("highlight-workspace-checkbox");
|
||||
const wasHighlighted = highlightWorkspaceCheckbox && highlightWorkspaceCheckbox.checked;
|
||||
if (wasHighlighted) {
|
||||
toggleWorkspaceHighlight(false);
|
||||
}
|
||||
|
||||
let paperWidth, paperHeight;
|
||||
if (paperSize === 'a4' || document.body.classList.contains('a4')) {
|
||||
console.log("Setting page size to A4");
|
||||
@@ -796,6 +675,11 @@ function adjustWorkspaceToFitPage({paperSize, margins}) {
|
||||
page.style.width = "";
|
||||
});
|
||||
console.log("Set page sizes to content area of paper size.");
|
||||
|
||||
// Reset the highlight workspace checkbox state
|
||||
if (wasHighlighted) {
|
||||
toggleWorkspaceHighlight(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions for calculating heights and workspace sizes
|
||||
@@ -855,6 +739,7 @@ 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.
|
||||
let pageBreaks = [];
|
||||
// An array for the minimum cost possible for rows i to the end.
|
||||
@@ -901,7 +786,9 @@ function findPageBreaks(rows, pageHeight) {
|
||||
return pageBreaks;
|
||||
}
|
||||
|
||||
// Function to set CSS variables and @page rules for page geometry. This will be called whenever the paper size or margins change (in practice, only when page size changes, since margins are fixed for now).
|
||||
function setPageGeometryCSS({paperSize, margins}) {
|
||||
console.log("*** Setting page geometry CSS for paper size:", paperSize, "with margins:", margins);
|
||||
// Remove any existing geometry CSS to avoid duplicates
|
||||
const existingStyle = document.getElementById("page-geometry-css");
|
||||
if (existingStyle) {
|
||||
@@ -948,7 +835,6 @@ function toggleWorkspaceHighlight(isChecked) {
|
||||
original.classList.add('original-workspace');
|
||||
const originalHeight = workspace.getAttribute('data-space') || '0px';
|
||||
original.setAttribute('title', 'Author-specified workspace height (' + originalHeight + ')');
|
||||
console.log("setting original workspace height for", workspace);
|
||||
// Use the data-space attribute for height of original workspace
|
||||
original.style.height = originalHeight;
|
||||
// insert original div before the workspace content
|
||||
@@ -964,55 +850,24 @@ function toggleWorkspaceHighlight(isChecked) {
|
||||
}
|
||||
} else {
|
||||
document.body.classList.remove("highlight-workspace");
|
||||
// Remove the original workspace divs. We don't want to keep these in, as they interfere with changing page sizes and workspace heights.
|
||||
document.querySelectorAll('.workspace-container').forEach(container => {
|
||||
const workspace = container.querySelector('.workspace');
|
||||
// Move the workspace out of the container
|
||||
container.parentNode.insertBefore(workspace, container);
|
||||
// Remove the container
|
||||
container.remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Printout print preview and page setup
|
||||
window.addEventListener("load",function(event) {
|
||||
// We condition on the existence of the papersize radio buttons, which only appear in the printout print preview.
|
||||
if (document.querySelector('input[name="papersize"]')) {
|
||||
// First, get the margins for pages to be passed around as needed.
|
||||
const marginList = document.querySelector('section.worksheet, section.handout').getAttribute('data-margins').split(' ');
|
||||
// Convert margin values to pixels if they are not already numbers
|
||||
function toPixels(value) {
|
||||
if (typeof value === "number") return value;
|
||||
if (typeof value !== "string") return 0;
|
||||
value = value.trim();
|
||||
if (value.endsWith("px")) {
|
||||
return parseFloat(value);
|
||||
} else if (value.endsWith("in")) {
|
||||
return Math.floor(parseFloat(value) * 96);
|
||||
} else if (value.endsWith("cm")) {
|
||||
return Math.floor(parseFloat(value) * 37.8);
|
||||
} else if (value.endsWith("mm")) {
|
||||
return Math.floor(parseFloat(value) * 3.78);
|
||||
} else if (value.endsWith("pt")) {
|
||||
return Math.floor(parseFloat(value) * (96 / 72));
|
||||
} else {
|
||||
// fallback: try to parse as px
|
||||
return parseFloat(value) || 0;
|
||||
}
|
||||
}
|
||||
const margins = {
|
||||
top: toPixels(marginList[0] || "0.75in"), // Default to 0.75in if not specified
|
||||
right: toPixels(marginList[1] || "0.75in"),
|
||||
bottom: toPixels(marginList[2] || "0.75in"),
|
||||
left: toPixels(marginList[3] || "0.75in")
|
||||
}
|
||||
// Get the papersize from localStorage or set it based on user's geographic region
|
||||
function getPaperSize() {
|
||||
let paperSize = localStorage.getItem("papersize");
|
||||
if (paperSize) {
|
||||
const radio = document.querySelector(`input[name="papersize"][value="${paperSize}"]`);
|
||||
if (radio) {
|
||||
radio.checked = true;
|
||||
}
|
||||
// Set the papersize class on body
|
||||
document.body.classList.remove("a4", "letter");
|
||||
document.body.classList.add(paperSize);
|
||||
setPageGeometryCSS({paperSize: paperSize, margins: margins});
|
||||
return paperSize;
|
||||
} else {
|
||||
// Try to set papersize based on user's geographic region
|
||||
// Default to 'letter' for North and South America, 'a4' elsewhere
|
||||
// Try to set papersize based on user's geographic region
|
||||
// Default to 'letter' for North and South America, 'a4' elsewhere
|
||||
try {
|
||||
fetch('https://ipapi.co/json/')
|
||||
.then(response => response.json())
|
||||
@@ -1037,69 +892,231 @@ window.addEventListener("load",function(event) {
|
||||
const radio = document.querySelector(`input[name="papersize"][value="letter"]`);
|
||||
if (radio) radio.checked = true;
|
||||
}
|
||||
//NB: the default papersize is set to 'letter' in the body class list.
|
||||
}
|
||||
const papersizeRadios = document.querySelectorAll('input[name="papersize"]');
|
||||
papersizeRadios.forEach(radio => {
|
||||
radio.addEventListener('change', function() {
|
||||
if (this.checked) {
|
||||
document.body.classList.remove("a4", "letter");
|
||||
document.body.classList.add(this.value);
|
||||
localStorage.setItem("papersize", this.value);
|
||||
console.log("Setting papersize to", this.value);
|
||||
return paperSize || "letter";
|
||||
}
|
||||
|
||||
// If the "highlight workspace" checkbox was already checked, then we should restart the process by reloading the page. Specifically, we run into issues when there are .workspace-container divs already present.
|
||||
if (document.querySelector(".workspace-container")) {
|
||||
console.log("Reloading page to apply new papersize with workspace highlight enabled.");
|
||||
window.location.reload();
|
||||
return;
|
||||
} else {
|
||||
// Otherwise, we can just adjust the workspace heights to fit the new paper size.
|
||||
console.log("Adjusting workspace heights to fit new papersize.");
|
||||
adjustWorkspaceToFitPage({paperSize: this.value, margins: margins});
|
||||
setPageGeometryCSS({paperSize: this.value, margins: margins});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
// Function to load the printout section and switch to print stylesheet. This will run whenever a user clicks on a print preview link (which adds ?printpreview=sectionID to the URL).
|
||||
async function loadPrintout(printableSectionID) {
|
||||
|
||||
// Open all details elements (knowls) on the page
|
||||
var born_hidden_knowls = document.querySelectorAll('details');
|
||||
console.log("born_hidden_knowls", born_hidden_knowls);
|
||||
born_hidden_knowls.forEach(function(detail) {
|
||||
detail.open = true;
|
||||
});
|
||||
// If the printout has authored pages, there will be at least one .onepage element.
|
||||
if (document.querySelector('.onepage')) {
|
||||
adjustPrintoutPages();
|
||||
/* not the right way: need to figure out what this needs to wait for */
|
||||
//window.setTimeout(adjustPrintoutPages, 1000);
|
||||
} else {
|
||||
createPrintoutPages(margins);
|
||||
}
|
||||
// After pages are set up, we adjust the workspace heights to fit the page (based on the paper size).
|
||||
adjustWorkspaceToFitPage({paperSize: paperSize, margins: margins});
|
||||
|
||||
console.log("finished adjusting workspace");
|
||||
|
||||
|
||||
// Get the 'highlight workspace' checkbox state from localStorage or set it to false by default
|
||||
const highlightWorkspaceCheckbox = document.getElementById("highlight-workspace-checkbox");
|
||||
if (highlightWorkspaceCheckbox) {
|
||||
highlightWorkspaceCheckbox.checked = localStorage.getItem("highlightWorkspace") === "true";
|
||||
highlightWorkspaceCheckbox.addEventListener("change", function() {
|
||||
localStorage.setItem("highlightWorkspace", this.checked);
|
||||
toggleWorkspaceHighlight(this.checked);
|
||||
// Switch to print-worksheet.css for print preview
|
||||
const themeStylesheetLink = document.querySelector('link[rel="stylesheet"][href*="theme"]');
|
||||
// get the href of the theme stylesheet link
|
||||
const themeStylesheetHref = themeStylesheetLink ? themeStylesheetLink.getAttribute('href') : null;
|
||||
if (themeStylesheetHref) {
|
||||
// replace 'theme.css' with 'print-worksheet.css' in the href
|
||||
const printStylesheetHref = themeStylesheetHref.replace(/theme.*\.css/, 'print-worksheet.css');
|
||||
// update the href of the theme stylesheet link
|
||||
themeStylesheetLink.setAttribute('href', printStylesheetHref);
|
||||
// Wait for the new stylesheet to load. This is important to ensure the styles are applied before the calling function tries to compute workspace sizes.
|
||||
await new Promise((resolve) => {
|
||||
themeStylesheetLink.addEventListener('load', resolve, { once: true });
|
||||
});
|
||||
// Initial toggle to apply the highlight class if checked
|
||||
toggleWorkspaceHighlight(highlightWorkspaceCheckbox.checked);
|
||||
}
|
||||
|
||||
// Find the section with this ID
|
||||
const printableSection = document.getElementById(printableSectionID);
|
||||
if (!printableSection) {
|
||||
console.error("No section found with ID:", printableSectionID);
|
||||
return;
|
||||
}
|
||||
// Remove any existing sections from .ptx-content and add only the printable section
|
||||
const ptxContent = document.querySelector('.ptx-content');
|
||||
const existingSections = ptxContent.querySelectorAll(':scope > section');
|
||||
existingSections.forEach(sec => ptxContent.removeChild(sec));
|
||||
ptxContent.appendChild(printableSection);
|
||||
}
|
||||
|
||||
// Function to redo solutions details to divs with summary as title
|
||||
function rewriteSolutions() {
|
||||
var born_hidden_knowls = document.querySelectorAll('.worksheet details, .handout details');
|
||||
born_hidden_knowls.forEach(function(detail) {
|
||||
const summary = detail.querySelector('summary');
|
||||
const content = detail.innerHTML.replace(summary.outerHTML, '');
|
||||
const div = document.createElement('div');
|
||||
div.classList = detail.classList;
|
||||
if (summary) {
|
||||
const title = document.createElement('h5');
|
||||
title.innerHTML = summary.innerHTML;
|
||||
div.appendChild(title);
|
||||
}
|
||||
const body = document.createElement('div');
|
||||
body.innerHTML = content;
|
||||
div.appendChild(body);
|
||||
detail.parentNode.replaceChild(div, detail);
|
||||
});
|
||||
}
|
||||
|
||||
// Not sure why this is here:
|
||||
window.setTimeout(urlattribute, 1500);
|
||||
}
|
||||
// Utility to convert various CSS length units to pixels
|
||||
function toPixels(value) {
|
||||
if (typeof value === "number") return value;
|
||||
if (typeof value !== "string") return 0;
|
||||
value = value.trim();
|
||||
if (value.endsWith("px")) {
|
||||
return parseFloat(value);
|
||||
} else if (value.endsWith("in")) {
|
||||
return Math.floor(parseFloat(value) * 96);
|
||||
} else if (value.endsWith("cm")) {
|
||||
return Math.floor(parseFloat(value) * 37.8);
|
||||
} else if (value.endsWith("mm")) {
|
||||
return Math.floor(parseFloat(value) * 3.78);
|
||||
} else if (value.endsWith("pt")) {
|
||||
return Math.floor(parseFloat(value) * (96 / 72));
|
||||
} else {
|
||||
// fallback: try to parse as px
|
||||
return parseFloat(value) || 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Event listener for page load to handle print preview setup
|
||||
window.addEventListener("DOMContentLoaded", async function(event) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
// We condition on the existence of the papersize radio buttons, which only appear in the printout print preview.
|
||||
if (urlParams.has("printpreview")) {
|
||||
const printableSectionID = urlParams.get("printpreview");
|
||||
await loadPrintout(printableSectionID);
|
||||
|
||||
// First, get the margins for pages to be passed around as needed.
|
||||
const marginList = document.querySelector('section.worksheet, section.handout').getAttribute('data-margins').split(' ');
|
||||
// Convert margin values to pixels if they are not already numbers
|
||||
const margins = {
|
||||
top: toPixels(marginList[0] || "0.75in"), // Default to 0.75in if not specified
|
||||
right: toPixels(marginList[1] || "0.75in"),
|
||||
bottom: toPixels(marginList[2] || "0.75in"),
|
||||
left: toPixels(marginList[3] || "0.75in")
|
||||
}
|
||||
|
||||
// Transform all solutions details elements to divs with the summary as a title
|
||||
rewriteSolutions();
|
||||
|
||||
// Get the papersize from localStorage or set it based on user's geographic region. This will always return a value (defaulting to 'letter' if all else fails).
|
||||
let paperSize = getPaperSize();
|
||||
if (paperSize) {
|
||||
const radio = document.querySelector(`input[name="papersize"][value="${paperSize}"]`);
|
||||
if (radio) {
|
||||
radio.checked = true;
|
||||
}
|
||||
// Set the papersize class on body
|
||||
document.body.classList.remove("a4", "letter");
|
||||
document.body.classList.add(paperSize);
|
||||
setPageGeometryCSS({paperSize: paperSize, margins: margins});
|
||||
} else {
|
||||
console.warning("Bug: paperSize should always have a value here.");
|
||||
}
|
||||
// Add event listeners to the papersize radio buttons to handle changes
|
||||
const papersizeRadios = document.querySelectorAll('input[name="papersize"]');
|
||||
papersizeRadios.forEach(radio => {
|
||||
radio.addEventListener('change', function() {
|
||||
if (this.checked) {
|
||||
document.body.classList.remove("a4", "letter");
|
||||
document.body.classList.add(this.value);
|
||||
localStorage.setItem("papersize", this.value);
|
||||
setPageGeometryCSS({paperSize: this.value, margins: margins});
|
||||
adjustWorkspaceToFitPage({paperSize: this.value, margins: margins});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add event listeners to the hide hints/answers/solutions checkboxes
|
||||
for (const solutionType of ["hint", "answer", "solution"]) {
|
||||
const checkbox = document.getElementById(`hide-${solutionType}-checkbox`);
|
||||
if (checkbox) {
|
||||
const storageKey = `hide-${solutionType}`;
|
||||
// by default, hide answer and solution divs
|
||||
if (solutionType === "answer" || solutionType === "solution") {
|
||||
if (!localStorage.getItem(storageKey)) {
|
||||
checkbox.checked = true;
|
||||
localStorage.setItem(storageKey, "true");
|
||||
}
|
||||
}
|
||||
// Now adjust based on local storage
|
||||
// set visibility based on current checkbox state
|
||||
checkbox.checked = localStorage.getItem(storageKey) === "true";
|
||||
document.querySelectorAll(`div.${solutionType}`).forEach(elem => {
|
||||
// add hidden to class list
|
||||
if (checkbox.checked) {
|
||||
elem.classList.add("hidden");
|
||||
} else {
|
||||
elem.classList.remove("hidden");
|
||||
}
|
||||
});
|
||||
// Add event listener to toggle visibility
|
||||
checkbox.addEventListener("change", function() {
|
||||
localStorage.setItem(storageKey, this.checked);
|
||||
// toggle visibility of solution divs
|
||||
document.querySelectorAll(`div.${solutionType}`).forEach(elem => {
|
||||
if (checkbox.checked) {
|
||||
elem.classList.add("hidden");
|
||||
} else {
|
||||
elem.classList.remove("hidden");
|
||||
}
|
||||
//adjustPrintoutPages();
|
||||
adjustWorkspaceToFitPage({paperSize: paperSize, margins: margins});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, with everything set up, we create or adjust the printout pages as needed.
|
||||
|
||||
// If the printout has authored pages, there will be at least one .onepage element.
|
||||
if (document.querySelector('.onepage')) {
|
||||
adjustPrintoutPages();
|
||||
} else {
|
||||
createPrintoutPages(margins);
|
||||
}
|
||||
|
||||
// Add headers and footers to all pages in the printout
|
||||
addHeadersAndFootersToPrintout();
|
||||
|
||||
// Add event listeners to the print header/footer checkboxes
|
||||
for (const hf of ["first-page-header", "running-header", "first-page-footer", "running-footer"]) {
|
||||
const checkbox = document.getElementById(`print-${hf}-checkbox`);
|
||||
if (checkbox) {
|
||||
// set visibility based on current checkbox state
|
||||
checkbox.checked = localStorage.getItem(`print-${hf}`) === "true";
|
||||
document.querySelectorAll(`.${hf}`).forEach(elem => {
|
||||
// add hidden to class list
|
||||
if (checkbox.checked) {
|
||||
elem.classList.remove("hidden");
|
||||
} else {
|
||||
elem.classList.add("hidden");
|
||||
}
|
||||
});
|
||||
// Add event listener to toggle visibility
|
||||
checkbox.addEventListener("change", function() {
|
||||
localStorage.setItem(`print-${hf}`, this.checked);
|
||||
// toggle visibility of header/footer divs
|
||||
document.querySelectorAll(`.${hf}`).forEach(elem => {
|
||||
if (checkbox.checked) {
|
||||
elem.classList.remove("hidden");
|
||||
} else {
|
||||
elem.classList.add("hidden");
|
||||
}
|
||||
adjustWorkspaceToFitPage({paperSize: paperSize, margins: margins});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// After pages are set up, we adjust the workspace heights to fit the page (based on the paper size).
|
||||
adjustWorkspaceToFitPage({paperSize: paperSize, margins: margins});
|
||||
|
||||
// Get the 'highlight workspace' checkbox state from localStorage or set it to false by default
|
||||
// NB we need to do this after the adjustment of workspace heights so that the additional original workspace divs don't throw off the calculations when the page is reloaded.
|
||||
const highlightWorkspaceCheckbox = document.getElementById("highlight-workspace-checkbox");
|
||||
if (highlightWorkspaceCheckbox) {
|
||||
highlightWorkspaceCheckbox.checked = localStorage.getItem("highlightWorkspace") === "true";
|
||||
highlightWorkspaceCheckbox.addEventListener("change", function() {
|
||||
localStorage.setItem("highlightWorkspace", this.checked);
|
||||
toggleWorkspaceHighlight(this.checked);
|
||||
});
|
||||
// Initial toggle to apply the highlight class if checked
|
||||
toggleWorkspaceHighlight(highlightWorkspaceCheckbox.checked);
|
||||
}
|
||||
|
||||
console.log("finished adjusting workspace");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"js":["prefix-runtime.fc0e080e84d54281.bundle.js","prefix-723.3e6434f80549315a.bundle.js","prefix-runestone.e2abc79debce15fd.bundle.js"],"css":["prefix-723.3bccd435914aa0ff.css","prefix-runestone.6ec5960970d8b364.css"]}
|
||||
{"js":["prefix-runtime.0d5f811896ddeac1.bundle.js","prefix-723.3e6434f80549315a.bundle.js","prefix-runestone.e2abc79debce15fd.bundle.js"],"css":["prefix-723.3bccd435914aa0ff.css","prefix-runestone.6ec5960970d8b364.css"]}
|
||||
Binary file not shown.
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" ?>
|
||||
<all>
|
||||
<js type="list">
|
||||
<item type="str">prefix-runtime.fc0e080e84d54281.bundle.js</item>
|
||||
<item type="str">prefix-runtime.0d5f811896ddeac1.bundle.js</item>
|
||||
<item type="str">prefix-723.3e6434f80549315a.bundle.js</item>
|
||||
<item type="str">prefix-runestone.e2abc79debce15fd.bundle.js</item>
|
||||
</js>
|
||||
@@ -10,5 +10,5 @@
|
||||
<item type="str">prefix-runestone.6ec5960970d8b364.css</item>
|
||||
</css>
|
||||
<cdn-url type="str">https://runestone.academy/cdn/runestone/</cdn-url>
|
||||
<version type="str">7.11.11</version>
|
||||
<version type="str">7.11.13</version>
|
||||
</all>
|
||||
|
||||
Reference in New Issue
Block a user