Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 31 additions & 17 deletions exercise/static/exercise/chapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@
response_selector: '.exercise-response',
navigation_selector: 'ul.nav a[class!="dropdown-toggle"]',
dropdown_selector: 'ul.nav .dropdown-toggle',
last_submission_selector: 'ul.nav ul.dropdown-menu li:first-child a',
last_submission_selector: 'ul.navbar-nav ul.dropdown-menu li:first-child a',
// For active elements:
active_element_attr: "data-aplus-active-element",
interactive_code_attr: "data-aplus-interactive-code",
Expand Down Expand Up @@ -377,8 +377,12 @@
.done(function(data) {
exercise.hideLoader();
exercise.update($(data));
const hasTextarea = exercise.element.find('textarea').length > 0;
const hasFieldset = exercise.element.find('fieldset').length > 0;
if (exercise.active_element) {
exercise.loadLastSubmission($(data));
} else if (hasTextarea && !hasFieldset) { // Identify acceptPost exercises
Comment on lines 380 to +384
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic to identify acceptPost exercises using hasTextarea && !hasFieldset is fragile and could incorrectly classify other exercise types. Consider using a more explicit identifier such as a data attribute or exercise type property to reliably distinguish acceptPost exercises.

Suggested change
if (exercise.active_element) {
exercise.loadLastSubmission($(data));
} else if (hasTextarea && !hasFieldset) { // Identify acceptPost exercises
const exerciseType = exercise.element.data('exercise-type');
if (exercise.active_element) {
exercise.loadLastSubmission($(data));
} else if (exerciseType === 'acceptPost') { // Identify acceptPost exercises explicitly

Copilot uses AI. Check for mistakes.
exercise.loadLastSubmission($(data), true);
} else {
exercise.renderMath();
if (!onlyThis) exercise.chapter.nextExercise();
Expand Down Expand Up @@ -849,15 +853,15 @@
}
},

loadLastSubmission: function(input) {
loadLastSubmission: function(input, fillInputs = false) {
var link = input.find(this.settings.last_submission_selector);
var exercise = this;
if (link.length > 0) {
var url = link.attr("href");

if (url && url !== "#") {
var data_type = "html";
if (exercise.active_element) {
if (exercise.active_element || fillInputs) {
// Active element input values are retrieved from the API, so
// we must extract the submission number from the submission url
var submission = url.match(/submissions\/\d+/)[0].split('/')[1];
Expand All @@ -875,20 +879,30 @@

if (!exercise.active_element) {
const responseElement = exercise.element.find(exercise.settings.response_selector);
// Remove only the exercise content, not the alerts above it
responseElement.children().not('.alert').remove();
responseElement.append(
$(data).filter(exercise.settings.exercise_selector).contents()
);
exercise.dom_element.dispatchEvent(
new CustomEvent("aplus:exercise-loaded",
{bubbles: true, detail: {type: exercise.exercise_type}}));
// TODO: remove magic constant (variable defined in group.js)
responseElement.removeClass('group-augmented');
exercise.bindFormEvents(exercise.element);
exercise.dom_element.dispatchEvent(
new CustomEvent("aplus:exercise-ready",
{bubbles: true, detail: {type: exercise.exercise_type}}));
if (!fillInputs) {
// Remove only the exercise content, not the alerts above it
responseElement.children().not('.alert').remove();
responseElement.append(
$(data).filter(exercise.settings.exercise_selector).contents()
);
exercise.dom_element.dispatchEvent(
new CustomEvent("aplus:exercise-loaded",
{bubbles: true, detail: {type: exercise.exercise_type}}));
// TODO: remove magic constant (variable defined in group.js)
responseElement.removeClass('group-augmented');
exercise.bindFormEvents(exercise.element);
exercise.dom_element.dispatchEvent(
new CustomEvent("aplus:exercise-ready",
{bubbles: true, detail: {type: exercise.exercise_type}}));
} else {
// Fill textareas with last submission inputs
const lastInputs = data.submission_data.map((x) => x[1]);
const textareas = responseElement.find('textarea');
textareas.each(function(index) {
$(this).val(lastInputs[index]);
});
Comment on lines +901 to +903
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No bounds checking when mapping submission data to textareas. If there are more textareas than submission data entries, or vice versa, this could result in undefined values or missed textareas. Add validation to ensure the arrays have matching lengths.

Suggested change
textareas.each(function(index) {
$(this).val(lastInputs[index]);
});
const minLen = Math.min(textareas.length, lastInputs.length);
// Set values for matching indices
textareas.each(function(index) {
if (index < lastInputs.length) {
$(this).val(lastInputs[index]);
} else {
// Clear extra textareas if there are more textareas than inputs
$(this).val('');
}
});
if (textareas.length !== lastInputs.length) {
// Optionally log a warning for debugging
if (window.console && console.warn) {
console.warn(
"Mismatch between number of textareas (" +
textareas.length +
") and submission data entries (" +
lastInputs.length +
")."
);
}
}

Copilot uses AI. Check for mistakes.
}

} else {
// Update the output box values
exercise.updateOutput(data.feedback);
Expand Down
Loading