forgejo/web_src/js/features/repo-projects.js
Lunny Xiao d91839692f
Fix various problems around projects board view (#30696)
The previous implementation will start multiple POST requests from the
frontend when moving a column and another bug is moving the default
column will never be remembered in fact.

- [x] This PR will allow the default column to move to a non-first
position
- [x] And it also uses one request instead of multiple requests when
moving the columns
- [x] Use a star instead of a pin as the icon for setting the default
column action
- [x] Inserted new column will be append to the end
- [x] Fix #30701 the newly added issue will be append to the end of the
default column
- [x] Fix when deleting a column, all issues in it will be displayed
from UI but database records exist.
- [x] Add a limitation for columns in a project to 20. So the sorting
will not be overflow because it's int8.

---------

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
(cherry picked from commit a303c973e0264dab45a787c4afa200e183e0d953)

Conflicts:
	routers/web/web.go
	e91733468ef726fc9365aa4820cdd5f2ddfdaa23 Add missing database transaction for new issue (#29490) was not cherry-picked
	services/issue/issue.go
	fe6792dff3 Enable/disable owner and repo projects independently (#28805) was not cherry-picked
(cherry picked from commit 7d3ca90dfe)

(cherry picked from commit 084bec89ed7ae0816fc2d8db6784ad22523d1fc4)
2024-05-14 15:51:15 +02:00

189 lines
5.8 KiB
JavaScript

import $ from 'jquery';
import {contrastColor} from '../utils/color.js';
import {createSortable} from '../modules/sortable.js';
import {POST, DELETE, PUT} from '../modules/fetch.js';
function updateIssueCount(cards) {
const parent = cards.parentElement;
const cnt = parent.getElementsByClassName('issue-card').length;
parent.getElementsByClassName('project-column-issue-count')[0].textContent = cnt;
}
async function createNewColumn(url, columnTitle, projectColorInput) {
try {
await POST(url, {
data: {
title: columnTitle.val(),
color: projectColorInput.val(),
},
});
} catch (error) {
console.error(error);
} finally {
columnTitle.closest('form').removeClass('dirty');
window.location.reload();
}
}
async function moveIssue({item, from, to, oldIndex}) {
const columnCards = to.getElementsByClassName('issue-card');
updateIssueCount(from);
updateIssueCount(to);
const columnSorting = {
issues: Array.from(columnCards, (card, i) => ({
issueID: parseInt(card.getAttribute('data-issue')),
sorting: i,
})),
};
try {
await POST(`${to.getAttribute('data-url')}/move`, {
data: columnSorting,
});
} catch (error) {
console.error(error);
from.insertBefore(item, from.children[oldIndex]);
}
}
async function initRepoProjectSortable() {
const els = document.querySelectorAll('#project-board > .board.sortable');
if (!els.length) return;
// the HTML layout is: #project-board > .board > .project-column .cards > .issue-card
const mainBoard = els[0];
let boardColumns = mainBoard.getElementsByClassName('project-column');
createSortable(mainBoard, {
group: 'project-column',
draggable: '.project-column',
handle: '.project-column-header',
delayOnTouchOnly: true,
delay: 500,
onSort: async () => {
boardColumns = mainBoard.getElementsByClassName('project-column');
const columnSorting = {
columns: Array.from(boardColumns, (column, i) => ({
columnID: parseInt(column.getAttribute('data-id')),
sorting: i,
})),
};
try {
await POST(mainBoard.getAttribute('data-url'), {
data: columnSorting,
});
} catch (error) {
console.error(error);
}
},
});
for (const boardColumn of boardColumns) {
const boardCardList = boardColumn.getElementsByClassName('cards')[0];
createSortable(boardCardList, {
group: 'shared',
onAdd: moveIssue,
onUpdate: moveIssue,
delayOnTouchOnly: true,
delay: 500,
});
}
}
export function initRepoProject() {
if (!document.querySelector('.repository.projects')) {
return;
}
const _promise = initRepoProjectSortable();
for (const modal of document.getElementsByClassName('edit-project-column-modal')) {
const projectHeader = modal.closest('.project-column-header');
const projectTitleLabel = projectHeader?.querySelector('.project-column-title-label');
const projectTitleInput = modal.querySelector('.project-column-title-input');
const projectColorInput = modal.querySelector('#new_project_column_color');
const boardColumn = modal.closest('.project-column');
modal.querySelector('.edit-project-column-button')?.addEventListener('click', async function (e) {
e.preventDefault();
try {
await PUT(this.getAttribute('data-url'), {
data: {
title: projectTitleInput?.value,
color: projectColorInput?.value,
},
});
} catch (error) {
console.error(error);
} finally {
projectTitleLabel.textContent = projectTitleInput?.value;
projectTitleInput.closest('form')?.classList.remove('dirty');
const dividers = boardColumn.querySelectorAll(':scope > .divider');
if (projectColorInput.value) {
const color = contrastColor(projectColorInput.value);
boardColumn.style.setProperty('background', projectColorInput.value, 'important');
boardColumn.style.setProperty('color', color, 'important');
for (const divider of dividers) {
divider.style.setProperty('color', color);
}
} else {
boardColumn.style.removeProperty('background');
boardColumn.style.removeProperty('color');
for (const divider of dividers) {
divider.style.removeProperty('color');
}
}
$('.ui.modal').modal('hide');
}
});
}
$('.default-project-column-modal').each(function () {
const $boardColumn = $(this).closest('.project-column');
const $showButton = $($boardColumn).find('.default-project-column-show');
const $commitButton = $(this).find('.actions > .ok.button');
$($commitButton).on('click', async (e) => {
e.preventDefault();
try {
await POST($($showButton).data('url'));
} catch (error) {
console.error(error);
} finally {
window.location.reload();
}
});
});
$('.show-delete-project-column-modal').each(function () {
const $deleteColumnModal = $(`${this.getAttribute('data-modal')}`);
const $deleteColumnButton = $deleteColumnModal.find('.actions > .ok.button');
const deleteUrl = this.getAttribute('data-url');
$deleteColumnButton.on('click', async (e) => {
e.preventDefault();
try {
await DELETE(deleteUrl);
} catch (error) {
console.error(error);
} finally {
window.location.reload();
}
});
});
$('#new_project_column_submit').on('click', (e) => {
e.preventDefault();
const $columnTitle = $('#new_project_column');
const $projectColorInput = $('#new_project_column_color_picker');
if (!$columnTitle.val()) {
return;
}
const url = e.target.getAttribute('data-url');
createNewColumn(url, $columnTitle, $projectColorInput);
});
}