mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 23:40:13 +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({
|
$listsDom.sortable({
|
||||||
connectWith: '.board-canvas',
|
connectWith: '.js-swimlane, .js-lists',
|
||||||
tolerance: 'pointer',
|
tolerance: 'pointer',
|
||||||
helper: 'clone',
|
helper: 'clone',
|
||||||
items: '.js-list:not(.js-list-composer)',
|
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 nextListDom = ui.item.next('.js-list').get(0);
|
||||||
const sortIndex = calculateIndex(prevListDom, nextListDom, 1);
|
const sortIndex = calculateIndex(prevListDom, nextListDom, 1);
|
||||||
|
|
||||||
$listsDom.sortable('cancel');
|
|
||||||
const listDomElement = ui.item.get(0);
|
const listDomElement = ui.item.get(0);
|
||||||
const list = Blaze.getData(listDomElement);
|
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,
|
Reverted incomplete change list width,
|
||||||
removed from below Lists.update:
|
removed from below Lists.update:
|
||||||
|
|
@ -95,10 +116,44 @@ function initSortable(boardComponent, $listsDom) {
|
||||||
height: list._id.height(),
|
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, {
|
Lists.update(list._id, {
|
||||||
$set: {
|
$set: updateData,
|
||||||
sort: sortIndex.base,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
boardComponent.setIsDragging(false);
|
boardComponent.setIsDragging(false);
|
||||||
|
|
@ -109,10 +164,12 @@ function initSortable(boardComponent, $listsDom) {
|
||||||
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
|
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
|
||||||
$listsDom.sortable({
|
$listsDom.sortable({
|
||||||
handle: '.js-list-handle',
|
handle: '.js-list-handle',
|
||||||
|
connectWith: '.js-swimlane, .js-lists',
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$listsDom.sortable({
|
$listsDom.sortable({
|
||||||
handle: '.js-list-header',
|
handle: '.js-list-header',
|
||||||
|
connectWith: '.js-swimlane, .js-lists',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -281,7 +338,7 @@ BlazeComponent.extendComponent({
|
||||||
boardId: Session.get('currentBoard'),
|
boardId: Session.get('currentBoard'),
|
||||||
sort: sortIndex,
|
sort: sortIndex,
|
||||||
type: this.isListTemplatesSwimlane ? 'template-list' : 'list',
|
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 = '';
|
titleInput.value = '';
|
||||||
|
|
|
||||||
|
|
@ -777,13 +777,22 @@ Boards.helpers({
|
||||||
{
|
{
|
||||||
boardId: this._id,
|
boardId: this._id,
|
||||||
archived: false,
|
archived: false,
|
||||||
|
// Get lists for all swimlanes in this board
|
||||||
|
swimlaneId: { $in: this.swimlanes().map(s => s._id) },
|
||||||
},
|
},
|
||||||
{ sort: sortKey },
|
{ sort: sortKey },
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
draggableLists() {
|
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
|
/** returns the last list
|
||||||
|
|
@ -1171,6 +1180,7 @@ Boards.helpers({
|
||||||
this.subtasksDefaultListId = Lists.insert({
|
this.subtasksDefaultListId = Lists.insert({
|
||||||
title: TAPi18n.__('queue'),
|
title: TAPi18n.__('queue'),
|
||||||
boardId: this._id,
|
boardId: this._id,
|
||||||
|
swimlaneId: this.getDefaultSwimline()._id, // Set default swimlane for subtasks list
|
||||||
});
|
});
|
||||||
this.setSubtasksDefaultListId(this.subtasksDefaultListId);
|
this.setSubtasksDefaultListId(this.subtasksDefaultListId);
|
||||||
}
|
}
|
||||||
|
|
@ -1189,6 +1199,7 @@ Boards.helpers({
|
||||||
this.dateSettingsDefaultListId = Lists.insert({
|
this.dateSettingsDefaultListId = Lists.insert({
|
||||||
title: TAPi18n.__('queue'),
|
title: TAPi18n.__('queue'),
|
||||||
boardId: this._id,
|
boardId: this._id,
|
||||||
|
swimlaneId: this.getDefaultSwimline()._id, // Set default swimlane for date settings list
|
||||||
});
|
});
|
||||||
this.setDateSettingsDefaultListId(this.dateSettingsDefaultListId);
|
this.setDateSettingsDefaultListId(this.dateSettingsDefaultListId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,10 +50,10 @@ Lists.attachSchema(
|
||||||
},
|
},
|
||||||
swimlaneId: {
|
swimlaneId: {
|
||||||
/**
|
/**
|
||||||
* the swimlane associated to this list. Used for templates
|
* the swimlane associated to this list. Required for per-swimlane list titles
|
||||||
*/
|
*/
|
||||||
type: String,
|
type: String,
|
||||||
defaultValue: '',
|
// Remove defaultValue to make it required
|
||||||
},
|
},
|
||||||
createdAt: {
|
createdAt: {
|
||||||
/**
|
/**
|
||||||
|
|
@ -196,7 +196,7 @@ Lists.helpers({
|
||||||
_id = existingListWithSameName._id;
|
_id = existingListWithSameName._id;
|
||||||
} else {
|
} else {
|
||||||
delete this._id;
|
delete this._id;
|
||||||
delete this.swimlaneId;
|
this.swimlaneId = swimlaneId; // Set the target swimlane for the copied list
|
||||||
_id = Lists.insert(this);
|
_id = Lists.insert(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -231,6 +231,7 @@ Lists.helpers({
|
||||||
type: this.type,
|
type: this.type,
|
||||||
archived: false,
|
archived: false,
|
||||||
wipLimit: this.wipLimit,
|
wipLimit: this.wipLimit,
|
||||||
|
swimlaneId: swimlaneId, // Set the target swimlane for the moved list
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -585,6 +586,7 @@ if (Meteor.isServer) {
|
||||||
title: req.body.title,
|
title: req.body.title,
|
||||||
boardId: paramBoardId,
|
boardId: paramBoardId,
|
||||||
sort: board.lists().length,
|
sort: board.lists().length,
|
||||||
|
swimlaneId: req.body.swimlaneId || board.getDefaultSwimline()._id, // Use provided swimlaneId or default
|
||||||
});
|
});
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,7 @@ Swimlanes.helpers({
|
||||||
type: list.type,
|
type: list.type,
|
||||||
archived: false,
|
archived: false,
|
||||||
wipLimit: list.wipLimit,
|
wipLimit: list.wipLimit,
|
||||||
|
swimlaneId: toSwimlaneId, // Set the target swimlane for the copied list
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,7 +214,7 @@ Swimlanes.helpers({
|
||||||
return ReactiveCache.getLists(
|
return ReactiveCache.getLists(
|
||||||
{
|
{
|
||||||
boardId: this.boardId,
|
boardId: this.boardId,
|
||||||
swimlaneId: { $in: [this._id, ''] },
|
swimlaneId: this._id, // Only get lists that belong to this specific swimlane
|
||||||
archived: false,
|
archived: false,
|
||||||
},
|
},
|
||||||
{ sort: { modifiedAt: -1 } },
|
{ sort: { modifiedAt: -1 } },
|
||||||
|
|
@ -223,7 +224,7 @@ Swimlanes.helpers({
|
||||||
return ReactiveCache.getLists(
|
return ReactiveCache.getLists(
|
||||||
{
|
{
|
||||||
boardId: this.boardId,
|
boardId: this.boardId,
|
||||||
swimlaneId: { $in: [this._id, ''] },
|
swimlaneId: this._id, // Only get lists that belong to this specific swimlane
|
||||||
//archived: false,
|
//archived: false,
|
||||||
},
|
},
|
||||||
{ sort: ['sort'] },
|
{ sort: ['sort'] },
|
||||||
|
|
|
||||||
|
|
@ -1489,3 +1489,64 @@ Migrations.add('remove-user-profile-hideCheckedItems', () => {
|
||||||
noValidateMulti,
|
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