|
| 1 | +import $ from 'jquery'; |
| 2 | +import {POST} from '../modules/fetch.ts'; |
| 3 | +import {updateIssuesMeta} from './repo-common.ts'; |
| 4 | +import {svg} from '../svg.ts'; |
| 5 | +import {htmlEscape} from 'escape-goat'; |
| 6 | + |
| 7 | +// if there are draft comments, confirm before reloading, to avoid losing comments |
| 8 | +function reloadConfirmDraftComment() { |
| 9 | + const commentTextareas = [ |
| 10 | + document.querySelector('.edit-content-zone:not(.tw-hidden) textarea'), |
| 11 | + document.querySelector('#comment-form textarea'), |
| 12 | + ]; |
| 13 | + for (const textarea of commentTextareas) { |
| 14 | + // Most users won't feel too sad if they lose a comment with 10 chars, they can re-type these in seconds. |
| 15 | + // But if they have typed more (like 50) chars and the comment is lost, they will be very unhappy. |
| 16 | + if (textarea && textarea.value.trim().length > 10) { |
| 17 | + textarea.parentElement.scrollIntoView(); |
| 18 | + if (!window.confirm('Page will be reloaded, but there are draft comments. Continuing to reload will discard the comments. Continue?')) { |
| 19 | + return; |
| 20 | + } |
| 21 | + break; |
| 22 | + } |
| 23 | + } |
| 24 | + window.location.reload(); |
| 25 | +} |
| 26 | + |
| 27 | +function initBranchSelector() { |
| 28 | + const elSelectBranch = document.querySelector('.ui.dropdown.select-branch'); |
| 29 | + if (!elSelectBranch) return; |
| 30 | + |
| 31 | + const urlUpdateIssueRef = elSelectBranch.getAttribute('data-url-update-issueref'); |
| 32 | + const $selectBranch = $(elSelectBranch); |
| 33 | + const $branchMenu = $selectBranch.find('.reference-list-menu'); |
| 34 | + $branchMenu.find('.item:not(.no-select)').on('click', async function (e) { |
| 35 | + e.preventDefault(); |
| 36 | + const selectedValue = this.getAttribute('data-id'); // eg: "refs/heads/my-branch" |
| 37 | + const selectedText = this.getAttribute('data-name'); // eg: "my-branch" |
| 38 | + if (urlUpdateIssueRef) { |
| 39 | + // for existing issue, send request to update issue ref, and reload page |
| 40 | + try { |
| 41 | + await POST(urlUpdateIssueRef, {data: new URLSearchParams({ref: selectedValue})}); |
| 42 | + window.location.reload(); |
| 43 | + } catch (error) { |
| 44 | + console.error(error); |
| 45 | + } |
| 46 | + } else { |
| 47 | + // for new issue, only update UI&form, do not send request/reload |
| 48 | + const selectedHiddenSelector = this.getAttribute('data-id-selector'); |
| 49 | + document.querySelector(selectedHiddenSelector).value = selectedValue; |
| 50 | + elSelectBranch.querySelector('.text-branch-name').textContent = selectedText; |
| 51 | + } |
| 52 | + }); |
| 53 | +} |
| 54 | + |
| 55 | +// List submits |
| 56 | +function initListSubmits(selector, outerSelector) { |
| 57 | + const $list = $(`.ui.${outerSelector}.list`); |
| 58 | + const $noSelect = $list.find('.no-select'); |
| 59 | + const $listMenu = $(`.${selector} .menu`); |
| 60 | + let hasUpdateAction = $listMenu.data('action') === 'update'; |
| 61 | + const items = {}; |
| 62 | + |
| 63 | + $(`.${selector}`).dropdown({ |
| 64 | + 'action': 'nothing', // do not hide the menu if user presses Enter |
| 65 | + fullTextSearch: 'exact', |
| 66 | + async onHide() { |
| 67 | + hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var |
| 68 | + if (hasUpdateAction) { |
| 69 | + // TODO: Add batch functionality and make this 1 network request. |
| 70 | + const itemEntries = Object.entries(items); |
| 71 | + for (const [elementId, item] of itemEntries) { |
| 72 | + await updateIssuesMeta( |
| 73 | + item['update-url'], |
| 74 | + item.action, |
| 75 | + item['issue-id'], |
| 76 | + elementId, |
| 77 | + ); |
| 78 | + } |
| 79 | + if (itemEntries.length) { |
| 80 | + reloadConfirmDraftComment(); |
| 81 | + } |
| 82 | + } |
| 83 | + }, |
| 84 | + }); |
| 85 | + |
| 86 | + $listMenu.find('.item:not(.no-select)').on('click', function (e) { |
| 87 | + e.preventDefault(); |
| 88 | + if (this.classList.contains('ban-change')) { |
| 89 | + return false; |
| 90 | + } |
| 91 | + |
| 92 | + hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var |
| 93 | + |
| 94 | + const clickedItem = this; // eslint-disable-line unicorn/no-this-assignment |
| 95 | + const scope = this.getAttribute('data-scope'); |
| 96 | + |
| 97 | + $(this).parent().find('.item').each(function () { |
| 98 | + if (scope) { |
| 99 | + // Enable only clicked item for scoped labels |
| 100 | + if (this.getAttribute('data-scope') !== scope) { |
| 101 | + return true; |
| 102 | + } |
| 103 | + if (this !== clickedItem && !this.classList.contains('checked')) { |
| 104 | + return true; |
| 105 | + } |
| 106 | + } else if (this !== clickedItem) { |
| 107 | + // Toggle for other labels |
| 108 | + return true; |
| 109 | + } |
| 110 | + |
| 111 | + if (this.classList.contains('checked')) { |
| 112 | + $(this).removeClass('checked'); |
| 113 | + $(this).find('.octicon-check').addClass('tw-invisible'); |
| 114 | + if (hasUpdateAction) { |
| 115 | + if (!($(this).data('id') in items)) { |
| 116 | + items[$(this).data('id')] = { |
| 117 | + 'update-url': $listMenu.data('update-url'), |
| 118 | + action: 'detach', |
| 119 | + 'issue-id': $listMenu.data('issue-id'), |
| 120 | + }; |
| 121 | + } else { |
| 122 | + delete items[$(this).data('id')]; |
| 123 | + } |
| 124 | + } |
| 125 | + } else { |
| 126 | + $(this).addClass('checked'); |
| 127 | + $(this).find('.octicon-check').removeClass('tw-invisible'); |
| 128 | + if (hasUpdateAction) { |
| 129 | + if (!($(this).data('id') in items)) { |
| 130 | + items[$(this).data('id')] = { |
| 131 | + 'update-url': $listMenu.data('update-url'), |
| 132 | + action: 'attach', |
| 133 | + 'issue-id': $listMenu.data('issue-id'), |
| 134 | + }; |
| 135 | + } else { |
| 136 | + delete items[$(this).data('id')]; |
| 137 | + } |
| 138 | + } |
| 139 | + } |
| 140 | + }); |
| 141 | + |
| 142 | + // TODO: Which thing should be done for choosing review requests |
| 143 | + // to make chosen items be shown on time here? |
| 144 | + if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') { |
| 145 | + return false; |
| 146 | + } |
| 147 | + |
| 148 | + const listIds = []; |
| 149 | + $(this).parent().find('.item').each(function () { |
| 150 | + if (this.classList.contains('checked')) { |
| 151 | + listIds.push($(this).data('id')); |
| 152 | + $($(this).data('id-selector')).removeClass('tw-hidden'); |
| 153 | + } else { |
| 154 | + $($(this).data('id-selector')).addClass('tw-hidden'); |
| 155 | + } |
| 156 | + }); |
| 157 | + if (!listIds.length) { |
| 158 | + $noSelect.removeClass('tw-hidden'); |
| 159 | + } else { |
| 160 | + $noSelect.addClass('tw-hidden'); |
| 161 | + } |
| 162 | + $($(this).parent().data('id')).val(listIds.join(',')); |
| 163 | + return false; |
| 164 | + }); |
| 165 | + $listMenu.find('.no-select.item').on('click', function (e) { |
| 166 | + e.preventDefault(); |
| 167 | + if (hasUpdateAction) { |
| 168 | + (async () => { |
| 169 | + await updateIssuesMeta( |
| 170 | + $listMenu.data('update-url'), |
| 171 | + 'clear', |
| 172 | + $listMenu.data('issue-id'), |
| 173 | + '', |
| 174 | + ); |
| 175 | + reloadConfirmDraftComment(); |
| 176 | + })(); |
| 177 | + } |
| 178 | + |
| 179 | + $(this).parent().find('.item').each(function () { |
| 180 | + $(this).removeClass('checked'); |
| 181 | + $(this).find('.octicon-check').addClass('tw-invisible'); |
| 182 | + }); |
| 183 | + |
| 184 | + if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') { |
| 185 | + return false; |
| 186 | + } |
| 187 | + |
| 188 | + $list.find('.item').each(function () { |
| 189 | + $(this).addClass('tw-hidden'); |
| 190 | + }); |
| 191 | + $noSelect.removeClass('tw-hidden'); |
| 192 | + $($(this).parent().data('id')).val(''); |
| 193 | + }); |
| 194 | +} |
| 195 | + |
| 196 | +function selectItem(select_id, input_id) { |
| 197 | + const $menu = $(`${select_id} .menu`); |
| 198 | + const $list = $(`.ui${select_id}.list`); |
| 199 | + const hasUpdateAction = $menu.data('action') === 'update'; |
| 200 | + |
| 201 | + $menu.find('.item:not(.no-select)').on('click', function () { |
| 202 | + $(this).parent().find('.item').each(function () { |
| 203 | + $(this).removeClass('selected active'); |
| 204 | + }); |
| 205 | + |
| 206 | + $(this).addClass('selected active'); |
| 207 | + if (hasUpdateAction) { |
| 208 | + (async () => { |
| 209 | + await updateIssuesMeta( |
| 210 | + $menu.data('update-url'), |
| 211 | + '', |
| 212 | + $menu.data('issue-id'), |
| 213 | + $(this).data('id'), |
| 214 | + ); |
| 215 | + reloadConfirmDraftComment(); |
| 216 | + })(); |
| 217 | + } |
| 218 | + |
| 219 | + let icon = ''; |
| 220 | + if (input_id === '#milestone_id') { |
| 221 | + icon = svg('octicon-milestone', 18, 'tw-mr-2'); |
| 222 | + } else if (input_id === '#project_id') { |
| 223 | + icon = svg('octicon-project', 18, 'tw-mr-2'); |
| 224 | + } else if (input_id === '#assignee_id') { |
| 225 | + icon = `<img class="ui avatar image tw-mr-2" alt="avatar" src=${$(this).data('avatar')}>`; |
| 226 | + } |
| 227 | + |
| 228 | + $list.find('.selected').html(` |
| 229 | + <a class="item muted sidebar-item-link" href="${htmlEscape(this.getAttribute('data-href'))}"> |
| 230 | + ${icon} |
| 231 | + ${htmlEscape(this.textContent)} |
| 232 | + </a> |
| 233 | + `); |
| 234 | + |
| 235 | + $(`.ui${select_id}.list .no-select`).addClass('tw-hidden'); |
| 236 | + $(input_id).val($(this).data('id')); |
| 237 | + }); |
| 238 | + $menu.find('.no-select.item').on('click', function () { |
| 239 | + $(this).parent().find('.item:not(.no-select)').each(function () { |
| 240 | + $(this).removeClass('selected active'); |
| 241 | + }); |
| 242 | + |
| 243 | + if (hasUpdateAction) { |
| 244 | + (async () => { |
| 245 | + await updateIssuesMeta( |
| 246 | + $menu.data('update-url'), |
| 247 | + '', |
| 248 | + $menu.data('issue-id'), |
| 249 | + $(this).data('id'), |
| 250 | + ); |
| 251 | + reloadConfirmDraftComment(); |
| 252 | + })(); |
| 253 | + } |
| 254 | + |
| 255 | + $list.find('.selected').html(''); |
| 256 | + $list.find('.no-select').removeClass('tw-hidden'); |
| 257 | + $(input_id).val(''); |
| 258 | + }); |
| 259 | +} |
| 260 | + |
| 261 | +export function initRepoIssueSidebar() { |
| 262 | + initBranchSelector(); |
| 263 | + |
| 264 | + // Init labels and assignees |
| 265 | + initListSubmits('select-label', 'labels'); |
| 266 | + initListSubmits('select-assignees', 'assignees'); |
| 267 | + initListSubmits('select-assignees-modify', 'assignees'); |
| 268 | + initListSubmits('select-reviewers-modify', 'assignees'); |
| 269 | + |
| 270 | + // Milestone, Assignee, Project |
| 271 | + selectItem('.select-project', '#project_id'); |
| 272 | + selectItem('.select-milestone', '#milestone_id'); |
| 273 | + selectItem('.select-assignee', '#assignee_id'); |
| 274 | +} |
0 commit comments