Skip to content
72 changes: 32 additions & 40 deletions web_src/js/features/repo-projects.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
import $ from 'jquery';
import {contrastColor} from '../utils/color.ts';
import {createSortable} from '../modules/sortable.ts';
import {POST, DELETE, PUT} from '../modules/fetch.ts';
import {fomanticQuery} from '../modules/fomantic/base.ts';

function updateIssueCount(cards) {
const parent = cards.parentElement;
function updateIssueCount(card: HTMLElement): void {
const parent = card.parentElement;
const cnt = parent.querySelectorAll('.issue-card').length;
parent.querySelectorAll('.project-column-issue-count')[0].textContent = cnt;
parent.querySelectorAll('.project-column-issue-count')[0].textContent = String(cnt);
}

async function createNewColumn(url, columnTitle, projectColorInput) {
async function createNewColumn(url: string, columnTitleInput: HTMLInputElement, projectColorInput: HTMLInputElement): Promise<void> {
try {
await POST(url, {
data: {
title: columnTitle.val(),
color: projectColorInput.val(),
title: columnTitleInput.value,
color: projectColorInput.value,
},
});
} catch (error) {
console.error(error);
} finally {
columnTitle.closest('form').removeClass('dirty');
columnTitleInput.closest('form').classList.remove('dirty');
window.location.reload();
}
}

async function moveIssue({item, from, to, oldIndex}: {item: HTMLElement, from: HTMLElement, to: HTMLElement, oldIndex: number}) {
async function moveIssue({item, from, to, oldIndex}: {item: HTMLElement, from: HTMLElement, to: HTMLElement, oldIndex: number}): Promise<void> {
const columnCards = to.querySelectorAll('.issue-card');
updateIssueCount(from);
updateIssueCount(to);
Expand All @@ -47,21 +47,21 @@ async function moveIssue({item, from, to, oldIndex}: {item: HTMLElement, from: H
}
}

async function initRepoProjectSortable() {
async function initRepoProjectSortable(): Promise<void> {
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.querySelectorAll('.project-column');
let boardColumns = mainBoard.querySelectorAll<HTMLDivElement>('.project-column');
createSortable(mainBoard, {
group: 'project-column',
draggable: '.project-column',
handle: '.project-column-header',
delayOnTouchOnly: true,
delay: 500,
onSort: async () => { // eslint-disable-line @typescript-eslint/no-misused-promises
boardColumns = mainBoard.querySelectorAll('.project-column');
boardColumns = mainBoard.querySelectorAll<HTMLDivElement>('.project-column');

const columnSorting = {
columns: Array.from(boardColumns, (column, i) => ({
Expand Down Expand Up @@ -92,14 +92,14 @@ async function initRepoProjectSortable() {
}
}

export function initRepoProject() {
export function initRepoProject(): void {
if (!document.querySelector('.repository.projects')) {
return;
}

initRepoProjectSortable(); // no await

for (const modal of document.querySelectorAll('.edit-project-column-modal')) {
for (const modal of document.querySelectorAll<HTMLDivElement>('.edit-project-column-modal')) {
const projectHeader = modal.closest<HTMLElement>('.project-column-header');
const projectTitleLabel = projectHeader?.querySelector<HTMLElement>('.project-column-title-label');
const projectTitleInput = modal.querySelector<HTMLInputElement>('.project-column-title-input');
Expand Down Expand Up @@ -134,55 +134,47 @@ export function initRepoProject() {
divider.style.removeProperty('color');
}
}
$('.ui.modal').modal('hide');
fomanticQuery('.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) => {
for (const modal of document.querySelectorAll('.default-project-column-modal')) {
const column = modal.closest('.project-column');
const showBtn = column.querySelector('.default-project-column-show');
const okBtn = modal.querySelector('.actions .ok.button');
okBtn.addEventListener('click', async (e: MouseEvent) => {
e.preventDefault();

try {
await POST($($showButton).data('url'));
await POST(showBtn.getAttribute('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) => {
for (const btn of document.querySelectorAll('.show-delete-project-column-modal')) {
const okBtn = document.querySelector(`${btn.getAttribute('data-modal')} .actions .ok.button`);
okBtn?.addEventListener('click', async (e: MouseEvent) => {
e.preventDefault();

try {
await DELETE(deleteUrl);
await DELETE(btn.getAttribute('data-url'));
} catch (error) {
console.error(error);
} finally {
window.location.reload();
}
});
});
}

$('#new_project_column_submit').on('click', (e) => {
document.querySelector('#new_project_column_submit')?.addEventListener('click', async (e: MouseEvent & {target: HTMLButtonElement}) => {
e.preventDefault();
const $columnTitle = $('#new_project_column');
const $projectColorInput = $('#new_project_column_color_picker');
if (!$columnTitle.val()) {
return;
}
const columnTitleInput = document.querySelector<HTMLInputElement>('#new_project_column');
const projectColorInput = document.querySelector<HTMLInputElement>('#new_project_column_color_picker');
if (!columnTitleInput.value) return;
const url = e.target.getAttribute('data-url');
createNewColumn(url, $columnTitle, $projectColorInput);
await createNewColumn(url, columnTitleInput, projectColorInput);
});
}
8 changes: 5 additions & 3 deletions web_src/js/features/repo-settings-branches.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import {beforeEach, describe, expect, test, vi} from 'vitest';
import {initRepoSettingsBranchesDrag} from './repo-settings-branches.ts';
import {POST} from '../modules/fetch.ts';
import {createSortable} from '../modules/sortable.ts';
import type {SortableEvent} from 'sortablejs';
import type {SortableEvent, SortableOptions} from 'sortablejs';
import type Sortable from 'sortablejs';

vi.mock('../modules/fetch.ts', () => ({
POST: vi.fn(),
Expand Down Expand Up @@ -55,9 +56,10 @@ describe('Repository Branch Settings', () => {
vi.mocked(POST).mockResolvedValue({ok: true} as Response);

// Mock createSortable to capture and execute the onEnd callback
vi.mocked(createSortable).mockImplementation(async (_el: Element, options) => {
vi.mocked(createSortable).mockImplementation(async (_el: Element, options: SortableOptions) => {
options.onEnd(new Event('SortableEvent') as SortableEvent);
return {destroy: vi.fn()};
// @ts-expect-error: mock is incomplete
return {destroy: vi.fn()} as Sortable;
});

initRepoSettingsBranchesDrag();
Expand Down
3 changes: 2 additions & 1 deletion web_src/js/modules/sortable.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {SortableOptions, SortableEvent} from 'sortablejs';
import type Sortable from 'sortablejs';

export async function createSortable(el: Element, opts: {handle?: string} & SortableOptions = {}) {
export async function createSortable(el: Element, opts: {handle?: string} & SortableOptions = {}): Promise<Sortable> {
// @ts-expect-error: wrong type derived by typescript
const {Sortable} = await import(/* webpackChunkName: "sortablejs" */'sortablejs');

Expand Down
Loading