code.gitea.io/gitea@v1.22.3/web_src/js/features/repo-legacy.js (about) 1 import $ from 'jquery'; 2 import { 3 initRepoIssueBranchSelect, initRepoIssueCodeCommentCancel, initRepoIssueCommentDelete, 4 initRepoIssueComments, initRepoIssueDependencyDelete, initRepoIssueReferenceIssue, 5 initRepoIssueTitleEdit, initRepoIssueWipToggle, 6 initRepoPullRequestUpdate, updateIssuesMeta, initIssueTemplateCommentEditors, initSingleCommentEditor, 7 } from './repo-issue.js'; 8 import {initUnicodeEscapeButton} from './repo-unicode-escape.js'; 9 import {svg} from '../svg.js'; 10 import {htmlEscape} from 'escape-goat'; 11 import {initRepoBranchTagSelector} from '../components/RepoBranchTagSelector.vue'; 12 import { 13 initRepoCloneLink, initRepoCommonBranchOrTagDropdown, initRepoCommonFilterSearchDropdown, 14 } from './repo-common.js'; 15 import {initCitationFileCopyContent} from './citation.js'; 16 import {initCompLabelEdit} from './comp/LabelEdit.js'; 17 import {initRepoDiffConversationNav} from './repo-diff.js'; 18 import {initCompReactionSelector} from './comp/ReactionSelector.js'; 19 import {initRepoSettingBranches} from './repo-settings.js'; 20 import {initRepoPullRequestMergeForm} from './repo-issue-pr-form.js'; 21 import {initRepoPullRequestCommitStatus} from './repo-issue-pr-status.js'; 22 import {hideElem, queryElemChildren, showElem} from '../utils/dom.js'; 23 import {POST} from '../modules/fetch.js'; 24 import {initRepoIssueCommentEdit} from './repo-issue-edit.js'; 25 26 // if there are draft comments, confirm before reloading, to avoid losing comments 27 function reloadConfirmDraftComment() { 28 const commentTextareas = [ 29 document.querySelector('.edit-content-zone:not(.tw-hidden) textarea'), 30 document.querySelector('#comment-form textarea'), 31 ]; 32 for (const textarea of commentTextareas) { 33 // Most users won't feel too sad if they lose a comment with 10 chars, they can re-type these in seconds. 34 // But if they have typed more (like 50) chars and the comment is lost, they will be very unhappy. 35 if (textarea && textarea.value.trim().length > 10) { 36 textarea.parentElement.scrollIntoView(); 37 if (!window.confirm('Page will be reloaded, but there are draft comments. Continuing to reload will discard the comments. Continue?')) { 38 return; 39 } 40 break; 41 } 42 } 43 window.location.reload(); 44 } 45 46 export function initRepoCommentForm() { 47 const $commentForm = $('.comment.form'); 48 if (!$commentForm.length) return; 49 50 if ($commentForm.find('.field.combo-editor-dropzone').length) { 51 // at the moment, if a form has multiple combo-markdown-editors, it must be an issue template form 52 initIssueTemplateCommentEditors($commentForm); 53 } else if ($commentForm.find('.combo-markdown-editor').length) { 54 // it's quite unclear about the "comment form" elements, sometimes it's for issue comment, sometimes it's for file editor/uploader message 55 initSingleCommentEditor($commentForm); 56 } 57 58 function initBranchSelector() { 59 const elSelectBranch = document.querySelector('.ui.dropdown.select-branch'); 60 if (!elSelectBranch) return; 61 62 const urlUpdateIssueRef = elSelectBranch.getAttribute('data-url-update-issueref'); 63 const $selectBranch = $(elSelectBranch); 64 const $branchMenu = $selectBranch.find('.reference-list-menu'); 65 $branchMenu.find('.item:not(.no-select)').on('click', async function (e) { 66 e.preventDefault(); 67 const selectedValue = this.getAttribute('data-id'); // eg: "refs/heads/my-branch" 68 const selectedText = this.getAttribute('data-name'); // eg: "my-branch" 69 if (urlUpdateIssueRef) { 70 // for existing issue, send request to update issue ref, and reload page 71 try { 72 await POST(urlUpdateIssueRef, {data: new URLSearchParams({ref: selectedValue})}); 73 window.location.reload(); 74 } catch (error) { 75 console.error(error); 76 } 77 } else { 78 // for new issue, only update UI&form, do not send request/reload 79 const selectedHiddenSelector = this.getAttribute('data-id-selector'); 80 document.querySelector(selectedHiddenSelector).value = selectedValue; 81 elSelectBranch.querySelector('.text-branch-name').textContent = selectedText; 82 } 83 }); 84 $selectBranch.find('.reference.column').on('click', function () { 85 hideElem($selectBranch.find('.scrolling.reference-list-menu')); 86 showElem(this.getAttribute('data-target')); 87 queryElemChildren(this.parentNode, '.branch-tag-item', (el) => el.classList.remove('active')); 88 this.classList.add('active'); 89 return false; 90 }); 91 } 92 93 initBranchSelector(); 94 95 // List submits 96 function initListSubmits(selector, outerSelector) { 97 const $list = $(`.ui.${outerSelector}.list`); 98 const $noSelect = $list.find('.no-select'); 99 const $listMenu = $(`.${selector} .menu`); 100 let hasUpdateAction = $listMenu.data('action') === 'update'; 101 const items = {}; 102 103 $(`.${selector}`).dropdown({ 104 'action': 'nothing', // do not hide the menu if user presses Enter 105 fullTextSearch: 'exact', 106 async onHide() { 107 hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var 108 if (hasUpdateAction) { 109 // TODO: Add batch functionality and make this 1 network request. 110 const itemEntries = Object.entries(items); 111 for (const [elementId, item] of itemEntries) { 112 await updateIssuesMeta( 113 item['update-url'], 114 item.action, 115 item['issue-id'], 116 elementId, 117 ); 118 } 119 if (itemEntries.length) { 120 reloadConfirmDraftComment(); 121 } 122 } 123 }, 124 }); 125 126 $listMenu.find('.item:not(.no-select)').on('click', function (e) { 127 e.preventDefault(); 128 if ($(this).hasClass('ban-change')) { 129 return false; 130 } 131 132 hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var 133 134 const clickedItem = this; // eslint-disable-line unicorn/no-this-assignment 135 const scope = this.getAttribute('data-scope'); 136 137 $(this).parent().find('.item').each(function () { 138 if (scope) { 139 // Enable only clicked item for scoped labels 140 if (this.getAttribute('data-scope') !== scope) { 141 return true; 142 } 143 if (this !== clickedItem && !$(this).hasClass('checked')) { 144 return true; 145 } 146 } else if (this !== clickedItem) { 147 // Toggle for other labels 148 return true; 149 } 150 151 if ($(this).hasClass('checked')) { 152 $(this).removeClass('checked'); 153 $(this).find('.octicon-check').addClass('tw-invisible'); 154 if (hasUpdateAction) { 155 if (!($(this).data('id') in items)) { 156 items[$(this).data('id')] = { 157 'update-url': $listMenu.data('update-url'), 158 action: 'detach', 159 'issue-id': $listMenu.data('issue-id'), 160 }; 161 } else { 162 delete items[$(this).data('id')]; 163 } 164 } 165 } else { 166 $(this).addClass('checked'); 167 $(this).find('.octicon-check').removeClass('tw-invisible'); 168 if (hasUpdateAction) { 169 if (!($(this).data('id') in items)) { 170 items[$(this).data('id')] = { 171 'update-url': $listMenu.data('update-url'), 172 action: 'attach', 173 'issue-id': $listMenu.data('issue-id'), 174 }; 175 } else { 176 delete items[$(this).data('id')]; 177 } 178 } 179 } 180 }); 181 182 // TODO: Which thing should be done for choosing review requests 183 // to make chosen items be shown on time here? 184 if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') { 185 return false; 186 } 187 188 const listIds = []; 189 $(this).parent().find('.item').each(function () { 190 if ($(this).hasClass('checked')) { 191 listIds.push($(this).data('id')); 192 $($(this).data('id-selector')).removeClass('tw-hidden'); 193 } else { 194 $($(this).data('id-selector')).addClass('tw-hidden'); 195 } 196 }); 197 if (!listIds.length) { 198 $noSelect.removeClass('tw-hidden'); 199 } else { 200 $noSelect.addClass('tw-hidden'); 201 } 202 $($(this).parent().data('id')).val(listIds.join(',')); 203 return false; 204 }); 205 $listMenu.find('.no-select.item').on('click', function (e) { 206 e.preventDefault(); 207 if (hasUpdateAction) { 208 (async () => { 209 await updateIssuesMeta( 210 $listMenu.data('update-url'), 211 'clear', 212 $listMenu.data('issue-id'), 213 '', 214 ); 215 reloadConfirmDraftComment(); 216 })(); 217 } 218 219 $(this).parent().find('.item').each(function () { 220 $(this).removeClass('checked'); 221 $(this).find('.octicon-check').addClass('tw-invisible'); 222 }); 223 224 if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') { 225 return false; 226 } 227 228 $list.find('.item').each(function () { 229 $(this).addClass('tw-hidden'); 230 }); 231 $noSelect.removeClass('tw-hidden'); 232 $($(this).parent().data('id')).val(''); 233 }); 234 } 235 236 // Init labels and assignees 237 initListSubmits('select-label', 'labels'); 238 initListSubmits('select-assignees', 'assignees'); 239 initListSubmits('select-assignees-modify', 'assignees'); 240 initListSubmits('select-reviewers-modify', 'assignees'); 241 242 function selectItem(select_id, input_id) { 243 const $menu = $(`${select_id} .menu`); 244 const $list = $(`.ui${select_id}.list`); 245 const hasUpdateAction = $menu.data('action') === 'update'; 246 247 $menu.find('.item:not(.no-select)').on('click', function () { 248 $(this).parent().find('.item').each(function () { 249 $(this).removeClass('selected active'); 250 }); 251 252 $(this).addClass('selected active'); 253 if (hasUpdateAction) { 254 (async () => { 255 await updateIssuesMeta( 256 $menu.data('update-url'), 257 '', 258 $menu.data('issue-id'), 259 $(this).data('id'), 260 ); 261 reloadConfirmDraftComment(); 262 })(); 263 } 264 265 let icon = ''; 266 if (input_id === '#milestone_id') { 267 icon = svg('octicon-milestone', 18, 'tw-mr-2'); 268 } else if (input_id === '#project_id') { 269 icon = svg('octicon-project', 18, 'tw-mr-2'); 270 } else if (input_id === '#assignee_id') { 271 icon = `<img class="ui avatar image tw-mr-2" alt="avatar" src=${$(this).data('avatar')}>`; 272 } 273 274 $list.find('.selected').html(` 275 <a class="item muted sidebar-item-link" href=${$(this).data('href')}> 276 ${icon} 277 ${htmlEscape($(this).text())} 278 </a> 279 `); 280 281 $(`.ui${select_id}.list .no-select`).addClass('tw-hidden'); 282 $(input_id).val($(this).data('id')); 283 }); 284 $menu.find('.no-select.item').on('click', function () { 285 $(this).parent().find('.item:not(.no-select)').each(function () { 286 $(this).removeClass('selected active'); 287 }); 288 289 if (hasUpdateAction) { 290 (async () => { 291 await updateIssuesMeta( 292 $menu.data('update-url'), 293 '', 294 $menu.data('issue-id'), 295 $(this).data('id'), 296 ); 297 reloadConfirmDraftComment(); 298 })(); 299 } 300 301 $list.find('.selected').html(''); 302 $list.find('.no-select').removeClass('tw-hidden'); 303 $(input_id).val(''); 304 }); 305 } 306 307 // Milestone, Assignee, Project 308 selectItem('.select-project', '#project_id'); 309 selectItem('.select-milestone', '#milestone_id'); 310 selectItem('.select-assignee', '#assignee_id'); 311 } 312 313 export function initRepository() { 314 if (!$('.page-content.repository').length) return; 315 316 initRepoBranchTagSelector('.js-branch-tag-selector'); 317 318 // Options 319 if ($('.repository.settings.options').length > 0) { 320 // Enable or select internal/external wiki system and issue tracker. 321 $('.enable-system').on('change', function () { 322 if (this.checked) { 323 $($(this).data('target')).removeClass('disabled'); 324 if (!$(this).data('context')) $($(this).data('context')).addClass('disabled'); 325 } else { 326 $($(this).data('target')).addClass('disabled'); 327 if (!$(this).data('context')) $($(this).data('context')).removeClass('disabled'); 328 } 329 }); 330 $('.enable-system-radio').on('change', function () { 331 if (this.value === 'false') { 332 $($(this).data('target')).addClass('disabled'); 333 if ($(this).data('context') !== undefined) $($(this).data('context')).removeClass('disabled'); 334 } else if (this.value === 'true') { 335 $($(this).data('target')).removeClass('disabled'); 336 if ($(this).data('context') !== undefined) $($(this).data('context')).addClass('disabled'); 337 } 338 }); 339 const $trackerIssueStyleRadios = $('.js-tracker-issue-style'); 340 $trackerIssueStyleRadios.on('change input', () => { 341 const checkedVal = $trackerIssueStyleRadios.filter(':checked').val(); 342 $('#tracker-issue-style-regex-box').toggleClass('disabled', checkedVal !== 'regexp'); 343 }); 344 } 345 346 // Labels 347 initCompLabelEdit('.repository.labels'); 348 349 // Milestones 350 if ($('.repository.new.milestone').length > 0) { 351 $('#clear-date').on('click', () => { 352 $('#deadline').val(''); 353 return false; 354 }); 355 } 356 357 // Repo Creation 358 if ($('.repository.new.repo').length > 0) { 359 $('input[name="gitignores"], input[name="license"]').on('change', () => { 360 const gitignores = $('input[name="gitignores"]').val(); 361 const license = $('input[name="license"]').val(); 362 if (gitignores || license) { 363 document.querySelector('input[name="auto_init"]').checked = true; 364 } 365 }); 366 } 367 368 // Compare or pull request 369 const $repoDiff = $('.repository.diff'); 370 if ($repoDiff.length) { 371 initRepoCommonBranchOrTagDropdown('.choose.branch .dropdown'); 372 initRepoCommonFilterSearchDropdown('.choose.branch .dropdown'); 373 } 374 375 initRepoCloneLink(); 376 initCitationFileCopyContent(); 377 initRepoSettingBranches(); 378 379 // Issues 380 if ($('.repository.view.issue').length > 0) { 381 initRepoIssueCommentEdit(); 382 383 initRepoIssueBranchSelect(); 384 initRepoIssueTitleEdit(); 385 initRepoIssueWipToggle(); 386 initRepoIssueComments(); 387 388 initRepoDiffConversationNav(); 389 initRepoIssueReferenceIssue(); 390 391 initRepoIssueCommentDelete(); 392 initRepoIssueDependencyDelete(); 393 initRepoIssueCodeCommentCancel(); 394 initRepoPullRequestUpdate(); 395 initCompReactionSelector(); 396 397 initRepoPullRequestMergeForm(); 398 initRepoPullRequestCommitStatus(); 399 } 400 401 // Pull request 402 const $repoComparePull = $('.repository.compare.pull'); 403 if ($repoComparePull.length > 0) { 404 // show pull request form 405 $repoComparePull.find('button.show-form').on('click', function (e) { 406 e.preventDefault(); 407 hideElem($(this).parent()); 408 409 const $form = $repoComparePull.find('.pullrequest-form'); 410 showElem($form); 411 }); 412 } 413 414 initUnicodeEscapeButton(); 415 }