Fixed Error in migrate-lists-to-per-swimlane migration.

Thanks to xet7 !

Fixes #5918
This commit is contained in:
Lauri Ojansivu 2025-10-13 20:34:23 +03:00
parent 9bd21e1d1b
commit cc99da5357
9 changed files with 157 additions and 135 deletions

View file

@ -21,6 +21,10 @@ template(name="boardBody")
if notDisplayThisBoard if notDisplayThisBoard
| {{_ 'tableVisibilityMode-allowPrivateOnly'}} | {{_ 'tableVisibilityMode-allowPrivateOnly'}}
else else
// Debug information (remove in production)
if debugBoardState
.debug-info(style="position: fixed; top: 0; left: 0; background: rgba(0,0,0,0.8); color: white; padding: 10px; z-index: 9999; font-size: 12px;")
| Board: {{currentBoard.title}} | View: {{boardView}} | HasSwimlanes: {{hasSwimlanes}} | Swimlanes: {{currentBoard.swimlanes.length}}
.board-wrapper(class=currentBoard.colorClass class="{{#if isMiniScreen}}mobile-view{{/if}}") .board-wrapper(class=currentBoard.colorClass class="{{#if isMiniScreen}}mobile-view{{/if}}")
.board-canvas.js-swimlanes( .board-canvas.js-swimlanes(
class="{{#if hasSwimlanes}}dragscroll{{/if}}" class="{{#if hasSwimlanes}}dragscroll{{/if}}"
@ -39,9 +43,8 @@ template(name="boardBody")
each currentBoard.swimlanes each currentBoard.swimlanes
+swimlane(this) +swimlane(this)
else else
a.js-empty-board-add-swimlane(title="{{_ 'add-swimlane'}}") // Fallback: If no swimlanes exist, show lists instead of empty message
h1.big-message.quiet +listsGroup(currentBoard)
| {{_ 'add-swimlane'}} +
else if isViewLists else if isViewLists
+listsGroup(currentBoard) +listsGroup(currentBoard)
else if isViewCalendar else if isViewCalendar

View file

@ -238,11 +238,16 @@ BlazeComponent.extendComponent({
} }
} }
// Observe for new popups/menus and set focus // Observe for new popups/menus and set focus (but exclude swimlane content)
const popupObserver = new MutationObserver(function(mutations) { const popupObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) { mutations.forEach(function(mutation) {
mutation.addedNodes.forEach(function(node) { mutation.addedNodes.forEach(function(node) {
if (node.nodeType === 1 && (node.classList.contains('popup') || node.classList.contains('modal') || node.classList.contains('menu'))) { if (node.nodeType === 1 &&
(node.classList.contains('popup') || node.classList.contains('modal') || node.classList.contains('menu')) &&
!node.closest('.js-swimlanes') &&
!node.closest('.swimlane') &&
!node.closest('.list') &&
!node.closest('.minicard')) {
setTimeout(function() { focusFirstInteractive(node); }, 10); setTimeout(function() { focusFirstInteractive(node); }, 10);
} }
}); });
@ -601,10 +606,20 @@ BlazeComponent.extendComponent({
hasSwimlanes() { hasSwimlanes() {
const currentBoard = Utils.getCurrentBoard(); const currentBoard = Utils.getCurrentBoard();
if (!currentBoard) return false; if (!currentBoard) {
console.log('hasSwimlanes: No current board');
return false;
}
try {
const swimlanes = currentBoard.swimlanes(); const swimlanes = currentBoard.swimlanes();
return swimlanes.length > 0; const hasSwimlanes = swimlanes && swimlanes.length > 0;
console.log('hasSwimlanes: Board has', swimlanes ? swimlanes.length : 0, 'swimlanes');
return hasSwimlanes;
} catch (error) {
console.error('hasSwimlanes: Error getting swimlanes:', error);
return false;
}
}, },
@ -618,6 +633,12 @@ BlazeComponent.extendComponent({
}, },
debugBoardState() { debugBoardState() {
// Enable debug mode by setting ?debug=1 in URL
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get('debug') === '1';
},
debugBoardStateData() {
const currentBoard = Utils.getCurrentBoard(); const currentBoard = Utils.getCurrentBoard();
const currentBoardId = Session.get('currentBoard'); const currentBoardId = Session.get('currentBoard');
const isBoardReady = this.isBoardReady.get(); const isBoardReady = this.isBoardReady.get();

View file

@ -1,37 +1,6 @@
import { ReactiveCache } from '/imports/reactiveCache'; import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n'; import { TAPi18n } from '/imports/i18n';
// Template helpers for attachmentSettings
Template.attachmentSettings.helpers({
loading() {
const instance = Template.instance();
if (instance && instance.loading) {
return instance.loading.get();
}
return attachmentSettings.loading.get();
},
showStorageSettings() {
const instance = Template.instance();
if (instance && instance.showStorageSettings) {
return instance.showStorageSettings.get();
}
return attachmentSettings.showStorageSettings.get();
},
showMigration() {
const instance = Template.instance();
if (instance && instance.showMigration) {
return instance.showMigration.get();
}
return attachmentSettings.showMigration.get();
},
showMonitoring() {
const instance = Template.instance();
if (instance && instance.showMonitoring) {
return instance.showMonitoring.get();
}
return attachmentSettings.showMonitoring.get();
}
});
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import { Session } from 'meteor/session'; import { Session } from 'meteor/session';
import { Tracker } from 'meteor/tracker'; import { Tracker } from 'meteor/tracker';
@ -102,6 +71,20 @@ BlazeComponent.extendComponent({
this.loadMonitoringData(); this.loadMonitoringData();
}, },
// Template helpers for this component
loading() {
return this.loading.get();
},
showStorageSettings() {
return this.showStorageSettings.get();
},
showMigration() {
return this.showMigration.get();
},
showMonitoring() {
return this.showMonitoring.get();
},
events() { events() {
return [ return [
{ {
@ -497,5 +480,25 @@ BlazeComponent.extendComponent({
} }
}).register('attachmentMonitoring'); }).register('attachmentMonitoring');
// Template helpers for attachmentSettings
Template.attachmentSettings.helpers({
loading() {
const instance = Template.instance();
return instance.loading && instance.loading.get();
},
showStorageSettings() {
const instance = Template.instance();
return instance.showStorageSettings && instance.showStorageSettings.get();
},
showMigration() {
const instance = Template.instance();
return instance.showMigration && instance.showMigration.get();
},
showMonitoring() {
const instance = Template.instance();
return instance.showMonitoring && instance.showMonitoring.get();
},
});
// Export the attachment settings for use in other components // Export the attachment settings for use in other components
export { attachmentSettings }; export { attachmentSettings };

View file

@ -526,3 +526,27 @@ function pollMigrationProgress(instance) {
}); });
}, 1000); }, 1000);
} }
// Template helpers for cronSettings
Template.cronSettings.helpers({
loading() {
const instance = Template.instance();
return instance.loading && instance.loading.get();
},
showMigrations() {
const instance = Template.instance();
return instance.showMigrations && instance.showMigrations.get();
},
showBoardOperations() {
const instance = Template.instance();
return instance.showBoardOperations && instance.showBoardOperations.get();
},
showJobs() {
const instance = Template.instance();
return instance.showJobs && instance.showJobs.get();
},
showAddJob() {
const instance = Template.instance();
return instance.showAddJob && instance.showAddJob.get();
},
});

View file

@ -3,86 +3,6 @@ import { TAPi18n } from '/imports/i18n';
import { ALLOWED_WAIT_SPINNERS } from '/config/const'; import { ALLOWED_WAIT_SPINNERS } from '/config/const';
import LockoutSettings from '/models/lockoutSettings'; import LockoutSettings from '/models/lockoutSettings';
// Template helpers for settingBody
Template.setting.helpers({
generalSetting() {
const instance = Template.instance();
if (instance && instance.generalSetting) {
return instance.generalSetting.get();
}
return false;
},
emailSetting() {
const instance = Template.instance();
if (instance && instance.emailSetting) {
return instance.emailSetting.get();
}
return false;
},
accountSetting() {
const instance = Template.instance();
if (instance && instance.accountSetting) {
return instance.accountSetting.get();
}
return false;
},
tableVisibilityModeSetting() {
const instance = Template.instance();
if (instance && instance.tableVisibilityModeSetting) {
return instance.tableVisibilityModeSetting.get();
}
return false;
},
announcementSetting() {
const instance = Template.instance();
if (instance && instance.announcementSetting) {
return instance.announcementSetting.get();
}
return false;
},
accessibilitySetting() {
const instance = Template.instance();
if (instance && instance.accessibilitySetting) {
return instance.accessibilitySetting.get();
}
return false;
},
layoutSetting() {
const instance = Template.instance();
if (instance && instance.layoutSetting) {
return instance.layoutSetting.get();
}
return false;
},
webhookSetting() {
const instance = Template.instance();
if (instance && instance.webhookSetting) {
return instance.webhookSetting.get();
}
return false;
},
attachmentSettings() {
const instance = Template.instance();
if (instance && instance.attachmentSettings) {
return instance.attachmentSettings.get();
}
return false;
},
cronSettings() {
const instance = Template.instance();
if (instance && instance.cronSettings) {
return instance.cronSettings.get();
}
return false;
},
loading() {
const instance = Template.instance();
if (instance && instance.loading) {
return instance.loading.get();
}
return false;
}
});
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
onCreated() { onCreated() {
@ -110,6 +30,7 @@ BlazeComponent.extendComponent({
Meteor.subscribe('lockoutSettings'); Meteor.subscribe('lockoutSettings');
}, },
setError(error) { setError(error) {
this.error.set(error); this.error.set(error);
}, },
@ -667,3 +588,51 @@ Template.selectSpinnerName.helpers({
return Template.instance().data.spinnerName === match; return Template.instance().data.spinnerName === match;
}, },
}); });
// Template helpers for the setting template
Template.setting.helpers({
generalSetting() {
const instance = Template.instance();
return instance.generalSetting && instance.generalSetting.get();
},
emailSetting() {
const instance = Template.instance();
return instance.emailSetting && instance.emailSetting.get();
},
accountSetting() {
const instance = Template.instance();
return instance.accountSetting && instance.accountSetting.get();
},
tableVisibilityModeSetting() {
const instance = Template.instance();
return instance.tableVisibilityModeSetting && instance.tableVisibilityModeSetting.get();
},
announcementSetting() {
const instance = Template.instance();
return instance.announcementSetting && instance.announcementSetting.get();
},
accessibilitySetting() {
const instance = Template.instance();
return instance.accessibilitySetting && instance.accessibilitySetting.get();
},
layoutSetting() {
const instance = Template.instance();
return instance.layoutSetting && instance.layoutSetting.get();
},
webhookSetting() {
const instance = Template.instance();
return instance.webhookSetting && instance.webhookSetting.get();
},
attachmentSettings() {
const instance = Template.instance();
return instance.attachmentSettings && instance.attachmentSettings.get();
},
cronSettings() {
const instance = Template.instance();
return instance.cronSettings && instance.cronSettings.get();
},
loading() {
const instance = Template.instance();
return instance.loading && instance.loading.get();
},
});

