From 5c7e724f99445ccca87aace03a6d349424419afb Mon Sep 17 00:00:00 2001 From: Yarden Shoham Date: Tue, 27 Feb 2024 20:03:07 +0000 Subject: [PATCH 1/3] Remove jQuery from the "find file" page - Switched to plain JavaScript - Tested the file searching functionality and it works as before Signed-off-by: Yarden Shoham --- web_src/js/features/repo-findfile.js | 53 ++++++++++++---------------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/web_src/js/features/repo-findfile.js b/web_src/js/features/repo-findfile.js index 158732acc278a..d7ec9b94fe273 100644 --- a/web_src/js/features/repo-findfile.js +++ b/web_src/js/features/repo-findfile.js @@ -1,13 +1,11 @@ -import $ from 'jquery'; import {svg} from '../svg.js'; import {toggleElem} from '../utils/dom.js'; import {pathEscapeSegments} from '../utils/url.js'; - -const {csrf} = window.config; +import {GET} from '../modules/fetch.js'; const threshold = 50; let files = []; -let $repoFindFileInput, $repoFindFileTableBody, $repoFindFileNoResult; +let repoFindFileInput, repoFindFileTableBody, repoFindFileNoResult; // return the case-insensitive sub-match result as an array: [unmatched, matched, unmatched, matched, ...] // res[even] is unmatched, res[odd] is matched, see unit tests for examples @@ -74,46 +72,41 @@ export function filterRepoFilesWeighted(files, filter) { } function filterRepoFiles(filter) { - const treeLink = $repoFindFileInput.attr('data-url-tree-link'); - $repoFindFileTableBody.empty(); + const treeLink = repoFindFileInput.getAttribute('data-url-tree-link'); + repoFindFileTableBody.innerHTML = ''; const filterResult = filterRepoFilesWeighted(files, filter); - const tmplRow = ``; - toggleElem($repoFindFileNoResult, filterResult.length === 0); + toggleElem(repoFindFileNoResult, filterResult.length === 0); for (const r of filterResult) { - const $row = $(tmplRow); - const $a = $row.find('a'); - $a.attr('href', `${treeLink}/${pathEscapeSegments(r.matchResult.join(''))}`); - const $octiconFile = $(svg('octicon-file')).addClass('gt-mr-3'); - $a.append($octiconFile); + const row = document.createElement('tr'); + const cell = document.createElement('td'); + const a = document.createElement('a'); + a.setAttribute('href', `${treeLink}/${pathEscapeSegments(r.matchResult.join(''))}`); + a.innerHTML = svg('octicon-file') + r.matchResult.map((part, index) => // if the target file path is "abc/xyz", to search "bx", then the matchResult is ['a', 'b', 'c/', 'x', 'yz'] // the matchResult[odd] is matched and highlighted to red. - for (let j = 0; j < r.matchResult.length; j++) { - if (!r.matchResult[j]) continue; - const $span = $('').text(r.matchResult[j]); - if (j % 2 === 1) $span.addClass('ui text red'); - $a.append($span); - } - $repoFindFileTableBody.append($row); + `${part}` + ).join(''); + cell.append(a); + row.append(cell); + repoFindFileTableBody.append(row); } } async function loadRepoFiles() { - files = await $.ajax({ - url: $repoFindFileInput.attr('data-url-data-link'), - headers: {'X-Csrf-Token': csrf} - }); - filterRepoFiles($repoFindFileInput.val()); + const response = await GET(repoFindFileInput.getAttribute('data-url-data-link')); + files = await response.json(); + filterRepoFiles(repoFindFileInput.value); } export function initFindFileInRepo() { - $repoFindFileInput = $('#repo-file-find-input'); - if (!$repoFindFileInput.length) return; + repoFindFileInput = document.getElementById('repo-file-find-input'); + if (!repoFindFileInput) return; - $repoFindFileTableBody = $('#repo-find-file-table tbody'); - $repoFindFileNoResult = $('#repo-find-file-no-result'); - $repoFindFileInput.on('input', () => filterRepoFiles($repoFindFileInput.val())); + repoFindFileTableBody = document.querySelector('#repo-find-file-table tbody'); + repoFindFileNoResult = document.getElementById('repo-find-file-no-result'); + repoFindFileInput.addEventListener('input', () => filterRepoFiles(repoFindFileInput.value)); loadRepoFiles(); } From 7364caa65ea6b427d25862be2db007dea3caa3fc Mon Sep 17 00:00:00 2001 From: Yarden Shoham Date: Wed, 28 Feb 2024 09:58:34 +0000 Subject: [PATCH 2/3] Fix XSS and margin --- web_src/js/features/repo-findfile.js | 17 +++++++++------ web_src/js/features/repo-home.js | 32 ++++++++++++++++------------ 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/web_src/js/features/repo-findfile.js b/web_src/js/features/repo-findfile.js index d7ec9b94fe273..cb03d9e8037b5 100644 --- a/web_src/js/features/repo-findfile.js +++ b/web_src/js/features/repo-findfile.js @@ -83,13 +83,18 @@ function filterRepoFiles(filter) { const cell = document.createElement('td'); const a = document.createElement('a'); a.setAttribute('href', `${treeLink}/${pathEscapeSegments(r.matchResult.join(''))}`); - a.innerHTML = svg('octicon-file') + r.matchResult.map((part, index) => - // if the target file path is "abc/xyz", to search "bx", then the matchResult is ['a', 'b', 'c/', 'x', 'yz'] - // the matchResult[odd] is matched and highlighted to red. - `${part}` - ).join(''); - cell.append(a); + a.innerHTML = svg('octicon-file', 16, 'gt-mr-3'); row.append(cell); + cell.append(a); + for (const [index, part] of r.matchResult.entries()) { + const span = document.createElement('span'); + // safely escape by using textContent + span.textContent = part; + // if the target file path is "abc/xyz", to search "bx", then the matchResult is ['a', 'b', 'c/', 'x', 'yz'] + // the matchResult[odd] is matched and highlighted to red. + if (index % 2 === 1) span.classList.add('ui', 'text', 'red'); + a.append(span); + } repoFindFileTableBody.append(row); } } diff --git a/web_src/js/features/repo-home.js b/web_src/js/features/repo-home.js index 3603fae2e928d..62133872ef6c1 100644 --- a/web_src/js/features/repo-home.js +++ b/web_src/js/features/repo-home.js @@ -1,8 +1,9 @@ import $ from 'jquery'; import {stripTags} from '../utils.js'; import {hideElem, showElem} from '../utils/dom.js'; +import {POST} from '../modules/fetch.js'; -const {appSubUrl, csrfToken} = window.config; +const {appSubUrl} = window.config; export function initRepoTopicBar() { const mgrBtn = $('#manage_topic'); @@ -30,14 +31,15 @@ export function initRepoTopicBar() { mgrBtn.focus(); }); - saveBtn.on('click', () => { + saveBtn.on('click', async () => { const topics = $('input[name=topics]').val(); + const formData = new FormData(); + formData.append('topics', topics); + try { + const response = await POST(saveBtn.attr('data-link'), {data: formData}); + const data = await response.json(); - $.post(saveBtn.attr('data-link'), { - _csrf: csrfToken, - topics - }, (_data, _textStatus, xhr) => { - if (xhr.responseJSON.status === 'ok') { + if (data.status === 'ok') { viewDiv.children('.topic').remove(); if (topics.length) { const topicArray = topics.split(','); @@ -52,12 +54,14 @@ export function initRepoTopicBar() { hideElem(editDiv); showElem(viewDiv); } - }).fail((xhr) => { + } catch (error) { + const xhr = error.response; // Assuming `error.response` contains the fetch response object if (xhr.status === 422) { - if (xhr.responseJSON.invalidTopics.length > 0) { - topicPrompts.formatPrompt = xhr.responseJSON.message; + const responseData = await xhr.json(); // Parse JSON response + if (responseData.invalidTopics.length > 0) { + topicPrompts.formatPrompt = responseData.message; - const {invalidTopics} = xhr.responseJSON; + const {invalidTopics} = responseData; const topicLabels = topicDropdown.children('a.ui.label'); for (const [index, value] of topics.split(',').entries()) { @@ -68,12 +72,12 @@ export function initRepoTopicBar() { } } } else { - topicPrompts.countPrompt = xhr.responseJSON.message; + topicPrompts.countPrompt = responseData.message; } } - }).always(() => { + } finally { topicForm.form('validate form'); - }); + } }); topicDropdown.dropdown({ From 8463312765e9f88f51943cd07f654d9e5f9f2ff1 Mon Sep 17 00:00:00 2001 From: Yarden Shoham Date: Wed, 28 Feb 2024 11:33:23 +0000 Subject: [PATCH 3/3] Revert push by accident --- web_src/js/features/repo-home.js | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/web_src/js/features/repo-home.js b/web_src/js/features/repo-home.js index 62133872ef6c1..3603fae2e928d 100644 --- a/web_src/js/features/repo-home.js +++ b/web_src/js/features/repo-home.js @@ -1,9 +1,8 @@ import $ from 'jquery'; import {stripTags} from '../utils.js'; import {hideElem, showElem} from '../utils/dom.js'; -import {POST} from '../modules/fetch.js'; -const {appSubUrl} = window.config; +const {appSubUrl, csrfToken} = window.config; export function initRepoTopicBar() { const mgrBtn = $('#manage_topic'); @@ -31,15 +30,14 @@ export function initRepoTopicBar() { mgrBtn.focus(); }); - saveBtn.on('click', async () => { + saveBtn.on('click', () => { const topics = $('input[name=topics]').val(); - const formData = new FormData(); - formData.append('topics', topics); - try { - const response = await POST(saveBtn.attr('data-link'), {data: formData}); - const data = await response.json(); - if (data.status === 'ok') { + $.post(saveBtn.attr('data-link'), { + _csrf: csrfToken, + topics + }, (_data, _textStatus, xhr) => { + if (xhr.responseJSON.status === 'ok') { viewDiv.children('.topic').remove(); if (topics.length) { const topicArray = topics.split(','); @@ -54,14 +52,12 @@ export function initRepoTopicBar() { hideElem(editDiv); showElem(viewDiv); } - } catch (error) { - const xhr = error.response; // Assuming `error.response` contains the fetch response object + }).fail((xhr) => { if (xhr.status === 422) { - const responseData = await xhr.json(); // Parse JSON response - if (responseData.invalidTopics.length > 0) { - topicPrompts.formatPrompt = responseData.message; + if (xhr.responseJSON.invalidTopics.length > 0) { + topicPrompts.formatPrompt = xhr.responseJSON.message; - const {invalidTopics} = responseData; + const {invalidTopics} = xhr.responseJSON; const topicLabels = topicDropdown.children('a.ui.label'); for (const [index, value] of topics.split(',').entries()) { @@ -72,12 +68,12 @@ export function initRepoTopicBar() { } } } else { - topicPrompts.countPrompt = responseData.message; + topicPrompts.countPrompt = xhr.responseJSON.message; } } - } finally { + }).always(() => { topicForm.form('validate form'); - } + }); }); topicDropdown.dropdown({