Skip to content

Commit a2304cb

Browse files
Remove jQuery .text() (#30506)
Remove and forbid [.text()](https://api.jquery.com/text/). Tested some, but not all functionality, but I think these are pretty safe replacements. --------- Co-authored-by: wxiaoguang <[email protected]>
1 parent 4f7d6fe commit a2304cb

12 files changed

+160
-168
lines changed

.eslintrc.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ rules:
324324
jquery/no-sizzle: [2]
325325
jquery/no-slide: [2]
326326
jquery/no-submit: [2]
327-
jquery/no-text: [0]
327+
jquery/no-text: [2]
328328
jquery/no-toggle: [2]
329329
jquery/no-trigger: [0]
330330
jquery/no-trim: [2]
@@ -477,7 +477,7 @@ rules:
477477
no-jquery/no-slide: [2]
478478
no-jquery/no-sub: [2]
479479
no-jquery/no-support: [2]
480-
no-jquery/no-text: [0]
480+
no-jquery/no-text: [2]
481481
no-jquery/no-trigger: [0]
482482
no-jquery/no-trim: [2]
483483
no-jquery/no-type: [2]

templates/repo/editor/commit_form.tmpl

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<div class="quick-pull-choice js-quick-pull-choice">
2424
<div class="field">
2525
<div class="ui radio checkbox {{if not .CanCommitToBranch.CanCommitToBranch}}disabled{{end}}">
26-
<input type="radio" class="js-quick-pull-choice-option" name="commit_choice" value="direct" button_text="{{ctx.Locale.Tr "repo.editor.commit_changes"}}" {{if eq .commit_choice "direct"}}checked{{end}}>
26+
<input type="radio" class="js-quick-pull-choice-option" name="commit_choice" value="direct" data-button-text="{{ctx.Locale.Tr "repo.editor.commit_changes"}}" {{if eq .commit_choice "direct"}}checked{{end}}>
2727
<label>
2828
{{svg "octicon-git-commit"}}
2929
{{ctx.Locale.Tr "repo.editor.commit_directly_to_this_branch" .BranchName}}
@@ -43,9 +43,9 @@
4343
<div class="field">
4444
<div class="ui radio checkbox">
4545
{{if .CanCreatePullRequest}}
46-
<input type="radio" class="js-quick-pull-choice-option" name="commit_choice" value="commit-to-new-branch" button_text="{{ctx.Locale.Tr "repo.editor.propose_file_change"}}" {{if eq .commit_choice "commit-to-new-branch"}}checked{{end}}>
46+
<input type="radio" class="js-quick-pull-choice-option" name="commit_choice" value="commit-to-new-branch" data-button-text="{{ctx.Locale.Tr "repo.editor.propose_file_change"}}" {{if eq .commit_choice "commit-to-new-branch"}}checked{{end}}>
4747
{{else}}
48-
<input type="radio" class="js-quick-pull-choice-option" name="commit_choice" value="commit-to-new-branch" button_text="{{ctx.Locale.Tr "repo.editor.commit_changes"}}" {{if eq .commit_choice "commit-to-new-branch"}}checked{{end}}>
48+
<input type="radio" class="js-quick-pull-choice-option" name="commit_choice" value="commit-to-new-branch" data-button-text="{{ctx.Locale.Tr "repo.editor.commit_changes"}}" {{if eq .commit_choice "commit-to-new-branch"}}checked{{end}}>
4949
{{end}}
5050
<label>
5151
{{svg "octicon-git-pull-request"}}

templates/repo/settings/collaboration.tmpl

+4-4
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@
1919
<div class="flex-item-trailing">
2020
<div class="flex-text-block">
2121
{{svg "octicon-shield-lock"}}
22-
<div class="ui inline dropdown access-mode" data-url="{{$.Link}}/access_mode" data-uid="{{.ID}}" data-last-value="{{printf "%d" .Collaboration.Mode}}">
22+
<div class="ui dropdown custom access-mode" data-url="{{$.Link}}/access_mode" data-uid="{{.ID}}" data-last-value="{{.Collaboration.Mode}}">
2323
<div class="text">{{if eq .Collaboration.Mode 1}}{{ctx.Locale.Tr "repo.settings.collaboration.read"}}{{else if eq .Collaboration.Mode 2}}{{ctx.Locale.Tr "repo.settings.collaboration.write"}}{{else if eq .Collaboration.Mode 3}}{{ctx.Locale.Tr "repo.settings.collaboration.admin"}}{{else}}{{ctx.Locale.Tr "repo.settings.collaboration.undefined"}}{{end}}</div>
2424
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
2525
<div class="menu">
26-
<div class="item" data-text="{{ctx.Locale.Tr "repo.settings.collaboration.admin"}}" data-value="3">{{ctx.Locale.Tr "repo.settings.collaboration.admin"}}</div>
27-
<div class="item" data-text="{{ctx.Locale.Tr "repo.settings.collaboration.write"}}" data-value="2">{{ctx.Locale.Tr "repo.settings.collaboration.write"}}</div>
28-
<div class="item" data-text="{{ctx.Locale.Tr "repo.settings.collaboration.read"}}" data-value="1">{{ctx.Locale.Tr "repo.settings.collaboration.read"}}</div>
26+
<div class="item" data-value="3">{{ctx.Locale.Tr "repo.settings.collaboration.admin"}}</div>
27+
<div class="item" data-value="2">{{ctx.Locale.Tr "repo.settings.collaboration.write"}}</div>
28+
<div class="item" data-value="1">{{ctx.Locale.Tr "repo.settings.collaboration.read"}}</div>
2929
</div>
3030
</div>
3131
</div>

web_src/js/features/common-global.js

+53-40
Original file line numberDiff line numberDiff line change
@@ -301,52 +301,65 @@ async function linkAction(e) {
301301
}
302302
}
303303

304-
export function initGlobalLinkActions() {
305-
function showDeletePopup(e) {
306-
e.preventDefault();
307-
const $this = $(this);
308-
const dataArray = $this.data();
309-
let filter = '';
310-
if (this.getAttribute('data-modal-id')) {
311-
filter += `#${this.getAttribute('data-modal-id')}`;
312-
}
304+
export function initGlobalDeleteButton() {
305+
// ".delete-button" shows a confirmation modal defined by `data-modal-id` attribute.
306+
// Some model/form elements will be filled by `data-id` / `data-name` / `data-data-xxx` attributes.
307+
// If there is a form defined by `data-form`, then the form will be submitted as-is (without any modification).
308+
// If there is no form, then the data will be posted to `data-url`.
309+
// TODO: it's not encouraged to use this method. `show-modal` does far better than this.
310+
for (const btn of document.querySelectorAll('.delete-button')) {
311+
btn.addEventListener('click', (e) => {
312+
e.preventDefault();
313313

314-
const $dialog = $(`.delete.modal${filter}`);
315-
$dialog.find('.name').text($this.data('name'));
316-
for (const [key, value] of Object.entries(dataArray)) {
317-
if (key && key.startsWith('data')) {
318-
$dialog.find(`.${key}`).text(value);
319-
}
320-
}
314+
// eslint-disable-next-line github/no-dataset -- code depends on the camel-casing
315+
const dataObj = btn.dataset;
316+
317+
const modalId = btn.getAttribute('data-modal-id');
318+
const modal = document.querySelector(`.delete.modal${modalId ? `#${modalId}` : ''}`);
321319

322-
$dialog.modal({
323-
closable: false,
324-
onApprove: async () => {
325-
if ($this.data('type') === 'form') {
326-
$($this.data('form')).trigger('submit');
327-
return;
320+
// set the modal "display name" by `data-name`
321+
const modalNameEl = modal.querySelector('.name');
322+
if (modalNameEl) modalNameEl.textContent = btn.getAttribute('data-name');
323+
324+
// fill the modal elements with data-xxx attributes: `data-data-organization-name="..."` => `<span class="dataOrganizationName">...</span>`
325+
for (const [key, value] of Object.entries(dataObj)) {
326+
if (key.startsWith('data')) {
327+
const textEl = modal.querySelector(`.${key}`);
328+
if (textEl) textEl.textContent = value;
328329
}
329-
const postData = new FormData();
330-
for (const [key, value] of Object.entries(dataArray)) {
331-
if (key && key.startsWith('data')) {
332-
postData.append(key.slice(4), value);
330+
}
331+
332+
$(modal).modal({
333+
closable: false,
334+
onApprove: async () => {
335+
// if `data-type="form"` exists, then submit the form by the selector provided by `data-form="..."`
336+
if (btn.getAttribute('data-type') === 'form') {
337+
const formSelector = btn.getAttribute('data-form');
338+
const form = document.querySelector(formSelector);
339+
if (!form) throw new Error(`no form named ${formSelector} found`);
340+
form.submit();
333341
}
334-
if (key === 'id') {
335-
postData.append('id', value);
342+
343+
// prepare an AJAX form by data attributes
344+
const postData = new FormData();
345+
for (const [key, value] of Object.entries(dataObj)) {
346+
if (key.startsWith('data')) { // for data-data-xxx (HTML) -> dataXxx (form)
347+
postData.append(key.slice(4), value);
348+
}
349+
if (key === 'id') { // for data-id="..."
350+
postData.append('id', value);
351+
}
336352
}
337-
}
338353

339-
const response = await POST($this.data('url'), {data: postData});
340-
if (response.ok) {
341-
const data = await response.json();
342-
window.location.href = data.redirect;
343-
}
344-
},
345-
}).modal('show');
354+
const response = await POST(btn.getAttribute('data-url'), {data: postData});
355+
if (response.ok) {
356+
const data = await response.json();
357+
window.location.href = data.redirect;
358+
}
359+
},
360+
}).modal('show');
361+
});
346362
}
347-
348-
// Helpers.
349-
$('.delete-button').on('click', showDeletePopup);
350363
}
351364

352365
function initGlobalShowModal() {
@@ -382,7 +395,7 @@ function initGlobalShowModal() {
382395
} else if ($attrTarget[0].matches('input, textarea')) {
383396
$attrTarget.val(attrib.value); // FIXME: add more supports like checkbox
384397
} else {
385-
$attrTarget.text(attrib.value); // FIXME: it should be more strict here, only handle div/span/p
398+
$attrTarget[0].textContent = attrib.value; // FIXME: it should be more strict here, only handle div/span/p
386399
}
387400
}
388401

web_src/js/features/imagediff.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -79,20 +79,20 @@ export function initImageDiff() {
7979
path: this.getAttribute('data-path-after'),
8080
mime: this.getAttribute('data-mime-after'),
8181
$images: $container.find('img.image-after'), // matches 3 <img>
82-
$boundsInfo: $container.find('.bounds-info-after'),
82+
boundsInfo: this.querySelector('.bounds-info-after'),
8383
}, {
8484
path: this.getAttribute('data-path-before'),
8585
mime: this.getAttribute('data-mime-before'),
8686
$images: $container.find('img.image-before'), // matches 3 <img>
87-
$boundsInfo: $container.find('.bounds-info-before'),
87+
boundsInfo: this.querySelector('.bounds-info-before'),
8888
}];
8989

9090
await Promise.all(imageInfos.map(async (info) => {
9191
const [success] = await Promise.all(Array.from(info.$images, (img) => {
9292
return loadElem(img, info.path);
9393
}));
94-
// only the first images is associated with $boundsInfo
95-
if (!success) info.$boundsInfo.text('(image error)');
94+
// only the first images is associated with boundsInfo
95+
if (!success && info.boundsInfo) info.boundsInfo.textContent = '(image error)';
9696
if (info.mime === 'image/svg+xml') {
9797
const resp = await GET(info.path);
9898
const text = await resp.text();
@@ -102,7 +102,7 @@ export function initImageDiff() {
102102
this.setAttribute('width', bounds.width);
103103
this.setAttribute('height', bounds.height);
104104
});
105-
hideElem(info.$boundsInfo);
105+
hideElem(info.boundsInfo);
106106
}
107107
}
108108
}));

web_src/js/features/notification.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,13 @@ async function receiveUpdateCount(event) {
4747
}
4848

4949
export function initNotificationCount() {
50-
const $notificationCount = $('.notification_count');
51-
52-
if (!$notificationCount.length) {
53-
return;
54-
}
50+
if (!document.querySelector('.notification_count')) return;
5551

5652
let usingPeriodicPoller = false;
5753
const startPeriodicPoller = (timeout, lastCount) => {
5854
if (timeout <= 0 || !Number.isFinite(timeout)) return;
5955
usingPeriodicPoller = true;
60-
lastCount = lastCount ?? $notificationCount.text();
56+
lastCount = lastCount ?? getCurrentCount();
6157
setTimeout(async () => {
6258
await updateNotificationCountWithCallback(startPeriodicPoller, timeout, lastCount);
6359
}, timeout);
@@ -121,8 +117,12 @@ export function initNotificationCount() {
121117
startPeriodicPoller(notificationSettings.MinTimeout);
122118
}
123119

120+
function getCurrentCount() {
121+
return document.querySelector('.notification_count').textContent;
122+
}
123+
124124
async function updateNotificationCountWithCallback(callback, timeout, lastCount) {
125-
const currentCount = $('.notification_count').text();
125+
const currentCount = getCurrentCount();
126126
if (lastCount !== currentCount) {
127127
callback(notificationSettings.MinTimeout, currentCount);
128128
return;

web_src/js/features/repo-editor.js

+44-67
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import $ from 'jquery';
22
import {htmlEscape} from 'escape-goat';
33
import {createCodeEditor} from './codeeditor.js';
4-
import {hideElem, showElem} from '../utils/dom.js';
4+
import {hideElem, queryElems, showElem} from '../utils/dom.js';
55
import {initMarkupContent} from '../markup/content.js';
66
import {attachRefIssueContextPopup} from './contextpopup.js';
77
import {POST} from '../modules/fetch.js';
@@ -40,98 +40,75 @@ function initEditPreviewTab($form) {
4040
}
4141
}
4242

43-
function initEditorForm() {
44-
const $form = $('.repository .edit.form');
45-
if (!$form) return;
46-
initEditPreviewTab($form);
47-
}
48-
49-
function getCursorPosition($e) {
50-
const el = $e.get(0);
51-
let pos = 0;
52-
if ('selectionStart' in el) {
53-
pos = el.selectionStart;
54-
} else if ('selection' in document) {
55-
el.focus();
56-
const Sel = document.selection.createRange();
57-
const SelLength = document.selection.createRange().text.length;
58-
Sel.moveStart('character', -el.value.length);
59-
pos = Sel.text.length - SelLength;
60-
}
61-
return pos;
62-
}
63-
6443
export function initRepoEditor() {
65-
initEditorForm();
66-
67-
$('.js-quick-pull-choice-option').on('change', function () {
68-
if ($(this).val() === 'commit-to-new-branch') {
69-
showElem('.quick-pull-branch-name');
70-
document.querySelector('.quick-pull-branch-name input').required = true;
71-
} else {
72-
hideElem('.quick-pull-branch-name');
73-
document.querySelector('.quick-pull-branch-name input').required = false;
74-
}
75-
$('#commit-button').text(this.getAttribute('button_text'));
76-
});
44+
const $editArea = $('.repository.editor textarea#edit_area');
45+
if (!$editArea.length) return;
7746

78-
const joinTreePath = ($fileNameEl) => {
79-
const parts = [];
80-
$('.breadcrumb span.section').each(function () {
81-
const $element = $(this);
82-
if ($element.find('a').length) {
83-
parts.push($element.find('a').text());
47+
for (const el of queryElems('.js-quick-pull-choice-option')) {
48+
el.addEventListener('input', () => {
49+
if (el.value === 'commit-to-new-branch') {
50+
showElem('.quick-pull-branch-name');
51+
document.querySelector('.quick-pull-branch-name input').required = true;
8452
} else {
85-
parts.push($element.text());
53+
hideElem('.quick-pull-branch-name');
54+
document.querySelector('.quick-pull-branch-name input').required = false;
8655
}
56+
document.querySelector('#commit-button').textContent = el.getAttribute('data-button-text');
8757
});
88-
if ($fileNameEl.val()) parts.push($fileNameEl.val());
89-
$('#tree_path').val(parts.join('/'));
90-
};
91-
92-
const $editFilename = $('#file-name');
93-
$editFilename.on('input', function () {
94-
const parts = $(this).val().split('/');
58+
}
9559

60+
const filenameInput = document.querySelector('#file-name');
61+
function joinTreePath() {
62+
const parts = [];
63+
for (const el of document.querySelectorAll('.breadcrumb span.section')) {
64+
const link = el.querySelector('a');
65+
parts.push(link ? link.textContent : el.textContent);
66+
}
67+
if (filenameInput.value) {
68+
parts.push(filenameInput.value);
69+
}
70+
document.querySelector('#tree_path').value = parts.join('/');
71+
}
72+
filenameInput.addEventListener('input', function () {
73+
const parts = filenameInput.value.split('/');
9674
if (parts.length > 1) {
9775
for (let i = 0; i < parts.length; ++i) {
9876
const value = parts[i];
9977
if (i < parts.length - 1) {
10078
if (value.length) {
101-
$(`<span class="section"><a href="#">${htmlEscape(value)}</a></span>`).insertBefore($(this));
102-
$('<div class="breadcrumb-divider">/</div>').insertBefore($(this));
79+
$(`<span class="section"><a href="#">${htmlEscape(value)}</a></span>`).insertBefore($(filenameInput));
80+
$('<div class="breadcrumb-divider">/</div>').insertBefore($(filenameInput));
10381
}
10482
} else {
105-
$(this).val(value);
83+
filenameInput.value = value;
10684
}
10785
this.setSelectionRange(0, 0);
10886
}
10987
}
110-
111-
joinTreePath($(this));
88+
joinTreePath();
11289
});
113-
114-
$editFilename.on('keydown', function (e) {
115-
const $section = $('.breadcrumb span.section');
116-
90+
filenameInput.addEventListener('keydown', function (e) {
91+
const sections = queryElems('.breadcrumb span.section');
92+
const dividers = queryElems('.breadcrumb .breadcrumb-divider');
11793
// Jump back to last directory once the filename is empty
118-
if (e.code === 'Backspace' && getCursorPosition($(this)) === 0 && $section.length > 0) {
94+
if (e.code === 'Backspace' && filenameInput.selectionStart === 0 && sections.length > 0) {
11995
e.preventDefault();
120-
const $divider = $('.breadcrumb .breadcrumb-divider');
121-
const value = $section.last().find('a').text();
122-
$(this).val(value + $(this).val());
96+
const lastSection = sections[sections.length - 1];
97+
const lastDivider = dividers.length ? dividers[dividers.length - 1] : null;
98+
const value = lastSection.querySelector('a').textContent;
99+
filenameInput.value = value + filenameInput.value;
123100
this.setSelectionRange(value.length, value.length);
124-
$section.last().remove();
125-
$divider.last().remove();
126-
joinTreePath($(this));
101+
lastDivider?.remove();
102+
lastSection.remove();
103+
joinTreePath();
127104
}
128105
});
129106

130-
const $editArea = $('.repository.editor textarea#edit_area');
131-
if (!$editArea.length) return;
107+
const $form = $('.repository.editor .edit.form');
108+
initEditPreviewTab($form);
132109

133110
(async () => {
134-
const editor = await createCodeEditor($editArea[0], $editFilename[0]);
111+
const editor = await createCodeEditor($editArea[0], filenameInput);
135112

136113
// Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
137114
// to enable or disable the commit button

0 commit comments

Comments
 (0)