View file

@ -40,7 +40,7 @@ if (Meteor.isServer) {
} }
} }
storagePath = path.join(process.env.WRITABLE_PATH, 'attachments'); storagePath = path.join(process.env.WRITABLE_PATH || process.cwd(), 'attachments');
} }
export const fileStoreStrategyFactory = new FileStoreStrategyFactory(AttachmentStoreStrategyFilesystem, storagePath, AttachmentStoreStrategyGridFs, attachmentBucket); export const fileStoreStrategyFactory = new FileStoreStrategyFactory(AttachmentStoreStrategyFilesystem, storagePath, AttachmentStoreStrategyGridFs, attachmentBucket);

View file

@ -40,7 +40,7 @@ if (Meteor.isServer) {
} }
avatarsBucket = createBucket('avatars'); avatarsBucket = createBucket('avatars');
storagePath = path.join(process.env.WRITABLE_PATH, 'avatars'); storagePath = path.join(process.env.WRITABLE_PATH || process.cwd(), 'avatars');
} }
const fileStoreStrategyFactory = new FileStoreStrategyFactory(FileStoreStrategyFilesystem, storagePath, FileStoreStrategyGridFs, avatarsBucket); const fileStoreStrategyFactory = new FileStoreStrategyFactory(FileStoreStrategyFilesystem, storagePath, FileStoreStrategyGridFs, avatarsBucket);

