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 %}
- {% trans 'Notes' %}: - {{ object.notes }} -
+