code.gitea.io/gitea@v1.22.3/web_src/js/features/repo-projects.js (about) 1 import $ from 'jquery'; 2 import {contrastColor} from '../utils/color.js'; 3 import {createSortable} from '../modules/sortable.js'; 4 import {POST, DELETE, PUT} from '../modules/fetch.js'; 5 6 function updateIssueCount(cards) { 7 const parent = cards.parentElement; 8 const cnt = parent.getElementsByClassName('issue-card').length; 9 parent.getElementsByClassName('project-column-issue-count')[0].textContent = cnt; 10 } 11 12 async function createNewColumn(url, columnTitle, projectColorInput) { 13 try { 14 await POST(url, { 15 data: { 16 title: columnTitle.val(), 17 color: projectColorInput.val(), 18 }, 19 }); 20 } catch (error) { 21 console.error(error); 22 } finally { 23 columnTitle.closest('form').removeClass('dirty'); 24 window.location.reload(); 25 } 26 } 27 28 async function moveIssue({item, from, to, oldIndex}) { 29 const columnCards = to.getElementsByClassName('issue-card'); 30 updateIssueCount(from); 31 updateIssueCount(to); 32 33 const columnSorting = { 34 issues: Array.from(columnCards, (card, i) => ({ 35 issueID: parseInt(card.getAttribute('data-issue')), 36 sorting: i, 37 })), 38 }; 39 40 try { 41 await POST(`${to.getAttribute('data-url')}/move`, { 42 data: columnSorting, 43 }); 44 } catch (error) { 45 console.error(error); 46 from.insertBefore(item, from.children[oldIndex]); 47 } 48 } 49 50 async function initRepoProjectSortable() { 51 const els = document.querySelectorAll('#project-board > .board.sortable'); 52 if (!els.length) return; 53 54 // the HTML layout is: #project-board > .board > .project-column .cards > .issue-card 55 const mainBoard = els[0]; 56 let boardColumns = mainBoard.getElementsByClassName('project-column'); 57 createSortable(mainBoard, { 58 group: 'project-column', 59 draggable: '.project-column', 60 handle: '.project-column-header', 61 delayOnTouchOnly: true, 62 delay: 500, 63 onSort: async () => { 64 boardColumns = mainBoard.getElementsByClassName('project-column'); 65 66 const columnSorting = { 67 columns: Array.from(boardColumns, (column, i) => ({ 68 columnID: parseInt(column.getAttribute('data-id')), 69 sorting: i, 70 })), 71 }; 72 73 try { 74 await POST(mainBoard.getAttribute('data-url'), { 75 data: columnSorting, 76 }); 77 } catch (error) { 78 console.error(error); 79 } 80 }, 81 }); 82 83 for (const boardColumn of boardColumns) { 84 const boardCardList = boardColumn.getElementsByClassName('cards')[0]; 85 createSortable(boardCardList, { 86 group: 'shared', 87 onAdd: moveIssue, 88 onUpdate: moveIssue, 89 delayOnTouchOnly: true, 90 delay: 500, 91 }); 92 } 93 } 94 95 export function initRepoProject() { 96 if (!document.querySelector('.repository.projects')) { 97 return; 98 } 99 100 const _promise = initRepoProjectSortable(); 101 102 for (const modal of document.getElementsByClassName('edit-project-column-modal')) { 103 const projectHeader = modal.closest('.project-column-header'); 104 const projectTitleLabel = projectHeader?.querySelector('.project-column-title-label'); 105 const projectTitleInput = modal.querySelector('.project-column-title-input'); 106 const projectColorInput = modal.querySelector('#new_project_column_color'); 107 const boardColumn = modal.closest('.project-column'); 108 modal.querySelector('.edit-project-column-button')?.addEventListener('click', async function (e) { 109 e.preventDefault(); 110 try { 111 await PUT(this.getAttribute('data-url'), { 112 data: { 113 title: projectTitleInput?.value, 114 color: projectColorInput?.value, 115 }, 116 }); 117 } catch (error) { 118 console.error(error); 119 } finally { 120 projectTitleLabel.textContent = projectTitleInput?.value; 121 projectTitleInput.closest('form')?.classList.remove('dirty'); 122 const dividers = boardColumn.querySelectorAll(':scope > .divider'); 123 if (projectColorInput.value) { 124 const color = contrastColor(projectColorInput.value); 125 boardColumn.style.setProperty('background', projectColorInput.value, 'important'); 126 boardColumn.style.setProperty('color', color, 'important'); 127 for (const divider of dividers) { 128 divider.style.setProperty('color', color); 129 } 130 } else { 131 boardColumn.style.removeProperty('background'); 132 boardColumn.style.removeProperty('color'); 133 for (const divider of dividers) { 134 divider.style.removeProperty('color'); 135 } 136 } 137 $('.ui.modal').modal('hide'); 138 } 139 }); 140 } 141 142 $('.default-project-column-modal').each(function () { 143 const $boardColumn = $(this).closest('.project-column'); 144 const $showButton = $($boardColumn).find('.default-project-column-show'); 145 const $commitButton = $(this).find('.actions > .ok.button'); 146 147 $($commitButton).on('click', async (e) => { 148 e.preventDefault(); 149 150 try { 151 await POST($($showButton).data('url')); 152 } catch (error) { 153 console.error(error); 154 } finally { 155 window.location.reload(); 156 } 157 }); 158 }); 159 160 $('.show-delete-project-column-modal').each(function () { 161 const $deleteColumnModal = $(`${this.getAttribute('data-modal')}`); 162 const $deleteColumnButton = $deleteColumnModal.find('.actions > .ok.button'); 163 const deleteUrl = this.getAttribute('data-url'); 164 165 $deleteColumnButton.on('click', async (e) => { 166 e.preventDefault(); 167 168 try { 169 await DELETE(deleteUrl); 170 } catch (error) { 171 console.error(error); 172 } finally { 173 window.location.reload(); 174 } 175 }); 176 }); 177 178 $('#new_project_column_submit').on('click', (e) => { 179 e.preventDefault(); 180 const $columnTitle = $('#new_project_column'); 181 const $projectColorInput = $('#new_project_column_color_picker'); 182 if (!$columnTitle.val()) { 183 return; 184 } 185 const url = e.target.getAttribute('data-url'); 186 createNewColumn(url, $columnTitle, $projectColorInput); 187 }); 188 }