diff --git a/tcms/core/widgets.py b/tcms/core/widgets.py index 1a2a88dc29..4a19710856 100644 --- a/tcms/core/widgets.py +++ b/tcms/core/widgets.py @@ -36,6 +36,14 @@ class Media: css = {"all": ["simplemde/dist/simplemde.min.css"]} +class SimpleMDENotes(SimpleMDE): + """ + SimpleMDE widget for notes field with unique file upload ID + """ + + file_upload_id = "simplemde-notes-file-upload" + + class DurationWidget(forms.Widget): template_name = "widgets/duration.html" diff --git a/tcms/static/js/index.js b/tcms/static/js/index.js index 4027f565d5..a962d89a08 100644 --- a/tcms/static/js/index.js +++ b/tcms/static/js/index.js @@ -80,12 +80,18 @@ $(() => { $('[data-toggle="tooltip"]').tooltip() } + window.markdownEditors = {} + $('.js-simplemde-textarea').each(function () { const fileUploadId = $(this).data('file-upload-id') const uploadField = $(`#${fileUploadId}`) - // this value is only used in testcases/js/mutable.js - window.markdownEditor = initSimpleMDE(this, uploadField) + // Use textarea id/name for unique autosave ID to prevent content sharing + const textareaId = $(this).attr('id') || $(this).attr('name') || 'default' + const autoSaveId = window.location.toString() + '#' + textareaId + + const editor = initSimpleMDE(this, uploadField, autoSaveId) + window.markdownEditors[textareaId] = editor }) $('#logout_link').click(function () { diff --git a/tcms/static/js/simplemde_security_override.js b/tcms/static/js/simplemde_security_override.js index 5fd69e2012..96e09eb538 100644 --- a/tcms/static/js/simplemde_security_override.js +++ b/tcms/static/js/simplemde_security_override.js @@ -24,6 +24,9 @@ export function initSimpleMDE (textArea, fileUploadElement, autoSaveId = window. return null } + // Capture the server-rendered value before SimpleMDE replaces it + const serverValue = textArea.value + const simpleMDE = new SimpleMDE({ element: textArea, autoDownloadFontAwesome: false, @@ -72,6 +75,11 @@ export function initSimpleMDE (textArea, fileUploadElement, autoSaveId = window. } }) + // Remove legacy shared autosave key (before per-textarea unique IDs) + // to prevent cross-field content leaking + const legacyKey = 'smde_' + window.location.toString() + try { localStorage.removeItem(legacyKey) } catch (e) { /* ignore */ } + fileUploadElement.change(function () { const attachment = this.files[0] const reader = new FileReader() diff --git a/tcms/testcases/forms.py b/tcms/testcases/forms.py index 482bc276ff..54e4ce0a6a 100644 --- a/tcms/testcases/forms.py +++ b/tcms/testcases/forms.py @@ -3,7 +3,7 @@ from django.forms import inlineformset_factory from tcms.core.forms.fields import UserField -from tcms.core.widgets import DurationWidget, SimpleMDE +from tcms.core.widgets import DurationWidget, SimpleMDE, SimpleMDENotes from tcms.management.models import Component, Priority, Product from tcms.testcases.fields import MultipleEmailField from tcms.testcases.models import ( @@ -42,6 +42,10 @@ class Meta: widget=DurationWidget(), required=False, ) + notes = forms.CharField( + widget=SimpleMDENotes(), + required=False, + ) text = forms.CharField( widget=SimpleMDE(), required=False, diff --git a/tcms/testcases/static/testcases/js/mutable.js b/tcms/testcases/static/testcases/js/mutable.js index c37d9c9e32..c84485928f 100644 --- a/tcms/testcases/static/testcases/js/mutable.js +++ b/tcms/testcases/static/testcases/js/mutable.js @@ -2,7 +2,10 @@ import { updateCategorySelectFromProduct } from '../../../../static/js/utils' export function pageTestcasesMutableReadyHandler () { $('#id_template').change(function () { - window.markdownEditor.codemirror.setValue($(this).val()) + const editor = window.markdownEditors && window.markdownEditors['id_text'] + if (editor) { + editor.codemirror.setValue($(this).val()) + } }) $('#add_id_template').click(function () { diff --git a/tcms/testcases/templates/testcases/get.html b/tcms/testcases/templates/testcases/get.html index 8665371a95..fca36da05e 100644 --- a/tcms/testcases/templates/testcases/get.html +++ b/tcms/testcases/templates/testcases/get.html @@ -8,12 +8,14 @@ {% block page_id %}page-testcases-get{% endblock %} {% block body_class %}cards-pf{% endblock %} + {% block contents %}

>TC-{{ object.pk }}: {{ object.summary }}

+ + + +
@@ -129,10 +164,10 @@

{{ object.text|markdown2html }}

-

- {% trans 'Notes' %}: - {{ object.notes }} -

+
+ {{ object.notes|markdown2html }} +
+
diff --git a/tcms/testcases/templates/testcases/mutable.html b/tcms/testcases/templates/testcases/mutable.html index 8da89e7a9b..ad95304f00 100644 --- a/tcms/testcases/templates/testcases/mutable.html +++ b/tcms/testcases/templates/testcases/mutable.html @@ -19,6 +19,9 @@
{% csrf_token %} + {% if request.GET.from_plan %} + + {% endif %}
@@ -169,7 +172,7 @@
- + {{ form.notes }} {{ form.notes.errors }}
@@ -260,7 +263,7 @@
- {% trans "Cancel" %}
diff --git a/tcms/testplans/static/testplans/js/get.js b/tcms/testplans/static/testplans/js/get.js index db4e15b0f0..07902281c7 100644 --- a/tcms/testplans/static/testplans/js/get.js +++ b/tcms/testplans/static/testplans/js/get.js @@ -141,7 +141,7 @@ function drawTestCases (testCases, testPlanId, permissions) { if (testCases.length > 0) { testCases.forEach(function (element) { - container.append(getTestCaseRowContent(testCaseRowDocumentFragment.cloneNode(true), element, permissions)) + container.append(getTestCaseRowContent(testCaseRowDocumentFragment.cloneNode(true), element, permissions, testPlanId)) }) attachEvents(testPlanId, permissions) } else { @@ -153,7 +153,7 @@ function drawTestCases (testCases, testPlanId, permissions) { function redrawSingleRow (testCaseId, testPlanId, permissions) { const testCaseRowDocumentFragment = $('#test_case_row')[0].content - const newRow = getTestCaseRowContent(testCaseRowDocumentFragment.cloneNode(true), allTestCases[testCaseId], permissions) + const newRow = getTestCaseRowContent(testCaseRowDocumentFragment.cloneNode(true), allTestCases[testCaseId], permissions, testPlanId) // remove from expanded list b/c the comment section may have changed expandedTestCaseIds.splice(expandedTestCaseIds.indexOf(testCaseId), 1) @@ -224,7 +224,7 @@ function getTestCaseRowContent (rowContent, testCase, permissions) { function getTestCaseExpandArea (row, testCase, permissions) { markdown2HTML(testCase.text, row.find('.js-test-case-expand-text')) if (testCase.notes.trim().length > 0) { - row.find('.js-test-case-expand-notes').html(testCase.notes) + markdown2HTML(testCase.notes, row.find('.js-test-case-expand-notes')) } // draw the attachments diff --git a/tcms/testruns/forms.py b/tcms/testruns/forms.py index 741dee150d..ef4f71a1e0 100644 --- a/tcms/testruns/forms.py +++ b/tcms/testruns/forms.py @@ -4,6 +4,7 @@ from django.utils.translation import gettext_lazy as _ from tcms.core.forms.fields import UserField +from tcms.core.widgets import SimpleMDENotes from tcms.management.models import Build, Product, Version from tcms.rpc.api.forms import DateTimeField from tcms.testcases.models import TestCase @@ -23,6 +24,10 @@ class Meta: stop_date = DateTimeField(required=False) planned_start = DateTimeField(required=False) planned_stop = DateTimeField(required=False) + notes = forms.CharField( + widget=SimpleMDENotes(), + required=False, + ) case = forms.ModelMultipleChoiceField( queryset=TestCase.objects.none(), diff --git a/tcms/testruns/static/testruns/js/get.js b/tcms/testruns/static/testruns/js/get.js index aead2c3ec6..3b6a0242b8 100644 --- a/tcms/testruns/static/testruns/js/get.js +++ b/tcms/testruns/static/testruns/js/get.js @@ -475,7 +475,7 @@ function getExpandArea (testExecution) { }], (data) => { data.forEach((entry) => { markdown2HTML(entry.text, container.find('.test-execution-text')[0]) - container.find('.test-execution-notes').append(entry.notes) + markdown2HTML(entry.notes, container.find('.test-execution-notes')) }) }) diff --git a/tcms/testruns/templates/testruns/mutable.html b/tcms/testruns/templates/testruns/mutable.html index d215b3c919..18d40f10bb 100644 --- a/tcms/testruns/templates/testruns/mutable.html +++ b/tcms/testruns/templates/testruns/mutable.html @@ -2,6 +2,10 @@ {% load i18n %} {% load static %} +{% block head %} + {{ form.media}} +{% endblock %} + {% block title %} {% if object %} {% trans "Edit TestRun" %} @@ -201,7 +205,8 @@
- + {{ form.notes }} + {{ form.notes.errors }}