From d6d91a2d184fe8f26d7c92c36172944551044cb4 Mon Sep 17 00:00:00 2001 From: Mikhail Loginov Date: Fri, 27 Mar 2026 15:16:38 +0000 Subject: [PATCH 01/15] settings wip --- .gitignore | 1 + icon.svg | 18 ++++++++++++++++++ metadata.json | 12 ++++++++++++ settings.css | 5 +++++ settings.html | 34 ++++++++++++++++++++++++++++++++++ settings.js | 44 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 114 insertions(+) create mode 100644 .gitignore create mode 100644 icon.svg create mode 100644 metadata.json create mode 100644 settings.css create mode 100644 settings.html create mode 100644 settings.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a09c56d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.idea diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..8c30b9e --- /dev/null +++ b/icon.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/metadata.json b/metadata.json new file mode 100644 index 0000000..16127b9 --- /dev/null +++ b/metadata.json @@ -0,0 +1,12 @@ +{ + "name": "Standard Code Test", + "type": "assessment", + "properties": { + "type": "code-output-compare", + "icon": "./icon.svg", + "defaultHeight": 500, + "gradingControls": { + + } + } +} diff --git a/settings.css b/settings.css new file mode 100644 index 0000000..3d6e420 --- /dev/null +++ b/settings.css @@ -0,0 +1,5 @@ +.settings-commands { + display: flex; + flex-direction: row; + gap: 10px; +} diff --git a/settings.html b/settings.html new file mode 100644 index 0000000..84c7e3f --- /dev/null +++ b/settings.html @@ -0,0 +1,34 @@ + + + + + Settings + + + + + + + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + diff --git a/settings.js b/settings.js new file mode 100644 index 0000000..35e6b74 --- /dev/null +++ b/settings.js @@ -0,0 +1,44 @@ +(function () { + const collectSettings = () => { + const instructions = $('#instructions').val() + const command = $('#command').val(); + const preExecCommand = $('#preExecCommand').val(); + const timeout = parseInt($('#timeout').val(), 10); + + return {instructions, command, preExecCommand, timeout}; + } + + const exportSettings = () => { + const data = collectSettings(); + window.codioAssessmentsHelper.send(window.codioAssessmentsHelper.METHODS.EXPORT_SETTINGS_RESPONSE, data); + } + + const applySettings = (settings = {}) => { + $('#instructions').val(settings.instructions || ''); + $('#command').val(settings.command || ''); + $('#preExecCommand').val(settings.preExecCommand || ''); + $('#timeout').val(settings.timeout || ''); + } + + const processMessage = (jsonData) => { + console.log('settings iframe processMessage', jsonData) + try { + const {method, data} = JSON.parse(jsonData); + switch (method) { + case window.codioAssessmentsHelper.METHODS.EXPORT_SETTINGS: + exportSettings(); + break; + case window.codioAssessmentsHelper.METHODS.GET_SETTINGS_RESPONSE: + applySettings(data.settings); + break; + } + } catch {} + } + + const onLoad = async () => { + window.codioAssessmentsHelper.registerMessageListener(processMessage) + window.codioAssessmentsHelper.send(window.codioAssessmentsHelper.METHODS.GET_SETTINGS) + } + + window.addEventListener('load', onLoad); +})() From 660005eb12233723989d0a91d8daafcab47c230b Mon Sep 17 00:00:00 2001 From: Mikhail Loginov Date: Mon, 30 Mar 2026 16:23:33 +0100 Subject: [PATCH 02/15] assessment rendering wip --- index.css | 0 index.html | 29 ++++++++ index.js | 214 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 243 insertions(+) create mode 100644 index.css create mode 100644 index.html create mode 100644 index.js diff --git a/index.css b/index.css new file mode 100644 index 0000000..e69de29 diff --git a/index.html b/index.html new file mode 100644 index 0000000..75ee3c8 --- /dev/null +++ b/index.html @@ -0,0 +1,29 @@ + + + + + Parsons assessment + + + + + + +
+