View file

@ -1148,13 +1148,13 @@ Boards.helpers({
permission: this.permission, permission: this.permission,
members: this.members, members: this.members,
color: this.color, color: this.color,
description: TAPi18n.__('default-subtasks-board', { description: TAPi18n && TAPi18n.i18n ? TAPi18n.__('default-subtasks-board', {
board: this.title, board: this.title,
}), }) : `Default subtasks board for ${this.title}`,
}); });
Swimlanes.insert({ Swimlanes.insert({
title: TAPi18n.__('default'), title: TAPi18n && TAPi18n.i18n ? TAPi18n.__('default') : 'Default',
boardId: this.subtasksDefaultBoardId, boardId: this.subtasksDefaultBoardId,
}); });
Boards.update(this._id, { Boards.update(this._id, {
@ -1181,13 +1181,13 @@ Boards.helpers({
permission: this.permission, permission: this.permission,
members: this.members, members: this.members,
color: this.color, color: this.color,
description: TAPi18n.__('default-dates-board', { description: TAPi18n && TAPi18n.i18n ? TAPi18n.__('default-dates-board', {
board: this.title, board: this.title,
}), }) : `Default dates board for ${this.title}`,
}); });
Swimlanes.insert({ Swimlanes.insert({
title: TAPi18n.__('default'), title: TAPi18n && TAPi18n.i18n ? TAPi18n.__('default') : 'Default',
boardId: this.dateSettingsDefaultBoardId, boardId: this.dateSettingsDefaultBoardId,
}); });
Boards.update(this._id, { Boards.update(this._id, {
@ -1209,7 +1209,7 @@ Boards.helpers({
this.subtasksDefaultListId === undefined this.subtasksDefaultListId === undefined
) { ) {
this.subtasksDefaultListId = Lists.insert({ this.subtasksDefaultListId = Lists.insert({
title: TAPi18n.__('queue'), title: TAPi18n && TAPi18n.i18n ? TAPi18n.__('queue') : 'Queue',
boardId: this._id, boardId: this._id,
swimlaneId: this.getDefaultSwimline()._id, // Set default swimlane for subtasks list swimlaneId: this.getDefaultSwimline()._id, // Set default swimlane for subtasks list
}); });
@ -1228,7 +1228,7 @@ Boards.helpers({
this.dateSettingsDefaultListId === undefined this.dateSettingsDefaultListId === undefined
) { ) {
this.dateSettingsDefaultListId = Lists.insert({ this.dateSettingsDefaultListId = Lists.insert({
title: TAPi18n.__('queue'), title: TAPi18n && TAPi18n.i18n ? TAPi18n.__('queue') : 'Queue',
boardId: this._id, boardId: this._id,
swimlaneId: this.getDefaultSwimline()._id, // Set default swimlane for date settings list swimlaneId: this.getDefaultSwimline()._id, // Set default swimlane for date settings list
}); });
@ -1244,8 +1244,10 @@ Boards.helpers({
getDefaultSwimline() { getDefaultSwimline() {
let result = ReactiveCache.getSwimlane({ boardId: this._id }); let result = ReactiveCache.getSwimlane({ boardId: this._id });
if (result === undefined) { if (result === undefined) {
// Use fallback title if i18n is not available (e.g., during migration)
const title = TAPi18n && TAPi18n.i18n ? TAPi18n.__('default') : 'Default';
Swimlanes.insert({ Swimlanes.insert({
title: TAPi18n.__('default'), title: title,
boardId: this._id, boardId: this._id,
}); });
result = ReactiveCache.getSwimlane({ boardId: this._id }); result = ReactiveCache.getSwimlane({ boardId: this._id });
@ -2212,7 +2214,7 @@ if (Meteor.isServer) {
migrationVersion: 1, // Latest version - no migration needed migrationVersion: 1, // Latest version - no migration needed
}); });
const swimlaneId = Swimlanes.insert({ const swimlaneId = Swimlanes.insert({
title: TAPi18n.__('default'), title: TAPi18n && TAPi18n.i18n ? TAPi18n.__('default') : 'Default',
boardId: id, boardId: id,
}); });
JsonRoutes.sendResult(res, { JsonRoutes.sendResult(res, {

View file

@ -2138,7 +2138,7 @@ if (Meteor.isServer) {
const future3 = new Future(); const future3 = new Future();
Boards.insert( Boards.insert(
{ {
title: TAPi18n.__('templates'), title: TAPi18n && TAPi18n.i18n ? TAPi18n.__('templates') : 'Templates',
permission: 'private', permission: 'private',
type: 'template-container', type: 'template-container',
}, },
@ -2154,7 +2154,7 @@ if (Meteor.isServer) {
// Insert the card templates swimlane // Insert the card templates swimlane
Swimlanes.insert( Swimlanes.insert(
{ {
title: TAPi18n.__('card-templates-swimlane'), title: TAPi18n && TAPi18n.i18n ? TAPi18n.__('card-templates-swimlane') : 'Card Templates',
boardId, boardId,
sort: 1, sort: 1,
type: 'template-container', type: 'template-container',
@ -2174,7 +2174,7 @@ if (Meteor.isServer) {
// Insert the list templates swimlane // Insert the list templates swimlane
Swimlanes.insert( Swimlanes.insert(
{ {
title: TAPi18n.__('list-templates-swimlane'), title: TAPi18n && TAPi18n.i18n ? TAPi18n.__('list-templates-swimlane') : 'List Templates',
boardId, boardId,
sort: 2, sort: 2,
type: 'template-container', type: 'template-container',
@ -2194,7 +2194,7 @@ if (Meteor.isServer) {
// Insert the board templates swimlane // Insert the board templates swimlane
Swimlanes.insert( Swimlanes.insert(
{ {
title: TAPi18n.__('board-templates-swimlane'), title: TAPi18n && TAPi18n.i18n ? TAPi18n.__('board-templates-swimlane') : 'Board Templates',
boardId, boardId,
sort: 3, sort: 3,
type: 'template-container', type: 'template-container',