diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index e785aafdac..50650a7571 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -29,6 +29,7 @@ jobs: libjson-perl \ libjson-xs-perl \ liblocale-maketext-lexicon-perl \ + libmojolicious-perl \ libtest2-suite-perl \ libtie-ixhash-perl \ libuuid-tiny-perl \ diff --git a/cpanfile b/cpanfile index 46f288e3fd..ca6cbe1f03 100644 --- a/cpanfile +++ b/cpanfile @@ -18,6 +18,7 @@ on runtime => sub { requires 'JSON::XS'; requires 'Locale::Maketext'; requires 'Locale::Maketext::Lexicon'; + requires 'Mojolicious'; requires 'Tie::IxHash'; requires 'Types::Serialiser'; requires 'UUID::Tiny'; diff --git a/docker/pg.Dockerfile b/docker/pg.Dockerfile index ffcf22c772..36741fc5ff 100644 --- a/docker/pg.Dockerfile +++ b/docker/pg.Dockerfile @@ -21,6 +21,7 @@ RUN apt-get update \ libjson-perl \ libjson-xs-perl \ liblocale-maketext-lexicon-perl \ + libmojolicious-perl \ libtest2-suite-perl \ libtie-ixhash-perl \ libuuid-tiny-perl \ diff --git a/htdocs/js/Essay/essay.js b/htdocs/js/Essay/essay.js index 2d0fab6b38..c3a6a59def 100644 --- a/htdocs/js/Essay/essay.js +++ b/htdocs/js/Essay/essay.js @@ -1,17 +1,28 @@ 'use strict'; (() => { - const addPreviewButton = (latexEntry) => { - if (latexEntry.dataset.previewBtnAdded) return; - latexEntry.dataset.previewBtnAdded = 'true'; + const initializePreviewButton = (latexEntry) => { + if (latexEntry.dataset.previewBtnInitialized) return; + latexEntry.dataset.previewBtnInitialized = 'true'; - const buttonContainer = document.createElement('div'); - buttonContainer.classList.add('latexentry-button-container', 'mt-1'); + const buttonContainer = + document.getElementById(`${latexEntry.id}-latexentry-button-container`) || document.createElement('div'); - const button = document.createElement('button'); - button.type = 'button'; - button.classList.add('latexentry-preview', 'btn', 'btn-secondary', 'btn-sm'); - button.textContent = 'Preview'; + if (!buttonContainer.classList.contains('latexentry-button-container')) { + buttonContainer.classList.add('latexentry-button-container', 'mt-1'); + buttonContainer.id = `${latexEntry.id}-latexentry-button-container`; + latexEntry.after(buttonContainer); + } + + const button = buttonContainer.querySelector('.latexentry-preview') || document.createElement('button'); + + if (!button.classList.contains('latexentry-preview')) { + button.type = 'button'; + button.classList.add('latexentry-preview', 'btn', 'btn-secondary', 'btn-sm'); + button.textContent = 'Preview'; + + buttonContainer.append(button); + } button.addEventListener('click', () => { button.dataset.bsContent = latexEntry.value @@ -49,19 +60,16 @@ popover.show(); } }); - - buttonContainer.append(button); - latexEntry.after(buttonContainer); }; - document.querySelectorAll('.latexentryfield').forEach(addPreviewButton); + document.querySelectorAll('.latexentryfield').forEach(initializePreviewButton); const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { for (const node of mutation.addedNodes) { if (node instanceof Element) { - if (node.classList.contains('latexentryfield')) addPreviewButton(node); - else node.querySelectorAll('.latexentryfield').forEach(addPreviewButton); + if (node.classList.contains('latexentryfield')) initializePreviewButton(node); + else node.querySelectorAll('.latexentryfield').forEach(initializePreviewButton); } } } diff --git a/htdocs/js/Feedback/feedback.js b/htdocs/js/Feedback/feedback.js new file mode 100644 index 0000000000..b7271cee39 --- /dev/null +++ b/htdocs/js/Feedback/feedback.js @@ -0,0 +1,49 @@ +(() => { + const initializeFeedback = (feedbackBtn) => { + if (feedbackBtn.dataset.popoverInitialized) return; + feedbackBtn.dataset.popoverInitialized = 'true'; + + new bootstrap.Popover(feedbackBtn, { sanitize: false, container: feedbackBtn.parentElement }); + + // Render MathJax previews. + if (window.MathJax) { + feedbackBtn.addEventListener('show.bs.popover', () => { + MathJax.startup.promise = MathJax.startup.promise.then(() => MathJax.typesetPromise(['.popover-body'])); + }); + } + + feedbackBtn.addEventListener('shown.bs.popover', () => { + const bsPopover = bootstrap.Popover.getInstance(feedbackBtn); + + // Execute javascript in the answer preview. + bsPopover.tip?.querySelectorAll('script').forEach((origScript) => { + const newScript = document.createElement('script'); + Array.from(origScript.attributes).forEach((attr) => newScript.setAttribute(attr.name, attr.value)); + newScript.appendChild(document.createTextNode(origScript.innerHTML)); + origScript.parentNode.replaceChild(newScript, origScript); + }); + + // Make a click on the popover header close the popover. + bsPopover.tip?.querySelector('.popover-header')?.addEventListener('click', () => bsPopover?.hide()); + }); + }; + + // Setup feedback popovers already on the page. + document.querySelectorAll('.ww-feedback-btn').forEach(initializeFeedback); + + // Deal with feedback popovers that are added to the page later. + const observer = new MutationObserver((mutationsList) => { + mutationsList.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node instanceof Element) { + if (node.classList.contains('ww-feedback-btn')) initializeFeedback(node.firstElementChild); + else node.querySelectorAll('.ww-feedback-btn').forEach(initializeFeedback); + } + }); + }); + }); + observer.observe(document.body, { childList: true, subtree: true }); + + // Stop the mutation observer when the window is closed. + window.addEventListener('unload', () => observer.disconnect()); +})(); diff --git a/htdocs/js/InputColor/color.js b/htdocs/js/InputColor/color.js deleted file mode 100644 index 025301ac18..0000000000 --- a/htdocs/js/InputColor/color.js +++ /dev/null @@ -1,69 +0,0 @@ -// For coloring the input elements with the proper color based on whether they are correct or incorrect. - -(() => { - const setupAnswerLink = (answerLink) => { - const answerId = answerLink.dataset.answerId; - const answerInput = document.getElementById(answerId); - - const type = answerLink.parentNode.classList.contains('ResultsWithoutError') ? 'correct' : 'incorrect'; - const radioGroups = {}; - - // Color all of the inputs and selects associated with this answer. On the first pass radio inputs are - // collected into groups by name, and on the second pass the checked radio is highlighted, or if none are - // checked all are highlighted. - document.querySelectorAll(`input[name*=${answerId}],select[name*=${answerId}`) - .forEach((input) => { - if (input.type.toLowerCase() === 'radio') { - if (!radioGroups[input.name]) radioGroups[input.name] = []; - radioGroups[input.name].push(input); - } else { - if (input.type.toLowerCase() === 'text') { - if (type === 'correct' || input.value !== '') input.classList.add(type); - } else - input.classList.add(type); - } - }); - - Object.values(radioGroups).forEach((group) => { - if (group.every((radio) => { - if (radio.checked) { - radio.classList.add(type); - return false; - } - return true; - })) { - group.forEach((radio) => radio.classList.add(type)); - } - }); - - if (answerInput) { - answerLink.addEventListener('click', (e) => { - e.preventDefault(); - answerInput.focus(); - }); - } else { - answerLink.href = ''; - } - }; - - // Color inputs already on the page. - document.querySelectorAll('td a[data-answer-id]').forEach(setupAnswerLink); - - // Deal with inputs that are added to the page later. - const observer = new MutationObserver((mutationsList) => { - mutationsList.forEach((mutation) => { - mutation.addedNodes.forEach((node) => { - if (node instanceof Element) { - if (node.type && node.type.toLowerCase() === 'td' && node.firstElementChild - && node.firstElementChild.type.toLowerCase() == 'a' && node.firstElementChild.dataset.answerId) - setupAnswerLink(node.firstElementChild); - else node.querySelectorAll('td a[data-answer-id]').forEach(setupAnswerLink); - } - }); - }); - }); - observer.observe(document.body, { childList: true, subtree: true }); - - // Stop the mutation observer when the window is closed. - window.addEventListener('unload', () => observer.disconnect()); -})(); diff --git a/htdocs/js/MathQuill/mqeditor.js b/htdocs/js/MathQuill/mqeditor.js index 57ad5fa0e1..8c313a79f9 100644 --- a/htdocs/js/MathQuill/mqeditor.js +++ b/htdocs/js/MathQuill/mqeditor.js @@ -31,6 +31,14 @@ input.classList.add('mq-edit'); answerQuill.latexInput = mq_input; + // Give the mathquill answer box the correct/incorrect colors. + if (input.classList.contains('correct')) answerQuill.classList.add('correct'); + if (input.classList.contains('incorrect')) answerQuill.classList.add('incorrect'); + if (input.classList.contains('partially-correct')) answerQuill.classList.add('partially-correct'); + + const ariaDescribedBy = input.getAttribute('aria-describedby'); + if (ariaDescribedBy) answerQuill.setAttribute('aria-describedby', ariaDescribedBy); + // Default options. const cfgOptions = { spaceBehavesLikeTab: true, @@ -130,8 +138,8 @@ button.append(icon); // Find the preview button container, and add the equation editor button to that. - const buttonContainer = container.nextElementSibling; - if (buttonContainer && buttonContainer.classList.contains('latexentry-button-container')) { + const buttonContainer = document.getElementById(`${answerLabel}-latexentry-button-container`); + if (buttonContainer) { buttonContainer.classList.add('d-flex', 'gap-1'); buttonContainer.prepend(button); innerContainer.append(buttonContainer); @@ -494,26 +502,6 @@ answerQuill.mathField.latex(answerQuill.latexInput.value); answerQuill.mathField.moveToLeftEnd(); answerQuill.mathField.blur(); - - // Look for a result in the attempts table for this answer. - for (const tableLink of document.querySelectorAll('td a[data-answer-id]')) { - // Give the mathquill answer box the correct/incorrect colors. - if (answerLabel.includes(tableLink.dataset.answerId)) { - if (tableLink.parentNode.classList.contains('ResultsWithoutError')) - answerQuill.classList.add('correct'); - else { - if (answerQuill.input.value !== '') answerQuill.classList.add('incorrect'); - } - } - - // Make a click on the results table link give focus to the mathquill answer box. - if (answerLabel === tableLink.dataset.answerId) { - tableLink.addEventListener('click', (e) => { - e.preventDefault(); - answerQuill.textarea.focus(); - }); - } - } }; // Set up MathQuill inputs that are already in the page. diff --git a/htdocs/js/MathQuill/mqeditor.scss b/htdocs/js/MathQuill/mqeditor.scss index ec2f517c38..e452eba03b 100644 --- a/htdocs/js/MathQuill/mqeditor.scss +++ b/htdocs/js/MathQuill/mqeditor.scss @@ -3,27 +3,11 @@ span[id^='mq-answer'] { /*rtl:ignore*/ direction: ltr; - padding: 4px 5px 2px 5px; + padding: 4px; border-radius: 4px !important; background-color: white; margin-right: 0; margin-left: 0; - - &.correct { - border-color: rgba(81, 153, 81, 0.8); - outline: 0; - outline: thin dotted \9; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(81, 153, 81, 0.6); - color: inherit; - } - - &.incorrect { - border-color: rgba(191, 84, 84, 0.8); - outline: 0; - outline: thin dotted \9; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(191, 84, 84, 0.6); - color: inherit; - } } input[type='text'].codeshard.mq-edit { diff --git a/htdocs/js/Problem/problem.scss b/htdocs/js/Problem/problem.scss index 20c10cf4b2..b2ee8ea366 100644 --- a/htdocs/js/Problem/problem.scss +++ b/htdocs/js/Problem/problem.scss @@ -33,13 +33,18 @@ } /* Problem elements */ - label, input[type=text], select, textarea { + label, + input[type='text'], + select, + textarea { font-weight: normal; line-height: 18px; width: auto; } - select, textarea, input[type=text] { + select, + textarea, + input[type='text'] { display: inline-block; padding: 4px 6px; margin-bottom: 0; @@ -50,56 +55,51 @@ background-color: white; } - textarea, input[type=text] { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + textarea, + input[type='text'] { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; } - input[type=text] { + input[type='text'] { height: 30px; font-size: 14px; line-height: 20px; } - select, input[type=text], input[type=radio], input[type=checkbox] { - &.correct { - border-color: rgba(81, 153, 81, 0.8); /* green */ + select, + input[type='text'], + input[type='radio'], + input[type='checkbox'], + span[id^='mq-answer'], + .graphtool-container { + &.correct:not(:focus):not(.mq-focused) { + border-color: rgba(81, 153, 81, 1); /* green */ outline: 0; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px 2px rgba(81, 153, 81, 0.6); - color: inherit; + box-shadow: inset 0 0 2px 1px rgba(0, 0, 0, 0.25), 0 0 8px 2px rgba(81, 153, 81, 1); } - &.incorrect { - border-color: rgba(191, 84, 84, 0.8); /* red */ + &.incorrect:not(:focus):not(.mq-focused) { + border-color: rgba(191, 84, 84, 1); /* red */ outline: 0; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px 2px rgba(191, 84, 84, 0.6); - color:inherit; + box-shadow: inset 0 0 2px 1px rgba(0, 0, 0, 0.25), 0 0 8px 2px rgba(191, 84, 84, 1); } - } - input[type=text], span.mq-editable-field { - background-size: 20px auto; - background-position: right 4px center; - background-repeat: no-repeat; - - &.correct { - padding-right: 29px; - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e"); - } - - &.incorrect { - padding-right: 29px; - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23AE5757'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23AE5757' stroke='none'/%3e%3c/svg%3e"); + &.partially-correct:not(:focus):not(.mq-focused) { + border-color: rgba(255, 193, 7, 1); /* yellow */ + outline: 0; + box-shadow: inset 0 0 2px 1px rgba(0, 0, 0, 0.25), 0 0 8px 2px rgba(255, 193, 7, 1); } } - input[type=radio] { + input[type='radio'] { margin-right: 0.25rem; } select { cursor: pointer; - &[multiple], &[size] { + &[multiple], + &[size] { height: auto; } } @@ -108,7 +108,10 @@ max-width: 100%; } - input[type=text], input[type=radio], textarea, select { + input[type='text'], + input[type='radio'], + textarea, + select { &:focus { border-color: rgba(82, 168, 236, 0.8); outline: 0; @@ -118,154 +121,163 @@ .pg-table { text-align: center; - thead, tbody, tfoot, tr, td, th { + thead, + tbody, + tfoot, + tr, + td, + th { padding: 0.25rem; border: 1px solid black; } } -} -/* rtl:raw: -.problem-content[dir=ltr] input[type=radio] { - margin-right: 0.25rem; -} -*/ + .ww-feedback-btn { + width: 25px; + line-height: 1; + position: relative; + --bs-btn-padding-x: 2px; + --bs-btn-padding-y: 2px; + + i { + &::before { + content: url("data:image/svg+xml,"); + } + + &.correct::before { + content: url("data:image/svg+xml,"); + } + + &.incorrect::before { + content: url("data:image/svg+xml,"); + } + + &.partially-correct::before { + content: url("data:image/svg+xml,"); + } + } -/* Answer template */ + &.with-message::before { + content: ' '; + position: absolute; + transform: translate(-50%, -50%); + top: 0; + left: 100%; + border: 1px solid black; + border-radius: 50%; + background-color: #dc3545; + padding: 0.25rem; + } + } -.attemptResultsHeader { - margin-bottom: 0.5rem; - padding: 0; - font-size: 1.4875rem; - font-weight: bold; - line-height: 1.2; -} + .radio-buttons-container, + .checkboxes-container, + .applet-container, + .graphtool-outer-container, + .ww-feedback-container { + position: relative; + width: fit-content; + + .ww-feedback-btn { + position: absolute; + left: 100%; + top: 0; + margin-left: 0.25rem; + } -table.attemptResults { - border-style: outset; - border-width: 1px; - margin-bottom: 1em; - border-spacing: 1px; - width: 100%; - border: 1px solid #ddd; - border-collapse: separate; - border-radius: 4px; + &.align-middle { + .ww-feedback-btn { + top: 50%; + transform: translateY(-50%); + } + } - thead:first-child tr:first-child > th:first-child, - tbody:first-child tr:first-child > td:first-child, - tbody:first-child tr:first-child > th:first-child { - border-top-left-radius: 4px; + &.align-bottom { + .ww-feedback-btn { + top: unset; + bottom: 0; + } + } } +} - thead:first-child tr:first-child > th:last-child, - tbody:first-child tr:first-child > td:last-child, - tbody:first-child tr:first-child > th:last-child { - border-top-right-radius: 4px; - } +/* rtl:raw: +.problem-content[dir=ltr] input[type=radio] { + margin-right: 0.25rem; +} +*/ - thead:last-child tr:last-child > th:first-child, - tbody:last-child tr:last-child > td:first-child, - tbody:last-child tr:last-child > th:first-child, - tfoot:last-child tr:last-child > td:first-child, - tfoot:last-child tr:last-child > th:first-child { - border-bottom-left-radius: 4px; - } +/* Feedback */ - thead:last-child tr:last-child > th:last-child, - tbody:last-child tr:last-child > td:last-child, - tbody:last-child tr:last-child > th:last-child, - tfoot:last-child tr:last-child > td:last-child, - tfoot:last-child tr:last-child > th:last-child { - border-bottom-right-radius: 4px; - } +.ww-feedback-popover { + --bs-popover-body-padding-x: 0; + --bs-popover-body-padding-y: 0; + --bs-popover-max-width: 600px; + --bs-popover-zindex: 18; - td, th { - border-style: inset; - border-width: 1px; + .popover-header { text-align: center; - vertical-align: middle; - padding: 2px 5px; - color: inherit; - border-color: #ddd; - background-color: #ddd; } - .ArrayLayout { - td { - border-style: none; - border-width: 0px; - padding: 0px; - background-color: transparent; + &.correct { + .popover-header { + --bs-popover-header-bg: var(--bs-success); + --bs-popover-header-color: white; + cursor: pointer; } } - .parsehilight { - color: inherit; - background-color: yellow; - } - - .popover { - max-width: 100%; - } - - td { - &.FeedbackMessage { - background-color: #ede275; /* Harvest Gold */ - } - - &.ResultsWithoutError { - background-color: #8f8; - } - - &.ResultsWithError { - background-color: #d69191; /* Light Red */ - color: black; + &.incorrect { + .popover-header { + --bs-popover-header-bg: var(--bs-danger); + --bs-popover-header-color: white; } } - div.answer-preview, - span.answer-preview { - display: block; - width: 100%; - height: 100%; - } - - a, a span { - color: #038; - text-decoration: none; - - &:hover { - text-decoration: underline; + &.partially-correct { + .popover-header { + --bs-popover-header-bg: var(--bs-warning); } } -} - -div, label, span { - &.ResultsWithoutError { - color: #0f5132; /* Dark Green */ - background-color: #8f8; /* Light Green */ - padding: 0.25rem; - border: 1px solid transparent; - border-radius: 0.25rem; - box-shadow: 3px 3px 3px darkgray; - } - &.ResultsWithError { - color: #400; /* Dark Red */ - background-color: #d69191; /* Light Red */ - padding: 0.25rem; - border: 1px solid transparent; - border-radius: 0.25rem; - box-shadow: 3px 3px 3px darkgray; - } - - &.ResultsAlert { - color: #0e038c; /* Dark Blue */ - background-color: #fbd2b8; /* Light Orange */ - padding: 0.25rem; - border: 1px solid transparent; - border-radius: 0.25rem; - box-shadow: 3px 3px 3px darkgray; + .popover-body { + .card { + border-top-left-radius: 0; + border-top-right-radius: 0; + --bs-card-spacer-y: 0.5rem; + --bs-card-cap-bg: #ddd; + + .card-header { + border-radius: 0; + + &:not(:first-child) { + border-top: var(--bs-card-border-width) solid var(--bs-card-border-color); + } + } + + .card-body { + mjx-container { + margin: 0; + } + + .parsehilight { + background-color: yellow; + } + + .ArrayLayout { + td { + border-style: none; + border-width: 0px; + padding: 0px; + background-color: transparent; + } + } + + &.feedback-message { + background-color: #ede275; + } + } + } } } diff --git a/htdocs/js/RadioMultiAnswer/RadioMultiAnswer.scss b/htdocs/js/RadioMultiAnswer/RadioMultiAnswer.scss index 6d6d23d3c0..9694f6547f 100644 --- a/htdocs/js/RadioMultiAnswer/RadioMultiAnswer.scss +++ b/htdocs/js/RadioMultiAnswer/RadioMultiAnswer.scss @@ -13,33 +13,52 @@ // # Artistic License for more details. // ################################################################################ -.radio-container { - display: flex; - align-items: baseline; - - .radio-content { - display: inline-block; - margin-left: 5px; - line-height: 26px; - } +.radio-multianswer-container { + position: relative; + width: fit-content; + max-width: calc(100% - 1rem - 25px); - input[type='radio'] { - flex-shrink: 0; - margin-left: 0; - line-height: 26px; - align-self: first baseline; - } + .radio-container { + display: flex; + align-items: baseline; + position: relative; + width: fit-content; - label { - flex-shrink: 0; - line-height: 26px; - } + .radio-content { + display: inline-block; + margin-left: 5px; + line-height: 26px; + } + + input[type='radio'] { + flex-shrink: 0; + margin-left: 0; + line-height: 26px; + align-self: first baseline; + } + + label { + flex-shrink: 0; + line-height: 26px; + } + + input[disabled] { + pointer-events: none; + } + + .rma-state-disabled { + opacity: 0.35; + } - input[disabled] { - pointer-events: none; + .ww-feedback-btn { + top: 50%; + transform: translateY(-50%); + } } - .rma-state-disabled { - opacity: 0.35; + .ww-feedback-btn { + position: absolute; + left: 100%; + top: 0; } } diff --git a/htdocs/js/Scaffold/scaffold.js b/htdocs/js/Scaffold/scaffold.js index d36b986291..060c00747b 100644 --- a/htdocs/js/Scaffold/scaffold.js +++ b/htdocs/js/Scaffold/scaffold.js @@ -4,12 +4,19 @@ section.addEventListener('shown.bs.collapse', () => { // Reflow MathQuill answer boxes so that their contents are rendered correctly if (window.answerQuills) { - Object.keys(answerQuills).forEach( - (quill) => { if (section.querySelector('#' + quill)) answerQuills[quill].mathField.reflow(); } - ); + Object.keys(answerQuills).forEach((quill) => { + if (section.querySelector('#' + quill)) answerQuills[quill].mathField.reflow(); + }); } }); - }) + + section.addEventListener('hide.bs.collapse', () => { + // Close any open feedback popovers in this scaffold. + for (const button of section.querySelectorAll('.ww-feedback-btn')) { + bootstrap.Popover.getInstance(button)?.hide(); + } + }); + }); }; // Set up any scaffolds already on the page. diff --git a/lib/Applet.pm b/lib/Applet.pm index ecdad095f6..cb65230a19 100644 --- a/lib/Applet.pm +++ b/lib/Applet.pm @@ -524,7 +524,11 @@ sub insertObject { my $objectText = $self->{objectText}; $objectText =~ s/(\$\w+)/$1/gee; - return $objectText; + return + qq{