mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 07:20:12 +01:00
Make possible for lists to have different names at different swimlanes. Make possible to drag list from one swimlane to another swimlane.
Thanks to xet7 !
This commit is contained in:
parent
39daf56811
commit
719ef87efc
5 changed files with 144 additions and 12 deletions
|
|
@ -63,7 +63,7 @@ function initSortable(boardComponent, $listsDom) {
|
|||
};
|
||||
|
||||
$listsDom.sortable({
|
||||
connectWith: '.board-canvas',
|
||||
connectWith: '.js-swimlane, .js-lists',
|
||||
tolerance: 'pointer',
|
||||
helper: 'clone',
|
||||
items: '.js-list:not(.js-list-composer)',
|
||||
|
|
@ -82,10 +82,31 @@ function initSortable(boardComponent, $listsDom) {
|
|||
const nextListDom = ui.item.next('.js-list').get(0);
|
||||
const sortIndex = calculateIndex(prevListDom, nextListDom, 1);
|
||||
|
||||
$listsDom.sortable('cancel');
|
||||
const listDomElement = ui.item.get(0);
|
||||
const list = Blaze.getData(listDomElement);
|
||||
|
||||
// Detect if the list was dropped in a different swimlane
|
||||
const targetSwimlaneDom = ui.item.closest('.js-swimlane');
|
||||
let targetSwimlaneId = null;
|
||||
|
||||
if (targetSwimlaneDom.length > 0) {
|
||||
// List was dropped in a swimlane
|
||||
targetSwimlaneId = targetSwimlaneDom.attr('id').replace('swimlane-', '');
|
||||
} else {
|
||||
// List was dropped in lists view (not swimlanes view)
|
||||
// In this case, assign to the default swimlane
|
||||
const currentBoard = ReactiveCache.getBoard(Session.get('currentBoard'));
|
||||
if (currentBoard) {
|
||||
const defaultSwimlane = currentBoard.getDefaultSwimline();
|
||||
if (defaultSwimlane) {
|
||||
targetSwimlaneId = defaultSwimlane._id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the original swimlane ID of the list
|
||||
const originalSwimlaneId = list.swimlaneId;
|
||||
|
||||
/*
|
||||
Reverted incomplete change list width,
|
||||
removed from below Lists.update:
|
||||
|
|
@ -95,10 +116,44 @@ function initSortable(boardComponent, $listsDom) {
|
|||
height: list._id.height(),
|
||||
*/
|
||||
|
||||
// Prepare update object
|
||||
const updateData = {
|
||||
sort: sortIndex.base,
|
||||
};
|
||||
|
||||
// Check if the list was dropped in a different swimlane
|
||||
const isDifferentSwimlane = targetSwimlaneId && targetSwimlaneId !== originalSwimlaneId;
|
||||
|
||||
// If the list was dropped in a different swimlane, update the swimlaneId
|
||||
if (isDifferentSwimlane) {
|
||||
updateData.swimlaneId = targetSwimlaneId;
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(`Moving list "${list.title}" from swimlane ${originalSwimlaneId} to swimlane ${targetSwimlaneId}`);
|
||||
}
|
||||
|
||||
// Move all cards in the list to the new swimlane
|
||||
const cardsInList = ReactiveCache.getCards({
|
||||
listId: list._id,
|
||||
archived: false
|
||||
});
|
||||
|
||||
cardsInList.forEach(card => {
|
||||
card.move(list.boardId, targetSwimlaneId, list._id);
|
||||
});
|
||||
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(`Moved ${cardsInList.length} cards to swimlane ${targetSwimlaneId}`);
|
||||
}
|
||||
|
||||
// Don't cancel the sortable when moving to a different swimlane
|
||||
// The DOM move should be allowed to complete
|
||||
} else {
|
||||
// If staying in the same swimlane, cancel the sortable to prevent DOM manipulation issues
|
||||
$listsDom.sortable('cancel');
|
||||
}
|
||||
|
||||
Lists.update(list._id, {
|
||||
$set: {
|
||||
sort: sortIndex.base,
|
||||
},
|
||||
$set: updateData,
|
||||
});
|
||||
|
||||
boardComponent.setIsDragging(false);
|
||||
|
|
@ -109,10 +164,12 @@ function initSortable(boardComponent, $listsDom) {
|
|||
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
|
||||
$listsDom.sortable({
|
||||
handle: '.js-list-handle',
|
||||
connectWith: '.js-swimlane, .js-lists',
|
||||
});
|
||||
} else {
|
||||
$listsDom.sortable({
|
||||
handle: '.js-list-header',
|
||||
connectWith: '.js-swimlane, .js-lists',
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -281,7 +338,7 @@ BlazeComponent.extendComponent({
|
|||
boardId: Session.get('currentBoard'),
|
||||
sort: sortIndex,
|
||||
type: this.isListTemplatesSwimlane ? 'template-list' : 'list',
|
||||
swimlaneId: this.currentBoard.isTemplatesBoard() ? this.currentSwimlane._id : '',
|
||||
swimlaneId: this.currentSwimlane._id, // Always set swimlaneId for per-swimlane list titles
|
||||
});
|
||||
|
||||
titleInput.value = '';
|
||||
|
|
|
|||
|
|
@ -777,13 +777,22 @@ Boards.helpers({
|
|||
{
|
||||
boardId: this._id,
|
||||
archived: false,
|
||||
// Get lists for all swimlanes in this board
|
||||
swimlaneId: { $in: this.swimlanes().map(s => s._id) },
|
||||
},
|
||||
{ sort: sortKey },
|
||||
);
|
||||
},
|
||||
|
||||
draggableLists() {
|
||||
return ReactiveCache.getLists({ boardId: this._id }, { sort: { sort: 1 } });
|
||||
return ReactiveCache.getLists(
|
||||
{
|
||||
boardId: this._id,
|
||||
// Get lists for all swimlanes in this board
|
||||
swimlaneId: { $in: this.swimlanes().map(s => s._id) }
|
||||
},
|
||||
{ sort: { sort: 1 } }
|
||||
);
|
||||
},
|
||||
|
||||
/** returns the last list
|
||||
|
|
@ -1171,6 +1180,7 @@ Boards.helpers({
|
|||
this.subtasksDefaultListId = Lists.insert({
|
||||
title: TAPi18n.__('queue'),
|
||||
boardId: this._id,
|
||||
swimlaneId: this.getDefaultSwimline()._id, // Set default swimlane for subtasks list
|
||||
});
|
||||
this.setSubtasksDefaultListId(this.subtasksDefaultListId);
|
||||
}
|
||||
|
|
@ -1189,6 +1199,7 @@ Boards.helpers({
|
|||
this.dateSettingsDefaultListId = Lists.insert({
|
||||
title: TAPi18n.__('queue'),
|
||||
boardId: this._id,
|
||||
swimlaneId: this.getDefaultSwimline()._id, // Set default swimlane for date settings list
|
||||
});
|
||||
this.setDateSettingsDefaultListId(this.dateSettingsDefaultListId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,10 +50,10 @@ Lists.attachSchema(
|
|||
},
|
||||
swimlaneId: {
|
||||
/**
|
||||
* the swimlane associated to this list. Used for templates
|
||||
* the swimlane associated to this list. Required for per-swimlane list titles
|
||||
*/
|
||||
type: String,
|
||||
defaultValue: '',
|
||||
// Remove defaultValue to make it required
|
||||
},
|
||||
createdAt: {
|
||||
/**
|
||||
|
|
@ -196,7 +196,7 @@ Lists.helpers({
|
|||
_id = existingListWithSameName._id;
|
||||
} else {
|
||||
delete this._id;
|
||||
delete this.swimlaneId;
|
||||
this.swimlaneId = swimlaneId; // Set the target swimlane for the copied list
|
||||
_id = Lists.insert(this);
|
||||
}
|
||||
|
||||
|
|
@ -231,6 +231,7 @@ Lists.helpers({
|
|||
type: this.type,
|
||||
archived: false,
|
||||
wipLimit: this.wipLimit,
|
||||
swimlaneId: swimlaneId, // Set the target swimlane for the moved list
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -585,6 +586,7 @@ if (Meteor.isServer) {
|
|||
title: req.body.title,
|
||||
boardId: paramBoardId,
|
||||
sort: board.lists().length,
|
||||
swimlaneId: req.body.swimlaneId || board.getDefaultSwimline()._id, // Use provided swimlaneId or default
|
||||
});
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
|
|
|
|||
|
|
@ -173,6 +173,7 @@ Swimlanes.helpers({
|
|||
type: list.type,
|
||||
archived: false,
|
||||
wipLimit: list.wipLimit,
|
||||
swimlaneId: toSwimlaneId, // Set the target swimlane for the copied list
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -213,7 +214,7 @@ Swimlanes.helpers({
|
|||
return ReactiveCache.getLists(
|
||||
{
|
||||
boardId: this.boardId,
|
||||
swimlaneId: { $in: [this._id, ''] },
|
||||
swimlaneId: this._id, // Only get lists that belong to this specific swimlane
|
||||
archived: false,
|
||||
},
|
||||
{ sort: { modifiedAt: -1 } },
|
||||
|
|
@ -223,7 +224,7 @@ Swimlanes.helpers({
|
|||
return ReactiveCache.getLists(
|
||||
{
|
||||
boardId: this.boardId,
|
||||
swimlaneId: { $in: [this._id, ''] },
|
||||
swimlaneId: this._id, // Only get lists that belong to this specific swimlane
|
||||
//archived: false,
|
||||
},
|
||||
{ sort: ['sort'] },
|
||||
|
|
|
|||
|
|
@ -1489,3 +1489,64 @@ Migrations.add('remove-user-profile-hideCheckedItems', () => {
|
|||
noValidateMulti,
|
||||
);
|
||||
});
|
||||
|
||||
Migrations.add('migrate-lists-to-per-swimlane', () => {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('Starting migration: migrate-lists-to-per-swimlane');
|
||||
}
|
||||
|
||||
try {
|
||||
// Get all boards
|
||||
const boards = Boards.find({}).fetch();
|
||||
|
||||
boards.forEach(board => {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(`Processing board: ${board.title} (${board._id})`);
|
||||
}
|
||||
|
||||
// Get the default swimlane for this board
|
||||
const defaultSwimlane = board.getDefaultSwimline();
|
||||
if (!defaultSwimlane) {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(`No default swimlane found for board ${board._id}, skipping`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all lists for this board that don't have a swimlaneId or have empty swimlaneId
|
||||
const listsWithoutSwimlane = Lists.find({
|
||||
boardId: board._id,
|
||||
$or: [
|
||||
{ swimlaneId: { $exists: false } },
|
||||
{ swimlaneId: '' },
|
||||
{ swimlaneId: null }
|
||||
]
|
||||
}).fetch();
|
||||
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(`Found ${listsWithoutSwimlane.length} lists without swimlaneId in board ${board._id}`);
|
||||
}
|
||||
|
||||
// Update each list to belong to the default swimlane
|
||||
listsWithoutSwimlane.forEach(list => {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(`Updating list "${list.title}" to belong to swimlane "${defaultSwimlane.title}"`);
|
||||
}
|
||||
|
||||
Lists.direct.update(list._id, {
|
||||
$set: {
|
||||
swimlaneId: defaultSwimlane._id
|
||||
}
|
||||
}, noValidate);
|
||||
});
|
||||
});
|
||||
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('Migration migrate-lists-to-per-swimlane completed successfully');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error during migration migrate-lists-to-per-swimlane:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue