diff --git a/bases/rsptx/book_server_api/routers/assessment.py b/bases/rsptx/book_server_api/routers/assessment.py
index 7f426d718..bb1a07acf 100644
--- a/bases/rsptx/book_server_api/routers/assessment.py
+++ b/bases/rsptx/book_server_api/routers/assessment.py
@@ -23,6 +23,8 @@
# -------------------
from bleach import clean
from fastapi import APIRouter, Depends, HTTPException, Request, status
+from fastapi.responses import JSONResponse
+from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel
# Local application imports
@@ -67,6 +69,23 @@
tags=["assessment"],
)
+@router.get("/getDoenetState")
+async def getdoenetstate(request: Request, div_id: str,
+ course_name: str, event: str,
+ user=Depends(auth_manager)
+):
+ request_data = AssessmentRequest(course=course_name, div_id=div_id, event=event)
+ assessment_results = await get_assessment_results_internal(request_data, request, user)
+
+ if assessment_results is not None:
+ doenet_state = assessment_results["answer"]["state"]
+ doenet_state["success"] = True
+ doenet_state["loadedState"] = True
+ return JSONResponse( status_code=200, content=jsonable_encoder(doenet_state) )
+ else:
+ return JSONResponse(
+ status_code=200, content=jsonable_encoder({"loadedState": False, "success": True})
+ )
# getAssessResults
# ----------------
@@ -75,6 +94,18 @@ async def get_assessment_results(
request_data: AssessmentRequest,
request: Request,
user=Depends(auth_manager),
+):
+ assessment_results = await get_assessment_results_internal(request_data, request, user)
+ if assessment_results is not None:
+ return make_json_response(detail=assessment_results)
+ else:
+ return make_json_response(detail="no data")
+
+
+async def get_assessment_results_internal(
+ request_data: AssessmentRequest,
+ request: Request,
+ user=Depends(auth_manager),
):
# if the user is not logged in an HTTP 401 will be returned.
# Otherwise if the user is an instructor then use the provided
@@ -95,7 +126,7 @@ async def get_assessment_results(
row = await fetch_last_answer_table_entry(request_data)
# mypy complains that ``row.id`` doesn't exist (true, but the return type wasn't exact and this does exist).
if not row or row.id is None: # type: ignore
- return make_json_response(detail="no data")
+ return None
ret = row.dict()
rslogger.debug(f"row is {ret}")
if "timestamp" in ret:
@@ -118,7 +149,7 @@ async def get_assessment_results(
ret["comment"] = grades.comment
ret["score"] = grades.score
rslogger.debug(f"Returning {ret}")
- return make_json_response(detail=ret)
+ return ret
# Define a simple model for the gethist request.
diff --git a/bases/rsptx/book_server_api/routers/rslogging.py b/bases/rsptx/book_server_api/routers/rslogging.py
index eae44d926..f9caabe63 100644
--- a/bases/rsptx/book_server_api/routers/rslogging.py
+++ b/bases/rsptx/book_server_api/routers/rslogging.py
@@ -149,7 +149,7 @@ async def log_book_event(
if entry.act in ["start", "pause", "resume"]:
# We don't need these in the answer table but want the event to be timedExam.
create_answer_table = False
- elif entry.event == "webwork" or entry.event == "hparsonsAnswer":
+ elif entry.event == "webwork" or entry.event == "hparsonsAnswer" or entry.event == "doenet":
entry.answer = json.loads(useinfo_dict["answer"])
if create_answer_table:
diff --git a/bases/rsptx/interactives/runestone/__init__.py b/bases/rsptx/interactives/runestone/__init__.py
index 0b544cac6..27b6afb64 100644
--- a/bases/rsptx/interactives/runestone/__init__.py
+++ b/bases/rsptx/interactives/runestone/__init__.py
@@ -11,6 +11,7 @@
from .datafile import DataFile
from .disqus import DisqusDirective
from .dragndrop import DragNDrop
+from .doenet import DoenetDirective
from .fitb import FillInTheBlank
from .groupsub import GroupSubmission
from .hparsons import HParsonsDirective
@@ -40,6 +41,7 @@
# TODO: clean up - many of the folders are not needed as the files are imported by webpack
+# TODO - Jason second's this TODO, I've been confused by duplicates copies of static assets
#
# runestone_static_dirs()
# -----------------------
@@ -251,6 +253,7 @@ def build(options):
"datafile": DataFile,
"disqus": DisqusDirective,
"dragndrop": DragNDrop,
+ "doenet": DoenetDirective,
"groupsub": GroupSubmission,
"hparsons": HParsonsDirective,
"parsonsprob": ParsonsProblem,
diff --git a/bases/rsptx/interactives/runestone/doenet/__init__.py b/bases/rsptx/interactives/runestone/doenet/__init__.py
new file mode 100755
index 000000000..14312333c
--- /dev/null
+++ b/bases/rsptx/interactives/runestone/doenet/__init__.py
@@ -0,0 +1 @@
+from .doenet import *
diff --git a/bases/rsptx/interactives/runestone/doenet/doenet.py b/bases/rsptx/interactives/runestone/doenet/doenet.py
new file mode 100644
index 000000000..b9c5909a3
--- /dev/null
+++ b/bases/rsptx/interactives/runestone/doenet/doenet.py
@@ -0,0 +1,104 @@
+
+# *********
+# |docname|
+# *********
+# Copyright (C) 2011 Bradley N. Miller
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+__author__ = "jaltekruse"
+
+from docutils import nodes
+from runestone.server.componentdb import (
+ addQuestionToDB,
+ addHTMLToDB,
+ maybeAddToAssignment,
+)
+from runestone.common.runestonedirective import (
+ RunestoneIdDirective,
+ RunestoneIdNode,
+)
+
+
+def setup(app):
+ app.add_directive("doenet", DoenetDirective)
+ app.add_node(DoenetNode, html=(visit_doenet_html, depart_doenet_html))
+
+
+TEMPLATE_START = """
+
+
+
+
+
+"""
+
+
+class DoenetNode(nodes.General, nodes.Element, RunestoneIdNode):
+ pass
+
+
+# self for these functions is an instance of the writer class. For example
+# in html, self is sphinx.writers.html.SmartyPantsHTMLTranslator
+# The node that is passed as a parameter is an instance of our node class.
+def visit_doenet_html(self, node):
+
+ node["delimiter"] = "_start__{}_".format(node["runestone_options"]["divid"])
+
+ self.body.append(node["delimiter"])
+
+ res = TEMPLATE_START % node["runestone_options"]
+ self.body.append(res)
+
+
+def depart_doenet_html(self, node):
+ res = TEMPLATE_END % node["runestone_options"]
+ self.body.append(res)
+
+ addHTMLToDB(
+ node["runestone_options"]["divid"],
+ node["runestone_options"]["basecourse"],
+ "".join(self.body[self.body.index(node["delimiter"]) + 1 :]),
+ )
+
+ self.body.remove(node["delimiter"])
+
+
+class DoenetDirective(RunestoneIdDirective):
+ """
+
+ 1+3000=4
+ """
+
+ required_arguments = 1
+ optional_arguments = 1
+ has_content = True
+ option_spec = RunestoneIdDirective.option_spec.copy()
+
+ def run(self):
+ super(DoenetDirective, self).run()
+ addQuestionToDB(self)
+
+ doenet_node = DoenetNode()
+ doenet_node["runestone_options"] = self.options
+ self.add_name(doenet_node) # make this divid available as a target for :ref:
+
+ maybeAddToAssignment(self)
+
+ return [doenet_node]
diff --git a/bases/rsptx/interactives/runestone/doenet/js/doenet.js b/bases/rsptx/interactives/runestone/doenet/js/doenet.js
new file mode 100644
index 000000000..5d981dbae
--- /dev/null
+++ b/bases/rsptx/interactives/runestone/doenet/js/doenet.js
@@ -0,0 +1,141 @@
+"use strict";
+
+import RunestoneBase from "../../common/js/runestonebase.js";
+// TODO fix this, in the meantime including from sphinx_static_files.html
+// ERROR in ./runestone/doenet/js/doenet-standalone.js 240673:19
+//Module parse failed: Unterminated template (240673:19)
+//You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
+//import "./doenet-standalone.js";
+
+console.log("IN DOENET - add event listener for splice");
+// SPLICE Events
+window.addEventListener("message", (event) => {
+ console.log("IN DOENET - got a message", event);
+ if (event.data.subject == "SPLICE.reportScoreAndState") {
+ console.log(event.data.score);
+ console.log(event.data.state);
+ event.data.course_name = eBookConfig.course;
+ event.data.div_id = event.data.state.activityId;
+ let ev = {
+ event: "doenet",
+ div_id: event.data.state.activityId,
+ percent: event.data.score,
+ correct: event.data.score == 1 ? 'T' : 'F',
+ act: JSON.stringify(event.data),
+ answer: JSON.stringify(event.data),
+ };
+ window.componentMap[event.data.state.activityId].logBookEvent(ev);
+ } else if (event.data.subject == "SPLICE.sendEvent") {
+ console.log(event.data.location);
+ console.log(event.data.name);
+ console.log(event.data.data);
+ }
+});
+
+// separate into constructor and init
+export class Doenet extends RunestoneBase {
+ constructor(opts) {
+ super(opts);
+ console.log(opts);
+ console.log("Jason update oct 24th");
+ this.doenetML = opts.doenetML;
+ console.log("opts.orig.id", opts.orig.id);
+ var orig = $(opts.orig).find("div")[0];
+ console.log(orig);
+ console.log(orig.id);
+ console.log(`${eBookConfig.new_server_prefix}/logger/bookevent`);
+ // todo - think about how we pass around the doenetML
+ //window.renderDoenetToContainer(orig, this.doenetML);
+
+ var loadPageStateUrl = `/ns/assessment/getDoenetState?div_id=${opts.orig.id}&course_name=${eBookConfig.course}&event=doenet`
+ // data.div_id = this.divid;
+ // data.course = eBookConfig.course;
+ // data.event = eventInfo;
+
+ window.renderDoenetToContainer(orig, this.doenetML, {
+ flags: {
+ // showCorrectness,
+ // readOnly,
+ // showFeedback,
+ // showHints,
+ showCorrectness: true,
+ readOnly: false,
+ solutionDisplayMode: "button",
+ showFeedback: true,
+ showHints: true,
+ allowLoadState: false,
+ allowSaveState: true,
+ allowLocalState: false,
+ allowSaveSubmissions: true,
+ allowSaveEvents: false,
+ autoSubmit: false,
+ },
+ addBottomPadding: false,
+ activityId: opts.orig.id,
+ apiURLs: {
+ postMessages: true,
+ loadPageState: loadPageStateUrl
+ },
+ });
+
+ //this.checkServer("hparsonsAnswer", true);
+ }
+
+ async logCurrentAnswer(sid) {}
+
+ renderFeedback() {}
+
+ disableInteraction() {}
+
+ checkLocalStorage() {}
+ setLocalStorage() {}
+
+ restoreAnswers(data) {
+ console.log("TODO IMPLEMENT loading data from doenet activity", data);
+ }
+}
+
+//
+// Page Initialization
+//
+
+$(document).on("runestone:login-complete", function () {
+ //ACFactory.createScratchActivecode();
+ $("[data-component=doenet]").each(function () {
+ if ($(this).closest("[data-component=timedAssessment]").length == 0) {
+ // If this element exists within a timed component, don't render it here
+ try {
+ window.componentMap[this.id] = new Doenet({orig : this});
+ // ACFactory.createActiveCode(
+ // this,
+ // $(this).find("textarea").data("lang")
+ // );
+ } catch (err) {
+ console.log(`Error rendering Activecode Problem ${this.id}
+ Details: ${err}`);
+ }
+ }
+ });
+ // The componentMap can have any component, not all of them have a disableSaveLoad
+ // method or an enableSaveLoad method. So we need to check for that before calling it.
+ // if (loggedout) {
+ // for (let k in window.componentMap) {
+ // if (window.componentMap[k].disableSaveLoad) {
+ // window.componentMap[k].disableSaveLoad();
+ // }
+ // }
+ // } else {
+ // for (let k in window.componentMap) {
+ // if (window.componentMap[k].enableSaveLoad) {
+ // window.componentMap[k].enableSaveLoad();
+ // }
+ // }
+ // }
+});
+
+if (typeof window.component_factory === "undefined") {
+ window.component_factory = {};
+}
+window.component_factory.doenet = (opts) => {
+ return new Doenet(opts);
+};
diff --git a/bases/rsptx/interactives/webpack.index.js b/bases/rsptx/interactives/webpack.index.js
index dacde2cb1..42b701044 100644
--- a/bases/rsptx/interactives/webpack.index.js
+++ b/bases/rsptx/interactives/webpack.index.js
@@ -64,6 +64,7 @@ const module_map = {
import("./runestone/clickableArea/js/timedclickable.js"),
codelens: () => import("./runestone/codelens/js/codelens.js"),
datafile: () => import("./runestone/datafile/js/datafile.js"),
+ doenet: () => import("./runestone/doenet/js/doenet.js"),
dragndrop: () => import("./runestone/dragndrop/js/timeddnd.js"),
fillintheblank: () => import("./runestone/fitb/js/timedfitb.js"),
groupsub: () => import("./runestone/groupsub/js/groupsub.js"),
@@ -87,6 +88,7 @@ const module_map = {
// TODO: since this isn't in a ``data-component``, need to trigger an import of this code manually.
webwork: () => import("./runestone/webwork/js/webwork.js"),
youtube: () => import("./runestone/video/js/runestonevideo.js"),
+ doenet: () => import("./runestone/doenet/js/doenet.js"),
};
const module_map_cache = {};
diff --git a/bases/rsptx/web2py_server/applications/runestone/controllers/admin.py b/bases/rsptx/web2py_server/applications/runestone/controllers/admin.py
index 3a25fb328..b51f96847 100644
--- a/bases/rsptx/web2py_server/applications/runestone/controllers/admin.py
+++ b/bases/rsptx/web2py_server/applications/runestone/controllers/admin.py
@@ -44,6 +44,7 @@
codelens=ALL_AUTOGRADE_OPTIONS,
datafile=[],
dragndrop=["manual", "all_or_nothing", "pct_correct", "interact"],
+ doenet=ALL_AUTOGRADE_OPTIONS,
external=[],
fillintheblank=ALL_AUTOGRADE_OPTIONS,
khanex=ALL_AUTOGRADE_OPTIONS,
@@ -85,6 +86,7 @@
clickablearea=ALL_WHICH_OPTIONS,
codelens=ALL_WHICH_OPTIONS,
datafile=[],
+ doenet=ALL_WHICH_OPTIONS,
dragndrop=ALL_WHICH_OPTIONS,
external=[],
fillintheblank=ALL_WHICH_OPTIONS,
diff --git a/bases/rsptx/web2py_server/applications/runestone/models/db_ebook.py b/bases/rsptx/web2py_server/applications/runestone/models/db_ebook.py
index 528f9a2c2..87337b56e 100644
--- a/bases/rsptx/web2py_server/applications/runestone/models/db_ebook.py
+++ b/bases/rsptx/web2py_server/applications/runestone/models/db_ebook.py
@@ -262,6 +262,18 @@
migrate=bookserver_owned("microparsons_answers"),
)
+db.define_table(
+ "doenet_answers",
+ Field("timestamp", "datetime"),
+ Field("div_id", "string"),
+ Field("sid", "string"),
+ Field("course_name", "string"),
+ Field("answer", "json"),
+ Field("correct", "boolean"),
+ Field("percent", "double"),
+ migrate=bookserver_owned("doenet_answers"),
+)
+
# payments
# --------
diff --git a/bases/rsptx/web2py_server/applications/runestone/modules/questions_report.py b/bases/rsptx/web2py_server/applications/runestone/modules/questions_report.py
index a8eabaa92..e738e170d 100644
--- a/bases/rsptx/web2py_server/applications/runestone/modules/questions_report.py
+++ b/bases/rsptx/web2py_server/applications/runestone/modules/questions_report.py
@@ -179,6 +179,12 @@ def questions_to_grades(
& (db.useinfo.div_id == db.mchoice_answers.div_id)
& (db.mchoice_answers.course_name == course_name)
),
+ db.doenet_answers.on(
+ (db.useinfo.timestamp == db.doenet_answers.timestamp)
+ & (db.useinfo.sid == db.doenet_answers.sid)
+ & (db.useinfo.div_id == db.doenet_answers.div_id)
+ & (db.doenet_answers.course_name == course_name)
+ ),
db.parsons_answers.on(
(db.useinfo.timestamp == db.parsons_answers.timestamp)
& (db.useinfo.sid == db.parsons_answers.sid)
@@ -399,6 +405,12 @@ def ts_get(table):
row.parsons_answers.correct,
ts_get(row.parsons_answers),
)
+ elif question_type == "doenet":
+ return (
+ row.doenet_answers.answer,
+ row.doenet_answers.correct,
+ ts_get(row.doenet_answers),
+ )
elif question_type == "shortanswer":
# Prefer data from the shortanswer table if we have it; otherwise, we can use useinfo's act.
answer, ts = (
@@ -517,6 +529,9 @@ def query_assignment(
db.parsons_answers.answer,
db.parsons_answers.correct,
db.parsons_answers.timestamp,
+ db.doenet_answers.answer,
+ db.doenet_answers.correct,
+ db.doenet_answers.timestamp,
##db.shortanswer_answers.answer,
##db.shortanswer_answers.timestamp,
db.useinfo.timestamp,
@@ -552,6 +567,10 @@ def query_assignment(
(db.questions.question_type == "mchoice")
& (db.question_grades.answer_id == db.mchoice_answers.id)
),
+ db.doenet_answers.on(
+ (db.questions.question_type == "doenet")
+ & (db.question_grades.answer_id == db.doenet_answers.id)
+ ),
db.parsons_answers.on(
(db.questions.question_type == "parsonsprob")
& (db.question_grades.answer_id == db.parsons_answers.id)
diff --git a/bases/rsptx/web2py_server/applications/runestone/modules/rs_grading.py b/bases/rsptx/web2py_server/applications/runestone/modules/rs_grading.py
index 6428d77e1..8c9035e3e 100644
--- a/bases/rsptx/web2py_server/applications/runestone/modules/rs_grading.py
+++ b/bases/rsptx/web2py_server/applications/runestone/modules/rs_grading.py
@@ -152,6 +152,17 @@ def _score_one_microparsons(row, points, autograde):
pct_correct = 0
return _score_from_pct_correct(pct_correct, points, autograde)
+def _score_one_doenet(row, points, autograde):
+ # row is from doenet_answers
+ if autograde == "pct_correct" and "percent" in row and row.percent is not None:
+ pct_correct = int(round(row.percent * 100))
+ else:
+ if row.correct:
+ pct_correct = 100
+ else:
+ pct_correct = 0
+ return _score_from_pct_correct(pct_correct, points, autograde)
+
def _score_one_fitb(row, points, autograde):
# row is from fitb_answers
@@ -401,6 +412,29 @@ def _scorable_microparsons_answers(
query = query & (db.microparsons_answers.timestamp <= now)
return db(query).select(orderby=db.microparsons_answers.timestamp)
+def _scorable_doenet_answers(
+ course_name,
+ sid,
+ question_name,
+ points,
+ deadline,
+ practice_start_time=None,
+ db=None,
+ now=None,
+):
+ query = (
+ (db.doenet_answers.course_name == course_name)
+ & (db.doenet_answers.sid == sid)
+ & (db.doenet_answers.div_id == question_name)
+ )
+ if deadline:
+ query = query & (db.doenet_answers.timestamp < deadline)
+ if practice_start_time:
+ query = query & (db.doenet_answers.timestamp >= practice_start_time)
+ if now:
+ query = query & (db.doenet_answers.timestamp <= now)
+ ret = db(query).select(orderby=db.doenet_answers.timestamp)
+ return ret
def _scorable_fitb_answers(
course_name,
@@ -781,6 +815,21 @@ def _autograde_one_q(
scoring_fn = _score_one_webwork
logger.debug("AGDB - done with webwork")
+ elif question_type == "doenet":
+ logger.debug("grading a doenet!!")
+ results = _scorable_doenet_answers(
+ course_name,
+ sid,
+ question_name,
+ points,
+ deadline,
+ practice_start_time,
+ db=db,
+ now=now,
+ )
+ scoring_fn = _score_one_doenet
+ logger.debug("AGDB - done with doenet")
+
elif question_type == "hparsons":
logger.debug("grading a microparsons!!")
results = _scorable_microparsons_answers(
diff --git a/bases/rsptx/web2py_server/applications/runestone/static/js/admin.js b/bases/rsptx/web2py_server/applications/runestone/static/js/admin.js
index a33fd26b3..b63ee26d4 100644
--- a/bases/rsptx/web2py_server/applications/runestone/static/js/admin.js
+++ b/bases/rsptx/web2py_server/applications/runestone/static/js/admin.js
@@ -1752,6 +1752,16 @@ function display_write() {
var hiddenwrite = document.getElementById("hiddenwrite");
hiddenwrite.style.visibility = "visible";
+
+ var hiddenDoenetEditor = document.getElementById("doenet-question-editor");
+ var hiddenRunestoneEditor = document.getElementById("runestone-question-editor");
+ if (questiontype === "doenet") {
+ hiddenDoenetEditor.style.display = "block";
+ hiddenRunestoneEditor.style.display = "none";
+ } else {
+ hiddenRunestoneEditor.style.display = "block";
+ hiddenDoenetEditor.style.display = "none";
+ }
}
function find_name(lines) {
@@ -1768,7 +1778,7 @@ function find_name(lines) {
}
// Called when the "Done" button of the "Write" dialog is clicked.
-function create_question(formdata) {
+async function create_question(formdata) {
if (formdata.qchapter.value == "Chapter") {
alert("Please select a chapter for this question");
return;
@@ -1776,6 +1786,29 @@ function create_question(formdata) {
if (formdata.createpoints.value == "") {
formdata.createpoints.value = "1";
}
+ var question;
+ if (formdata.template.value == "doenet") {
+ question = (await returnAllStateVariables1())['/_codeeditor1'].stateValues.text;
+ var questionId = "doenet-" + Math.floor(Math.random() * 10000000);
+ question = "" + question;
+
+ formdata.qrawhtml.value =
+ `
+
+
+
+
`
+ } else {
+ question = formdata.qcode.value;
+ }
+
+ var lines = question.split("\n");
+ var name = find_name(lines);
+
+ if (formdata.template.value == "doenet") {
+ }
if (!formdata.qrawhtml.value) {
alert("No HTML for this question, please generate it.");
return;
@@ -1786,11 +1819,7 @@ function create_question(formdata) {
var assignmentid = select.options[select.selectedIndex].value;
var assignmentname = select.options[select.selectedIndex].text;
var template = formdata.template.value;
- var qcode = formdata.qcode.value;
- var lines = qcode.split("\n");
var htmlsrc = formdata.qrawhtml.value;
- var name = find_name(lines);
- var question = formdata.qcode.value;
var difficulty = formdata.difficulty;
for (var i = 0; i < difficulty.length; i++) {
if (difficulty[i].checked == true) {
@@ -1985,6 +2014,10 @@ async function renderRunestoneComponent(componentSrc, whereDiv, moreOpts) {
) {
componentKind = "webwork";
}
+
+ if (componentSrc.indexOf("doenet") >= 0) {
+ componentKind = "doenet";
+ }
// Import all the js needed for this component before rendering
await runestoneComponents.runestone_import(componentKind);
let opt = {};
@@ -2004,12 +2037,15 @@ async function renderRunestoneComponent(componentSrc, whereDiv, moreOpts) {
}
}
- if (typeof component_factory === "undefined") {
+ if (typeof component_factory === "undefined" && componentKind != "doenet") {
alert(
"Error: Missing the component factory! probably a webpack version mismatch"
);
} else {
- if (!component_factory[componentKind] && !jQuery(`#${whereDiv}`).html()) {
+ if (false && componentKind == "doenet") {
+ console.log("wonder if I need to do something here");
+
+ } else if (!component_factory[componentKind] && !jQuery(`#${whereDiv}`).html()) {
jQuery(`#${whereDiv}`).html(
`
Preview not available for ${componentKind}
`
);
@@ -2017,7 +2053,8 @@ async function renderRunestoneComponent(componentSrc, whereDiv, moreOpts) {
try {
let res = component_factory[componentKind](opt);
res.multiGrader = moreOpts.multiGrader;
- if (componentKind === "activecode") {
+ if (componentKind === "activecode"
+ || componentKind == "doenet") {
if (moreOpts.multiGrader) {
window.componentMap[
`${moreOpts.gradingContainer} ${res.divid}`
@@ -2101,7 +2138,13 @@ async function renderRunestoneComponent(componentSrc, whereDiv, moreOpts) {
}
// $(`#${whereDiv}`).css("background-color", "white");
}
- MathJax.typeset([document.querySelector(`#${whereDiv}`)]);
+
+
+ if (componentKind == "doenet") {
+ //window.renderDoenetToContainer(document.querySelector(".doenetml-applet"));
+ } else {
+ MathJax.typeset([document.querySelector(`#${whereDiv}`)]);
+ }
}
// Called by the "Search" button in the "Search question bank" panel.
diff --git a/bases/rsptx/web2py_server/applications/runestone/views/_sphinx_static_files.html b/bases/rsptx/web2py_server/applications/runestone/views/_sphinx_static_files.html
index 11fbe101c..7337188ec 100644
--- a/bases/rsptx/web2py_server/applications/runestone/views/_sphinx_static_files.html
+++ b/bases/rsptx/web2py_server/applications/runestone/views/_sphinx_static_files.html
@@ -10,6 +10,9 @@
+
+
+
{{ if 'ptx_js_version' in globals() and 'webwork_js_version' in globals(): }}
diff --git a/bases/rsptx/web2py_server/applications/runestone/views/admin/assignments.html b/bases/rsptx/web2py_server/applications/runestone/views/admin/assignments.html
index 84d282eff..54fd9fbca 100644
--- a/bases/rsptx/web2py_server/applications/runestone/views/admin/assignments.html
+++ b/bases/rsptx/web2py_server/applications/runestone/views/admin/assignments.html
@@ -313,6 +313,9 @@