From cc99da5357fb1fc00e3b5aece20c57917f88301b Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Mon, 13 Oct 2025 20:34:23 +0300 Subject: [PATCH] Fixed Error in migrate-lists-to-per-swimlane migration. Thanks to xet7 ! Fixes #5918 --- client/components/boards/boardBody.jade | 9 +- client/components/boards/boardBody.js | 31 ++++- .../components/settings/attachmentSettings.js | 65 ++++----- client/components/settings/cronSettings.js | 24 ++++ client/components/settings/settingBody.js | 129 +++++++----------- models/attachments.js | 2 +- models/avatars.js | 2 +- models/boards.js | 22 +-- models/users.js | 8 +- 9 files changed, 157 insertions(+), 135 deletions(-) diff --git a/client/components/boards/boardBody.jade b/client/components/boards/boardBody.jade index 7e5530ffe..d2118112b 100644 --- a/client/components/boards/boardBody.jade +++ b/client/components/boards/boardBody.jade @@ -21,6 +21,10 @@ template(name="boardBody") if notDisplayThisBoard | {{_ 'tableVisibilityMode-allowPrivateOnly'}} 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-canvas.js-swimlanes( class="{{#if hasSwimlanes}}dragscroll{{/if}}" @@ -39,9 +43,8 @@ template(name="boardBody") each currentBoard.swimlanes +swimlane(this) else - a.js-empty-board-add-swimlane(title="{{_ 'add-swimlane'}}") - h1.big-message.quiet - | {{_ 'add-swimlane'}} + + // Fallback: If no swimlanes exist, show lists instead of empty message + +listsGroup(currentBoard) else if isViewLists +listsGroup(currentBoard) else if isViewCalendar diff --git a/client/components/boards/boardBody.js b/client/components/boards/boardBody.js index 5ebb23bec..cf99e9150 100644 --- a/client/components/boards/boardBody.js +++ b/client/components/boards/boardBody.js @@ -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) { mutations.forEach(function(mutation) { 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); } }); @@ -601,10 +606,20 @@ BlazeComponent.extendComponent({ hasSwimlanes() { const currentBoard = Utils.getCurrentBoard(); - if (!currentBoard) return false; + if (!currentBoard) { + console.log('hasSwimlanes: No current board'); + return false; + } - const swimlanes = currentBoard.swimlanes(); - return swimlanes.length > 0; + try { + const swimlanes = currentBoard.swimlanes(); + 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() { + // 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 currentBoardId = Session.get('currentBoard'); const isBoardReady = this.isBoardReady.get(); diff --git a/client/components/settings/attachmentSettings.js b/client/components/settings/attachmentSettings.js index 7f253b2c8..9e84bad5e 100644 --- a/client/components/settings/attachmentSettings.js +++ b/client/components/settings/attachmentSettings.js @@ -1,37 +1,6 @@ import { ReactiveCache } from '/imports/reactiveCache'; 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 { Session } from 'meteor/session'; import { Tracker } from 'meteor/tracker'; @@ -102,6 +71,20 @@ BlazeComponent.extendComponent({ 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() { return [ { @@ -497,5 +480,25 @@ BlazeComponent.extendComponent({ } }).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 { attachmentSettings }; diff --git a/client/components/settings/cronSettings.js b/client/components/settings/cronSettings.js index bfa45cedf..7fb1af2ce 100644 --- a/client/components/settings/cronSettings.js +++ b/client/components/settings/cronSettings.js @@ -526,3 +526,27 @@ function pollMigrationProgress(instance) { }); }, 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(); + }, +}); diff --git a/client/components/settings/settingBody.js b/client/components/settings/settingBody.js index 91beb5944..38d234564 100644 --- a/client/components/settings/settingBody.js +++ b/client/components/settings/settingBody.js @@ -3,86 +3,6 @@ import { TAPi18n } from '/imports/i18n'; import { ALLOWED_WAIT_SPINNERS } from '/config/const'; 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({ onCreated() { @@ -110,6 +30,7 @@ BlazeComponent.extendComponent({ Meteor.subscribe('lockoutSettings'); }, + setError(error) { this.error.set(error); }, @@ -667,3 +588,51 @@ Template.selectSpinnerName.helpers({ 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(); + }, +}); diff --git a/models/attachments.js b/models/attachments.js index 31b7c0c02..ebbd1bed7 100644 --- a/models/attachments.js +++ b/models/attachments.js @@ -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); diff --git a/models/avatars.js b/models/avatars.js index 760ef75b4..065728322 100644 --- a/models/avatars.js +++ b/models/avatars.js @@ -40,7 +40,7 @@ if (Meteor.isServer) { } 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); diff --git a/models/boards.js b/models/boards.js index 2ce9d018a..dcb09a3cc 100644 --- a/models/boards.js +++ b/models/boards.js @@ -1148,13 +1148,13 @@ Boards.helpers({ permission: this.permission, members: this.members, color: this.color, - description: TAPi18n.__('default-subtasks-board', { + description: TAPi18n && TAPi18n.i18n ? TAPi18n.__('default-subtasks-board', { board: this.title, - }), + }) : `Default subtasks board for ${this.title}`, }); Swimlanes.insert({ - title: TAPi18n.__('default'), + title: TAPi18n && TAPi18n.i18n ? TAPi18n.__('default') : 'Default', boardId: this.subtasksDefaultBoardId, }); Boards.update(this._id, { @@ -1181,13 +1181,13 @@ Boards.helpers({ permission: this.permission, members: this.members, color: this.color, - description: TAPi18n.__('default-dates-board', { + description: TAPi18n && TAPi18n.i18n ? TAPi18n.__('default-dates-board', { board: this.title, - }), + }) : `Default dates board for ${this.title}`, }); Swimlanes.insert({ - title: TAPi18n.__('default'), + title: TAPi18n && TAPi18n.i18n ? TAPi18n.__('default') : 'Default', boardId: this.dateSettingsDefaultBoardId, }); Boards.update(this._id, { @@ -1209,7 +1209,7 @@ Boards.helpers({ this.subtasksDefaultListId === undefined ) { this.subtasksDefaultListId = Lists.insert({ - title: TAPi18n.__('queue'), + title: TAPi18n && TAPi18n.i18n ? TAPi18n.__('queue') : 'Queue', boardId: this._id, swimlaneId: this.getDefaultSwimline()._id, // Set default swimlane for subtasks list }); @@ -1228,7 +1228,7 @@ Boards.helpers({ this.dateSettingsDefaultListId === undefined ) { this.dateSettingsDefaultListId = Lists.insert({ - title: TAPi18n.__('queue'), + title: TAPi18n && TAPi18n.i18n ? TAPi18n.__('queue') : 'Queue', boardId: this._id, swimlaneId: this.getDefaultSwimline()._id, // Set default swimlane for date settings list }); @@ -1244,8 +1244,10 @@ Boards.helpers({ getDefaultSwimline() { let result = ReactiveCache.getSwimlane({ boardId: this._id }); 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({ - title: TAPi18n.__('default'), + title: title, boardId: this._id, }); result = ReactiveCache.getSwimlane({ boardId: this._id }); @@ -2212,7 +2214,7 @@ if (Meteor.isServer) { migrationVersion: 1, // Latest version - no migration needed }); const swimlaneId = Swimlanes.insert({ - title: TAPi18n.__('default'), + title: TAPi18n && TAPi18n.i18n ? TAPi18n.__('default') : 'Default', boardId: id, }); JsonRoutes.sendResult(res, { diff --git a/models/users.js b/models/users.js index 69f14a6b3..329a42fab 100644 --- a/models/users.js +++ b/models/users.js @@ -2138,7 +2138,7 @@ if (Meteor.isServer) { const future3 = new Future(); Boards.insert( { - title: TAPi18n.__('templates'), + title: TAPi18n && TAPi18n.i18n ? TAPi18n.__('templates') : 'Templates', permission: 'private', type: 'template-container', }, @@ -2154,7 +2154,7 @@ if (Meteor.isServer) { // Insert the card templates swimlane Swimlanes.insert( { - title: TAPi18n.__('card-templates-swimlane'), + title: TAPi18n && TAPi18n.i18n ? TAPi18n.__('card-templates-swimlane') : 'Card Templates', boardId, sort: 1, type: 'template-container', @@ -2174,7 +2174,7 @@ if (Meteor.isServer) { // Insert the list templates swimlane Swimlanes.insert( { - title: TAPi18n.__('list-templates-swimlane'), + title: TAPi18n && TAPi18n.i18n ? TAPi18n.__('list-templates-swimlane') : 'List Templates', boardId, sort: 2, type: 'template-container', @@ -2194,7 +2194,7 @@ if (Meteor.isServer) { // Insert the board templates swimlane Swimlanes.insert( { - title: TAPi18n.__('board-templates-swimlane'), + title: TAPi18n && TAPi18n.i18n ? TAPi18n.__('board-templates-swimlane') : 'Board Templates', boardId, sort: 3, type: 'template-container',