+
+
+
+
+
+
+ +
+
+ + diff --git a/index.js b/index.js new file mode 100644 index 0000000..629ead4 --- /dev/null +++ b/index.js @@ -0,0 +1,214 @@ +(function (){ + let assessmentOptions = null + let assessment = null + let processing = false + let showAsDiff = false + let currentData = null + + const updateProcessing = (status) => { + processing = status + updateHtml() + } + + const applyStateInitial = (data) => { + const {state, result, ...dataWithoutState} = data + assessment = dataWithoutState.assessment + assessmentOptions = dataWithoutState.options + + render() + } + + const applyState = (data) => { + console.log('assessment iframe applyState', data) + currentData = data + if (!assessment) { + applyStateInitial(data) + return + } + if (data.state) { + updateHtml() + renderGuidance() + return + } + // reset + if (currentData.state && !data.state) { + updateHtml() + renderGuidance() + } + } + + const onCheck = (event) => { + event.preventDefault() + updateProcessing(true) + + window.codioAssessmentsHelper.send( + window.codioAssessmentsHelper.METHODS.SUBMIT_ANSWER + ) + } + + const onUnblock = (event) => { + event.preventDefault() + codioAssessmentsHelper.send(window.codioAssessmentsHelper.METHODS.UNBLOCK) + } + + const onReset = (event) => { + event.preventDefault() + codioAssessmentsHelper.send(window.codioAssessmentsHelper.METHODS.RESET) + } + + const renderContent = () => { + $('.instructions-text').html(assessment.source.settings.instructions) + } + + const updateVisibility = (el, visible) => { + visible ? el.removeClass('hide') : el.addClass('hide') + } + + const hasDiff = () => { + // todo + } + + const updateFooterButtons = () => { + const assessmentState = getAssessmentState() + const {teacherInStudentsProject, showModify, isDisabled, canAnswerAgain, passed, answered} = assessmentState + + const checkVisibility = !showModify && assessmentOptions.useSubmitButtons + const checkBtn = $('.check-button') + updateVisibility(checkBtn, checkVisibility) + $('.check-button').attr('disabled', isDisabled) + + const unblockVisibility = !teacherInStudentsProject && showModify + updateVisibility($('.unblock-button'), unblockVisibility) + + const state = processing ? window.codioAssessmentsHelper.States.PROGRESS : currentData?.result?.state + const resetVisibility = !showModify && answered && assessmentOptions.owner && + state !== window.codioAssessmentsHelper.States.PROGRESS && !canAnswerAgain + updateVisibility($('.reset-button'), resetVisibility) + + const diffBtn = $('.diff-button') + const diffBtnTitle = showAsDiff ? 'Show output' : 'Show diff' + updateVisibility(diffBtn, hasDiff()) + diffBtn.html(diffBtnTitle) + } + + const renderFooter = () => { + const footerContainer = $('.codio-assessment-footer') + const caption = window.codioAssessmentsHelper.getButtonCaption(assessmentOptions, assessment.source.maxAttemptsCount) + footerContainer.find('.check-button').html(caption) + } + + const renderGuidance = () => { + const guidanceBlock = $('.codio-assessment-guidance-block') + guidanceBlock.empty() + const assessmentState = getAssessmentState() + const {result} = currentData || {} + const guidance = window.codioAssessmentsHelper.calculateGuidance( + !assessmentOptions.eduStartedAssignment, + assessmentOptions.showAsTeacher, + assessmentState.answered, + assessment.source, + result ? + { + answerGuidance: result.guidance, + answerPoints: result.points, + attemptsCount: result.usedAttempts, + passed: result.state === window.codioAssessmentsHelper.States.PASS, + isCompletedAndReleased: window.codioAssessmentsHelper.calculateCompletedAndReleased( + assessmentOptions.eduStartedAssignment + ) + } : {} + ) + if (guidance) { + const guidanceContainer = $('
') + const guidanceText = $('
').html(guidance) + guidanceContainer.append(guidanceText) + guidanceBlock.append(guidanceContainer) + } + } + + const renderResult = () => { + // todo render result + } + + const getAssessmentState = () => { + const result = currentData ? currentData.result : null + const answered = result?.state && result?.state !== window.codioAssessmentsHelper.States.RESET + const usedAttempts = result?.usedAttempts || 0 + const canAnswerAgain = !assessment.source.maxAttemptsCount || usedAttempts < assessment.source.maxAttemptsCount + const showModify = assessmentOptions.showUnblock && (!answered || canAnswerAgain) + const isDisabled = processing || + assessmentOptions.isDisabled || + result?.state === window.codioAssessmentsHelper.States.PROGRESS || + answered && !canAnswerAgain + const teacherInStudentsProject = assessmentOptions.showAsTeacher && !assessmentOptions.owner + + return { + isDisabled, + answered, + usedAttempts, + canAnswerAgain, + showModify, + teacherInStudentsProject + } + } + + const updateHtml = () => { + if (!assessment) { + return + } + // processing, new state/results + const assessmentState = getAssessmentState() + $('.check-button').attr('disabled', assessmentState.isDisabled) + const blockActionsEl = $('.block-actions') + assessmentState.isDisabled ? blockActionsEl.removeClass('hide') : blockActionsEl.addClass('hide') + + updateFooterButtons() + } + + const bindEvents = () => { + $('.check-button').on('click', onCheck) + $('.unblock-button').on('click', onUnblock) + $('.reset-button').on('click', onReset) + + window.codioAssessmentsHelper.addBodyHeightListener() + } + + const render = () => { + const container = $('.codio-assessment') + const nameEl = container.find('.codio-assessment-name') + assessment.source.showName ? nameEl.text(assessment.source.name) : nameEl.remove() + renderContent() + renderFooter() + renderGuidance() + renderResult() + updateHtml() + bindEvents() + container.removeClass('hide') + } + + const processMessage = (jsonData) => { + try { + const {method, data} = JSON.parse(jsonData) + console.log('assessment iframe processMessage', jsonData, method, data) + switch (method) { + case window.codioAssessmentsHelper.METHODS.GET_STYLES_RESPONSE: + window.codioAssessmentsHelper.addStyle(data.css) + break + case window.codioAssessmentsHelper.METHODS.GET_STATE_RESPONSE: + updateProcessing(false) + applyState(data) + break + case window.codioAssessmentsHelper.METHODS.CALLBACK: { + window.codioAssessmentsHelper.processCallback(data) + break + } + } + } catch {} + } + + window.addEventListener('load', () => { + window.codioAssessmentsHelper.registerMessageListener(processMessage) + window.codioAssessmentsHelper.send(window.codioAssessmentsHelper.METHODS.GET_STATE) + window.codioAssessmentsHelper.send(window.codioAssessmentsHelper.METHODS.GET_STYLES) + }) +})() From d27122e890206fc35bb8f7219d2f8862471c3d93 Mon Sep 17 00:00:00 2001 From: Mikhail Loginov Date: Wed, 1 Apr 2026 11:37:13 +0100 Subject: [PATCH 03/15] render output --- index.html | 7 +- index.js | 200 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 202 insertions(+), 5 deletions(-) diff --git a/index.html b/index.html index 75ee3c8..3b0fd59 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,12 @@ - Parsons assessment + Standard code test assessment + + diff --git a/index.js b/index.js index 629ead4..de107df 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,18 @@ let showAsDiff = false let currentData = null + const LONG_OUTPUT_LENGTH = 20000 + + const isEmptyObject = (obj) => { + for (const prop in obj) { + if (Object.hasOwn(obj, prop)) { + return false; + } + } + + return true; + } + const updateProcessing = (status) => { processing = status updateHtml() @@ -65,17 +77,46 @@ } const hasDiff = () => { - // todo + if (!currentData?.result?.output) { + return false + } + let parsed + try { + parsed = JSON.parse(currentData?.result.output) + if (!parsed.sequence) { + return false + } + } catch { + return false + } + + return parsed.sequence.some((item, pos) => { + if (item && !item.passed) { + const showExpectedAnswer = window.codioAssessmentsHelper.calculateShowExpectedAnswer( + assessmentOptions.eduStartedAssignment, + assessment.source.showExpectedAnswerOption + ) + const showAnswerFromSource = assessmentOptions.showAsTeacher || showExpectedAnswer + const sourceSequence = assessment.source.sequence && assessment.source.sequence[pos] + const sourceExpectedOutput = assessment.source.expectedOutputs && assessment.source.expectedOutputs[pos] + const resultExpectedOutput = !assessment.source.sequence && + currentData?.result?.expectedOutputs && currentData?.result?.expectedOutputs[pos] + const expectedResult = (showAnswerFromSource && (sourceSequence || sourceExpectedOutput)) || + resultExpectedOutput + return !!expectedResult + } + return false + }) } const updateFooterButtons = () => { const assessmentState = getAssessmentState() - const {teacherInStudentsProject, showModify, isDisabled, canAnswerAgain, passed, answered} = assessmentState + const {teacherInStudentsProject, showModify, isDisabled, canAnswerAgain, answered} = assessmentState const checkVisibility = !showModify && assessmentOptions.useSubmitButtons const checkBtn = $('.check-button') updateVisibility(checkBtn, checkVisibility) - $('.check-button').attr('disabled', isDisabled) + checkBtn.attr('disabled', isDisabled) const unblockVisibility = !teacherInStudentsProject && showModify updateVisibility($('.unblock-button'), unblockVisibility) @@ -126,8 +167,159 @@ } } + const onExpandClick = () => { + // todo expand action + } + + const getValidHtml = (text) => { + return new DOMParser().parseFromString(text, 'text/html').querySelector("body").innerHTML + } + + const diffOutput = (expectedOutput, output) => { + const dmp = new window.DiffMatchPatch() + const pattern_para = /\n/g + const diff = dmp.diff_main(output, expectedOutput) + dmp.diff_cleanupSemantic(diff) + const diffItems = diff.map(item => { + const text = item[1].replace(pattern_para, '¶
') + switch (item[0]) { + case 1: + return `${getValidHtml(text)}` + case -1: + return `${getValidHtml(text)}` + case 0: + default: + return getValidHtml(text) + } + }) + + return `
${diffItems.join('')}
` + } + + const renderOutput = () => { + const {showAsTeacher, eduStartedAssignment} = assessmentOptions + const result = currentData?.result + const currentState = processing ? window.codioAssessmentsHelper.States.PROGRESS : result?.state + const resultClasses = `codio-assessment-result-output codio-assessment-result-output-${currentState}` + let output = null + let currentLimit = LONG_OUTPUT_LENGTH + + function cutOutput(inputText) { + return inputText.length < currentLimit ? inputText : inputText.substring(0, currentLimit) + '\n...' + } + + if (result?.output) { + try { + const parsed = JSON.parse(result.output) + const sequenceSize = parsed?.sequence.length || 0 + if (sequenceSize > 0) { + currentLimit = LONG_OUTPUT_LENGTH / sequenceSize + } + if (parsed.sequence) { + output = parsed.sequence.map((item, pos) => { + let state = `failed` + let reason = '' + let feedback = '' + if (item && item.passed) { + state = `passed` + } else { + let expectedStr = '' + const showExpectedAnswer = window.codioAssessmentsHelper.calculateShowExpectedAnswer( + eduStartedAssignment, + assessment.source.showExpectedAnswerOption + ) + const showAnswerFromSource = showAsTeacher || showExpectedAnswer + const sourceSequence = assessment.source.sequence && assessment.source.sequence[pos] + const sourceExpectedOutput = assessment.source.expectedOutputs && + !isEmptyObject(assessment.source.expectedOutputs[pos]) && + assessment.source.expectedOutputs[pos] + const resultExpectedOutput = !assessment.source.sequence && + result.expectedOutputs && !isEmptyObject(result.expectedOutputs[pos]) && result.expectedOutputs[pos] + const expectedResult = (showAnswerFromSource && (sourceSequence || sourceExpectedOutput)) || + resultExpectedOutput + if (expectedResult) { + if (showAsDiff) { + const truncatedOutput = cutOutput(item.stdout + item.stderr) + reason = diffOutput(expectedResult.output, truncatedOutput) + } else { + const escapedExpectedOutput = window.codioAssessmentsHelper.escapeHTML(expectedResult.output) + expectedStr = `Expected:
${escapedExpectedOutput}
` + + const outputText = cutOutput(window.codioAssessmentsHelper.escapeHTML(item.stdout + item.stderr)) + const outputStr = `Output:
${outputText}
` + + reason = `
${outputStr}${expectedStr}
` + } + } + } + if (!item.passed) { + const sourceSequence = assessment.source.sequence && assessment.source.sequence[pos] + const sourceFeedback = sourceSequence && sourceSequence.showFeedback && sourceSequence.feedback + const resultFeedbackObject = !assessment.source.sequence && assessment.source.feedbacks && + assessment.source.feedbacks[pos] + const feedbackToShow = sourceFeedback || (resultFeedbackObject.show && resultFeedbackObject.feedback) + if (feedbackToShow && !showAsDiff) { + const escapedFeedback = window.codioAssessmentsHelper.escapeHTML(feedbackToShow) + feedback = `
Feedback:
${escapedFeedback}
` + } + } + return `
Check ${pos + 1} ${state}${reason}${feedback}
` + }).join('') + } else if (parsed.error) { + const errorOutput = cutOutput(parsed.error) + output = `
Error: ${errorOutput}
` + } + } catch { + output = cutOutput(result.output) + } + } + const outputEl = $(`
`) + outputEl.attr('tabIndex', 0) + outputEl.attr('aria-label', `Scrollable feedback ${assessment.source.showName ? assessment.source.name : ''}`) + outputEl.html(result?.state !== window.codioAssessmentsHelper.States.RESET ? output : '') + + return outputEl + } + const renderResult = () => { - // todo render result + const assessmentState = getAssessmentState() + const resultBlock = $('.codio-assessment-results-block') + resultBlock.empty() + if (!assessmentState.answered && !processing) { + return + } + const result = currentData?.result + const state = processing ? window.codioAssessmentsHelper.States.PROGRESS : result?.state + const resultEl = $(`
`) + + const assessmentStatus = window.codioAssessmentsHelper.getAssessmentResultStatus( + assessment.source, result, processing + ) + const iconStr = window.codioAssessmentsHelper.getIconByResultStatus(assessmentStatus) + const iconEl = $(iconStr).addClass('codio-assessment-result-status-icon') + const iconContainer = $('
') + iconContainer.append(iconEl) + resultEl.append(iconContainer) + const resultInfoContainer = $('
') + + if (result?.timestamp) { + const timestamp = new Date(result.timestamp).toLocaleString() + const timestampEl = $(` +
LAST RUN  +on ${timestamp} +
+`) + resultInfoContainer.append(timestampEl) + } + resultInfoContainer.append(renderOutput()) + + const resultActionsContainer = $('
') + const expandButton = $('') + expandButton.attr('aria-label', `Expand output ${assessment.source.showName ? assessment.source.name : ''}`) + expandButton.on('click', onExpandClick) + resultActionsContainer.append(expandButton) + resultEl.append(resultActionsContainer) + resultBlock.append(resultEl) } const getAssessmentState = () => { From dea4e8c1379d126c52ad36f9f64a47124f3ce3c1 Mon Sep 17 00:00:00 2001 From: Mikhail Loginov Date: Wed, 1 Apr 2026 16:34:58 +0100 Subject: [PATCH 04/15] render output --- index.css | 97 +++++++++++++++++++++++++++++++++++++ index.html | 1 + index.js | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 229 insertions(+), 8 deletions(-) diff --git a/index.css b/index.css index e69de29..5a58742 100644 --- a/index.css +++ b/index.css @@ -0,0 +1,97 @@ +.codio-assessment-result { + display: flex; + gap: 20px; + background: #fff; + border: 1px solid #ddd; + border-radius: 2px; + line-height: 25px; + position: relative; + padding: 1em 20px; + font-weight: normal; + margin: 1em 0; + min-height: 30px; +} + +.codio-assessment-result:before { + position: absolute; + display: inline-block; + left: 19px; + bottom: 100%; + content: ''; + border-left: 10px solid transparent; + border-bottom: 11px solid #ddd; + border-right: 10px solid transparent; +} + +.codio-assessment-result:after { + position: absolute; + display: inline-block; + left: 20px; + bottom: 100%; + content: ''; + border-left: 8px solid transparent; + border-bottom: 9px solid #fff; + border-right: 9px solid transparent; +} + +.codio-assessment-result-status-icon.passed { + color: #008000; +} + +.codio-assessment-result-status-icon.failed { + color: #de0d23; +} + +.codio-assessment-result-status-icon.partial { + color: #eb7100; +} + +.codio-assessment-result-result-info-container { + flex: 1; +} + +.codio-assessment-last-run-time { + color: #888; +} + +.codio-assessment-output-line { + font-weight: bold; +} + +.codio-assessment-result-success-text { + color: #468847; +} + +.codio-assessment-result-error-text { + color: #b94a48; +} + +.codio-assessment-output-text { + background-color: #ffe6e6; +} + +.codio-assessment-expected-output-text { + background-color: #e9fff2; +} + +.codio-assessment-output-diff-del { + background-color: #ffe6e6; +} + +.codio-assessment-output-diff-ins { + background-color: #e9fff2; +} + +.codio-assessment-result-actions-expand { + padding: 0; + width: 24px; + height: 24px; + border: none; + background: no-repeat; + cursor: pointer; + opacity: 0.8; + + &:hover { + opacity: 1; + } +} diff --git a/index.html b/index.html index 3b0fd59..5483224 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,7 @@ Standard code test assessment + -
+
- - + +
-
- +
+
-
- +
+
-
- +
+
From 3d091a6fb1a8af5db887ded0bfc64837d1288408 Mon Sep 17 00:00:00 2001 From: Mikhail Loginov Date: Tue, 7 Apr 2026 09:26:58 +0100 Subject: [PATCH 06/15] remove duplicates --- index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/index.html b/index.html index 5483224..4f20c4e 100644 --- a/index.html +++ b/index.html @@ -8,7 +8,6 @@ import diffMatchPatch from 'https://cdn.jsdelivr.net/npm/diff-match-patch@1.0.5/+esm' window.DiffMatchPatch = diffMatchPatch - From 4082d0b626b730ace61f054355ff24cfcc715137 Mon Sep 17 00:00:00 2001 From: Mikhail Loginov Date: Mon, 13 Apr 2026 11:50:18 +0100 Subject: [PATCH 07/15] collapse --- index.js | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/index.js b/index.js index 746c50c..8692bf6 100644 --- a/index.js +++ b/index.js @@ -4,6 +4,7 @@ let processing = false let showAsDiff = false let currentData = null + let expanded = false const LONG_OUTPUT_LENGTH = 20000 @@ -115,7 +116,7 @@ const updateProcessing = (status) => { processing = status - updateHtml() + refreshResultAndFooter() } const applyStateInitial = (data) => { @@ -141,13 +142,15 @@ return } if (data.state) { - updateHtml() + renderResult() + refreshResultAndFooter() renderGuidance() return } // reset if (currentData.state && !data.state) { - updateHtml() + renderResult() + refreshResultAndFooter() renderGuidance() } } @@ -271,16 +274,10 @@ } const onExpandClick = () => { - // todo expand action - // const {result} = currentData || {} - // const data = { - // outputs: getOutputs(), - // view: showAsDiff ? 'diff' : 'output', - // timestamp: result?.timestamp, - // state: processing ? window.codioAssessmentsHelper.States.PROGRESS : result?.state, - // iconState: getAssessmentStatusIconState(source, result, processing), - // focusedElId - // } + const {EXPAND, COLLAPSE} = window.codioAssessmentsHelper.METHODS + const action = expanded ? COLLAPSE :EXPAND + expanded = !expanded + window.codioAssessmentsHelper.send(action) } const getValidHtml = (text) => { @@ -467,16 +464,11 @@ updateFooterButtons() } - const updateHtml = () => { + const refreshResultAndFooter = () => { if (!assessment) { return } - // processing, new state/results - const assessmentState = getAssessmentState() - $('.check-button').attr('disabled', assessmentState.isDisabled) - const blockActionsEl = $('.block-actions') - assessmentState.isDisabled ? blockActionsEl.removeClass('hide') : blockActionsEl.addClass('hide') - + renderResult() updateFooterButtons() } @@ -496,8 +488,7 @@ renderContent() renderFooter() renderGuidance() - renderResult() - updateHtml() + refreshResultAndFooter() bindEvents() container.removeClass('hide') } From 6025156b7df7be955374ecab1dbce8482f2146d6 Mon Sep 17 00:00:00 2001 From: Mikhail Loginov Date: Fri, 17 Apr 2026 13:41:32 +0100 Subject: [PATCH 08/15] remove dummy data fixed button states fixed result update --- index.js | 131 +++++----------------------------------------------- settings.js | 6 +-- 2 files changed, 15 insertions(+), 122 deletions(-) diff --git a/index.js b/index.js index 8692bf6..05d6395 100644 --- a/index.js +++ b/index.js @@ -8,102 +8,6 @@ const LONG_OUTPUT_LENGTH = 20000 - const dummyResult = { - "points": 40, - "guidance": "

Rationale

\n", - "usedAttempts": 1, - "timestamp": "2026-04-01T10:23:33.229Z", - "code": 2, - "output": "{\"sequence\": [{\"returnCode\": 0, \"stderr\": \"\", \"passed\": true, \"stdout\": \"5\\n\"}, {\"returnCode\": 0, \"stderr\": \"\", \"passed\": true, \"stdout\": \"4\\n\"}, {\"returnCode\": 0, \"stderr\": \"\", \"passed\": false, \"stdout\": \"6\\n\"}, {\"returnCode\": 0, \"stderr\": \"\", \"passed\": false, \"stdout\": \"2\\n\"}, {\"returnCode\": 0, \"stderr\": \"\", \"passed\": false, \"stdout\": \"2\\n\"}]}", - "state": "pass" - } - const dummySource = { - "name": "standard partial arguments count", - "showName": true, - "settings": { - "instructions": "

Instructions here

\n", - "command": "python code_tests/standard_partial_argumentscount.py", - "preExecuteCommand": "", - "timeout": 30 - }, - "options": { - "ignoreCase": false, - "ignoreWhitespaces": true, - "ignoreNewline": true, - "matchSubstring": false - }, - "metadata": { - "tags": [ - { - "name": "Assessment Type", - "value": "Standard Code Test" - }, - { - "name": "Content", - "value": "code test" - }, - { - "name": "Programming Language", - "value": "python" - } - ], - "files": [ - "code_tests/standard_partial_argumentscount.py" - ], - "opened": [] - }, - "bloomsObjectiveLevel": "1", - "learningObjectives": "learning", - "guidance": "

Rationale

\n", - "showGuidanceAfterResponseOption": { - "type": "Always" - }, - "maxAttemptsCount": 0, - "points": 100, - "showExpectedAnswerOption": { - "type": "Always" - }, - "arePartialPointsAllowed": true, - "useMaximumScore": false, - "sequence": [ - { - "arguments": "one two three four", - "input": "", - "output": "5", - "showFeedback": false, - "feedback": "" - }, - { - "arguments": "1 2 3", - "input": "", - "output": "4", - "showFeedback": false, - "feedback": "" - }, - { - "arguments": "1 2 3 4 5", - "input": "", - "output": "10", - "showFeedback": false, - "feedback": "" - }, - { - "arguments": "1", - "input": "", - "output": "4", - "showFeedback": false, - "feedback": "" - }, - { - "arguments": "1", - "input": "", - "output": "4", - "showFeedback": false, - "feedback": "" - } - ] - } - const isEmptyObject = (obj) => { for (const prop in obj) { if (Object.hasOwn(obj, prop)) { @@ -122,10 +26,6 @@ const applyStateInitial = (data) => { const {state, result, ...dataWithoutState} = data assessment = dataWithoutState.assessment - // todo remove dummy start - assessment.source = dummySource - // todo remove dummy end - assessmentOptions = dataWithoutState.options render() @@ -133,26 +33,14 @@ const applyState = (data) => { console.log('assessment iframe applyState', data) - // todo remove dummy start - data.result = dummyResult - // todo remove dummy end currentData = data if (!assessment) { applyStateInitial(data) return } - if (data.state) { - renderResult() - refreshResultAndFooter() - renderGuidance() - return - } - // reset - if (currentData.state && !data.state) { - renderResult() - refreshResultAndFooter() - renderGuidance() - } + updateCheckButtonText() + refreshResultAndFooter() + renderGuidance() } const onCheck = (event) => { @@ -222,7 +110,7 @@ const checkVisibility = !showModify && assessmentOptions.useSubmitButtons const checkBtn = $('.check-button') updateVisibility(checkBtn, checkVisibility) - checkBtn.attr('disabled', isDisabled) + checkBtn.prop('disabled', isDisabled) const unblockVisibility = !teacherInStudentsProject && showModify updateVisibility($('.unblock-button'), unblockVisibility) @@ -238,9 +126,14 @@ diffBtn.html(diffBtnTitle) } - const renderFooter = () => { + const updateCheckButtonText = () => { const footerContainer = $('.codio-assessment-footer') - const caption = window.codioAssessmentsHelper.getButtonCaption(assessmentOptions, assessment.source.maxAttemptsCount) + const {result} = currentData || {} + const caption = window.codioAssessmentsHelper.getButtonCaption( + assessmentOptions, + assessment.source.maxAttemptsCount, + result?.usedAttempts || 0 + ) footerContainer.find('.check-button').html(caption) } @@ -486,7 +379,7 @@ const nameEl = container.find('.codio-assessment-name') assessment.source.showName ? nameEl.text(assessment.source.name) : nameEl.remove() renderContent() - renderFooter() + updateCheckButtonText() renderGuidance() refreshResultAndFooter() bindEvents() diff --git a/settings.js b/settings.js index 35e6b74..ab755b1 100644 --- a/settings.js +++ b/settings.js @@ -2,10 +2,10 @@ const collectSettings = () => { const instructions = $('#instructions').val() const command = $('#command').val(); - const preExecCommand = $('#preExecCommand').val(); + const preExecuteCommand = $('#preExecCommand').val(); const timeout = parseInt($('#timeout').val(), 10); - return {instructions, command, preExecCommand, timeout}; + return {instructions, command, preExecuteCommand, timeout}; } const exportSettings = () => { @@ -16,7 +16,7 @@ const applySettings = (settings = {}) => { $('#instructions').val(settings.instructions || ''); $('#command').val(settings.command || ''); - $('#preExecCommand').val(settings.preExecCommand || ''); + $('#preExecCommand').val(settings.preExecuteCommand || ''); $('#timeout').val(settings.timeout || ''); } From 2b563fb299298fc56e07581c5c8ef0abadb37464 Mon Sep 17 00:00:00 2001 From: Mikhail Loginov Date: Fri, 17 Apr 2026 17:02:59 +0100 Subject: [PATCH 09/15] settings styling --- settings.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/settings.html b/settings.html index 24a5add..fd84b64 100644 --- a/settings.html +++ b/settings.html @@ -11,23 +11,23 @@
-
+
-
+
- +
-
+
- +
-
+
- +
From 6fe317a838d0fa5df9d4967efb348aa954081e9a Mon Sep 17 00:00:00 2001 From: Mikhail Loginov Date: Thu, 23 Apr 2026 16:08:04 +0100 Subject: [PATCH 10/15] rename classes --- settings.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/settings.html b/settings.html index fd84b64..98df6f1 100644 --- a/settings.html +++ b/settings.html @@ -11,21 +11,21 @@
-
+
-
+
-
+
-
+
From 70a6e23ca278fe545019312ffcd439190d78728c Mon Sep 17 00:00:00 2001 From: Mikhail Loginov Date: Wed, 29 Apr 2026 14:09:07 +0100 Subject: [PATCH 11/15] move styles to common helpers --- index.css | 90 ------------------------------------------------------- index.js | 2 +- 2 files changed, 1 insertion(+), 91 deletions(-) diff --git a/index.css b/index.css index 5a58742..94a153e 100644 --- a/index.css +++ b/index.css @@ -1,79 +1,3 @@ -.codio-assessment-result { - display: flex; - gap: 20px; - background: #fff; - border: 1px solid #ddd; - border-radius: 2px; - line-height: 25px; - position: relative; - padding: 1em 20px; - font-weight: normal; - margin: 1em 0; - min-height: 30px; -} - -.codio-assessment-result:before { - position: absolute; - display: inline-block; - left: 19px; - bottom: 100%; - content: ''; - border-left: 10px solid transparent; - border-bottom: 11px solid #ddd; - border-right: 10px solid transparent; -} - -.codio-assessment-result:after { - position: absolute; - display: inline-block; - left: 20px; - bottom: 100%; - content: ''; - border-left: 8px solid transparent; - border-bottom: 9px solid #fff; - border-right: 9px solid transparent; -} - -.codio-assessment-result-status-icon.passed { - color: #008000; -} - -.codio-assessment-result-status-icon.failed { - color: #de0d23; -} - -.codio-assessment-result-status-icon.partial { - color: #eb7100; -} - -.codio-assessment-result-result-info-container { - flex: 1; -} - -.codio-assessment-last-run-time { - color: #888; -} - -.codio-assessment-output-line { - font-weight: bold; -} - -.codio-assessment-result-success-text { - color: #468847; -} - -.codio-assessment-result-error-text { - color: #b94a48; -} - -.codio-assessment-output-text { - background-color: #ffe6e6; -} - -.codio-assessment-expected-output-text { - background-color: #e9fff2; -} - .codio-assessment-output-diff-del { background-color: #ffe6e6; } @@ -81,17 +5,3 @@ .codio-assessment-output-diff-ins { background-color: #e9fff2; } - -.codio-assessment-result-actions-expand { - padding: 0; - width: 24px; - height: 24px; - border: none; - background: no-repeat; - cursor: pointer; - opacity: 0.8; - - &:hover { - opacity: 1; - } -} diff --git a/index.js b/index.js index 05d6395..2bb9e83 100644 --- a/index.js +++ b/index.js @@ -292,7 +292,7 @@ } const result = currentData?.result const state = processing ? window.codioAssessmentsHelper.States.PROGRESS : result?.state - const resultEl = $(`
`) + const resultEl = $(`
`) const assessmentStatus = window.codioAssessmentsHelper.getAssessmentResultStatus( assessment.source, result, processing From 50f72c4425b78712e230b204ae033871d8e776ae Mon Sep 17 00:00:00 2001 From: Mikhail Loginov Date: Wed, 13 May 2026 15:51:01 +0100 Subject: [PATCH 12/15] update classes --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 4f20c4e..c6f32bd 100644 --- a/index.html +++ b/index.html @@ -14,7 +14,7 @@ -
+

From 0374582281bedf8a219940fa029e00e1ab728e9a Mon Sep 17 00:00:00 2001 From: Mikhail Loginov Date: Tue, 2 Jun 2026 16:51:08 +0100 Subject: [PATCH 13/15] refactor settings --- settings.html | 24 ++++++++++++++++-------- settings.js | 13 +++++++++++-- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/settings.html b/settings.html index 98df6f1..21853d0 100644 --- a/settings.html +++ b/settings.html @@ -11,23 +11,31 @@
-
+
- +
+ +
-
+
- +
+ +
-
+
- +
+ +
-
+
- +
+ +
diff --git a/settings.js b/settings.js index ab755b1..d8143aa 100644 --- a/settings.js +++ b/settings.js @@ -1,11 +1,20 @@ (function () { + const DEFAULT_TIMEOUT = 30 + const collectSettings = () => { + const errors = [] const instructions = $('#instructions').val() const command = $('#command').val(); const preExecuteCommand = $('#preExecCommand').val(); const timeout = parseInt($('#timeout').val(), 10); - return {instructions, command, preExecuteCommand, timeout}; + !instructions && errors.push('Instructions field must be completed'); + !command && errors.push('Command field must be completed'); + + return { + data: {instructions, command, preExecuteCommand, timeout: timeout || DEFAULT_TIMEOUT}, + errors + }; } const exportSettings = () => { @@ -17,7 +26,7 @@ $('#instructions').val(settings.instructions || ''); $('#command').val(settings.command || ''); $('#preExecCommand').val(settings.preExecuteCommand || ''); - $('#timeout').val(settings.timeout || ''); + $('#timeout').val(settings.timeout || DEFAULT_TIMEOUT); } const processMessage = (jsonData) => { From f29539e597c42912317169a0a7aa08aee098f15c Mon Sep 17 00:00:00 2001 From: Mikhail Loginov Date: Tue, 9 Jun 2026 12:36:28 +0100 Subject: [PATCH 14/15] tiny mde as markdown editor --- settings.html | 1 + settings.js | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/settings.html b/settings.html index 21853d0..452894c 100644 --- a/settings.html +++ b/settings.html @@ -14,6 +14,7 @@
+
diff --git a/settings.js b/settings.js index d8143aa..1ccd9b8 100644 --- a/settings.js +++ b/settings.js @@ -1,9 +1,10 @@ (function () { const DEFAULT_TIMEOUT = 30 + let instructionsEditor = null const collectSettings = () => { const errors = [] - const instructions = $('#instructions').val() + const instructions = instructionsEditor.getContent() const command = $('#command').val(); const preExecuteCommand = $('#preExecCommand').val(); const timeout = parseInt($('#timeout').val(), 10); @@ -23,7 +24,7 @@ } const applySettings = (settings = {}) => { - $('#instructions').val(settings.instructions || ''); + instructionsEditor.setContent(settings.instructions || '') $('#command').val(settings.command || ''); $('#preExecCommand').val(settings.preExecuteCommand || ''); $('#timeout').val(settings.timeout || DEFAULT_TIMEOUT); @@ -47,6 +48,7 @@ const onLoad = async () => { window.codioAssessmentsHelper.registerMessageListener(processMessage) window.codioAssessmentsHelper.send(window.codioAssessmentsHelper.METHODS.GET_SETTINGS) + instructionsEditor = window.codioAssessmentsHelper.initializeMarkdownEditor('instructions', 'instructions-command-bar') } window.addEventListener('load', onLoad); From 5f49190e2d524e559875d173ddf3201c4c908f2f Mon Sep 17 00:00:00 2001 From: Mikhail Loginov Date: Mon, 29 Jun 2026 15:55:08 +0100 Subject: [PATCH 15/15] added tiny-mde --- settings.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/settings.html b/settings.html index 452894c..f17c4b0 100644 --- a/settings.html +++ b/settings.html @@ -6,6 +6,12 @@ + +