mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 11:28:24 +00:00 
			
		
		
		
	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 <wxiaoguang@gmail.com>
This commit is contained in:
		| @@ -324,7 +324,7 @@ rules: | |||||||
|   jquery/no-sizzle: [2] |   jquery/no-sizzle: [2] | ||||||
|   jquery/no-slide: [2] |   jquery/no-slide: [2] | ||||||
|   jquery/no-submit: [2] |   jquery/no-submit: [2] | ||||||
|   jquery/no-text: [0] |   jquery/no-text: [2] | ||||||
|   jquery/no-toggle: [2] |   jquery/no-toggle: [2] | ||||||
|   jquery/no-trigger: [0] |   jquery/no-trigger: [0] | ||||||
|   jquery/no-trim: [2] |   jquery/no-trim: [2] | ||||||
| @@ -477,7 +477,7 @@ rules: | |||||||
|   no-jquery/no-slide: [2] |   no-jquery/no-slide: [2] | ||||||
|   no-jquery/no-sub: [2] |   no-jquery/no-sub: [2] | ||||||
|   no-jquery/no-support: [2] |   no-jquery/no-support: [2] | ||||||
|   no-jquery/no-text: [0] |   no-jquery/no-text: [2] | ||||||
|   no-jquery/no-trigger: [0] |   no-jquery/no-trigger: [0] | ||||||
|   no-jquery/no-trim: [2] |   no-jquery/no-trim: [2] | ||||||
|   no-jquery/no-type: [2] |   no-jquery/no-type: [2] | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ | |||||||
| 		<div class="quick-pull-choice js-quick-pull-choice"> | 		<div class="quick-pull-choice js-quick-pull-choice"> | ||||||
| 			<div class="field"> | 			<div class="field"> | ||||||
| 				<div class="ui radio checkbox {{if not .CanCommitToBranch.CanCommitToBranch}}disabled{{end}}"> | 				<div class="ui radio checkbox {{if not .CanCommitToBranch.CanCommitToBranch}}disabled{{end}}"> | ||||||
| 					<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}}> | 					<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}}> | ||||||
| 					<label> | 					<label> | ||||||
| 						{{svg "octicon-git-commit"}} | 						{{svg "octicon-git-commit"}} | ||||||
| 						{{ctx.Locale.Tr "repo.editor.commit_directly_to_this_branch" .BranchName}} | 						{{ctx.Locale.Tr "repo.editor.commit_directly_to_this_branch" .BranchName}} | ||||||
| @@ -43,9 +43,9 @@ | |||||||
| 				<div class="field"> | 				<div class="field"> | ||||||
| 					<div class="ui radio checkbox"> | 					<div class="ui radio checkbox"> | ||||||
| 						{{if .CanCreatePullRequest}} | 						{{if .CanCreatePullRequest}} | ||||||
| 							<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}}> | 							<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}}> | ||||||
| 						{{else}} | 						{{else}} | ||||||
| 							<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}}> | 							<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}}> | ||||||
| 						{{end}} | 						{{end}} | ||||||
| 						<label> | 						<label> | ||||||
| 							{{svg "octicon-git-pull-request"}} | 							{{svg "octicon-git-pull-request"}} | ||||||
|   | |||||||
| @@ -19,13 +19,13 @@ | |||||||
| 						<div class="flex-item-trailing"> | 						<div class="flex-item-trailing"> | ||||||
| 							<div class="flex-text-block"> | 							<div class="flex-text-block"> | ||||||
| 								{{svg "octicon-shield-lock"}} | 								{{svg "octicon-shield-lock"}} | ||||||
| 								<div class="ui inline dropdown access-mode" data-url="{{$.Link}}/access_mode" data-uid="{{.ID}}" data-last-value="{{printf "%d" .Collaboration.Mode}}"> | 								<div class="ui dropdown custom access-mode" data-url="{{$.Link}}/access_mode" data-uid="{{.ID}}" data-last-value="{{.Collaboration.Mode}}"> | ||||||
| 									<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> | 									<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> | ||||||
| 									{{svg "octicon-triangle-down" 14 "dropdown icon"}} | 									{{svg "octicon-triangle-down" 14 "dropdown icon"}} | ||||||
| 									<div class="menu"> | 									<div class="menu"> | ||||||
| 										<div class="item" data-text="{{ctx.Locale.Tr "repo.settings.collaboration.admin"}}" data-value="3">{{ctx.Locale.Tr "repo.settings.collaboration.admin"}}</div> | 										<div class="item" data-value="3">{{ctx.Locale.Tr "repo.settings.collaboration.admin"}}</div> | ||||||
| 										<div class="item" data-text="{{ctx.Locale.Tr "repo.settings.collaboration.write"}}" data-value="2">{{ctx.Locale.Tr "repo.settings.collaboration.write"}}</div> | 										<div class="item" data-value="2">{{ctx.Locale.Tr "repo.settings.collaboration.write"}}</div> | ||||||
| 										<div class="item" data-text="{{ctx.Locale.Tr "repo.settings.collaboration.read"}}" data-value="1">{{ctx.Locale.Tr "repo.settings.collaboration.read"}}</div> | 										<div class="item" data-value="1">{{ctx.Locale.Tr "repo.settings.collaboration.read"}}</div> | ||||||
| 									</div> | 									</div> | ||||||
| 								</div> | 								</div> | ||||||
| 							</div> | 							</div> | ||||||
|   | |||||||
| @@ -301,52 +301,65 @@ async function linkAction(e) { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| export function initGlobalLinkActions() { | export function initGlobalDeleteButton() { | ||||||
|   function showDeletePopup(e) { |   // ".delete-button" shows a confirmation modal defined by `data-modal-id` attribute. | ||||||
|     e.preventDefault(); |   // Some model/form elements will be filled by `data-id` / `data-name` / `data-data-xxx` attributes. | ||||||
|     const $this = $(this); |   // If there is a form defined by `data-form`, then the form will be submitted as-is (without any modification). | ||||||
|     const dataArray = $this.data(); |   // If there is no form, then the data will be posted to `data-url`. | ||||||
|     let filter = ''; |   // TODO: it's not encouraged to use this method. `show-modal` does far better than this. | ||||||
|     if (this.getAttribute('data-modal-id')) { |   for (const btn of document.querySelectorAll('.delete-button')) { | ||||||
|       filter += `#${this.getAttribute('data-modal-id')}`; |     btn.addEventListener('click', (e) => { | ||||||
|     } |       e.preventDefault(); | ||||||
|  |  | ||||||
|     const $dialog = $(`.delete.modal${filter}`); |       // eslint-disable-next-line github/no-dataset -- code depends on the camel-casing | ||||||
|     $dialog.find('.name').text($this.data('name')); |       const dataObj = btn.dataset; | ||||||
|     for (const [key, value] of Object.entries(dataArray)) { |  | ||||||
|       if (key && key.startsWith('data')) { |       const modalId = btn.getAttribute('data-modal-id'); | ||||||
|         $dialog.find(`.${key}`).text(value); |       const modal = document.querySelector(`.delete.modal${modalId ? `#${modalId}` : ''}`); | ||||||
|  |  | ||||||
|  |       // set the modal "display name" by `data-name` | ||||||
|  |       const modalNameEl = modal.querySelector('.name'); | ||||||
|  |       if (modalNameEl) modalNameEl.textContent = btn.getAttribute('data-name'); | ||||||
|  |  | ||||||
|  |       // fill the modal elements with data-xxx attributes: `data-data-organization-name="..."` => `<span class="dataOrganizationName">...</span>` | ||||||
|  |       for (const [key, value] of Object.entries(dataObj)) { | ||||||
|  |         if (key.startsWith('data')) { | ||||||
|  |           const textEl = modal.querySelector(`.${key}`); | ||||||
|  |           if (textEl) textEl.textContent = value; | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|     } |  | ||||||
|  |  | ||||||
|     $dialog.modal({ |       $(modal).modal({ | ||||||
|       closable: false, |         closable: false, | ||||||
|       onApprove: async () => { |         onApprove: async () => { | ||||||
|         if ($this.data('type') === 'form') { |           // if `data-type="form"` exists, then submit the form by the selector provided by `data-form="..."` | ||||||
|           $($this.data('form')).trigger('submit'); |           if (btn.getAttribute('data-type') === 'form') { | ||||||
|           return; |             const formSelector = btn.getAttribute('data-form'); | ||||||
|         } |             const form = document.querySelector(formSelector); | ||||||
|         const postData = new FormData(); |             if (!form) throw new Error(`no form named ${formSelector} found`); | ||||||
|         for (const [key, value] of Object.entries(dataArray)) { |             form.submit(); | ||||||
|           if (key && key.startsWith('data')) { |  | ||||||
|             postData.append(key.slice(4), value); |  | ||||||
|           } |           } | ||||||
|           if (key === 'id') { |  | ||||||
|             postData.append('id', value); |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         const response = await POST($this.data('url'), {data: postData}); |           // prepare an AJAX form by data attributes | ||||||
|         if (response.ok) { |           const postData = new FormData(); | ||||||
|           const data = await response.json(); |           for (const [key, value] of Object.entries(dataObj)) { | ||||||
|           window.location.href = data.redirect; |             if (key.startsWith('data')) { // for data-data-xxx (HTML) -> dataXxx (form) | ||||||
|         } |               postData.append(key.slice(4), value); | ||||||
|       }, |             } | ||||||
|     }).modal('show'); |             if (key === 'id') { // for data-id="..." | ||||||
|  |               postData.append('id', value); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |  | ||||||
|  |           const response = await POST(btn.getAttribute('data-url'), {data: postData}); | ||||||
|  |           if (response.ok) { | ||||||
|  |             const data = await response.json(); | ||||||
|  |             window.location.href = data.redirect; | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |       }).modal('show'); | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Helpers. |  | ||||||
|   $('.delete-button').on('click', showDeletePopup); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| function initGlobalShowModal() { | function initGlobalShowModal() { | ||||||
| @@ -382,7 +395,7 @@ function initGlobalShowModal() { | |||||||
|       } else if ($attrTarget[0].matches('input, textarea')) { |       } else if ($attrTarget[0].matches('input, textarea')) { | ||||||
|         $attrTarget.val(attrib.value); // FIXME: add more supports like checkbox |         $attrTarget.val(attrib.value); // FIXME: add more supports like checkbox | ||||||
|       } else { |       } else { | ||||||
|         $attrTarget.text(attrib.value); // FIXME: it should be more strict here, only handle div/span/p |         $attrTarget[0].textContent = attrib.value; // FIXME: it should be more strict here, only handle div/span/p | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -79,20 +79,20 @@ export function initImageDiff() { | |||||||
|       path: this.getAttribute('data-path-after'), |       path: this.getAttribute('data-path-after'), | ||||||
|       mime: this.getAttribute('data-mime-after'), |       mime: this.getAttribute('data-mime-after'), | ||||||
|       $images: $container.find('img.image-after'), // matches 3 <img> |       $images: $container.find('img.image-after'), // matches 3 <img> | ||||||
|       $boundsInfo: $container.find('.bounds-info-after'), |       boundsInfo: this.querySelector('.bounds-info-after'), | ||||||
|     }, { |     }, { | ||||||
|       path: this.getAttribute('data-path-before'), |       path: this.getAttribute('data-path-before'), | ||||||
|       mime: this.getAttribute('data-mime-before'), |       mime: this.getAttribute('data-mime-before'), | ||||||
|       $images: $container.find('img.image-before'), // matches 3 <img> |       $images: $container.find('img.image-before'), // matches 3 <img> | ||||||
|       $boundsInfo: $container.find('.bounds-info-before'), |       boundsInfo: this.querySelector('.bounds-info-before'), | ||||||
|     }]; |     }]; | ||||||
|  |  | ||||||
|     await Promise.all(imageInfos.map(async (info) => { |     await Promise.all(imageInfos.map(async (info) => { | ||||||
|       const [success] = await Promise.all(Array.from(info.$images, (img) => { |       const [success] = await Promise.all(Array.from(info.$images, (img) => { | ||||||
|         return loadElem(img, info.path); |         return loadElem(img, info.path); | ||||||
|       })); |       })); | ||||||
|       // only the first images is associated with $boundsInfo |       // only the first images is associated with boundsInfo | ||||||
|       if (!success) info.$boundsInfo.text('(image error)'); |       if (!success && info.boundsInfo) info.boundsInfo.textContent = '(image error)'; | ||||||
|       if (info.mime === 'image/svg+xml') { |       if (info.mime === 'image/svg+xml') { | ||||||
|         const resp = await GET(info.path); |         const resp = await GET(info.path); | ||||||
|         const text = await resp.text(); |         const text = await resp.text(); | ||||||
| @@ -102,7 +102,7 @@ export function initImageDiff() { | |||||||
|             this.setAttribute('width', bounds.width); |             this.setAttribute('width', bounds.width); | ||||||
|             this.setAttribute('height', bounds.height); |             this.setAttribute('height', bounds.height); | ||||||
|           }); |           }); | ||||||
|           hideElem(info.$boundsInfo); |           hideElem(info.boundsInfo); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     })); |     })); | ||||||
|   | |||||||
| @@ -47,17 +47,13 @@ async function receiveUpdateCount(event) { | |||||||
| } | } | ||||||
|  |  | ||||||
| export function initNotificationCount() { | export function initNotificationCount() { | ||||||
|   const $notificationCount = $('.notification_count'); |   if (!document.querySelector('.notification_count')) return; | ||||||
|  |  | ||||||
|   if (!$notificationCount.length) { |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   let usingPeriodicPoller = false; |   let usingPeriodicPoller = false; | ||||||
|   const startPeriodicPoller = (timeout, lastCount) => { |   const startPeriodicPoller = (timeout, lastCount) => { | ||||||
|     if (timeout <= 0 || !Number.isFinite(timeout)) return; |     if (timeout <= 0 || !Number.isFinite(timeout)) return; | ||||||
|     usingPeriodicPoller = true; |     usingPeriodicPoller = true; | ||||||
|     lastCount = lastCount ?? $notificationCount.text(); |     lastCount = lastCount ?? getCurrentCount(); | ||||||
|     setTimeout(async () => { |     setTimeout(async () => { | ||||||
|       await updateNotificationCountWithCallback(startPeriodicPoller, timeout, lastCount); |       await updateNotificationCountWithCallback(startPeriodicPoller, timeout, lastCount); | ||||||
|     }, timeout); |     }, timeout); | ||||||
| @@ -121,8 +117,12 @@ export function initNotificationCount() { | |||||||
|   startPeriodicPoller(notificationSettings.MinTimeout); |   startPeriodicPoller(notificationSettings.MinTimeout); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function getCurrentCount() { | ||||||
|  |   return document.querySelector('.notification_count').textContent; | ||||||
|  | } | ||||||
|  |  | ||||||
| async function updateNotificationCountWithCallback(callback, timeout, lastCount) { | async function updateNotificationCountWithCallback(callback, timeout, lastCount) { | ||||||
|   const currentCount = $('.notification_count').text(); |   const currentCount = getCurrentCount(); | ||||||
|   if (lastCount !== currentCount) { |   if (lastCount !== currentCount) { | ||||||
|     callback(notificationSettings.MinTimeout, currentCount); |     callback(notificationSettings.MinTimeout, currentCount); | ||||||
|     return; |     return; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import $ from 'jquery'; | import $ from 'jquery'; | ||||||
| import {htmlEscape} from 'escape-goat'; | import {htmlEscape} from 'escape-goat'; | ||||||
| import {createCodeEditor} from './codeeditor.js'; | import {createCodeEditor} from './codeeditor.js'; | ||||||
| import {hideElem, showElem} from '../utils/dom.js'; | import {hideElem, queryElems, showElem} from '../utils/dom.js'; | ||||||
| import {initMarkupContent} from '../markup/content.js'; | import {initMarkupContent} from '../markup/content.js'; | ||||||
| import {attachRefIssueContextPopup} from './contextpopup.js'; | import {attachRefIssueContextPopup} from './contextpopup.js'; | ||||||
| import {POST} from '../modules/fetch.js'; | import {POST} from '../modules/fetch.js'; | ||||||
| @@ -40,98 +40,75 @@ function initEditPreviewTab($form) { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| function initEditorForm() { |  | ||||||
|   const $form = $('.repository .edit.form'); |  | ||||||
|   if (!$form) return; |  | ||||||
|   initEditPreviewTab($form); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function getCursorPosition($e) { |  | ||||||
|   const el = $e.get(0); |  | ||||||
|   let pos = 0; |  | ||||||
|   if ('selectionStart' in el) { |  | ||||||
|     pos = el.selectionStart; |  | ||||||
|   } else if ('selection' in document) { |  | ||||||
|     el.focus(); |  | ||||||
|     const Sel = document.selection.createRange(); |  | ||||||
|     const SelLength = document.selection.createRange().text.length; |  | ||||||
|     Sel.moveStart('character', -el.value.length); |  | ||||||
|     pos = Sel.text.length - SelLength; |  | ||||||
|   } |  | ||||||
|   return pos; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export function initRepoEditor() { | export function initRepoEditor() { | ||||||
|   initEditorForm(); |   const $editArea = $('.repository.editor textarea#edit_area'); | ||||||
|  |   if (!$editArea.length) return; | ||||||
|  |  | ||||||
|   $('.js-quick-pull-choice-option').on('change', function () { |   for (const el of queryElems('.js-quick-pull-choice-option')) { | ||||||
|     if ($(this).val() === 'commit-to-new-branch') { |     el.addEventListener('input', () => { | ||||||
|       showElem('.quick-pull-branch-name'); |       if (el.value === 'commit-to-new-branch') { | ||||||
|       document.querySelector('.quick-pull-branch-name input').required = true; |         showElem('.quick-pull-branch-name'); | ||||||
|     } else { |         document.querySelector('.quick-pull-branch-name input').required = true; | ||||||
|       hideElem('.quick-pull-branch-name'); |  | ||||||
|       document.querySelector('.quick-pull-branch-name input').required = false; |  | ||||||
|     } |  | ||||||
|     $('#commit-button').text(this.getAttribute('button_text')); |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   const joinTreePath = ($fileNameEl) => { |  | ||||||
|     const parts = []; |  | ||||||
|     $('.breadcrumb span.section').each(function () { |  | ||||||
|       const $element = $(this); |  | ||||||
|       if ($element.find('a').length) { |  | ||||||
|         parts.push($element.find('a').text()); |  | ||||||
|       } else { |       } else { | ||||||
|         parts.push($element.text()); |         hideElem('.quick-pull-branch-name'); | ||||||
|  |         document.querySelector('.quick-pull-branch-name input').required = false; | ||||||
|       } |       } | ||||||
|  |       document.querySelector('#commit-button').textContent = el.getAttribute('data-button-text'); | ||||||
|     }); |     }); | ||||||
|     if ($fileNameEl.val()) parts.push($fileNameEl.val()); |   } | ||||||
|     $('#tree_path').val(parts.join('/')); |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   const $editFilename = $('#file-name'); |  | ||||||
|   $editFilename.on('input', function () { |  | ||||||
|     const parts = $(this).val().split('/'); |  | ||||||
|  |  | ||||||
|  |   const filenameInput = document.querySelector('#file-name'); | ||||||
|  |   function joinTreePath() { | ||||||
|  |     const parts = []; | ||||||
|  |     for (const el of document.querySelectorAll('.breadcrumb span.section')) { | ||||||
|  |       const link = el.querySelector('a'); | ||||||
|  |       parts.push(link ? link.textContent : el.textContent); | ||||||
|  |     } | ||||||
|  |     if (filenameInput.value) { | ||||||
|  |       parts.push(filenameInput.value); | ||||||
|  |     } | ||||||
|  |     document.querySelector('#tree_path').value = parts.join('/'); | ||||||
|  |   } | ||||||
|  |   filenameInput.addEventListener('input', function () { | ||||||
|  |     const parts = filenameInput.value.split('/'); | ||||||
|     if (parts.length > 1) { |     if (parts.length > 1) { | ||||||
|       for (let i = 0; i < parts.length; ++i) { |       for (let i = 0; i < parts.length; ++i) { | ||||||
|         const value = parts[i]; |         const value = parts[i]; | ||||||
|         if (i < parts.length - 1) { |         if (i < parts.length - 1) { | ||||||
|           if (value.length) { |           if (value.length) { | ||||||
|             $(`<span class="section"><a href="#">${htmlEscape(value)}</a></span>`).insertBefore($(this)); |             $(`<span class="section"><a href="#">${htmlEscape(value)}</a></span>`).insertBefore($(filenameInput)); | ||||||
|             $('<div class="breadcrumb-divider">/</div>').insertBefore($(this)); |             $('<div class="breadcrumb-divider">/</div>').insertBefore($(filenameInput)); | ||||||
|           } |           } | ||||||
|         } else { |         } else { | ||||||
|           $(this).val(value); |           filenameInput.value = value; | ||||||
|         } |         } | ||||||
|         this.setSelectionRange(0, 0); |         this.setSelectionRange(0, 0); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |     joinTreePath(); | ||||||
|     joinTreePath($(this)); |  | ||||||
|   }); |   }); | ||||||
|  |   filenameInput.addEventListener('keydown', function (e) { | ||||||
|   $editFilename.on('keydown', function (e) { |     const sections = queryElems('.breadcrumb span.section'); | ||||||
|     const $section = $('.breadcrumb span.section'); |     const dividers = queryElems('.breadcrumb .breadcrumb-divider'); | ||||||
|  |  | ||||||
|     // Jump back to last directory once the filename is empty |     // Jump back to last directory once the filename is empty | ||||||
|     if (e.code === 'Backspace' && getCursorPosition($(this)) === 0 && $section.length > 0) { |     if (e.code === 'Backspace' && filenameInput.selectionStart === 0 && sections.length > 0) { | ||||||
|       e.preventDefault(); |       e.preventDefault(); | ||||||
|       const $divider = $('.breadcrumb .breadcrumb-divider'); |       const lastSection = sections[sections.length - 1]; | ||||||
|       const value = $section.last().find('a').text(); |       const lastDivider = dividers.length ? dividers[dividers.length - 1] : null; | ||||||
|       $(this).val(value + $(this).val()); |       const value = lastSection.querySelector('a').textContent; | ||||||
|  |       filenameInput.value = value + filenameInput.value; | ||||||
|       this.setSelectionRange(value.length, value.length); |       this.setSelectionRange(value.length, value.length); | ||||||
|       $section.last().remove(); |       lastDivider?.remove(); | ||||||
|       $divider.last().remove(); |       lastSection.remove(); | ||||||
|       joinTreePath($(this)); |       joinTreePath(); | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   const $editArea = $('.repository.editor textarea#edit_area'); |   const $form = $('.repository.editor .edit.form'); | ||||||
|   if (!$editArea.length) return; |   initEditPreviewTab($form); | ||||||
|  |  | ||||||
|   (async () => { |   (async () => { | ||||||
|     const editor = await createCodeEditor($editArea[0], $editFilename[0]); |     const editor = await createCodeEditor($editArea[0], filenameInput); | ||||||
|  |  | ||||||
|     // Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage |     // Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage | ||||||
|     // to enable or disable the commit button |     // to enable or disable the commit button | ||||||
|   | |||||||
| @@ -189,11 +189,12 @@ export function initRepoIssueCommentEdit() { | |||||||
|   // Quote reply |   // Quote reply | ||||||
|   $(document).on('click', '.quote-reply', async function (event) { |   $(document).on('click', '.quote-reply', async function (event) { | ||||||
|     event.preventDefault(); |     event.preventDefault(); | ||||||
|     const target = $(this).data('target'); |     const target = this.getAttribute('data-target'); | ||||||
|     const quote = $(`#${target}`).text().replace(/\n/g, '\n> '); |     const quote = document.querySelector(`#${target}`).textContent.replace(/\n/g, '\n> '); | ||||||
|     const content = `> ${quote}\n\n`; |     const content = `> ${quote}\n\n`; | ||||||
|  |  | ||||||
|     let editor; |     let editor; | ||||||
|     if ($(this).hasClass('quote-reply-diff')) { |     if (this.classList.contains('quote-reply-diff')) { | ||||||
|       const $replyBtn = $(this).closest('.comment-code-cloud').find('button.comment-form-reply'); |       const $replyBtn = $(this).closest('.comment-code-cloud').find('button.comment-form-reply'); | ||||||
|       editor = await handleReply($replyBtn); |       editor = await handleReply($replyBtn); | ||||||
|     } else { |     } else { | ||||||
|   | |||||||
| @@ -278,11 +278,12 @@ export function initRepoPullRequestUpdate() { | |||||||
|  |  | ||||||
|   $('.update-button > .dropdown').dropdown({ |   $('.update-button > .dropdown').dropdown({ | ||||||
|     onChange(_text, _value, $choice) { |     onChange(_text, _value, $choice) { | ||||||
|       const url = $choice[0].getAttribute('data-do'); |       const choiceEl = $choice[0]; | ||||||
|  |       const url = choiceEl.getAttribute('data-do'); | ||||||
|       if (url) { |       if (url) { | ||||||
|         const buttonText = pullUpdateButton.querySelector('.button-text'); |         const buttonText = pullUpdateButton.querySelector('.button-text'); | ||||||
|         if (buttonText) { |         if (buttonText) { | ||||||
|           buttonText.textContent = $choice.text(); |           buttonText.textContent = choiceEl.textContent; | ||||||
|         } |         } | ||||||
|         pullUpdateButton.setAttribute('data-do', url); |         pullUpdateButton.setAttribute('data-do', url); | ||||||
|       } |       } | ||||||
| @@ -567,14 +568,15 @@ export function initRepoPullRequestReview() { | |||||||
| export function initRepoIssueReferenceIssue() { | export function initRepoIssueReferenceIssue() { | ||||||
|   // Reference issue |   // Reference issue | ||||||
|   $(document).on('click', '.reference-issue', function (event) { |   $(document).on('click', '.reference-issue', function (event) { | ||||||
|     const $this = $(this); |     const target = this.getAttribute('data-target'); | ||||||
|     const content = $(`#${$this.data('target')}`).text(); |     const content = document.querySelector(`#${target}`)?.textContent ?? ''; | ||||||
|     const poster = $this.data('poster-username'); |     const poster = this.getAttribute('data-poster-username'); | ||||||
|     const reference = toAbsoluteUrl($this.data('reference')); |     const reference = toAbsoluteUrl(this.getAttribute('data-reference')); | ||||||
|     const $modal = $($this.data('modal')); |     const modalSelector = this.getAttribute('data-modal'); | ||||||
|     $modal.find('textarea[name="content"]').val(`${content}\n\n_Originally posted by @${poster} in ${reference}_`); |     const modal = document.querySelector(modalSelector); | ||||||
|     $modal.modal('show'); |     const textarea = modal.querySelector('textarea[name="content"]'); | ||||||
|  |     textarea.value = `${content}\n\n_Originally posted by @${poster} in ${reference}_`; | ||||||
|  |     $(modal).modal('show'); | ||||||
|     event.preventDefault(); |     event.preventDefault(); | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -272,9 +272,9 @@ export function initRepoCommentForm() { | |||||||
|       } |       } | ||||||
|  |  | ||||||
|       $list.find('.selected').html(` |       $list.find('.selected').html(` | ||||||
|         <a class="item muted sidebar-item-link" href=${$(this).data('href')}> |         <a class="item muted sidebar-item-link" href=${htmlEscape(this.getAttribute('href'))}> | ||||||
|           ${icon} |           ${icon} | ||||||
|           ${htmlEscape($(this).text())} |           ${htmlEscape(this.textContent)} | ||||||
|         </a> |         </a> | ||||||
|       `); |       `); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,47 +1,46 @@ | |||||||
| import $ from 'jquery'; | import $ from 'jquery'; | ||||||
| import {minimatch} from 'minimatch'; | import {minimatch} from 'minimatch'; | ||||||
| import {createMonaco} from './codeeditor.js'; | import {createMonaco} from './codeeditor.js'; | ||||||
| import {onInputDebounce, toggleElem} from '../utils/dom.js'; | import {onInputDebounce, queryElems, toggleElem} from '../utils/dom.js'; | ||||||
| import {POST} from '../modules/fetch.js'; | import {POST} from '../modules/fetch.js'; | ||||||
|  |  | ||||||
| const {appSubUrl, csrfToken} = window.config; | const {appSubUrl, csrfToken} = window.config; | ||||||
|  |  | ||||||
| export function initRepoSettingsCollaboration() { | export function initRepoSettingsCollaboration() { | ||||||
|   // Change collaborator access mode |   // Change collaborator access mode | ||||||
|   $('.page-content.repository .ui.dropdown.access-mode').each((_, el) => { |   for (const dropdownEl of queryElems('.page-content.repository .ui.dropdown.access-mode')) { | ||||||
|     const $dropdown = $(el); |     const textEl = dropdownEl.querySelector(':scope > .text'); | ||||||
|     const $text = $dropdown.find('> .text'); |     $(dropdownEl).dropdown({ | ||||||
|     $dropdown.dropdown({ |       async action(text, value) { | ||||||
|       async action(_text, value) { |         dropdownEl.classList.add('is-loading', 'loading-icon-2px'); | ||||||
|         const lastValue = el.getAttribute('data-last-value'); |         const lastValue = dropdownEl.getAttribute('data-last-value'); | ||||||
|  |         $(dropdownEl).dropdown('hide'); | ||||||
|         try { |         try { | ||||||
|           el.setAttribute('data-last-value', value); |           const uid = dropdownEl.getAttribute('data-uid'); | ||||||
|           $dropdown.dropdown('hide'); |           await POST(dropdownEl.getAttribute('data-url'), {data: new URLSearchParams({uid, 'mode': value})}); | ||||||
|           const data = new FormData(); |           textEl.textContent = text; | ||||||
|           data.append('uid', el.getAttribute('data-uid')); |           dropdownEl.setAttribute('data-last-value', value); | ||||||
|           data.append('mode', value); |  | ||||||
|           await POST(el.getAttribute('data-url'), {data}); |  | ||||||
|         } catch { |         } catch { | ||||||
|           $text.text('(error)'); // prevent from misleading users when error occurs |           textEl.textContent = '(error)'; // prevent from misleading users when error occurs | ||||||
|           el.setAttribute('data-last-value', lastValue); |           dropdownEl.setAttribute('data-last-value', lastValue); | ||||||
|  |         } finally { | ||||||
|  |           dropdownEl.classList.remove('is-loading'); | ||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
|       onChange(_value, text, _$choice) { |  | ||||||
|         $text.text(text); // update the text when using keyboard navigating |  | ||||||
|       }, |  | ||||||
|       onHide() { |       onHide() { | ||||||
|         // set to the really selected value, defer to next tick to make sure `action` has finished its work because the calling order might be onHide -> action |         // set to the really selected value, defer to next tick to make sure `action` has finished | ||||||
|  |         // its work because the calling order might be onHide -> action | ||||||
|         setTimeout(() => { |         setTimeout(() => { | ||||||
|           const $item = $dropdown.dropdown('get item', el.getAttribute('data-last-value')); |           const $item = $(dropdownEl).dropdown('get item', dropdownEl.getAttribute('data-last-value')); | ||||||
|           if ($item) { |           if ($item) { | ||||||
|             $dropdown.dropdown('set selected', el.getAttribute('data-last-value')); |             $(dropdownEl).dropdown('set selected', dropdownEl.getAttribute('data-last-value')); | ||||||
|           } else { |           } else { | ||||||
|             $text.text('(none)'); // prevent from misleading users when the access mode is undefined |             textEl.textContent = '(none)'; // prevent from misleading users when the access mode is undefined | ||||||
|           } |           } | ||||||
|         }, 0); |         }, 0); | ||||||
|       }, |       }, | ||||||
|     }); |     }); | ||||||
|   }); |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| export function initRepoSettingSearchTeamBox() { | export function initRepoSettingSearchTeamBox() { | ||||||
|   | |||||||
| @@ -43,7 +43,7 @@ import { | |||||||
|   initGlobalDropzone, |   initGlobalDropzone, | ||||||
|   initGlobalEnterQuickSubmit, |   initGlobalEnterQuickSubmit, | ||||||
|   initGlobalFormDirtyLeaveConfirm, |   initGlobalFormDirtyLeaveConfirm, | ||||||
|   initGlobalLinkActions, |   initGlobalDeleteButton, | ||||||
|   initHeadNavbarContentToggle, |   initHeadNavbarContentToggle, | ||||||
| } from './features/common-global.js'; | } from './features/common-global.js'; | ||||||
| import {initRepoTopicBar} from './features/repo-home.js'; | import {initRepoTopicBar} from './features/repo-home.js'; | ||||||
| @@ -103,7 +103,7 @@ onDomReady(() => { | |||||||
|   initGlobalDropzone(); |   initGlobalDropzone(); | ||||||
|   initGlobalEnterQuickSubmit(); |   initGlobalEnterQuickSubmit(); | ||||||
|   initGlobalFormDirtyLeaveConfirm(); |   initGlobalFormDirtyLeaveConfirm(); | ||||||
|   initGlobalLinkActions(); |   initGlobalDeleteButton(); | ||||||
|  |  | ||||||
|   initCommonOrganization(); |   initCommonOrganization(); | ||||||
|   initCommonIssueListQuickGoto(); |   initCommonIssueListQuickGoto(); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user