diff --git a/.eslintrc.yaml b/.eslintrc.yaml index e55349969..aa2950762 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -460,7 +460,7 @@ rules: no-jquery/no-param: [2] no-jquery/no-parent: [0] no-jquery/no-parents: [2] - no-jquery/no-parse-html-literal: [0] + no-jquery/no-parse-html-literal: [2] no-jquery/no-parse-html: [2] no-jquery/no-parse-json: [2] no-jquery/no-parse-xml: [2] diff --git a/web_src/js/features/repo-editor.js b/web_src/js/features/repo-editor.js index 01dc4b95a..faf8ba1f8 100644 --- a/web_src/js/features/repo-editor.js +++ b/web_src/js/features/repo-editor.js @@ -1,7 +1,7 @@ import $ from 'jquery'; import {htmlEscape} from 'escape-goat'; import {createCodeEditor} from './codeeditor.js'; -import {hideElem, showElem} from '../utils/dom.js'; +import {hideElem, showElem, createElementFromHTML} from '../utils/dom.js'; import {initMarkupContent} from '../markup/content.js'; import {attachRefIssueContextPopup} from './contextpopup.js'; import {POST} from '../modules/fetch.js'; @@ -9,7 +9,9 @@ import {POST} from '../modules/fetch.js'; function initEditPreviewTab($form) { const $tabMenu = $form.find('.tabular.menu'); $tabMenu.find('.item').tab(); - const $previewTab = $tabMenu.find(`.item[data-tab="${$tabMenu.data('preview')}"]`); + const $previewTab = $tabMenu.find( + `.item[data-tab="${$tabMenu.data('preview')}"]`, + ); if ($previewTab.length) { $previewTab.on('click', async function () { const $this = $(this); @@ -24,12 +26,17 @@ function initEditPreviewTab($form) { const formData = new FormData(); formData.append('mode', mode); formData.append('context', context); - formData.append('text', $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val()); + formData.append( + 'text', + $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val(), + ); formData.append('file_path', $treePathEl.val()); try { const response = await POST($this.data('url'), {data: formData}); const data = await response.text(); - const $previewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('preview')}"]`); + const $previewPanel = $form.find( + `.tab[data-tab="${$tabMenu.data('preview')}"]`, + ); renderPreviewPanelContent($previewPanel, data); } catch (error) { console.error('Error:', error); @@ -96,8 +103,14 @@ export function initRepoEditor() { const value = parts[i]; if (i < parts.length - 1) { if (value.length) { - $(`${htmlEscape(value)}`).insertBefore($(this)); - $('').insertBefore($(this)); + $editFilename[0].before( + createElementFromHTML( + `${htmlEscape(value)}`, + ), + ); + $editFilename[0].before( + createElementFromHTML(``), + ); } } else { $(this).val(value); @@ -113,7 +126,11 @@ export function initRepoEditor() { const $section = $('.breadcrumb span.section'); // Jump back to last directory once the filename is empty - if (e.code === 'Backspace' && getCursorPosition($(this)) === 0 && $section.length > 0) { + if ( + e.code === 'Backspace' && + getCursorPosition($(this)) === 0 && + $section.length > 0 + ) { e.preventDefault(); const $divider = $('.breadcrumb .breadcrumb-divider'); const value = $section.last().find('a').text(); @@ -164,11 +181,13 @@ export function initRepoEditor() { commitButton?.addEventListener('click', (e) => { // A modal which asks if an empty file should be committed if (!$editArea.val()) { - $('#edit-empty-content-modal').modal({ - onApprove() { - $('.edit.form').trigger('submit'); - }, - }).modal('show'); + $('#edit-empty-content-modal') + .modal({ + onApprove() { + $('.edit.form').trigger('submit'); + }, + }) + .modal('show'); e.preventDefault(); } }); diff --git a/web_src/js/utils/dom.js b/web_src/js/utils/dom.js index a2a79e91e..44adc795c 100644 --- a/web_src/js/utils/dom.js +++ b/web_src/js/utils/dom.js @@ -296,3 +296,10 @@ export function replaceTextareaSelection(textarea, text) { textarea.dispatchEvent(new CustomEvent('change', {bubbles: true, cancelable: true})); } } + +// Warning: Do not enter any unsanitized variables here +export function createElementFromHTML(htmlString) { + const div = document.createElement('div'); + div.innerHTML = htmlString.trim(); + return div.firstChild; +} diff --git a/web_src/js/utils/dom.test.js b/web_src/js/utils/dom.test.js new file mode 100644 index 000000000..fd7d97cad --- /dev/null +++ b/web_src/js/utils/dom.test.js @@ -0,0 +1,5 @@ +import {createElementFromHTML} from './dom.js'; + +test('createElementFromHTML', () => { + expect(createElementFromHTML('foobar').outerHTML).toEqual('foobar'); +});