Skip to content
This repository was archived by the owner on Jun 7, 2023. It is now read-only.

Commit a358768

Browse files
committed
Merge branch 'master' of github.com:RunestoneInteractive/RunestoneComponents
2 parents b230ffa + 70bd5f1 commit a358768

File tree

2 files changed

+36
-19
lines changed

2 files changed

+36
-19
lines changed

runestone/lp/js/lp.js

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,27 @@ class LP extends RunestoneBase {
5454
.siblings(".lp-feedback")
5555
.children("div");
5656
// Use a nice editor.
57-
let that = this;
5857
this.textAreas = [];
59-
$(".code_snippet").each(function (index, element) {
58+
this.initTextAreas();
59+
// Handle clicks to the "Save and run" button.
60+
let that = this;
61+
$(this.element).click((eventObject) =>
62+
that.onSaveAndRun(eventObject).then(null)
63+
);
64+
this.checkServer("lp_build", true);
65+
}
66+
67+
// Look for each code snippet (a textarea). Make it a nice CodeMirror editor.
68+
initTextAreas(is_read_only = false) {
69+
let that = this;
70+
// Select only textareas that haven't been initialized with CodeMirror (which sets style="display:none" on the textarea).
71+
$('textarea.code_snippet:not([style="display: none;"])').each(function (index, element) {
6072
let editor = CodeMirror.fromTextArea(element, {
6173
lineNumbers: true,
6274
mode: $(that.element).attr("data-lang"),
6375
indentUnit: 4,
6476
matchBrackets: true,
77+
readOnly: is_read_only,
6578
autoMatchParens: true,
6679
extraKeys: { Tab: "indentMore", "Shift-Tab": "indentLess" },
6780
});
@@ -75,11 +88,6 @@ class LP extends RunestoneBase {
7588
// Keep track of it.
7689
that.textAreas.push(editor);
7790
});
78-
// Handle clicks to the "Save and run" button.
79-
$(this.element).click((eventObject) =>
80-
that.onSaveAndRun(eventObject).then(null)
81-
);
82-
this.checkServer("lp_build", true);
8391
}
8492

8593
// Data structures:
@@ -137,7 +145,7 @@ class LP extends RunestoneBase {
137145
// Store the answer as a string, since this is what goes in to / comes out from the database. We have to translate this back to a data structure when restoring from the db or local storage.
138146
let code_snippets = this.textareasToData();
139147
this.setLocalStorage({
140-
answer: JSON.stringify({ code_snippets: code_snippets }),
148+
answer: { code_snippets: code_snippets },
141149
timestamp: new Date(),
142150
});
143151
// Store the answer that the server returns, which includes additional data (correct/incorrect, feedback from the build, etc.).
@@ -170,8 +178,6 @@ class LP extends RunestoneBase {
170178
}
171179
serverAnswer.answer.code_snippets = code_snippets;
172180
this.displayAnswer(serverAnswer);
173-
// JSON-encode the answer for storage.
174-
serverAnswer.answer = JSON.stringify(serverAnswer.answer);
175181
this.setLocalStorage(serverAnswer);
176182
}
177183

@@ -213,17 +219,29 @@ class LP extends RunestoneBase {
213219

214220
// Store an array of strings in ``data.code_snippets`` into each textarea.
215221
dataToTextareas(data) {
216-
// Find all code snippet textareas.
222+
// For places where textareas aren't provided (instructor grading interface, assignment page), create them. However, these aren't runnable, so disable the "Save and run" button. Assume this is the case when there are no textareas. Reasoning: the page containing the problem contains code intended to be intermingled with the student's answer. If that's not present, we can't build.
223+
if (!this.textAreas.length) {
224+
// The unusual case -- a problem not on its original page. Create textareas to display the code, then initialize them. Note that we can't simply create these in the constructor (which would be convenient) since we don't yet know how many code snippets are associated with the problem.
225+
$(this.element).before((data.answer.code_snippets || []).map(() => `<textarea class="code_snippet"></textarea><br />`));
226+
// Put CodeMirror in read-only mode.
227+
this.initTextAreas(true);
228+
// Disable the "Save and run" button.
229+
$(this.element).prop("disabled", true);
230+
// Now that we have textareas, let the usual case code run.
231+
}
232+
233+
// The usual case -- a problem on its original page. Find all code snippet textareas.
217234
$(this.textAreas).each(function (index, value) {
218235
// Silently ignore if ``data.answer.code_snippets`` or ``data.answer.code_snippets[index]`` isn't defined.
219236
value.setValue((data.answer.code_snippets || "")[index] || "");
220237
});
238+
// Corner case TODO: if a problem is edited after students already submit code so that there are now fewer textareas, display these but don't save them when "Save and run" is pressed. This allows students to (hopefully) re-use code from now-discarded textareas.
221239
}
222240

223241
// Restore answers from storage retrieval done in RunestoneBase.
224242
restoreAnswers(data) {
225-
// We store the answer as a JSON-encoded string in the db / local storage. Restore the actual data structure from it.
226-
data.answer = JSON.parse(data.answer);
243+
// We store the answer as a JSON-encoded string in the db / local storage. Restore the actual data structure from it. Avoid exceptions if no data is available.
244+
data.answer = JSON.parse(data.answer || "{}");
227245
this.dataToTextareas(data);
228246
this.displayAnswer(data);
229247
}
@@ -249,7 +267,10 @@ class LP extends RunestoneBase {
249267
}
250268

251269
setLocalStorage(data) {
252-
localStorage.setItem(this.localStorageKey(), JSON.stringify(data));
270+
// Make a shallow copy, so we can JSON-encode the code snippets (matching what comes from the server) without changing the original object.
271+
data_clone = object.assign({}, data);
272+
data_clone.answer = JSON.stringify(data.answer);
273+
localStorage.setItem(this.localStorageKey(), JSON.stringify(data_clone));
253274
}
254275
}
255276

runestone/lp/lp.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ class _LpBuildButtonDirective(RunestoneIdDirective):
195195
# No optional arguments.
196196
optional_arguments = 0
197197
# Per http://docutils.sourceforge.net/docs/howto/rst-directives.html, True if content is allowed. However, this isn't checked or enforced.
198-
has_content = True
198+
has_content = False
199199
# Options. Everything but language is currently ignored. This is based on activecode, so in the future similar support would be provided for these options.
200200
option_spec = RunestoneIdDirective.option_spec.copy()
201201
option_spec.update(
@@ -232,10 +232,6 @@ def run(self):
232232
lp_node["source"], lp_node["line"] = self.state_machine.get_source_and_line(
233233
self.lineno
234234
)
235-
# Insert the question number.
236-
self.content.append(self.options["qnumber"], "lp")
237-
# Parse it, since the number may be a role.
238-
self.state.nested_parse(self.content, self.content_offset, lp_node)
239235

240236
return [lp_node]
241237

0 commit comments

Comments
 (0)