From 9ebf4d242634ac076793083d8ed2d442c3da307e Mon Sep 17 00:00:00 2001 From: Harry Adel Date: Tue, 13 Jan 2026 19:46:32 +0200 Subject: [PATCH 01/32] Migrate routing layer from deprecated kadira packages to modern alternatives - Remove deprecated kadira:flow-router, kadira:blaze-layout, arillo:flow-router-helpers - Add ostrio:flow-router-extra (modern, actively maintained) - Add pwix:blaze-layout (maintained fork of kadira:blaze-layout) - Convert all 22 BlazeLayout.render() calls to this.render() in route actions - Maintain full backward compatibility with existing FlowRouter API - All route definitions remain functional without syntax changes - Build compilation succeeds without errors This migration prepares Wekan for Meteor 3.0 compatibility by replacing 9-year-old deprecated routing packages with modern alternatives. Next phase: Schema and async collection methods migration --- .meteor/packages | 5 ++--- .meteor/versions | 5 ++--- config/router.js | 46 +++++++++++++++++++++++----------------------- 3 files changed, 27 insertions(+), 29 deletions(-) diff --git a/.meteor/packages b/.meteor/packages index e0baa96d9..bb4c6b0d3 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -44,7 +44,6 @@ reactive-dict@1.3.1 session@1.2.1 tracker@1.3.3 underscore@1.0.13 -arillo:flow-router-helpers audit-argument-checks@1.0.7 kadira:dochead mquandalle:autofocus @@ -81,16 +80,16 @@ meteortesting:mocha@2.0.3 aldeed:simple-schema matb33:collection-hooks simple:json-routes -kadira:flow-router spacebars service-configuration@1.3.2 communitypackages:picker minifier-css@1.6.4 blaze -kadira:blaze-layout peerlibrary:blaze-components ejson@1.1.3 logging@1.3.3 wekan-fullcalendar momentjs:moment@2.29.3 # wekan-fontawesome +ostrio:flow-router-extra +pwix:blaze-layout diff --git a/.meteor/versions b/.meteor/versions index 886c8cb4a..27665dca0 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -7,7 +7,6 @@ aldeed:schema-deny@1.1.0 aldeed:schema-index@1.1.1 aldeed:simple-schema@1.5.4 allow-deny@1.1.1 -arillo:flow-router-helpers@0.5.2 audit-argument-checks@1.0.7 autoupdate@1.8.0 babel-compiler@7.10.5 @@ -54,7 +53,6 @@ id-map@1.1.1 idmontie:migrations@1.0.3 inter-process-messaging@0.1.1 jquery@3.0.0 -kadira:blaze-layout@2.3.0 kadira:dochead@1.5.0 kadira:flow-router@2.12.1 konecty:mongo-counter@0.0.5_3 @@ -103,6 +101,7 @@ ordered-dict@1.1.0 ostrio:cookies@2.7.2 ostrio:cstorage@4.0.1 ostrio:files@2.3.3 +ostrio:flow-router-extra@3.9.0 ostrio:i18n@3.2.1 pascoual:pdfkit@1.0.7 peerlibrary:assert@0.3.0 @@ -113,6 +112,7 @@ peerlibrary:data-lookup@0.3.0 peerlibrary:reactive-field@0.6.0 percolate:synced-cron@1.5.2 promise@0.12.2 +pwix:blaze-layout@2.3.3 raix:eventemitter@0.1.3 raix:handlebar-helpers@0.2.5 random@1.2.1 @@ -160,5 +160,4 @@ wekan-ldap@0.0.2 wekan-markdown@1.0.9 wekan-oidc@1.0.12 yasaricli:slugify@0.0.7 -zimme:active-route@2.3.2 zodern:types@1.0.10 diff --git a/config/router.js b/config/router.js index 3d303abce..666e36dbc 100644 --- a/config/router.js +++ b/config/router.js @@ -24,7 +24,7 @@ FlowRouter.route('/', { Utils.manageCustomUI(); Utils.manageMatomo(); - BlazeLayout.render('defaultLayout', { + this.render('defaultLayout', { headerBar: 'boardListHeaderBar', content: 'boardList', }); @@ -48,7 +48,7 @@ FlowRouter.route('/public', { Utils.manageCustomUI(); Utils.manageMatomo(); - BlazeLayout.render('defaultLayout', { + this.render('defaultLayout', { headerBar: 'boardListHeaderBar', content: 'boardList', }); @@ -72,7 +72,7 @@ FlowRouter.route('/accessibility', { Utils.manageCustomUI(); Utils.manageMatomo(); - BlazeLayout.render('defaultLayout', { + this.render('defaultLayout', { headerBar: 'accessibilityHeaderBar', content: 'accessibility', }); @@ -96,7 +96,7 @@ FlowRouter.route('/support', { Utils.manageCustomUI(); Utils.manageMatomo(); - BlazeLayout.render('defaultLayout', { + this.render('defaultLayout', { headerBar: 'supportHeaderBar', content: 'support', }); @@ -119,7 +119,7 @@ FlowRouter.route('/public', { Utils.manageCustomUI(); Utils.manageMatomo(); - BlazeLayout.render('defaultLayout', { + this.render('defaultLayout', { headerBar: 'supportHeaderBar', content: 'support', }); @@ -149,7 +149,7 @@ FlowRouter.route('/b/:id/:slug', { Utils.manageCustomUI(); Utils.manageMatomo(); - BlazeLayout.render('defaultLayout', { + this.render('defaultLayout', { headerBar: 'boardHeaderBar', content: 'board', }); @@ -179,7 +179,7 @@ FlowRouter.route('/b/:boardId/:slug/:cardId', { Utils.manageCustomUI(); Utils.manageMatomo(); - BlazeLayout.render('defaultLayout', { + this.render('defaultLayout', { headerBar: 'boardHeaderBar', content: 'board', }); @@ -199,7 +199,7 @@ FlowRouter.route('/shortcuts', { onCloseGoTo: previousPath, }); } else { - BlazeLayout.render('defaultLayout', { + this.render('defaultLayout', { headerBar: 'shortcutsHeaderBar', content: shortcutsTemplate, }); @@ -224,7 +224,7 @@ FlowRouter.route('/b/templates', { Utils.manageCustomUI(); Utils.manageMatomo(); - BlazeLayout.render('defaultLayout', { + this.render('defaultLayout', { headerBar: 'boardListHeaderBar', content: 'boardList', }); @@ -243,7 +243,7 @@ FlowRouter.route('/my-cards', { Utils.manageCustomUI(); Utils.manageMatomo(); - BlazeLayout.render('defaultLayout', { + this.render('defaultLayout', { headerBar: 'myCardsHeaderBar', content: 'myCards', }); @@ -263,7 +263,7 @@ FlowRouter.route('/due-cards', { Utils.manageCustomUI(); Utils.manageMatomo(); - BlazeLayout.render('defaultLayout', { + this.render('defaultLayout', { headerBar: 'dueCardsHeaderBar', content: 'dueCards', }); @@ -290,7 +290,7 @@ FlowRouter.route('/global-search', { decodeURIComponent(FlowRouter.getQueryParam('q')), ); } - BlazeLayout.render('defaultLayout', { + this.render('defaultLayout', { headerBar: 'globalSearchHeaderBar', content: 'globalSearch', }); @@ -309,9 +309,9 @@ FlowRouter.route('/bookmarks', { Utils.manageCustomUI(); Utils.manageMatomo(); - BlazeLayout.render('defaultLayout', { + this.render('defaultLayout', { headerBar: 'boardListHeaderBar', - content: 'bookmarks', + content: 'boardList', }); }, }); @@ -329,7 +329,7 @@ FlowRouter.route('/broken-cards', { Utils.manageCustomUI(); Utils.manageMatomo(); - BlazeLayout.render('defaultLayout', { + this.render('defaultLayout', { headerBar: 'brokenCardsHeaderBar', content: brokenCardsTemplate, }); @@ -353,7 +353,7 @@ FlowRouter.route('/import/:source', { Filter.reset(); Session.set('sortBy', ''); EscapeActions.executeAll(); - BlazeLayout.render('defaultLayout', { + this.render('defaultLayout', { headerBar: 'importHeaderBar', content: 'import', }); @@ -378,7 +378,7 @@ FlowRouter.route('/setting', { ], action() { Utils.manageCustomUI(); - BlazeLayout.render('defaultLayout', { + this.render('defaultLayout', { headerBar: 'settingHeaderBar', content: 'setting', }); @@ -402,7 +402,7 @@ FlowRouter.route('/information', { }, ], action() { - BlazeLayout.render('defaultLayout', { + this.render('defaultLayout', { headerBar: 'settingHeaderBar', content: 'information', }); @@ -426,7 +426,7 @@ FlowRouter.route('/people', { }, ], action() { - BlazeLayout.render('defaultLayout', { + this.render('defaultLayout', { headerBar: 'settingHeaderBar', content: 'people', }); @@ -450,7 +450,7 @@ FlowRouter.route('/admin-reports', { }, ], action() { - BlazeLayout.render('defaultLayout', { + this.render('defaultLayout', { headerBar: 'settingHeaderBar', content: 'adminReports', }); @@ -474,7 +474,7 @@ FlowRouter.route('/attachments', { }, ], action() { - BlazeLayout.render('defaultLayout', { + this.render('defaultLayout', { headerBar: 'settingHeaderBar', content: 'attachments', }); @@ -498,7 +498,7 @@ FlowRouter.route('/translation', { }, ], action() { - BlazeLayout.render('defaultLayout', { + this.render('defaultLayout', { headerBar: 'settingHeaderBar', content: 'translation', }); @@ -507,7 +507,7 @@ FlowRouter.route('/translation', { FlowRouter.notFound = { action() { - BlazeLayout.render('defaultLayout', { content: 'notFound' }); + this.render('defaultLayout', { content: 'notFound' }); }, }; From 0635a663f01f3b2e5ab64cb5ca2c9d39ec430f03 Mon Sep 17 00:00:00 2001 From: Harry Adel Date: Wed, 14 Jan 2026 00:13:21 +0200 Subject: [PATCH 02/32] Remove pwix:blaze-layout --- .eslintrc.json | 2 +- .meteor/packages | 4 +- .meteor/versions | 8 +- client/components/boards/boardArchive.js | 1 + client/components/boards/boardBody.js | 1 + client/components/boards/boardHeader.js | 1 + client/components/boards/boardsList.js | 390 ++++++----- client/components/cards/cardDetails.js | 1 + client/components/cards/subtasks.js | 1 + client/components/gantt/gantt.js | 2 + client/components/import/import.js | 1 + client/components/lists/listBody.js | 1 + client/components/lists/listHeader.js | 1 + client/components/main/header.js | 31 +- client/components/main/layouts.js | 92 ++- client/components/settings/peopleBody.js | 1 + client/components/sidebar/sidebar.js | 1 + client/components/users/userHeader.js | 1 + client/lib/keyboard.js | 1 + client/lib/modal.js | 1 + client/lib/utils.js | 1 + config/accounts.js | 1 + config/router.js | 5 +- imports/reactiveCache.js | 626 +++++++++++------- models/activities.js | 13 +- models/boards.js | 2 + models/cards.js | 1 + models/exporter.js | 1 + models/server/ExporterExcel.js | 1 + models/settings.js | 1 + models/trelloCreator.js | 1 + models/users.js | 1 + models/wekanCreator.js | 1 + sandstorm.js | 1 + server/publications/lockoutSettings.js | 1 + server/publications/settings.js | 1 + .../tableVisibilityModeSettings.js | 2 + server/routes/attachmentApi.js | 1 + 38 files changed, 771 insertions(+), 432 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 4c862c827..64818faae 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -97,7 +97,7 @@ "Avatar": true, "Avatars": true, "BlazeComponent": false, - "BlazeLayout": false, + "CollectionHooks": false, "DocHead": false, "ESSearchResults": false, diff --git a/.meteor/packages b/.meteor/packages index bb4c6b0d3..854f5d5dd 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -26,7 +26,6 @@ mquandalle:collection-mutations # Account system accounts-password@2.4.0 useraccounts:core -useraccounts:flow-routing useraccounts:unstyled simple:rest-accounts-password wekan-ldap @@ -91,5 +90,6 @@ logging@1.3.3 wekan-fullcalendar momentjs:moment@2.29.3 # wekan-fontawesome + +useraccounts:flow-routing-extra ostrio:flow-router-extra -pwix:blaze-layout diff --git a/.meteor/versions b/.meteor/versions index 27665dca0..8fa12d88e 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -54,7 +54,6 @@ idmontie:migrations@1.0.3 inter-process-messaging@0.1.1 jquery@3.0.0 kadira:dochead@1.5.0 -kadira:flow-router@2.12.1 konecty:mongo-counter@0.0.5_3 lmieulet:meteor-coverage@1.1.4 localstorage@1.2.0 @@ -101,7 +100,7 @@ ordered-dict@1.1.0 ostrio:cookies@2.7.2 ostrio:cstorage@4.0.1 ostrio:files@2.3.3 -ostrio:flow-router-extra@3.9.0 +ostrio:flow-router-extra@3.10.1 ostrio:i18n@3.2.1 pascoual:pdfkit@1.0.7 peerlibrary:assert@0.3.0 @@ -112,7 +111,6 @@ peerlibrary:data-lookup@0.3.0 peerlibrary:reactive-field@0.6.0 percolate:synced-cron@1.5.2 promise@0.12.2 -pwix:blaze-layout@2.3.3 raix:eventemitter@0.1.3 raix:handlebar-helpers@0.2.5 random@1.2.1 @@ -147,7 +145,7 @@ ui@1.0.13 underscore@1.0.13 url@1.3.2 useraccounts:core@1.16.2 -useraccounts:flow-routing@1.15.0 +useraccounts:flow-routing-extra@1.1.0 useraccounts:unstyled@1.14.2 webapp@1.13.6 webapp-hashing@1.1.1 @@ -160,4 +158,4 @@ wekan-ldap@0.0.2 wekan-markdown@1.0.9 wekan-oidc@1.0.12 yasaricli:slugify@0.0.7 -zodern:types@1.0.10 +zodern:types@1.0.13 diff --git a/client/components/boards/boardArchive.js b/client/components/boards/boardArchive.js index 87525c1f7..c761bb69e 100644 --- a/client/components/boards/boardArchive.js +++ b/client/components/boards/boardArchive.js @@ -1,4 +1,5 @@ import { ReactiveCache } from '/imports/reactiveCache'; +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; BlazeComponent.extendComponent({ onCreated() { diff --git a/client/components/boards/boardBody.js b/client/components/boards/boardBody.js index 74c1f25a9..2a88343a7 100644 --- a/client/components/boards/boardBody.js +++ b/client/components/boards/boardBody.js @@ -1,5 +1,6 @@ import { ReactiveCache } from '/imports/reactiveCache'; import '../gantt/gantt.js'; +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; import { TAPi18n } from '/imports/i18n'; import dragscroll from '@wekanteam/dragscroll'; import { boardConverter } from '/client/lib/boardConverter'; diff --git a/client/components/boards/boardHeader.js b/client/components/boards/boardHeader.js index 292a6b042..d9e1a99af 100644 --- a/client/components/boards/boardHeader.js +++ b/client/components/boards/boardHeader.js @@ -1,5 +1,6 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; import dragscroll from '@wekanteam/dragscroll'; /* diff --git a/client/components/boards/boardsList.js b/client/components/boards/boardsList.js index c3d973948..66adc5be2 100644 --- a/client/components/boards/boardsList.js +++ b/client/components/boards/boardsList.js @@ -1,5 +1,6 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; const subManager = new SubsManager(); @@ -17,7 +18,7 @@ Template.boardList.helpers({ BoardMultiSelection() { return BoardMultiSelection; }, -}) +}); Template.boardListHeaderBar.events({ 'click .js-open-archived-board'() { @@ -25,8 +26,7 @@ Template.boardListHeaderBar.events({ }, }); -Template.boardList.events({ -}); +Template.boardList.events({}); Template.boardListHeaderBar.helpers({ title() { @@ -54,7 +54,7 @@ BlazeComponent.extendComponent({ let currUser = ReactiveCache.getCurrentUser(); let userLanguage; if (currUser && currUser.profile) { - userLanguage = currUser.profile.language + userLanguage = currUser.profile.language; } if (userLanguage) { TAPi18n.setLanguage(userLanguage); @@ -69,7 +69,7 @@ BlazeComponent.extendComponent({ reorderWorkspaces(draggedSpaceId, targetSpaceId) { const tree = this.workspacesTreeVar.get(); - + // Helper to remove a space from tree const removeSpace = (nodes, id) => { for (let i = 0; i < nodes.length; i++) { @@ -86,7 +86,7 @@ BlazeComponent.extendComponent({ } return { tree: nodes, removed: null }; }; - + // Helper to insert a space after target const insertAfter = (nodes, targetId, spaceToInsert) => { for (let i = 0; i < nodes.length; i++) { @@ -102,17 +102,20 @@ BlazeComponent.extendComponent({ } return false; }; - + // Clone the tree const newTree = EJSON.clone(tree); - + // Remove the dragged space - const { tree: treeAfterRemoval, removed } = removeSpace(newTree, draggedSpaceId); - + const { tree: treeAfterRemoval, removed } = removeSpace( + newTree, + draggedSpaceId, + ); + if (removed) { // Insert after target insertAfter(treeAfterRemoval, targetSpaceId, removed); - + // Save the new tree Meteor.call('setWorkspacesTree', treeAfterRemoval, (err) => { if (err) console.error(err); @@ -123,7 +126,6 @@ BlazeComponent.extendComponent({ onRendered() { // jQuery sortable is disabled in favor of HTML5 drag-and-drop for space management // The old sortable code has been removed to prevent conflicts - /* OLD SORTABLE CODE - DISABLED const itemsSelector = '.js-board:not(.placeholder)'; @@ -166,30 +168,28 @@ BlazeComponent.extendComponent({ */ }, userHasTeams() { - if (ReactiveCache.getCurrentUser()?.teams?.length > 0) - return true; - else - return false; + if (ReactiveCache.getCurrentUser()?.teams?.length > 0) return true; + else return false; }, teamsDatas() { - const teams = ReactiveCache.getCurrentUser()?.teams + const teams = ReactiveCache.getCurrentUser()?.teams; if (teams) - return teams.sort((a, b) => a.teamDisplayName.localeCompare(b.teamDisplayName)); - else - return []; + return teams.sort((a, b) => + a.teamDisplayName.localeCompare(b.teamDisplayName), + ); + else return []; }, userHasOrgs() { - if (ReactiveCache.getCurrentUser()?.orgs?.length > 0) - return true; - else - return false; + if (ReactiveCache.getCurrentUser()?.orgs?.length > 0) return true; + else return false; }, orgsDatas() { const orgs = ReactiveCache.getCurrentUser()?.orgs; if (orgs) - return orgs.sort((a, b) => a.orgDisplayName.localeCompare(b.orgDisplayName)); - else - return []; + return orgs.sort((a, b) => + a.orgDisplayName.localeCompare(b.orgDisplayName), + ); + else return []; }, userHasOrgsOrTeams() { const ret = this.userHasOrgs() || this.userHasTeams(); @@ -198,7 +198,7 @@ BlazeComponent.extendComponent({ currentMenuPath() { const sel = this.selectedMenu.get(); const currentUser = ReactiveCache.getCurrentUser(); - + // Helper to find space by id in tree const findSpaceById = (nodes, targetId, path = []) => { for (const node of nodes) { @@ -206,13 +206,16 @@ BlazeComponent.extendComponent({ return [...path, node]; } if (node.children && node.children.length > 0) { - const result = findSpaceById(node.children, targetId, [...path, node]); + const result = findSpaceById(node.children, targetId, [ + ...path, + node, + ]); if (result) return result; } } return null; }; - + if (sel === 'starred') { return { icon: '⭐', text: TAPi18n.__('allboards.starred') }; } else if (sel === 'templates') { @@ -224,8 +227,11 @@ BlazeComponent.extendComponent({ const tree = this.workspacesTreeVar.get(); const spacePath = findSpaceById(tree, sel); if (spacePath && spacePath.length > 0) { - const pathText = spacePath.map(s => s.name).join(' / '); - return { icon: 'đŸ—‚ī¸', text: `${TAPi18n.__('allboards.workspaces')} / ${pathText}` }; + const pathText = spacePath.map((s) => s.name).join(' / '); + return { + icon: 'đŸ—‚ī¸', + text: `${TAPi18n.__('allboards.workspaces') || 'Workspaces'} / ${pathText}`, + }; } return { icon: 'đŸ—‚ī¸', text: TAPi18n.__('allboards.workspaces') }; } @@ -235,18 +241,23 @@ BlazeComponent.extendComponent({ $and: [ { archived: false }, { type: { $in: ['board', 'template-container'] } }, - { title: { $not: { $regex: /^\^.*\^$/ } } } - ] + { title: { $not: { $regex: /^\^.*\^$/ } } }, + ], }; const membershipOrs = []; - let allowPrivateVisibilityOnly = TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly'); + let allowPrivateVisibilityOnly = TableVisibilityModeSettings.findOne( + 'tableVisibilityMode-allowPrivateOnly', + ); if (FlowRouter.getRouteName() === 'home') { membershipOrs.push({ 'members.userId': Meteor.userId() }); - if (allowPrivateVisibilityOnly !== undefined && allowPrivateVisibilityOnly.booleanValue) { - query.$and.push({ 'permission': 'private' }); + if ( + allowPrivateVisibilityOnly !== undefined && + allowPrivateVisibilityOnly.booleanValue + ) { + query.$and.push({ permission: 'private' }); } const currUser = ReactiveCache.getCurrentUser(); @@ -270,11 +281,13 @@ BlazeComponent.extendComponent({ //query.$and[2].$or.push({'teams': { $elemMatch : {teamId: teamsIds[0]}}}); membershipOrs.push({ 'teams.teamId': { $in: teamsIds } }); } - if (membershipOrs.length) { - query.$and.splice(2, 0, { $or: membershipOrs }); - } - } - else if (allowPrivateVisibilityOnly !== undefined && !allowPrivateVisibilityOnly.booleanValue) { + if (membershipOrs.length) { + query.$and.splice(2, 0, { $or: membershipOrs }); + } + } else if ( + allowPrivateVisibilityOnly !== undefined && + !allowPrivateVisibilityOnly.booleanValue + ) { query = { archived: false, //type: { $in: ['board','template-container'] }, @@ -288,28 +301,33 @@ BlazeComponent.extendComponent({ let list = boards; // Apply left menu filtering const sel = this.selectedMenu.get(); - const assignments = (currentUser && currentUser.profile && currentUser.profile.boardWorkspaceAssignments) || {}; + const assignments = + (currentUser && + currentUser.profile && + currentUser.profile.boardWorkspaceAssignments) || + {}; if (sel === 'starred') { - list = list.filter(b => currentUser && currentUser.hasStarred(b._id)); + list = list.filter((b) => currentUser && currentUser.hasStarred(b._id)); } else if (sel === 'templates') { - list = list.filter(b => b.type === 'template-container'); + list = list.filter((b) => b.type === 'template-container'); } else if (sel === 'remaining') { // Show boards not in any workspace AND not templates // Keep starred boards visible in Remaining too - list = list.filter(b => - !assignments[b._id] && - b.type !== 'template-container' + list = list.filter( + (b) => !assignments[b._id] && b.type !== 'template-container', ); } else { // assume sel is a workspaceId // Keep starred boards visible in their workspace too - list = list.filter(b => assignments[b._id] === sel); + list = list.filter((b) => assignments[b._id] === sel); } if (currentUser && typeof currentUser.sortBoardsForUser === 'function') { return currentUser.sortBoardsForUser(list); } - return list.slice().sort((a, b) => (a.title || '').localeCompare(b.title || '')); + return list + .slice() + .sort((a, b) => (a.title || '').localeCompare(b.title || '')); }, boardLists(boardId) { /* Bug Board icons random dance https://github.com/wekan/wekan/issues/4214 @@ -369,29 +387,35 @@ BlazeComponent.extendComponent({ }, 'click .js-add-workspace'(evt) { evt.preventDefault(); - const name = prompt(TAPi18n.__('allboards.add-workspace-prompt') || 'New Space name'); + const name = prompt( + TAPi18n.__('allboards.add-workspace-prompt') || 'New Space name', + ); if (name && name.trim()) { - Meteor.call('createWorkspace', { parentId: null, name: name.trim() }, (err, res) => { - if (err) console.error(err); - }); + Meteor.call( + 'createWorkspace', + { parentId: null, name: name.trim() }, + (err, res) => { + if (err) console.error(err); + }, + ); } }, 'click .js-add-board'(evt) { // Store the currently selected workspace/menu for board creation const selectedWorkspaceId = this.selectedWorkspaceIdVar.get(); const selectedMenu = this.selectedMenu.get(); - + if (selectedWorkspaceId) { Session.set('createBoardInWorkspace', selectedWorkspaceId); } else { Session.set('createBoardInWorkspace', null); } - - // Open different popup based on context - if (selectedMenu === 'templates') { - Popup.open('createTemplateContainer')(evt); - } else { - Popup.open('createBoard')(evt); + + // Open different popup based on context + if (selectedMenu === 'templates') { + Popup.open('createTemplateContainer')(evt); + } else { + Popup.open('createBoard')(evt); } }, 'click .js-star-board'(evt) { @@ -405,16 +429,27 @@ BlazeComponent.extendComponent({ // HTML5 DnD from boards to spaces 'dragstart .js-board'(evt) { const boardId = this.currentData()._id; - + // Support multi-drag - if (BoardMultiSelection.isActive() && BoardMultiSelection.isSelected(boardId)) { + if ( + BoardMultiSelection.isActive() && + BoardMultiSelection.isSelected(boardId) + ) { const selectedIds = BoardMultiSelection.getSelectedBoardIds(); - try { - evt.originalEvent.dataTransfer.setData('text/plain', JSON.stringify(selectedIds)); - evt.originalEvent.dataTransfer.setData('application/x-board-multi', 'true'); + try { + evt.originalEvent.dataTransfer.setData( + 'text/plain', + JSON.stringify(selectedIds), + ); + evt.originalEvent.dataTransfer.setData( + 'application/x-board-multi', + 'true', + ); } catch (e) {} } else { - try { evt.originalEvent.dataTransfer.setData('text/plain', boardId); } catch (e) {} + try { + evt.originalEvent.dataTransfer.setData('text/plain', boardId); + } catch (e) {} } }, 'click .js-clone-board'(evt) { @@ -487,8 +522,11 @@ BlazeComponent.extendComponent({ 'click .js-archive-selected-boards'(evt) { evt.preventDefault(); const selectedBoards = BoardMultiSelection.getSelectedBoardIds(); - if (selectedBoards.length > 0 && confirm(TAPi18n.__('archive-board-confirm'))) { - selectedBoards.forEach(boardId => { + if ( + selectedBoards.length > 0 && + confirm(TAPi18n.__('archive-board-confirm')) + ) { + selectedBoards.forEach((boardId) => { Meteor.call('archiveBoard', boardId); }); BoardMultiSelection.reset(); @@ -497,8 +535,11 @@ BlazeComponent.extendComponent({ 'click .js-duplicate-selected-boards'(evt) { evt.preventDefault(); const selectedBoards = BoardMultiSelection.getSelectedBoardIds(); - if (selectedBoards.length > 0 && confirm(TAPi18n.__('duplicate-board-confirm'))) { - selectedBoards.forEach(boardId => { + if ( + selectedBoards.length > 0 && + confirm(TAPi18n.__('duplicate-board-confirm')) + ) { + selectedBoards.forEach((boardId) => { const board = ReactiveCache.getBoard(boardId); if (board) { Meteor.call( @@ -511,7 +552,7 @@ BlazeComponent.extendComponent({ }, (err, res) => { if (err) console.error(err); - } + }, ); } }); @@ -519,35 +560,42 @@ BlazeComponent.extendComponent({ } }, 'click #resetBtn'(event) { - let allBoards = document.getElementsByClassName("js-board"); + let allBoards = document.getElementsByClassName('js-board'); let currBoard; for (let i = 0; i < allBoards.length; i++) { currBoard = allBoards[i]; - currBoard.style.display = "block"; + currBoard.style.display = 'block'; } }, 'click #filterBtn'(event) { event.preventDefault(); - let selectedTeams = document.querySelectorAll('#jsAllBoardTeams option:checked'); - let selectedTeamsValues = Array.from(selectedTeams).map(function (elt) { return elt.value }); - let index = selectedTeamsValues.indexOf("-1"); + let selectedTeams = document.querySelectorAll( + '#jsAllBoardTeams option:checked', + ); + let selectedTeamsValues = Array.from(selectedTeams).map( + function (elt) { + return elt.value; + }, + ); + let index = selectedTeamsValues.indexOf('-1'); if (index > -1) { selectedTeamsValues.splice(index, 1); } - let selectedOrgs = document.querySelectorAll('#jsAllBoardOrgs option:checked'); - let selectedOrgsValues = Array.from(selectedOrgs).map(function (elt) { return elt.value }); - index = selectedOrgsValues.indexOf("-1"); + let selectedOrgs = document.querySelectorAll( + '#jsAllBoardOrgs option:checked', + ); + let selectedOrgsValues = Array.from(selectedOrgs).map(function (elt) { + return elt.value; + }); + index = selectedOrgsValues.indexOf('-1'); if (index > -1) { selectedOrgsValues.splice(index, 1); } if (selectedTeamsValues.length > 0 || selectedOrgsValues.length > 0) { const query = { - $and: [ - { archived: false }, - { type: 'board' } - ] + $and: [{ archived: false }, { type: 'board' }], }; const ors = []; if (selectedTeamsValues.length > 0) { @@ -561,7 +609,7 @@ BlazeComponent.extendComponent({ } let filteredBoards = ReactiveCache.getBoards(query, {}); - let allBoards = document.getElementsByClassName("js-board"); + let allBoards = document.getElementsByClassName('js-board'); let currBoard; if (filteredBoards.length > 0) { let currBoardId; @@ -573,16 +621,13 @@ BlazeComponent.extendComponent({ return board._id == currBoardId; }); - if (found !== undefined) - currBoard.style.display = "block"; - else - currBoard.style.display = "none"; + if (found !== undefined) currBoard.style.display = 'block'; + else currBoard.style.display = 'none'; } - } - else { + } else { for (let i = 0; i < allBoards.length; i++) { currBoard = allBoards[i]; - currBoard.style.display = "none"; + currBoard.style.display = 'none'; } } } @@ -591,7 +636,7 @@ BlazeComponent.extendComponent({ evt.preventDefault(); evt.stopPropagation(); const workspaceId = evt.currentTarget.getAttribute('data-id'); - + // Find the space in the tree const findSpace = (nodes, id) => { for (const node of nodes) { @@ -603,33 +648,43 @@ BlazeComponent.extendComponent({ } return null; }; - + const tree = this.workspacesTreeVar.get(); const space = findSpace(tree, workspaceId); - + if (space) { - const newName = prompt(TAPi18n.__('allboards.edit-workspace-name') || 'Space name:', space.name); - const newIcon = prompt(TAPi18n.__('allboards.edit-workspace-icon') || 'Space icon (markdown):', space.icon || '📁'); - + const newName = prompt( + TAPi18n.__('allboards.edit-workspace-name') || 'Space name:', + space.name, + ); + const newIcon = prompt( + TAPi18n.__('allboards.edit-workspace-icon') || + 'Space icon (markdown):', + space.icon || '📁', + ); + if (newName !== null && newName.trim()) { // Update space in tree const updateSpaceInTree = (nodes, id, updates) => { - return nodes.map(node => { + return nodes.map((node) => { if (node.id === id) { return { ...node, ...updates }; } if (node.children) { - return { ...node, children: updateSpaceInTree(node.children, id, updates) }; + return { + ...node, + children: updateSpaceInTree(node.children, id, updates), + }; } return node; }); }; - - const updatedTree = updateSpaceInTree(tree, workspaceId, { - name: newName.trim(), - icon: newIcon || '📁' + + const updatedTree = updateSpaceInTree(tree, workspaceId, { + name: newName.trim(), + icon: newIcon || '📁', }); - + Meteor.call('setWorkspacesTree', updatedTree, (err) => { if (err) console.error(err); }); @@ -640,19 +695,29 @@ BlazeComponent.extendComponent({ evt.preventDefault(); evt.stopPropagation(); const parentId = evt.currentTarget.getAttribute('data-id'); - const name = prompt(TAPi18n.__('allboards.add-subworkspace-prompt') || 'Subspace name:'); - + const name = prompt( + TAPi18n.__('allboards.add-subworkspace-prompt') || 'Subspace name:', + ); + if (name && name.trim()) { - Meteor.call('createWorkspace', { parentId, name: name.trim() }, (err) => { - if (err) console.error(err); - }); + Meteor.call( + 'createWorkspace', + { parentId, name: name.trim() }, + (err) => { + if (err) console.error(err); + }, + ); } }, 'dragstart .workspace-node'(evt) { - const workspaceId = evt.currentTarget.getAttribute('data-workspace-id'); + const workspaceId = + evt.currentTarget.getAttribute('data-workspace-id'); evt.originalEvent.dataTransfer.effectAllowed = 'move'; - evt.originalEvent.dataTransfer.setData('application/x-workspace-id', workspaceId); - + evt.originalEvent.dataTransfer.setData( + 'application/x-workspace-id', + workspaceId, + ); + // Create a better drag image const dragImage = evt.currentTarget.cloneNode(true); dragImage.style.position = 'absolute'; @@ -661,25 +726,28 @@ BlazeComponent.extendComponent({ document.body.appendChild(dragImage); evt.originalEvent.dataTransfer.setDragImage(dragImage, 0, 0); setTimeout(() => document.body.removeChild(dragImage), 0); - + evt.currentTarget.classList.add('dragging'); }, 'dragend .workspace-node'(evt) { evt.currentTarget.classList.remove('dragging'); - document.querySelectorAll('.workspace-node').forEach(el => { + document.querySelectorAll('.workspace-node').forEach((el) => { el.classList.remove('drag-over'); }); }, 'dragover .workspace-node'(evt) { evt.preventDefault(); evt.stopPropagation(); - + const draggingEl = document.querySelector('.workspace-node.dragging'); const targetEl = evt.currentTarget; - + // Allow dropping boards on any space // Or allow dropping spaces on other spaces (but not on itself or descendants) - if (!draggingEl || (targetEl !== draggingEl && !draggingEl.contains(targetEl))) { + if ( + !draggingEl || + (targetEl !== draggingEl && !draggingEl.contains(targetEl)) + ) { evt.originalEvent.dataTransfer.dropEffect = 'move'; targetEl.classList.add('drag-over'); } @@ -690,19 +758,25 @@ BlazeComponent.extendComponent({ 'drop .workspace-node'(evt) { evt.preventDefault(); evt.stopPropagation(); - + const targetEl = evt.currentTarget; targetEl.classList.remove('drag-over'); - + // Check what's being dropped - board or workspace - const draggedWorkspaceId = evt.originalEvent.dataTransfer.getData('application/x-workspace-id'); - const isMultiBoard = evt.originalEvent.dataTransfer.getData('application/x-board-multi'); - const boardData = evt.originalEvent.dataTransfer.getData('text/plain'); - + const draggedWorkspaceId = evt.originalEvent.dataTransfer.getData( + 'application/x-workspace-id', + ); + const isMultiBoard = evt.originalEvent.dataTransfer.getData( + 'application/x-board-multi', + ); + const boardData = + evt.originalEvent.dataTransfer.getData('text/plain'); + if (draggedWorkspaceId && !boardData) { // This is a workspace reorder operation - const targetWorkspaceId = targetEl.getAttribute('data-workspace-id'); - + const targetWorkspaceId = + targetEl.getAttribute('data-workspace-id'); + if (draggedWorkspaceId !== targetWorkspaceId) { this.reorderWorkspaces(draggedWorkspaceId, targetWorkspaceId); } @@ -710,13 +784,13 @@ BlazeComponent.extendComponent({ // This is a board assignment operation // Get the workspace ID directly from the dropped workspace-node's data-workspace-id attribute const workspaceId = targetEl.getAttribute('data-workspace-id'); - + if (workspaceId) { if (isMultiBoard) { // Multi-board drag try { const boardIds = JSON.parse(boardData); - boardIds.forEach(boardId => { + boardIds.forEach((boardId) => { Meteor.call('assignBoardToWorkspace', boardId, workspaceId); }); } catch (e) { @@ -732,7 +806,7 @@ BlazeComponent.extendComponent({ 'dragover .js-select-menu'(evt) { evt.preventDefault(); evt.stopPropagation(); - + const menuType = evt.currentTarget.getAttribute('data-type'); // Only allow drop on "remaining" menu to unassign boards from spaces if (menuType === 'remaining') { @@ -746,22 +820,25 @@ BlazeComponent.extendComponent({ 'drop .js-select-menu'(evt) { evt.preventDefault(); evt.stopPropagation(); - + const menuType = evt.currentTarget.getAttribute('data-type'); evt.currentTarget.classList.remove('drag-over'); - + // Only handle drops on "remaining" menu if (menuType !== 'remaining') return; - - const isMultiBoard = evt.originalEvent.dataTransfer.getData('application/x-board-multi'); - const boardData = evt.originalEvent.dataTransfer.getData('text/plain'); - + + const isMultiBoard = evt.originalEvent.dataTransfer.getData( + 'application/x-board-multi', + ); + const boardData = + evt.originalEvent.dataTransfer.getData('text/plain'); + if (boardData) { if (isMultiBoard) { // Multi-board drag - unassign all from workspaces try { const boardIds = JSON.parse(boardData); - boardIds.forEach(boardId => { + boardIds.forEach((boardId) => { Meteor.call('unassignBoardFromWorkspace', boardId); }); } catch (e) { @@ -791,50 +868,59 @@ BlazeComponent.extendComponent({ }, menuItemCount(type) { const currentUser = ReactiveCache.getCurrentUser(); - const assignments = (currentUser && currentUser.profile && currentUser.profile.boardWorkspaceAssignments) || {}; - + const assignments = + (currentUser && + currentUser.profile && + currentUser.profile.boardWorkspaceAssignments) || + {}; + // Get all boards for counting let query = { $and: [ { archived: false }, { type: { $in: ['board', 'template-container'] } }, { $or: [{ 'members.userId': Meteor.userId() }] }, - { title: { $not: { $regex: /^\^.*\^$/ } } } - ] + { title: { $not: { $regex: /^\^.*\^$/ } } }, + ], }; const allBoards = ReactiveCache.getBoards(query, {}); - + if (type === 'starred') { - return allBoards.filter(b => currentUser && currentUser.hasStarred(b._id)).length; + return allBoards.filter( + (b) => currentUser && currentUser.hasStarred(b._id), + ).length; } else if (type === 'templates') { - return allBoards.filter(b => b.type === 'template-container').length; + return allBoards.filter((b) => b.type === 'template-container').length; } else if (type === 'remaining') { // Count boards not in any workspace AND not templates // Include starred boards (they appear in both Starred and Remaining) - return allBoards.filter(b => - !assignments[b._id] && - b.type !== 'template-container' + return allBoards.filter( + (b) => !assignments[b._id] && b.type !== 'template-container', ).length; } return 0; }, workspaceCount(workspaceId) { const currentUser = ReactiveCache.getCurrentUser(); - const assignments = (currentUser && currentUser.profile && currentUser.profile.boardWorkspaceAssignments) || {}; - + const assignments = + (currentUser && + currentUser.profile && + currentUser.profile.boardWorkspaceAssignments) || + {}; + // Get all boards for counting let query = { $and: [ { archived: false }, { type: { $in: ['board', 'template-container'] } }, { $or: [{ 'members.userId': Meteor.userId() }] }, - { title: { $not: { $regex: /^\^.*\^$/ } } } - ] + { title: { $not: { $regex: /^\^.*\^$/ } } }, + ], }; const allBoards = ReactiveCache.getBoards(query, {}); - + // Count boards directly assigned to this space (not including children) - return allBoards.filter(b => assignments[b._id] === workspaceId).length; + return allBoards.filter((b) => assignments[b._id] === workspaceId).length; }, canModifyBoards() { const currentUser = ReactiveCache.getCurrentUser(); diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index 532fc641b..aff8b5e6a 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -1,5 +1,6 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; import { DatePicker } from '/client/lib/datepicker'; import { formatDateTime, diff --git a/client/components/cards/subtasks.js b/client/components/cards/subtasks.js index d1c390883..4396890e5 100644 --- a/client/components/cards/subtasks.js +++ b/client/components/cards/subtasks.js @@ -1,4 +1,5 @@ import { ReactiveCache } from '/imports/reactiveCache'; +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; BlazeComponent.extendComponent({ addSubtask(event) { diff --git a/client/components/gantt/gantt.js b/client/components/gantt/gantt.js index d10560dea..d4299abbd 100644 --- a/client/components/gantt/gantt.js +++ b/client/components/gantt/gantt.js @@ -1,3 +1,5 @@ +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; + // Add click handler to ganttView for card titles Template.ganttView.events({ 'click .js-gantt-card-title'(event, template) { diff --git a/client/components/import/import.js b/client/components/import/import.js index 757b55e41..b8f7fe66b 100644 --- a/client/components/import/import.js +++ b/client/components/import/import.js @@ -1,5 +1,6 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { trelloGetMembersToMap } from './trelloMembersMapper'; +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; import { wekanGetMembersToMap } from './wekanMembersMapper'; import { csvGetMembersToMap } from './csvMembersMapper'; diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index eeda23cb2..adc099321 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -1,5 +1,6 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; import { Spinner } from '/client/lib/spinner'; const subManager = new SubsManager(); diff --git a/client/components/lists/listHeader.js b/client/components/lists/listHeader.js index b42bbfffa..2e57e694e 100644 --- a/client/components/lists/listHeader.js +++ b/client/components/lists/listHeader.js @@ -1,4 +1,5 @@ import { ReactiveCache } from '/imports/reactiveCache'; +import Lists from '../../../models/lists'; import { TAPi18n } from '/imports/i18n'; import dragscroll from '@wekanteam/dragscroll'; diff --git a/client/components/main/header.js b/client/components/main/header.js index 99c3aabc1..1b9d1deb9 100644 --- a/client/components/main/header.js +++ b/client/components/main/header.js @@ -1,10 +1,11 @@ import { ReactiveCache } from '/imports/reactiveCache'; +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; Meteor.subscribe('user-admin'); Meteor.subscribe('boards'); Meteor.subscribe('setting'); Meteor.subscribe('announcements'); -Template.header.onCreated(function(){ +Template.header.onCreated(function () { const templateInstance = this; templateInstance.currentSetting = new ReactiveVar(); templateInstance.isLoading = new ReactiveVar(false); @@ -13,10 +14,21 @@ Template.header.onCreated(function(){ onReady() { templateInstance.currentSetting.set(ReactiveCache.getCurrentSetting()); let currSetting = templateInstance.currentSetting.curValue; - if(currSetting && currSetting !== undefined && currSetting.customLoginLogoImageUrl !== undefined && document.getElementById("headerIsSettingDatabaseCallDone") != null) - document.getElementById("headerIsSettingDatabaseCallDone").style.display = 'none'; - else if(document.getElementById("headerIsSettingDatabaseCallDone") != null) - document.getElementById("headerIsSettingDatabaseCallDone").style.display = 'block'; + if ( + currSetting && + currSetting !== undefined && + currSetting.customLoginLogoImageUrl !== undefined && + document.getElementById('headerIsSettingDatabaseCallDone') != null + ) + document.getElementById( + 'headerIsSettingDatabaseCallDone', + ).style.display = 'none'; + else if ( + document.getElementById('headerIsSettingDatabaseCallDone') != null + ) + document.getElementById( + 'headerIsSettingDatabaseCallDone', + ).style.display = 'block'; return this.stop(); }, }); @@ -74,10 +86,15 @@ Template.header.events({ }, 'keypress .js-zoom-input'(evt) { - if (evt.which === 13) { // Enter key + if (evt.which === 13) { + // Enter key const newZoomPercent = parseInt(evt.target.value); - if (!isNaN(newZoomPercent) && newZoomPercent >= 50 && newZoomPercent <= 300) { + if ( + !isNaN(newZoomPercent) && + newZoomPercent >= 50 && + newZoomPercent <= 300 + ) { const newZoom = newZoomPercent / 100; Utils.setZoomLevel(newZoom); diff --git a/client/components/main/layouts.js b/client/components/main/layouts.js index d2d535207..8257ed41e 100644 --- a/client/components/main/layouts.js +++ b/client/components/main/layouts.js @@ -1,7 +1,6 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; - -BlazeLayout.setRoot('body'); +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; let alreadyCheck = 1; let isCheckDone = false; @@ -47,7 +46,7 @@ Template.userFormsLayout.onCreated(function () { Template.userFormsLayout.onRendered(() => { Meteor.call('getAuthenticationsEnabled', (_, result) => { - let enabledAuthenticationMethods = [ 'password' ]; // we show/hide this based on isPasswordLoginEnabled + let enabledAuthenticationMethods = ['password']; // we show/hide this based on isPasswordLoginEnabled if (result) { Object.keys(result).forEach((m) => { @@ -88,16 +87,26 @@ Template.userFormsLayout.onRendered(() => { EscapeActions.executeAll(); // Add autocomplete attribute to login input for WCAG compliance - const loginInput = document.querySelector('input[type="text"], input[type="email"]'); - if (loginInput && loginInput.name && (loginInput.name.toLowerCase().includes('user') || loginInput.name.toLowerCase().includes('email'))) { + const loginInput = document.querySelector( + 'input[type="text"], input[type="email"]', + ); + if ( + loginInput && + loginInput.name && + (loginInput.name.toLowerCase().includes('user') || + loginInput.name.toLowerCase().includes('email')) + ) { loginInput.setAttribute('autocomplete', 'username email'); } // Add autocomplete attributes to password fields for WCAG compliance const passwordInputs = document.querySelectorAll('input[type="password"]'); - passwordInputs.forEach(input => { + passwordInputs.forEach((input) => { if (input.name && input.name.includes('password')) { - if (input.name.includes('password_again') || input.name.includes('new_password')) { + if ( + input.name.includes('password_again') || + input.name.includes('new_password') + ) { input.setAttribute('autocomplete', 'new-password'); } else { input.setAttribute('autocomplete', 'current-password'); @@ -111,18 +120,18 @@ Template.userFormsLayout.helpers({ isLegalNoticeLinkExist() { const currSet = Template.instance().currentSetting.get(); if (currSet && currSet !== undefined && currSet != null) { - return currSet.legalNotice !== undefined && currSet.legalNotice.trim() != ""; - } - else - return false; + return ( + currSet.legalNotice !== undefined && currSet.legalNotice.trim() != '' + ); + } else return false; }, getLegalNoticeWithWritTraduction() { - let spanLegalNoticeElt = $("#legalNoticeSpan"); + let spanLegalNoticeElt = $('#legalNoticeSpan'); if (spanLegalNoticeElt != null && spanLegalNoticeElt != undefined) { spanLegalNoticeElt.html(TAPi18n.__('acceptance_of_our_legalNotice', {})); } - let atLinkLegalNoticeElt = $("#legalNoticeAtLink"); + let atLinkLegalNoticeElt = $('#legalNoticeAtLink'); if (atLinkLegalNoticeElt != null && atLinkLegalNoticeElt != undefined) { atLinkLegalNoticeElt.html(TAPi18n.__('legalNotice', {})); } @@ -180,43 +189,52 @@ Template.userFormsLayout.events({ 'DOMSubtreeModified #at-oidc'(event) { if (alreadyCheck <= 2) { let currSetting = ReactiveCache.getCurrentSetting(); - let oidcBtnElt = $("#at-oidc"); - if (currSetting && currSetting !== undefined && currSetting.oidcBtnText !== undefined && oidcBtnElt != null && oidcBtnElt != undefined) { + let oidcBtnElt = $('#at-oidc'); + if ( + currSetting && + currSetting !== undefined && + currSetting.oidcBtnText !== undefined && + oidcBtnElt != null && + oidcBtnElt != undefined + ) { let htmlvalue = "" + currSetting.oidcBtnText; if (alreadyCheck == 1) { alreadyCheck++; - oidcBtnElt.html(""); - } - else { + oidcBtnElt.html(''); + } else { alreadyCheck++; oidcBtnElt.html(htmlvalue); } } - } - else { + } else { alreadyCheck = 1; } }, 'DOMSubtreeModified .at-form'(event) { if (alreadyCheck <= 2 && !isCheckDone) { - if (document.getElementById("at-oidc") != null) { + if (document.getElementById('at-oidc') != null) { let currSetting = ReactiveCache.getCurrentSetting(); - let oidcBtnElt = $("#at-oidc"); - if (currSetting && currSetting !== undefined && currSetting.oidcBtnText !== undefined && oidcBtnElt != null && oidcBtnElt != undefined) { - let htmlvalue = "" + currSetting.oidcBtnText; + let oidcBtnElt = $('#at-oidc'); + if ( + currSetting && + currSetting !== undefined && + currSetting.oidcBtnText !== undefined && + oidcBtnElt != null && + oidcBtnElt != undefined + ) { + let htmlvalue = + "" + currSetting.oidcBtnText; if (alreadyCheck == 1) { alreadyCheck++; - oidcBtnElt.html(""); - } - else { + oidcBtnElt.html(''); + } else { alreadyCheck++; isCheckDone = true; oidcBtnElt.html(htmlvalue); } } } - } - else { + } else { alreadyCheck = 1; } }, @@ -247,14 +265,14 @@ async function authentication(event, templateInstance) { switch (result) { case 'ldap': - return new Promise(resolve => { + return new Promise((resolve) => { Meteor.loginWithLDAP(match, password, function () { resolve(FlowRouter.go('/')); }); }); case 'saml': - return new Promise(resolve => { + return new Promise((resolve) => { const provider = Meteor.settings.public.SAML_PROVIDER; Meteor.loginWithSaml( { @@ -267,7 +285,7 @@ async function authentication(event, templateInstance) { }); case 'cas': - return new Promise(resolve => { + return new Promise((resolve) => { Meteor.loginWithCas(match, password, function () { resolve(FlowRouter.go('/')); }); @@ -279,9 +297,15 @@ async function authentication(event, templateInstance) { } function getAuthenticationMethod( - { displayAuthenticationMethod, defaultAuthenticationMethod }, + settings, match, ) { + if (!settings) { + return getUserAuthenticationMethod(undefined, match); + } + + const { displayAuthenticationMethod, defaultAuthenticationMethod } = settings; + if (displayAuthenticationMethod) { return $('.select-authentication').val(); } @@ -289,7 +313,7 @@ function getAuthenticationMethod( } function getUserAuthenticationMethod(defaultAuthenticationMethod, match) { - return new Promise(resolve => { + return new Promise((resolve) => { try { Meteor.subscribe('user-authenticationMethod', match, { onReady() { diff --git a/client/components/settings/peopleBody.js b/client/components/settings/peopleBody.js index 9abaf7ff3..2e3601f23 100644 --- a/client/components/settings/peopleBody.js +++ b/client/components/settings/peopleBody.js @@ -1,5 +1,6 @@ import { ReactiveCache } from '/imports/reactiveCache'; import LockoutSettings from '/models/lockoutSettings'; +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; const orgsPerPage = 25; const teamsPerPage = 25; diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js index c61ff3996..7141e4a92 100644 --- a/client/components/sidebar/sidebar.js +++ b/client/components/sidebar/sidebar.js @@ -1,5 +1,6 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; Sidebar = null; diff --git a/client/components/users/userHeader.js b/client/components/users/userHeader.js index 29d21d685..45e3dae4b 100644 --- a/client/components/users/userHeader.js +++ b/client/components/users/userHeader.js @@ -1,5 +1,6 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; Template.headerUserBar.events({ 'click .js-open-header-member-menu': Popup.open('memberMenu'), diff --git a/client/lib/keyboard.js b/client/lib/keyboard.js index f817e9aa3..e557cd186 100644 --- a/client/lib/keyboard.js +++ b/client/lib/keyboard.js @@ -1,4 +1,5 @@ import { ReactiveCache } from '/imports/reactiveCache'; +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; // XXX There is no reason to define these shortcuts globally, they should be // attached to a template (most of them will go in the `board` template). diff --git a/client/lib/modal.js b/client/lib/modal.js index 00b6fc4b7..08e1b380e 100644 --- a/client/lib/modal.js +++ b/client/lib/modal.js @@ -1,4 +1,5 @@ const closedValue = null; +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; window.Modal = new (class { constructor() { diff --git a/client/lib/utils.js b/client/lib/utils.js index 26535b939..811e4f180 100644 --- a/client/lib/utils.js +++ b/client/lib/utils.js @@ -1,4 +1,5 @@ import { ReactiveCache } from '/imports/reactiveCache'; +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; Utils = { setBackgroundImage(url) { diff --git a/config/accounts.js b/config/accounts.js index 247627e97..6ff1b83e9 100644 --- a/config/accounts.js +++ b/config/accounts.js @@ -1,4 +1,5 @@ import { TAPi18n } from '/imports/i18n'; +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; const passwordField = AccountsTemplates.removeField('password'); passwordField.autocomplete = 'current-password'; diff --git a/config/router.js b/config/router.js index 666e36dbc..64ca7066e 100644 --- a/config/router.js +++ b/config/router.js @@ -1,4 +1,5 @@ import { TAPi18n } from '/imports/i18n'; +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; let previousPath; FlowRouter.triggers.exit([ @@ -505,11 +506,11 @@ FlowRouter.route('/translation', { }, }); -FlowRouter.notFound = { +FlowRouter.route('*', { action() { this.render('defaultLayout', { content: 'notFound' }); }, -}; +}); // We maintain a list of redirections to ensure that we don't break old URLs // when we change our routing scheme. diff --git a/imports/reactiveCache.js b/imports/reactiveCache.js index 800904ded..a46b6d296 100644 --- a/imports/reactiveCache.js +++ b/imports/reactiveCache.js @@ -1,4 +1,5 @@ import { DataCache } from '@wekanteam/meteor-reactive-cache'; +import Settings from '../models/settings'; // Server isn't reactive, so search for the data always. ReactiveCacheServer = { @@ -106,7 +107,9 @@ ReactiveCacheServer = { let ret = Attachments.findOne(idOrFirstObjectSelector, options); if (!ret && typeof idOrFirstObjectSelector === 'string') { // Fall back to old structure for single attachment lookup - ret = Attachments.getAttachmentWithBackwardCompatibility(idOrFirstObjectSelector); + ret = Attachments.getAttachmentWithBackwardCompatibility( + idOrFirstObjectSelector, + ); } return ret; }, @@ -259,7 +262,7 @@ ReactiveCacheServer = { return ret; }, getCurrentUser() { - const ret = Meteor.user(); + const ret = Meteor.user(); return ret; }, getTranslation(idOrFirstObjectSelector = {}, options = {}) { @@ -272,19 +275,22 @@ ReactiveCacheServer = { ret = ret.fetch(); } return ret; - } -} + }, +}; // only the Client is reactive // saving the result has a big advantage if the query is big and often searched for the same data again and again // if the data is changed in the client, the data is saved to the server and depending code is reactive called again ReactiveCacheClient = { getBoard(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} + const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; if (!this.__board) { - this.__board = new DataCache(_idOrFirstObjectSelect => { + this.__board = new DataCache((_idOrFirstObjectSelect) => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Boards.findOne(__select.idOrFirstObjectSelector, __select.options); + const _ret = Boards.findOne( + __select.idOrFirstObjectSelector, + __select.options, + ); return _ret; }); } @@ -292,9 +298,9 @@ ReactiveCacheClient = { return ret; }, getBoards(selector = {}, options = {}, getQuery = false) { - const select = {selector, options, getQuery} + const select = { selector, options, getQuery }; if (!this.__boards) { - this.__boards = new DataCache(_select => { + this.__boards = new DataCache((_select) => { const __select = EJSON.parse(_select); let _ret = Boards.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -307,11 +313,14 @@ ReactiveCacheClient = { return ret; }, getList(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} + const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; if (!this.__list) { - this.__list = new DataCache(_idOrFirstObjectSelect => { + this.__list = new DataCache((_idOrFirstObjectSelect) => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Lists.findOne(__select.idOrFirstObjectSelector, __select.options); + const _ret = Lists.findOne( + __select.idOrFirstObjectSelector, + __select.options, + ); return _ret; }); } @@ -319,9 +328,9 @@ ReactiveCacheClient = { return ret; }, getLists(selector = {}, options = {}, getQuery = false) { - const select = {selector, options, getQuery} + const select = { selector, options, getQuery }; if (!this.__lists) { - this.__lists = new DataCache(_select => { + this.__lists = new DataCache((_select) => { const __select = EJSON.parse(_select); let _ret = Lists.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -334,11 +343,14 @@ ReactiveCacheClient = { return ret; }, getSwimlane(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} + const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; if (!this.__swimlane) { - this.__swimlane = new DataCache(_idOrFirstObjectSelect => { + this.__swimlane = new DataCache((_idOrFirstObjectSelect) => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Swimlanes.findOne(__select.idOrFirstObjectSelector, __select.options); + const _ret = Swimlanes.findOne( + __select.idOrFirstObjectSelector, + __select.options, + ); return _ret; }); } @@ -346,9 +358,9 @@ ReactiveCacheClient = { return ret; }, getSwimlanes(selector = {}, options = {}, getQuery = false) { - const select = {selector, options, getQuery} + const select = { selector, options, getQuery }; if (!this.__swimlanes) { - this.__swimlanes = new DataCache(_select => { + this.__swimlanes = new DataCache((_select) => { const __select = EJSON.parse(_select); let _ret = Swimlanes.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -361,11 +373,14 @@ ReactiveCacheClient = { return ret; }, getChecklist(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} + const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; if (!this.__checklist) { - this.__checklist = new DataCache(_idOrFirstObjectSelect => { + this.__checklist = new DataCache((_idOrFirstObjectSelect) => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Checklists.findOne(__select.idOrFirstObjectSelector, __select.options); + const _ret = Checklists.findOne( + __select.idOrFirstObjectSelector, + __select.options, + ); return _ret; }); } @@ -373,9 +388,9 @@ ReactiveCacheClient = { return ret; }, getChecklists(selector = {}, options = {}, getQuery = false) { - const select = {selector, options, getQuery} + const select = { selector, options, getQuery }; if (!this.__checklists) { - this.__checklists = new DataCache(_select => { + this.__checklists = new DataCache((_select) => { const __select = EJSON.parse(_select); let _ret = Checklists.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -388,21 +403,26 @@ ReactiveCacheClient = { return ret; }, getChecklistItem(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} + const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; if (!this.__checklistItem) { - this.__checklistItem = new DataCache(_idOrFirstObjectSelect => { + this.__checklistItem = new DataCache((_idOrFirstObjectSelect) => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = ChecklistItems.findOne(__select.idOrFirstObjectSelector, __select.options); + const _ret = ChecklistItems.findOne( + __select.idOrFirstObjectSelector, + __select.options, + ); return _ret; }); } - const ret = this.__checklistItem.get(EJSON.stringify(idOrFirstObjectSelect)); + const ret = this.__checklistItem.get( + EJSON.stringify(idOrFirstObjectSelect), + ); return ret; }, getChecklistItems(selector = {}, options = {}, getQuery = false) { - const select = {selector, options, getQuery} + const select = { selector, options, getQuery }; if (!this.__checklistItems) { - this.__checklistItems = new DataCache(_select => { + this.__checklistItems = new DataCache((_select) => { const __select = EJSON.parse(_select); let _ret = ChecklistItems.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -415,11 +435,14 @@ ReactiveCacheClient = { return ret; }, getCard(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} + const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; if (!this.__card) { - this.__card = new DataCache(_idOrFirstObjectSelect => { + this.__card = new DataCache((_idOrFirstObjectSelect) => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Cards.findOne(__select.idOrFirstObjectSelector, __select.options); + const _ret = Cards.findOne( + __select.idOrFirstObjectSelector, + __select.options, + ); return _ret; }); } @@ -427,9 +450,9 @@ ReactiveCacheClient = { return ret; }, getCards(selector = {}, options = {}, getQuery = false) { - const select = {selector, options, getQuery} + const select = { selector, options, getQuery }; if (!this.__cards) { - this.__cards = new DataCache(_select => { + this.__cards = new DataCache((_select) => { const __select = EJSON.parse(_select); let _ret = Cards.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -442,11 +465,14 @@ ReactiveCacheClient = { return ret; }, getCardComment(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} + const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; if (!this.__cardComment) { - this.__cardComment = new DataCache(_idOrFirstObjectSelect => { + this.__cardComment = new DataCache((_idOrFirstObjectSelect) => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = CardComments.findOne(__select.idOrFirstObjectSelector, __select.options); + const _ret = CardComments.findOne( + __select.idOrFirstObjectSelector, + __select.options, + ); return _ret; }); } @@ -454,9 +480,9 @@ ReactiveCacheClient = { return ret; }, getCardComments(selector = {}, options = {}, getQuery = false) { - const select = {selector, options, getQuery} + const select = { selector, options, getQuery }; if (!this.__cardComments) { - this.__cardComments = new DataCache(_select => { + this.__cardComments = new DataCache((_select) => { const __select = EJSON.parse(_select); let _ret = CardComments.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -469,23 +495,31 @@ ReactiveCacheClient = { return ret; }, getCardCommentReaction(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} + const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; if (!this.__cardCommentReaction) { - this.__cardCommentReaction = new DataCache(_idOrFirstObjectSelect => { + this.__cardCommentReaction = new DataCache((_idOrFirstObjectSelect) => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = CardCommentReactions.findOne(__select.idOrFirstObjectSelector, __select.options); + const _ret = CardCommentReactions.findOne( + __select.idOrFirstObjectSelector, + __select.options, + ); return _ret; }); } - const ret = this.__cardCommentReaction.get(EJSON.stringify(idOrFirstObjectSelect)); + const ret = this.__cardCommentReaction.get( + EJSON.stringify(idOrFirstObjectSelect), + ); return ret; }, getCardCommentReactions(selector = {}, options = {}, getQuery = false) { - const select = {selector, options, getQuery} + const select = { selector, options, getQuery }; if (!this.__cardCommentReactions) { - this.__cardCommentReactions = new DataCache(_select => { + this.__cardCommentReactions = new DataCache((_select) => { const __select = EJSON.parse(_select); - let _ret = CardCommentReactions.find(__select.selector, __select.options); + let _ret = CardCommentReactions.find( + __select.selector, + __select.options, + ); if (__select.getQuery !== true) { _ret = _ret.fetch(); } @@ -496,11 +530,14 @@ ReactiveCacheClient = { return ret; }, getCustomField(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} + const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; if (!this.__customField) { - this.__customField = new DataCache(_idOrFirstObjectSelect => { + this.__customField = new DataCache((_idOrFirstObjectSelect) => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = CustomFields.findOne(__select.idOrFirstObjectSelector, __select.options); + const _ret = CustomFields.findOne( + __select.idOrFirstObjectSelector, + __select.options, + ); return _ret; }); } @@ -508,9 +545,9 @@ ReactiveCacheClient = { return ret; }, getCustomFields(selector = {}, options = {}, getQuery = false) { - const select = {selector, options, getQuery} + const select = { selector, options, getQuery }; if (!this.__customFields) { - this.__customFields = new DataCache(_select => { + this.__customFields = new DataCache((_select) => { const __select = EJSON.parse(_select); let _ret = CustomFields.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -523,15 +560,20 @@ ReactiveCacheClient = { return ret; }, getAttachment(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} + const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; if (!this.__attachment) { - this.__attachment = new DataCache(_idOrFirstObjectSelect => { + this.__attachment = new DataCache((_idOrFirstObjectSelect) => { const __select = EJSON.parse(_idOrFirstObjectSelect); // Try new structure first - let _ret = Attachments.findOne(__select.idOrFirstObjectSelector, __select.options); + let _ret = Attachments.findOne( + __select.idOrFirstObjectSelector, + __select.options, + ); if (!_ret && typeof __select.idOrFirstObjectSelector === 'string') { // Fall back to old structure for single attachment lookup - _ret = Attachments.getAttachmentWithBackwardCompatibility(__select.idOrFirstObjectSelector); + _ret = Attachments.getAttachmentWithBackwardCompatibility( + __select.idOrFirstObjectSelector, + ); } return _ret; }); @@ -540,9 +582,9 @@ ReactiveCacheClient = { return ret; }, getAttachments(selector = {}, options = {}, getQuery = false) { - const select = {selector, options, getQuery} + const select = { selector, options, getQuery }; if (!this.__attachments) { - this.__attachments = new DataCache(_select => { + this.__attachments = new DataCache((_select) => { const __select = EJSON.parse(_select); // Try new structure first let _ret = Attachments.find(__select.selector, __select.options); @@ -550,7 +592,9 @@ ReactiveCacheClient = { _ret = _ret.fetch(); // If no results and we have a cardId selector, try old structure if (_ret.length === 0 && __select.selector['meta.cardId']) { - _ret = Attachments.getAttachmentsWithBackwardCompatibility(__select.selector); + _ret = Attachments.getAttachmentsWithBackwardCompatibility( + __select.selector, + ); } } return _ret; @@ -560,11 +604,14 @@ ReactiveCacheClient = { return ret; }, getAvatar(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} + const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; if (!this.__avatar) { - this.__avatar = new DataCache(_idOrFirstObjectSelect => { + this.__avatar = new DataCache((_idOrFirstObjectSelect) => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Avatars.findOne(__select.idOrFirstObjectSelector, __select.options); + const _ret = Avatars.findOne( + __select.idOrFirstObjectSelector, + __select.options, + ); return _ret; }); } @@ -572,9 +619,9 @@ ReactiveCacheClient = { return ret; }, getAvatars(selector = {}, options = {}, getQuery = false) { - const select = {selector, options, getQuery} + const select = { selector, options, getQuery }; if (!this.__avatars) { - this.__avatars = new DataCache(_select => { + this.__avatars = new DataCache((_select) => { const __select = EJSON.parse(_select); let _ret = Avatars.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -587,11 +634,14 @@ ReactiveCacheClient = { return ret; }, getUser(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} + const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; if (!this.__user) { - this.__user = new DataCache(_idOrFirstObjectSelect => { + this.__user = new DataCache((_idOrFirstObjectSelect) => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Users.findOne(__select.idOrFirstObjectSelector, __select.options); + const _ret = Users.findOne( + __select.idOrFirstObjectSelector, + __select.options, + ); return _ret; }); } @@ -599,9 +649,9 @@ ReactiveCacheClient = { return ret; }, getUsers(selector = {}, options = {}, getQuery = false) { - const select = {selector, options, getQuery} + const select = { selector, options, getQuery }; if (!this.__users) { - this.__users = new DataCache(_select => { + this.__users = new DataCache((_select) => { const __select = EJSON.parse(_select); let _ret = Users.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -614,11 +664,14 @@ ReactiveCacheClient = { return ret; }, getOrg(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} + const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; if (!this.__org) { - this.__org = new DataCache(_idOrFirstObjectSelect => { + this.__org = new DataCache((_idOrFirstObjectSelect) => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Org.findOne(__select.idOrFirstObjectSelector, __select.options); + const _ret = Org.findOne( + __select.idOrFirstObjectSelector, + __select.options, + ); return _ret; }); } @@ -626,9 +679,9 @@ ReactiveCacheClient = { return ret; }, getOrgs(selector = {}, options = {}, getQuery = false) { - const select = {selector, options, getQuery} + const select = { selector, options, getQuery }; if (!this.__orgs) { - this.__orgs = new DataCache(_select => { + this.__orgs = new DataCache((_select) => { const __select = EJSON.parse(_select); let _ret = Org.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -641,11 +694,14 @@ ReactiveCacheClient = { return ret; }, getTeam(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} + const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; if (!this.__team) { - this.__team = new DataCache(_idOrFirstObjectSelect => { + this.__team = new DataCache((_idOrFirstObjectSelect) => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Team.findOne(__select.idOrFirstObjectSelector, __select.options); + const _ret = Team.findOne( + __select.idOrFirstObjectSelector, + __select.options, + ); return _ret; }); } @@ -653,9 +709,9 @@ ReactiveCacheClient = { return ret; }, getTeams(selector = {}, options = {}, getQuery = false) { - const select = {selector, options, getQuery} + const select = { selector, options, getQuery }; if (!this.__teams) { - this.__teams = new DataCache(_select => { + this.__teams = new DataCache((_select) => { const __select = EJSON.parse(_select); let _ret = Team.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -668,11 +724,14 @@ ReactiveCacheClient = { return ret; }, getActivity(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} + const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; if (!this.__activity) { - this.__activity = new DataCache(_idOrFirstObjectSelect => { + this.__activity = new DataCache((_idOrFirstObjectSelect) => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Activities.findOne(__select.idOrFirstObjectSelector, __select.options); + const _ret = Activities.findOne( + __select.idOrFirstObjectSelector, + __select.options, + ); return _ret; }); } @@ -680,9 +739,9 @@ ReactiveCacheClient = { return ret; }, getActivities(selector = {}, options = {}, getQuery = false) { - const select = {selector, options, getQuery} + const select = { selector, options, getQuery }; if (!this.__activities) { - this.__activities = new DataCache(_select => { + this.__activities = new DataCache((_select) => { const __select = EJSON.parse(_select); let _ret = Activities.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -695,11 +754,14 @@ ReactiveCacheClient = { return ret; }, getRule(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} + const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; if (!this.__rule) { - this.__rule = new DataCache(_idOrFirstObjectSelect => { + this.__rule = new DataCache((_idOrFirstObjectSelect) => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Rules.findOne(__select.idOrFirstObjectSelector, __select.options); + const _ret = Rules.findOne( + __select.idOrFirstObjectSelector, + __select.options, + ); return _ret; }); } @@ -707,9 +769,9 @@ ReactiveCacheClient = { return ret; }, getRules(selector = {}, options = {}, getQuery = false) { - const select = {selector, options, getQuery} + const select = { selector, options, getQuery }; if (!this.__rules) { - this.__rules = new DataCache(_select => { + this.__rules = new DataCache((_select) => { const __select = EJSON.parse(_select); let _ret = Rules.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -722,11 +784,14 @@ ReactiveCacheClient = { return ret; }, getAction(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} + const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; if (!this.__action) { - this.__action = new DataCache(_idOrFirstObjectSelect => { + this.__action = new DataCache((_idOrFirstObjectSelect) => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Actions.findOne(__select.idOrFirstObjectSelector, __select.options); + const _ret = Actions.findOne( + __select.idOrFirstObjectSelector, + __select.options, + ); return _ret; }); } @@ -734,9 +799,9 @@ ReactiveCacheClient = { return ret; }, getActions(selector = {}, options = {}, getQuery = false) { - const select = {selector, options, getQuery} + const select = { selector, options, getQuery }; if (!this.__actions) { - this.__actions = new DataCache(_select => { + this.__actions = new DataCache((_select) => { const __select = EJSON.parse(_select); let _ret = Actions.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -749,11 +814,14 @@ ReactiveCacheClient = { return ret; }, getTrigger(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} + const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; if (!this.__trigger) { - this.__trigger = new DataCache(_idOrFirstObjectSelect => { + this.__trigger = new DataCache((_idOrFirstObjectSelect) => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Triggers.findOne(__select.idOrFirstObjectSelector, __select.options); + const _ret = Triggers.findOne( + __select.idOrFirstObjectSelector, + __select.options, + ); return _ret; }); } @@ -761,9 +829,9 @@ ReactiveCacheClient = { return ret; }, getTriggers(selector = {}, options = {}, getQuery = false) { - const select = {selector, options, getQuery} + const select = { selector, options, getQuery }; if (!this.__triggers) { - this.__triggers = new DataCache(_select => { + this.__triggers = new DataCache((_select) => { const __select = EJSON.parse(_select); let _ret = Triggers.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -776,21 +844,26 @@ ReactiveCacheClient = { return ret; }, getImpersonatedUser(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} + const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; if (!this.__impersonatedUser) { - this.__impersonatedUser = new DataCache(_idOrFirstObjectSelect => { + this.__impersonatedUser = new DataCache((_idOrFirstObjectSelect) => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = ImpersonatedUsers.findOne(__select.idOrFirstObjectSelector, __select.options); + const _ret = ImpersonatedUsers.findOne( + __select.idOrFirstObjectSelector, + __select.options, + ); return _ret; }); } - const ret = this.__impersonatedUser.get(EJSON.stringify(idOrFirstObjectSelect)); + const ret = this.__impersonatedUser.get( + EJSON.stringify(idOrFirstObjectSelect), + ); return ret; }, getImpersonatedUsers(selector = {}, options = {}, getQuery = false) { - const select = {selector, options, getQuery} + const select = { selector, options, getQuery }; if (!this.__impersonatedUsers) { - this.__impersonatedUsers = new DataCache(_select => { + this.__impersonatedUsers = new DataCache((_select) => { const __select = EJSON.parse(_select); let _ret = ImpersonatedUsers.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -803,11 +876,14 @@ ReactiveCacheClient = { return ret; }, getIntegration(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} + const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; if (!this.__integration) { - this.__integration = new DataCache(_idOrFirstObjectSelect => { + this.__integration = new DataCache((_idOrFirstObjectSelect) => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Integrations.findOne(__select.idOrFirstObjectSelector, __select.options); + const _ret = Integrations.findOne( + __select.idOrFirstObjectSelector, + __select.options, + ); return _ret; }); } @@ -815,9 +891,9 @@ ReactiveCacheClient = { return ret; }, getIntegrations(selector = {}, options = {}, getQuery = false) { - const select = {selector, options, getQuery} + const select = { selector, options, getQuery }; if (!this.__integrations) { - this.__integrations = new DataCache(_select => { + this.__integrations = new DataCache((_select) => { const __select = EJSON.parse(_select); let _ret = Integrations.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -830,21 +906,26 @@ ReactiveCacheClient = { return ret; }, getInvitationCode(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} + const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; if (!this.__invitationCode) { - this.__invitationCode = new DataCache(_idOrFirstObjectSelect => { + this.__invitationCode = new DataCache((_idOrFirstObjectSelect) => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = InvitationCodes.findOne(__select.idOrFirstObjectSelector, __select.options); + const _ret = InvitationCodes.findOne( + __select.idOrFirstObjectSelector, + __select.options, + ); return _ret; }); } - const ret = this.__invitationCode.get(EJSON.stringify(idOrFirstObjectSelect)); + const ret = this.__invitationCode.get( + EJSON.stringify(idOrFirstObjectSelect), + ); return ret; }, getInvitationCodes(selector = {}, options = {}, getQuery = false) { - const select = {selector, options, getQuery} + const select = { selector, options, getQuery }; if (!this.__invitationCodes) { - this.__invitationCodes = new DataCache(_select => { + this.__invitationCodes = new DataCache((_select) => { const __select = EJSON.parse(_select); let _ret = InvitationCodes.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -877,11 +958,14 @@ ReactiveCacheClient = { return ret; }, getTranslation(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} + const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; if (!this.__translation) { - this.__translation = new DataCache(_idOrFirstObjectSelect => { + this.__translation = new DataCache((_idOrFirstObjectSelect) => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Translation.findOne(__select.idOrFirstObjectSelector, __select.options); + const _ret = Translation.findOne( + __select.idOrFirstObjectSelector, + __select.options, + ); return _ret; }); } @@ -889,9 +973,9 @@ ReactiveCacheClient = { return ret; }, getTranslations(selector = {}, options = {}, getQuery = false) { - const select = {selector, options, getQuery} + const select = { selector, options, getQuery }; if (!this.__translations) { - this.__translations = new DataCache(_select => { + this.__translations = new DataCache((_select) => { const __select = EJSON.parse(_select); let _ret = Translation.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -902,8 +986,8 @@ ReactiveCacheClient = { } const ret = this.__translations.get(EJSON.stringify(select)); return ret; - } -} + }, +}; // global Reactive Cache class to avoid big overhead while searching for the same data often again // This class calls 2 implementation, for server and client code @@ -987,9 +1071,15 @@ ReactiveCache = { getChecklistItem(idOrFirstObjectSelector = {}, options = {}) { let ret; if (Meteor.isServer) { - ret = ReactiveCacheServer.getChecklistItem(idOrFirstObjectSelector, options); + ret = ReactiveCacheServer.getChecklistItem( + idOrFirstObjectSelector, + options, + ); } else { - ret = ReactiveCacheClient.getChecklistItem(idOrFirstObjectSelector, options); + ret = ReactiveCacheClient.getChecklistItem( + idOrFirstObjectSelector, + options, + ); } return ret; }, @@ -1023,9 +1113,15 @@ ReactiveCache = { getCardComment(idOrFirstObjectSelector = {}, options = {}) { let ret; if (Meteor.isServer) { - ret = ReactiveCacheServer.getCardComment(idOrFirstObjectSelector, options); + ret = ReactiveCacheServer.getCardComment( + idOrFirstObjectSelector, + options, + ); } else { - ret = ReactiveCacheClient.getCardComment(idOrFirstObjectSelector, options); + ret = ReactiveCacheClient.getCardComment( + idOrFirstObjectSelector, + options, + ); } return ret; }, @@ -1041,27 +1137,47 @@ ReactiveCache = { getCardCommentReaction(idOrFirstObjectSelector = {}, options = {}) { let ret; if (Meteor.isServer) { - ret = ReactiveCacheServer.getCardCommentReaction(idOrFirstObjectSelector, options); + ret = ReactiveCacheServer.getCardCommentReaction( + idOrFirstObjectSelector, + options, + ); } else { - ret = ReactiveCacheClient.getCardCommentReaction(idOrFirstObjectSelector, options); + ret = ReactiveCacheClient.getCardCommentReaction( + idOrFirstObjectSelector, + options, + ); } return ret; }, getCardCommentReactions(selector = {}, options = {}, getQuery = false) { let ret; if (Meteor.isServer) { - ret = ReactiveCacheServer.getCardCommentReactions(selector, options, getQuery); + ret = ReactiveCacheServer.getCardCommentReactions( + selector, + options, + getQuery, + ); } else { - ret = ReactiveCacheClient.getCardCommentReactions(selector, options, getQuery); + ret = ReactiveCacheClient.getCardCommentReactions( + selector, + options, + getQuery, + ); } return ret; }, getCustomField(idOrFirstObjectSelector = {}, options = {}) { let ret; if (Meteor.isServer) { - ret = ReactiveCacheServer.getCustomField(idOrFirstObjectSelector, options); + ret = ReactiveCacheServer.getCustomField( + idOrFirstObjectSelector, + options, + ); } else { - ret = ReactiveCacheClient.getCustomField(idOrFirstObjectSelector, options); + ret = ReactiveCacheClient.getCustomField( + idOrFirstObjectSelector, + options, + ); } return ret; }, @@ -1239,27 +1355,47 @@ ReactiveCache = { getImpersonatedUser(idOrFirstObjectSelector = {}, options = {}) { let ret; if (Meteor.isServer) { - ret = ReactiveCacheServer.getImpersonatedUser(idOrFirstObjectSelector, options); + ret = ReactiveCacheServer.getImpersonatedUser( + idOrFirstObjectSelector, + options, + ); } else { - ret = ReactiveCacheClient.getImpersonatedUser(idOrFirstObjectSelector, options); + ret = ReactiveCacheClient.getImpersonatedUser( + idOrFirstObjectSelector, + options, + ); } return ret; }, getImpersonatedUsers(selector = {}, options = {}, getQuery = false) { let ret; if (Meteor.isServer) { - ret = ReactiveCacheServer.getImpersonatedUsers(selector, options, getQuery); + ret = ReactiveCacheServer.getImpersonatedUsers( + selector, + options, + getQuery, + ); } else { - ret = ReactiveCacheClient.getImpersonatedUsers(selector, options, getQuery); + ret = ReactiveCacheClient.getImpersonatedUsers( + selector, + options, + getQuery, + ); } return ret; }, getIntegration(idOrFirstObjectSelector = {}, options = {}) { let ret; if (Meteor.isServer) { - ret = ReactiveCacheServer.getIntegration(idOrFirstObjectSelector, options); + ret = ReactiveCacheServer.getIntegration( + idOrFirstObjectSelector, + options, + ); } else { - ret = ReactiveCacheClient.getIntegration(idOrFirstObjectSelector, options); + ret = ReactiveCacheClient.getIntegration( + idOrFirstObjectSelector, + options, + ); } return ret; }, @@ -1274,7 +1410,10 @@ ReactiveCache = { }, getSessionData(idOrFirstObjectSelector = {}, options = {}) { // no reactive cache, otherwise global search will not work anymore - let ret = ReactiveCacheServer.getSessionData(idOrFirstObjectSelector, options); + let ret = ReactiveCacheServer.getSessionData( + idOrFirstObjectSelector, + options, + ); return ret; }, getSessionDatas(selector = {}, options = {}, getQuery = false) { @@ -1285,9 +1424,15 @@ ReactiveCache = { getInvitationCode(idOrFirstObjectSelector = {}, options = {}) { let ret; if (Meteor.isServer) { - ret = ReactiveCacheServer.getInvitationCode(idOrFirstObjectSelector, options); + ret = ReactiveCacheServer.getInvitationCode( + idOrFirstObjectSelector, + options, + ); } else { - ret = ReactiveCacheClient.getInvitationCode(idOrFirstObjectSelector, options); + ret = ReactiveCacheClient.getInvitationCode( + idOrFirstObjectSelector, + options, + ); } return ret; }, @@ -1321,9 +1466,15 @@ ReactiveCache = { getTranslation(idOrFirstObjectSelector = {}, options = {}) { let ret; if (Meteor.isServer) { - ret = ReactiveCacheServer.getTranslation(idOrFirstObjectSelector, options); + ret = ReactiveCacheServer.getTranslation( + idOrFirstObjectSelector, + options, + ); } else { - ret = ReactiveCacheClient.getTranslation(idOrFirstObjectSelector, options); + ret = ReactiveCacheClient.getTranslation( + idOrFirstObjectSelector, + options, + ); } return ret; }, @@ -1336,76 +1487,67 @@ ReactiveCache = { } return ret; }, -} +}; // Server isn't reactive, so search for the data always. ReactiveMiniMongoIndexServer = { getSubTasksWithParentId(parentId, addSelect = {}, options = {}) { - let ret = [] + let ret = []; if (parentId) { - ret = ReactiveCache.getCards( - { parentId, - ...addSelect, - }, options); + ret = ReactiveCache.getCards({ parentId, ...addSelect }, options); } return ret; }, getChecklistsWithCardId(cardId, addSelect = {}, options = {}) { - let ret = [] + let ret = []; if (cardId) { - ret = ReactiveCache.getChecklists( - { cardId, - ...addSelect, - }, options); + ret = ReactiveCache.getChecklists({ cardId, ...addSelect }, options); } return ret; }, getChecklistItemsWithChecklistId(checklistId, addSelect = {}, options = {}) { - let ret = [] + let ret = []; if (checklistId) { ret = ReactiveCache.getChecklistItems( - { checklistId, - ...addSelect, - }, options); + { checklistId, ...addSelect }, + options, + ); } return ret; }, getCardCommentsWithCardId(cardId, addSelect = {}, options = {}) { - let ret = [] + let ret = []; if (cardId) { - ret = ReactiveCache.getCardComments( - { cardId, - ...addSelect, - }, options); + ret = ReactiveCache.getCardComments({ cardId, ...addSelect }, options); } return ret; }, getActivityWithId(activityId, addSelect = {}, options = {}) { - let ret = [] + let ret = []; if (activityId) { ret = ReactiveCache.getActivities( - { _id: activityId, - ...addSelect, - }, options); + { _id: activityId, ...addSelect }, + options, + ); } return ret; - } -} + }, +}; // Client side little MiniMongo DB "Index" ReactiveMiniMongoIndexClient = { getSubTasksWithParentId(parentId, addSelect = {}, options = {}) { - let ret = [] + let ret = []; if (parentId) { - const select = {addSelect, options} + const select = { addSelect, options }; if (!this.__subTasksWithId) { - this.__subTasksWithId = new DataCache(_select => { + this.__subTasksWithId = new DataCache((_select) => { const __select = EJSON.parse(_select); const _subTasks = ReactiveCache.getCards( - { parentId: { $exists: true }, - ...__select.addSelect, - }, __select.options); - const _ret = _.groupBy(_subTasks, 'parentId') + { parentId: { $exists: true }, ...__select.addSelect }, + __select.options, + ); + const _ret = _.groupBy(_subTasks, 'parentId'); return _ret; }); } @@ -1417,17 +1559,17 @@ ReactiveMiniMongoIndexClient = { return ret; }, getChecklistsWithCardId(cardId, addSelect = {}, options = {}) { - let ret = [] + let ret = []; if (cardId) { - const select = {addSelect, options} + const select = { addSelect, options }; if (!this.__checklistsWithId) { - this.__checklistsWithId = new DataCache(_select => { + this.__checklistsWithId = new DataCache((_select) => { const __select = EJSON.parse(_select); const _checklists = ReactiveCache.getChecklists( - { cardId: { $exists: true }, - ...__select.addSelect, - }, __select.options); - const _ret = _.groupBy(_checklists, 'cardId') + { cardId: { $exists: true }, ...__select.addSelect }, + __select.options, + ); + const _ret = _.groupBy(_checklists, 'cardId'); return _ret; }); } @@ -1439,17 +1581,17 @@ ReactiveMiniMongoIndexClient = { return ret; }, getChecklistItemsWithChecklistId(checklistId, addSelect = {}, options = {}) { - let ret = [] + let ret = []; if (checklistId) { - const select = {addSelect, options} + const select = { addSelect, options }; if (!this.__checklistItemsWithId) { - this.__checklistItemsWithId = new DataCache(_select => { + this.__checklistItemsWithId = new DataCache((_select) => { const __select = EJSON.parse(_select); const _checklistItems = ReactiveCache.getChecklistItems( - { checklistId: { $exists: true }, - ...__select.addSelect, - }, __select.options); - const _ret = _.groupBy(_checklistItems, 'checklistId') + { checklistId: { $exists: true }, ...__select.addSelect }, + __select.options, + ); + const _ret = _.groupBy(_checklistItems, 'checklistId'); return _ret; }); } @@ -1457,9 +1599,9 @@ ReactiveMiniMongoIndexClient = { if (ret) { if (Meteor.isServer) { ret[checklistId] = ReactiveCache.getChecklistItems( - {checklistId: checklistId, - ...addSelect - }, options); + { checklistId: checklistId, ...addSelect }, + options, + ); } ret = ret[checklistId] || []; } @@ -1467,17 +1609,17 @@ ReactiveMiniMongoIndexClient = { return ret; }, getCardCommentsWithCardId(cardId, addSelect = {}, options = {}) { - let ret = [] + let ret = []; if (cardId) { - const select = {addSelect, options} + const select = { addSelect, options }; if (!this.__cardCommentsWithId) { - this.__cardCommentsWithId = new DataCache(_select => { + this.__cardCommentsWithId = new DataCache((_select) => { const __select = EJSON.parse(_select); const _cardComments = ReactiveCache.getCardComments( - { cardId: { $exists: true }, - ...__select.addSelect, - }, __select.options); - const _ret = _.groupBy(_cardComments, 'cardId') + { cardId: { $exists: true }, ...__select.addSelect }, + __select.options, + ); + const _ret = _.groupBy(_cardComments, 'cardId'); return _ret; }); } @@ -1489,17 +1631,17 @@ ReactiveMiniMongoIndexClient = { return ret; }, getActivityWithId(activityId, addSelect = {}, options = {}) { - let ret = [] + let ret = []; if (activityId) { - const select = {addSelect, options} + const select = { addSelect, options }; if (!this.__activityWithId) { - this.__activityWithId = new DataCache(_select => { + this.__activityWithId = new DataCache((_select) => { const __select = EJSON.parse(_select); const _activities = ReactiveCache.getActivities( - { _id: { $exists: true }, - ...__select.addSelect, - }, __select.options); - const _ret = _.indexBy(_activities, '_id') + { _id: { $exists: true }, ...__select.addSelect }, + __select.options, + ); + const _ret = _.indexBy(_activities, '_id'); return _ret; }); } @@ -1509,8 +1651,8 @@ ReactiveMiniMongoIndexClient = { } } return ret; - } -} + }, +}; // global Reactive MiniMongo Index Cache class to avoid big overhead while searching for the same data often again // This class calls 2 implementation, for server and client code @@ -1522,48 +1664,88 @@ ReactiveMiniMongoIndex = { getSubTasksWithParentId(parentId, addSelect = {}, options = {}) { let ret; if (Meteor.isServer) { - ret = ReactiveMiniMongoIndexServer.getSubTasksWithParentId(parentId, addSelect, options); + ret = ReactiveMiniMongoIndexServer.getSubTasksWithParentId( + parentId, + addSelect, + options, + ); } else { - ret = ReactiveMiniMongoIndexClient.getSubTasksWithParentId(parentId, addSelect, options); + ret = ReactiveMiniMongoIndexClient.getSubTasksWithParentId( + parentId, + addSelect, + options, + ); } return ret; }, getChecklistsWithCardId(cardId, addSelect = {}, options = {}) { let ret; if (Meteor.isServer) { - ret = ReactiveMiniMongoIndexServer.getChecklistsWithCardId(cardId, addSelect, options); + ret = ReactiveMiniMongoIndexServer.getChecklistsWithCardId( + cardId, + addSelect, + options, + ); } else { - ret = ReactiveMiniMongoIndexClient.getChecklistsWithCardId(cardId, addSelect, options); + ret = ReactiveMiniMongoIndexClient.getChecklistsWithCardId( + cardId, + addSelect, + options, + ); } return ret; }, getChecklistItemsWithChecklistId(checklistId, addSelect = {}, options = {}) { let ret; if (Meteor.isServer) { - ret = ReactiveMiniMongoIndexServer.getChecklistItemsWithChecklistId(checklistId, addSelect, options); + ret = ReactiveMiniMongoIndexServer.getChecklistItemsWithChecklistId( + checklistId, + addSelect, + options, + ); } else { - ret = ReactiveMiniMongoIndexClient.getChecklistItemsWithChecklistId(checklistId, addSelect, options); + ret = ReactiveMiniMongoIndexClient.getChecklistItemsWithChecklistId( + checklistId, + addSelect, + options, + ); } return ret; }, getCardCommentsWithCardId(cardId, addSelect = {}, options = {}) { let ret; if (Meteor.isServer) { - ret = ReactiveMiniMongoIndexServer.getCardCommentsWithCardId(cardId, addSelect, options); + ret = ReactiveMiniMongoIndexServer.getCardCommentsWithCardId( + cardId, + addSelect, + options, + ); } else { - ret = ReactiveMiniMongoIndexClient.getCardCommentsWithCardId(cardId, addSelect, options); + ret = ReactiveMiniMongoIndexClient.getCardCommentsWithCardId( + cardId, + addSelect, + options, + ); } return ret; }, getActivityWithId(activityId, addSelect = {}, options = {}) { let ret; if (Meteor.isServer) { - ret = ReactiveMiniMongoIndexServer.getActivityWithId(activityId, addSelect, options); + ret = ReactiveMiniMongoIndexServer.getActivityWithId( + activityId, + addSelect, + options, + ); } else { - ret = ReactiveMiniMongoIndexClient.getActivityWithId(activityId, addSelect, options); + ret = ReactiveMiniMongoIndexClient.getActivityWithId( + activityId, + addSelect, + options, + ); } return ret; - } -} + }, +}; export { ReactiveCache, ReactiveMiniMongoIndex }; diff --git a/models/activities.js b/models/activities.js index 943fb9306..626656138 100644 --- a/models/activities.js +++ b/models/activities.js @@ -202,7 +202,7 @@ if (Meteor.isServer) { params.comment = comment.text; if (board) { const comment = params.comment; - const knownUsers = board.members.map(member => { + const knownUsers = board.members.map((member) => { const u = ReactiveCache.getUser(member.userId); if (u) { member.username = u.username; @@ -223,7 +223,7 @@ if (Meteor.isServer) { if (activity.boardId && username === 'board_members') { // mentions all board members - const knownUids = knownUsers.map(u => u.userId); + const knownUids = knownUsers.map((u) => u.userId); watchers = _.union(watchers, [...knownUids]); title = 'act-atUserComment'; } else if (activity.cardId && username === 'card_members') { @@ -243,7 +243,6 @@ if (Meteor.isServer) { title = 'act-atUserComment'; watchers = _.union(watchers, [uid]); } - } } params.commentId = comment._id; @@ -300,7 +299,7 @@ if (Meteor.isServer) { // due time reminder, if it doesn't have old value, it's a brand new set, need some differentiation title = activity.timeOldValue ? 'act-withDue' : 'act-newDue'; } - ['timeValue', 'timeOldValue'].forEach(key => { + ['timeValue', 'timeOldValue'].forEach((key) => { // copy time related keys & values to params const value = activity[key]; if (value) params[key] = value; @@ -313,7 +312,7 @@ if (Meteor.isServer) { if (new RegExp(BIGEVENTS).exec(atype)) { watchers = _.union( watchers, - board.activeMembers().map(member => member.userId), + board.activeMembers().map((member) => member.userId), ); // notify all active members for important events } } catch (e) { @@ -335,7 +334,7 @@ if (Meteor.isServer) { _.intersection(participants, trackingUsers), ); } - Notifications.getUsers(watchers).forEach(user => { + Notifications.getUsers(watchers).forEach((user) => { // don't notify a user of their own behavior if (user._id !== userId) { Notifications.notify(user, title, description, params); @@ -350,7 +349,7 @@ if (Meteor.isServer) { }); if (integrations.length > 0) { params.watchers = watchers; - integrations.forEach(integration => { + integrations.forEach((integration) => { Meteor.call( 'outgoingWebhooks', integration, diff --git a/models/boards.js b/models/boards.js index 8fe525843..0f99ef1fd 100644 --- a/models/boards.js +++ b/models/boards.js @@ -1,6 +1,7 @@ import { ReactiveCache } from '/imports/reactiveCache'; import escapeForRegex from 'escape-string-regexp'; import { TAPi18n } from '/imports/i18n'; +import { CustomFields } from './customFields'; import { ALLOWED_BOARD_COLORS, ALLOWED_COLORS, @@ -9,6 +10,7 @@ import { TYPE_TEMPLATE_CONTAINER, } from '/config/const'; import Users from "./users"; +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; import TableVisibilityModeSettings from "./tableVisibilityModeSettings"; // const escapeForRegex = require('escape-string-regexp'); diff --git a/models/cards.js b/models/cards.js index d5bbb8bec..fb97bd99e 100644 --- a/models/cards.js +++ b/models/cards.js @@ -1,4 +1,5 @@ import { ReactiveCache, ReactiveMiniMongoIndex } from '/imports/reactiveCache'; +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; import { formatDateTime, formatDate, diff --git a/models/exporter.js b/models/exporter.js index a42dc0f7a..f1f601540 100644 --- a/models/exporter.js +++ b/models/exporter.js @@ -1,6 +1,7 @@ import { ReactiveCache } from '/imports/reactiveCache'; const Papa = require('papaparse'); import { TAPi18n } from '/imports/i18n'; +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; import { formatDateTime, formatDate, diff --git a/models/server/ExporterExcel.js b/models/server/ExporterExcel.js index e9775baff..66684917e 100644 --- a/models/server/ExporterExcel.js +++ b/models/server/ExporterExcel.js @@ -1,5 +1,6 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; import { createWorkbook } from './createWorkbook'; import { formatDateTime, diff --git a/models/settings.js b/models/settings.js index 3a85e5013..8645e2117 100644 --- a/models/settings.js +++ b/models/settings.js @@ -1,5 +1,6 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; //var nodemailer = require('nodemailer'); // Sandstorm context is detected using the METEOR_SETTINGS environment variable diff --git a/models/trelloCreator.js b/models/trelloCreator.js index 5dde821c0..c0400b443 100644 --- a/models/trelloCreator.js +++ b/models/trelloCreator.js @@ -1,5 +1,6 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; +import { CustomFields } from './customFields'; import { formatDateTime, formatDate, diff --git a/models/users.js b/models/users.js index 54de4a94e..f83ecd3c2 100644 --- a/models/users.js +++ b/models/users.js @@ -2018,6 +2018,7 @@ Meteor.methods({ }, // Spaces: create a new space under parentId (or root when null) createWorkspace({ parentId = null, name }) { + check(parentId, Match.OneOf(String, null)); check(name, String); if (!this.userId) throw new Meteor.Error('not-logged-in'); const user = Users.findOne(this.userId) || {}; diff --git a/models/wekanCreator.js b/models/wekanCreator.js index 2bf142d50..503cba3fc 100644 --- a/models/wekanCreator.js +++ b/models/wekanCreator.js @@ -1,4 +1,5 @@ import { ReactiveCache } from '/imports/reactiveCache'; +import { CustomFields } from './customFields'; import { formatDateTime, formatDate, diff --git a/sandstorm.js b/sandstorm.js index b50922794..6539a1942 100644 --- a/sandstorm.js +++ b/sandstorm.js @@ -1,6 +1,7 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { Meteor } from 'meteor/meteor'; import { Picker } from 'meteor/communitypackages:picker'; +import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; // Sandstorm context is detected using the METEOR_SETTINGS environment variable // in the package definition. diff --git a/server/publications/lockoutSettings.js b/server/publications/lockoutSettings.js index c94309c33..089083f2a 100644 --- a/server/publications/lockoutSettings.js +++ b/server/publications/lockoutSettings.js @@ -1,4 +1,5 @@ import LockoutSettings from '/models/lockoutSettings'; +import { Settings } from '../../models/settings'; Meteor.publish('lockoutSettings', function() { const ret = LockoutSettings.find(); diff --git a/server/publications/settings.js b/server/publications/settings.js index e2365d523..a8c073187 100644 --- a/server/publications/settings.js +++ b/server/publications/settings.js @@ -1,4 +1,5 @@ import { ReactiveCache } from '/imports/reactiveCache'; +import { Settings } from '../../models/settings'; Meteor.publish('globalwebhooks', () => { const boardId = Integrations.Const.GLOBAL_WEBHOOK_ID; diff --git a/server/publications/tableVisibilityModeSettings.js b/server/publications/tableVisibilityModeSettings.js index 4326e59a6..9e04875c4 100644 --- a/server/publications/tableVisibilityModeSettings.js +++ b/server/publications/tableVisibilityModeSettings.js @@ -1,3 +1,5 @@ +import { Settings } from '../../models/settings'; + Meteor.publish('tableVisibilityModeSettings', function() { const ret = TableVisibilityModeSettings.find(); return ret; diff --git a/server/routes/attachmentApi.js b/server/routes/attachmentApi.js index ae10ca64b..490c54f7f 100644 --- a/server/routes/attachmentApi.js +++ b/server/routes/attachmentApi.js @@ -3,6 +3,7 @@ import { Accounts } from 'meteor/accounts-base'; import { WebApp } from 'meteor/webapp'; import { ReactiveCache } from '/imports/reactiveCache'; import { Attachments, fileStoreStrategyFactory } from '/models/attachments'; +import { Settings } from '../../models/settings'; import { moveToStorage } from '/models/lib/fileStoreStrategy'; import { STORAGE_NAME_FILESYSTEM, STORAGE_NAME_GRIDFS, STORAGE_NAME_S3 } from '/models/lib/fileStoreStrategy'; import AttachmentStorageSettings from '/models/attachmentStorageSettings'; From 1d62322b3c7664513608aa30ba0b572f0ad364ff Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Mon, 12 Jan 2026 02:21:03 +0200 Subject: [PATCH 03/32] Added FerretDB2/PostgreSQL Docs. Thanks to juri_ at WeKan Libera.Chat IRC and xet7 ! --- docs/Platforms/FOSS/FerretDB2-PostgreSQL.md | 94 +++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 docs/Platforms/FOSS/FerretDB2-PostgreSQL.md diff --git a/docs/Platforms/FOSS/FerretDB2-PostgreSQL.md b/docs/Platforms/FOSS/FerretDB2-PostgreSQL.md new file mode 100644 index 000000000..b99e1653b --- /dev/null +++ b/docs/Platforms/FOSS/FerretDB2-PostgreSQL.md @@ -0,0 +1,94 @@ +# Install WeKan, FerretDB 2, PostgreSQL + +## WeKan + +``` +git clone --branch main --depth 1 https://github.com/wekan/wekan.git +cd wekan +sudo apt update +sudo apt install -y build-essential gcc g++ make git curl wget \ +p7zip-full zip unzip unp npm p7zip-full +sudo npm install -g n +export N_NODE_MIRROR=https://github.com/wekan/node-v14-esm/releases/download +sudo -E n 14.21.4 +sudo npm -g install @mapbox/node-pre-gyp +sudo npm -g install meteor@2.14 --unsafe-perm +export PATH=$PATH:/home/demo/.meteor +meteor npm install production +meteor build .build --directory --platforms=web.browser +``` + +## Postgres 17 + DocumentDB + +``` +sudo bash -c 'curl -fsSL https://repo.pigsty.io/pig | bash' +pig repo add all -u +pig ext install pg17 +pig ext install documentdb +``` +Edit `/etc/postgresql/17/main/postgresql.conf`, there set +``` +shared_preload_libraries = 'pg_cron, pg_documentdb, pg_documentdb_core' +``` +Restart PostgreSQL: +``` +sudo service postgresql restart +``` + +## FerretDB + +Download: +``` +curl -L \ +https://github.com/FerretDB/FerretDB/releases/download/v2.7.0/ferretdb-amd64-linux.deb \ +-o /tmp/ferretdb-amd64-linux.deb +``` +Install: +``` +sudo apt -y install /tmp/ferretdb-amd64-linux.deb +``` +Edit your `/etc/systemd/system/ferritdb.service` file, +add your username/password pair to the following line: +``` +Environment="FERRETDB_POSTGRESQL_URL=postgres://ferret:DB_PASSWORD_GOES_HERE@127.0.0.1:5432/postgres" +``` +Then enable and start FerretDB: +``` +sudo systemctl enable ferretdb.service +sudo service ferretdb start +``` + +## Initializing the Database +``` +su - +su - postgres +CREATE ROLE ferret WITH PASSWORD 'DB_PASSWORD_GOES_HERE'; +CREATE DATABASE ferretdb; +GRANT ALL PRIVILEGES ON DATABASE ferretdb TO ferret; +ALTER ROLE ferret WITH LOGIN; +CREATE EXTENSION documentdb CASCADE; +GRANT USAGE ON SCHEMA documentdb_api to ferret; +GRANT USAGE ON SCHEMA documentdb_core to ferret; +GRANT USAGE ON SCHEMA documentdb_api_internal to ferret; +GRANT USAGE ON SCHEMA documentdb_api_catalog to ferret; +GRANT INSERT ON TABLE documentdb_api_catalog.collections to ferret; +GRANT ALL ON SCHEMA documentdb_data to ferret; +GRANT documentdb_admin_role to ferret; +``` +## Launching WeKan +``` +export PATH=$PATH:/home/demo/.meteor +cd ~/wekan + +MONGO_URL=mongodb://ferret:DB_PASSWORD_GOES_HERE@127.0.0.1:27017/wekan \ +WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false \ +ROOT_URL=https://wekan.example.com meteor run \ +--exclude-archs web.browser.legacy,web.cordova \ +--port 8080 2>&1 | tee ../wekan-log.`date +%s`.txt +``` + +## Notes + +- Machine must have at least 3 gigs of ram. Crashes at npm installing meteor, with 384 megs. +- Machine must have at least 4 gigs of ram. Crashes at meteor build with 3 gigs. +- Be ready to read rebuild-wekan.sh if you want to actually get it running. From 46100cfd1d4f4b0c0adf498ab8a019267e449a47 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Mon, 12 Jan 2026 02:24:36 +0200 Subject: [PATCH 04/32] Updated ChangeLog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 086bb77c0..e5f5c962a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,8 @@ and adds the following updates: Thanks to dependabot. - [Updated dependencies and published as @wekanteam npm packages to npmjs.com](https://github.com/wekan/wekan/commit/a9a89b501a91ffcdbdd611a05029d9483c59e4db). Thanks to xet7. +- [Added FerretDB2/PostgreSQL Docs](https://github.com/wekan/wekan/commit/9fb1aeb8272b011c3d0b6b2c26ff7cb498c7b37f). + Thanks to juri_ at WeKan Libera.Chat IRC and xet7. and fixes the following bugs: From fd45ae2a62caebe32ffac5cdd95fd32a245aa98c Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Mon, 12 Jan 2026 03:54:41 +0200 Subject: [PATCH 05/32] Added FerretDB2/PostgreSQL Docs. Part 2. Thanks to juri_ at WeKan Libera.Chat IRC and xet7 ! --- docs/Platforms/FOSS/FerretDB2-PostgreSQL.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/Platforms/FOSS/FerretDB2-PostgreSQL.md b/docs/Platforms/FOSS/FerretDB2-PostgreSQL.md index b99e1653b..2158a601e 100644 --- a/docs/Platforms/FOSS/FerretDB2-PostgreSQL.md +++ b/docs/Platforms/FOSS/FerretDB2-PostgreSQL.md @@ -30,6 +30,9 @@ Edit `/etc/postgresql/17/main/postgresql.conf`, there set ``` shared_preload_libraries = 'pg_cron, pg_documentdb, pg_documentdb_core' ``` +edit `/etc/postgresql/17/main/pg_hba.conf` , +replace **scram-sha-256` with trust on the host lines for `127.0.0.1/32` and `::1/128` + Restart PostgreSQL: ``` sudo service postgresql restart @@ -47,7 +50,7 @@ Install: ``` sudo apt -y install /tmp/ferretdb-amd64-linux.deb ``` -Edit your `/etc/systemd/system/ferritdb.service` file, +Edit your `/etc/systemd/system/ferretdb.service` file, add your username/password pair to the following line: ``` Environment="FERRETDB_POSTGRESQL_URL=postgres://ferret:DB_PASSWORD_GOES_HERE@127.0.0.1:5432/postgres" @@ -77,7 +80,7 @@ GRANT documentdb_admin_role to ferret; ``` ## Launching WeKan ``` -export PATH=$PATH:/home/demo/.meteor +export PATH=$HOME/.meteor cd ~/wekan MONGO_URL=mongodb://ferret:DB_PASSWORD_GOES_HERE@127.0.0.1:27017/wekan \ From 2bc9fa16298004737ec6efb2bd0cf4463edeb236 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Mon, 12 Jan 2026 03:55:35 +0200 Subject: [PATCH 06/32] Updated ChangeLog. --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5f5c962a..992b5e785 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,7 +61,9 @@ and adds the following updates: Thanks to dependabot. - [Updated dependencies and published as @wekanteam npm packages to npmjs.com](https://github.com/wekan/wekan/commit/a9a89b501a91ffcdbdd611a05029d9483c59e4db). Thanks to xet7. -- [Added FerretDB2/PostgreSQL Docs](https://github.com/wekan/wekan/commit/9fb1aeb8272b011c3d0b6b2c26ff7cb498c7b37f). +- Added FerretDB2/PostgreSQL Docs. + [Part 1](https://github.com/wekan/wekan/commit/9fb1aeb8272b011c3d0b6b2c26ff7cb498c7b37f), + [Part 2](https://github.com/wekan/wekan/commit/f198421f10dd3be9d58f64a242d12ea1ef45fee3). Thanks to juri_ at WeKan Libera.Chat IRC and xet7. and fixes the following bugs: From 306305a95c9756a7f2dea1604406e35cb999e94a Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Mon, 12 Jan 2026 15:21:49 +0200 Subject: [PATCH 07/32] Docs: Added s390x firewall info. Thanks to xet7 ! --- docs/Platforms/FOSS/s390x.md | 37 +++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/docs/Platforms/FOSS/s390x.md b/docs/Platforms/FOSS/s390x.md index 7a83e69b0..ca727310a 100644 --- a/docs/Platforms/FOSS/s390x.md +++ b/docs/Platforms/FOSS/s390x.md @@ -14,7 +14,42 @@ - https://morethanmoore.substack.com/p/the-future-of-big-iron-telum-ii-and - https://news.ycombinator.com/item?id=41846592 -*** +## s390x Ubuntu Firewall + +Updates and reboot: +``` +sudo apt update +sudo apt -y dist-upgrade +sudo snap refresh +sudo reboot +``` +Firewall and Mosh: +``` +sudo apt -y install ufw mosh +sudo ufw allow https +sudo ufw allow http +sudo ufw allow ssh +sudo ufw allow mosh +sudo ufw enable +``` + +## s390x RHEL 9.1 Firewall + +Updates and reboot: +``` +sudo dnf upgrade +sudo reboot +``` +Firewall and Mosh: +``` +sudo dnf -y install mosh +sudo systemctl enable --now firewalld +sudo firewall-cmd --permanent --add-service=http +sudo firewall-cmd --permanent --add-service=https +sudo firewall-cmd --permanent --add-service=ssh +sudo firewall-cmd --permanent --add-service=mosh +sudo firewall-cmd --reload +``` ## Petclinic s390x From 71d84f58a429ab9715da60c5154ec26724f1dba5 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Mon, 12 Jan 2026 15:26:24 +0200 Subject: [PATCH 08/32] Updated ChangeLog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 992b5e785..21ba68959 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,8 @@ and adds the following updates: [Part 1](https://github.com/wekan/wekan/commit/9fb1aeb8272b011c3d0b6b2c26ff7cb498c7b37f), [Part 2](https://github.com/wekan/wekan/commit/f198421f10dd3be9d58f64a242d12ea1ef45fee3). Thanks to juri_ at WeKan Libera.Chat IRC and xet7. +- [Added s390x firewall Docs](https://github.com/wekan/wekan/commit/ec7c0e6dc3641f43b1a110d285f6ef15c146584a). + Thanks to xet7. and fixes the following bugs: From 715d47dd2c2628d3a5d8542d5185bdfdfa795cbf Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 13 Jan 2026 03:50:52 +0200 Subject: [PATCH 09/32] Added FerretDB2/PostgreSQL Docs. Part 3. Thanks to juri_ at WeKan Libera.Chat IRC and xet7 ! --- docs/Platforms/FOSS/FerretDB2-PostgreSQL.md | 80 +++++++++++++++++++-- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/docs/Platforms/FOSS/FerretDB2-PostgreSQL.md b/docs/Platforms/FOSS/FerretDB2-PostgreSQL.md index 2158a601e..ec6fa65a4 100644 --- a/docs/Platforms/FOSS/FerretDB2-PostgreSQL.md +++ b/docs/Platforms/FOSS/FerretDB2-PostgreSQL.md @@ -7,13 +7,13 @@ git clone --branch main --depth 1 https://github.com/wekan/wekan.git cd wekan sudo apt update sudo apt install -y build-essential gcc g++ make git curl wget \ -p7zip-full zip unzip unp npm p7zip-full +p7zip-full zip unzip unp npm sudo npm install -g n export N_NODE_MIRROR=https://github.com/wekan/node-v14-esm/releases/download sudo -E n 14.21.4 sudo npm -g install @mapbox/node-pre-gyp sudo npm -g install meteor@2.14 --unsafe-perm -export PATH=$PATH:/home/demo/.meteor +export PATH=$PATH:$HOME/.meteor meteor npm install production meteor build .build --directory --platforms=web.browser ``` @@ -22,7 +22,7 @@ meteor build .build --directory --platforms=web.browser ``` sudo bash -c 'curl -fsSL https://repo.pigsty.io/pig | bash' -pig repo add all -u +pig repo add pgsql -u pig ext install pg17 pig ext install documentdb ``` @@ -31,7 +31,7 @@ Edit `/etc/postgresql/17/main/postgresql.conf`, there set shared_preload_libraries = 'pg_cron, pg_documentdb, pg_documentdb_core' ``` edit `/etc/postgresql/17/main/pg_hba.conf` , -replace **scram-sha-256` with trust on the host lines for `127.0.0.1/32` and `::1/128` +replace `scram-sha-256` with trust on the host lines for `127.0.0.1/32` and `::1/128` Restart PostgreSQL: ``` @@ -65,6 +65,7 @@ sudo service ferretdb start ``` su - su - postgres +psql CREATE ROLE ferret WITH PASSWORD 'DB_PASSWORD_GOES_HERE'; CREATE DATABASE ferretdb; GRANT ALL PRIVILEGES ON DATABASE ferretdb TO ferret; @@ -79,6 +80,9 @@ GRANT ALL ON SCHEMA documentdb_data to ferret; GRANT documentdb_admin_role to ferret; ``` ## Launching WeKan + +a) At screen: + ``` export PATH=$HOME/.meteor cd ~/wekan @@ -90,6 +94,74 @@ ROOT_URL=https://wekan.example.com meteor run \ --port 8080 2>&1 | tee ../wekan-log.`date +%s`.txt ``` +b) SystemD Service: + +/etc/default/wekan: +``` +NODE_ENV=production +MAIL_FROM="WeKan kanban " +MAIL_URL=smtp://username:password@email-smtp.eu-west-1.amazonaws.com:587?tls={ciphers:"SSLv3"}&secureConnection=false +OAUTH2_AUTH_ENDPOINT=https://accounts.google.com/o/oauth2/v2/auth +OAUTH2_CLIENT_ID=example.apps.googleusercontent.com +OAUTH2_EMAIL_MAP=email +OAUTH2_ENABLED=true +OAUTH2_FULLNAME_MAP=name +OAUTH2_ID_MAP=sub +OAUTH2_REQUEST_PERMISSIONS="openid https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email" +OAUTH2_SECRET=topsecret +OAUTH2_TOKEN_ENDPOINT=https://oauth2.googleapis.com/token +OAUTH2_USERINFO_ENDPOINT=https://openidconnect.googleapis.com/v1/userinfo +OAUTH2_USERNAME_MAP=nickname +MONGO_LOG_DESTINATION=mongodb-log.txt +MONGODB_PORT=27017 +ROOT_URL=https://boards.example.com +WRITABLE_PATH=../files +MONGO_URL=mongodb://ferret:DB_PASSWORD_GOES_HERE@127.0.0.1:27017/wekan +WITH_API=true +RICHER_CARD_COMMENT_EDITOR=false +CARD_OPENED_WEBHOOK_ENABLED=false +BIGEVENTS_PATTERN=NONE +BROWSER_POLICY_ENABLED=true +TRUSTED_URL='' +WEBHOOKS_ATTRIBUTES='' +LDAP_BACKGROUND_SYNC_INTERVAL='' +``` +`/etc/systemd/system/wekan.service` running as user boards, +`sudo adduser boards` and copy files and update permissions +with `sudo chown boards:boards /home/boards -R` +``` +[Unit] +Description=The Wekan Service +After=syslog.target network.target + +[Service] +EnvironmentFile=/etc/default/wekan +User=boards +Group=boards +WorkingDirectory=/home/boards/repos/bundle +ExecStart=/usr/local/bin/node main.js +Restart=on-failure +SuccessExitStatus=143 +StandardOutput=file:/home/boards/repos/wekan-output-log.txt +StandardError=file:/home/boards/repos/wekan-error-log.txt + +[Install] +WantedBy=multi-user.target +``` +Then enable service: +``` +sudo systemctl enable wekan +sudo systemctl start wekan +``` +For SSL/TLS, I run Caddy at front of Node.js: +https://github.com/wekan/wekan/blob/main/docs/Webserver/Caddy.md + +Related is docs about Raspberry Pi: +https://github.com/wekan/wekan/blob/main/docs/Platforms/FOSS/RaspberryPi/Raspberry-Pi.md + +And also about Windows bundle: +https://github.com/wekan/wekan/blob/main/docs/Platforms/Propietary/Windows/Offline.md + ## Notes - Machine must have at least 3 gigs of ram. Crashes at npm installing meteor, with 384 megs. From 36cbd3a6061ba373fce91a5cbb2548f6311231b2 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 13 Jan 2026 03:52:46 +0200 Subject: [PATCH 10/32] Updated ChangeLog. --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21ba68959..ab49acea8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,7 +63,8 @@ and adds the following updates: Thanks to xet7. - Added FerretDB2/PostgreSQL Docs. [Part 1](https://github.com/wekan/wekan/commit/9fb1aeb8272b011c3d0b6b2c26ff7cb498c7b37f), - [Part 2](https://github.com/wekan/wekan/commit/f198421f10dd3be9d58f64a242d12ea1ef45fee3). + [Part 2](https://github.com/wekan/wekan/commit/f198421f10dd3be9d58f64a242d12ea1ef45fee3), + [Part 3](https://github.com/wekan/wekan/commit/9431b2d53014289bebb06567f5662fdcb6dd409c). Thanks to juri_ at WeKan Libera.Chat IRC and xet7. - [Added s390x firewall Docs](https://github.com/wekan/wekan/commit/ec7c0e6dc3641f43b1a110d285f6ef15c146584a). Thanks to xet7. From 4d4046986f61a14e2a267dda9d723689d0382f69 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 13 Jan 2026 16:12:01 +0200 Subject: [PATCH 11/32] Updated GitHub issue templates. Thanks to xet7 ! --- .github/ISSUE_TEMPLATE.md | 56 ------------------ .github/ISSUE_TEMPLATE/bug_report.md | 69 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 31 ++++++++++ .github/ISSUE_TEMPLATE/security-report.md | 14 +++++ .github/ISSUE_TEMPLATE/ucs-issue.md | 23 ++++++++ 5 files changed, 137 insertions(+), 56 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/security-report.md create mode 100644 .github/ISSUE_TEMPLATE/ucs-issue.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 07765d05f..000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,56 +0,0 @@ -## Issue - -UPGRADE: https://wekan.fi/upgrade/ - -Pull requests welcome to fix any broken links at docs directory, and organizing docs/Features and their screenshots to subdirectories of each feature. - -Please report these issues elsewhere: - -- SECURITY ISSUES, PGP EMAIL: https://github.com/wekan/wekan/blob/main/SECURITY.md -- UCS: https://github.com/wekan/univention/issues - -If WeKan Snap is slow, try this: https://github.com/wekan/wekan/wiki/Cron - -Please search existing Open and Closed issues, most questions have already been answered. - -If you can not login for any reason: https://github.com/wekan/wekan/wiki/Forgot-Password -Email settings, only SMTP MAIL_URL and MAIL_FROM are in use: -https://github.com/wekan/wekan/wiki/Troubleshooting-Mail - -### Server Setup Information - -Please anonymize info, and do not any of your Wekan board URLs, passwords, -API tokens etc to this public issue. - -* Did you test in newest Wekan?: -* Did you configure root-url correctly so Wekan cards open correctly (see https://github.com/wekan/wekan/wiki/Settings)? -* Operating System: -* Deployment Method (Snap/Docker/Sandstorm/bundle/source): -* Http frontend if any (Caddy, Nginx, Apache, see config examples from Wekan GitHub wiki first): -* Node.js Version: -* MongoDB Version: -* What webbrowser version are you using (Wekan should work on all modern browsers that support Javascript)? - -### Problem description - -Add a recorded animated gif (e.g. with https://github.com/phw/peek) about -how it works currently, and screenshot mockups how it should work. - - -#### Reproduction Steps - - - -#### Logs - -Check Right Click / Inspect / Console in you browser - generally Chromium -based browsers show more detailed info than Firefox based browsers. - -Please anonymize logs. - -Snap: sudo snap logs wekan.wekan - -Docker: sudo docker logs wekan-app - -If logs are very long, attach them in .zip file - diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..6b2b93b69 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,69 @@ +name: 🐛 Bug Report +description: Report a bug to help improve Wekan +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thanks for reporting a bug! Please ensure you are using the [latest version of WeKan](https://wekan.fi/install) and [Upgrade](https://wekan.fi/upgrade) before submitting. + - type: textarea + id: description + attributes: + label: Bug Description + description: A clear and concise description of what the bug is. Mention versions of WeKan, Node.js, database name and version, frontend webserver version like Caddy etc. + validations: + required: true + - type: dropdown + id: platform + attributes: + label: Platform / Installation Method + options: + - Snap Stable + - Snap Candidate + - Docker + - Sandstorm + - Source (Meteor) + - Windows + - Mac + - Other + validations: + required: true + - type: dropdown + id: CPU + attributes: + label: CPU + options: + - amd64 + - arm64 + - s390x + - Other + validations: + required: true + - type: textarea + id: reproduction + attributes: + label: Steps to Reproduce + description: How can we recreate this issue? + placeholder: | + 1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + 4. See error + validations: + required: true + - type: textarea + id: logs + attributes: + label: Relevant Logs + description: | + - Please paste any relevant anonymized server logs or browser console errors here. + - Snap: sudo snap logs wekan.wekan + - Docker: sudo docker logs wekan-app + - If logs are very long, attach them in .zip file + render: shell + - type: textarea + id: context + attributes: + label: Additional Context + description: Add any other context, anonymized screenshots or GIF video about the bug, and screenshot mockups about how it should work. + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..53e349829 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,31 @@ +name: ✨ Feature Request +description: Suggest a new feature for Wekan +labels: ["Feature:Request"] +body: + - type: textarea + id: feature-description + attributes: + label: Problem Statement + description: Is your feature request related to a problem? Please describe. + placeholder: I'm always frustrated when [...] + validations: + required: true + - type: textarea + id: solution + attributes: + label: Proposed Solution + description: A clear and concise description of what you want to happen. + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives Considered + description: Any alternative solutions or features you've considered. + - type: textarea + id: context + attributes: + label: Additional Context + description: Add any other context, like anonymized screenshot mockups about how it should work. + + diff --git a/.github/ISSUE_TEMPLATE/security-report.md b/.github/ISSUE_TEMPLATE/security-report.md new file mode 100644 index 000000000..c08310ed2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/security-report.md @@ -0,0 +1,14 @@ +name: đŸ›Ąī¸ Security Issue +description: Report a security vulnerability +labels: ["security", "critical"] +body: + - type: markdown + attributes: + value: | + ## âš ī¸ IMPORTANT: Please do not report security vulnerabilities via public issues. + + To protect the Wekan community, we ask that you report security bugs privately. This allows us to fix the issue before it can be exploited by malicious actors. + + ### How to report: + Please read our **[Security Policy (SECURITY.md)](https://github.com/wekan/wekan/blob/main/SECURITY.md)** for the official reporting process and contact information. + diff --git a/.github/ISSUE_TEMPLATE/ucs-issue.md b/.github/ISSUE_TEMPLATE/ucs-issue.md new file mode 100644 index 000000000..665ece6f1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/ucs-issue.md @@ -0,0 +1,23 @@ +name: đŸ—ŗī¸ Univention (UCS) Issue +description: Problems specifically related to the Wekan app on Univention Corporate Server +labels: ["UCS"] +body: + - type: markdown + attributes: + value: | + ## 🛑 Is this a UCS-specific issue? + + If your issue is related to the **Univention Corporate Server (UCS) integration**, packaging, or installation via the Univention App Center, it should be reported in the dedicated Univention repository. + + ### âžĄī¸ [Report UCS Issues Here](https://github.com/wekan/univention/issues) + + --- + **Why?** + Reporting there ensures that the maintainers specifically focused on the UCS environment see your request. + + If you are certain this is a **core Wekan bug** that affects all platforms (Docker, Snap, etc.), please go back and use the standard [Bug Report](https://github.com/wekan/wekan/issues/new?template=bug-report.yml) template. + - type: textarea + id: ucs-details + attributes: + label: Brief Description (Optional) + description: If you still wish to post here, please provide a brief summary of why this is a core Wekan issue and not a UCS-specific integration bug. From c326e58d5e9afbe43fd56ee45a7efbeaf1fcce24 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 13 Jan 2026 16:13:44 +0200 Subject: [PATCH 12/32] Updated ChangeLog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab49acea8..aa66fee13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,8 @@ and adds the following updates: Thanks to juri_ at WeKan Libera.Chat IRC and xet7. - [Added s390x firewall Docs](https://github.com/wekan/wekan/commit/ec7c0e6dc3641f43b1a110d285f6ef15c146584a). Thanks to xet7. +- [Updated GitHub issue templates](https://github.com/wekan/wekan/commit/bd37b88e4d508c1f2712184a27dbbfd9df0e4c4e). + Thanks to xet7. and fixes the following bugs: From b5177e387033fb049cd272094dfc1307279e7ba2 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 13 Jan 2026 16:21:44 +0200 Subject: [PATCH 13/32] Added FerretDB2/PostgreSQL Docs. Part 4. Thanks to juri_ at WeKan Libera.Chat IRC and xet7 ! --- docs/Platforms/FOSS/FerretDB2-PostgreSQL.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/Platforms/FOSS/FerretDB2-PostgreSQL.md b/docs/Platforms/FOSS/FerretDB2-PostgreSQL.md index ec6fa65a4..c5835818c 100644 --- a/docs/Platforms/FOSS/FerretDB2-PostgreSQL.md +++ b/docs/Platforms/FOSS/FerretDB2-PostgreSQL.md @@ -1,7 +1,11 @@ # Install WeKan, FerretDB 2, PostgreSQL +- https://blog.ferretdb.io/building-project-management-stack-wekan-ferretdb/ + ## WeKan +- Alternatively, use wekan-app Docker container from https://raw.githubusercontent.com/wekan/wekan/refs/heads/main/docker-compose.yml + ``` git clone --branch main --depth 1 https://github.com/wekan/wekan.git cd wekan From 0ffb0fa24f0f81ddf4233276b009f4e04fedba21 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 13 Jan 2026 16:23:12 +0200 Subject: [PATCH 14/32] Updated ChangeLog. --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa66fee13..4dce2d514 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,7 +64,8 @@ and adds the following updates: - Added FerretDB2/PostgreSQL Docs. [Part 1](https://github.com/wekan/wekan/commit/9fb1aeb8272b011c3d0b6b2c26ff7cb498c7b37f), [Part 2](https://github.com/wekan/wekan/commit/f198421f10dd3be9d58f64a242d12ea1ef45fee3), - [Part 3](https://github.com/wekan/wekan/commit/9431b2d53014289bebb06567f5662fdcb6dd409c). + [Part 3](https://github.com/wekan/wekan/commit/9431b2d53014289bebb06567f5662fdcb6dd409c), + [Part 4](https://github.com/wekan/wekan/commit/ffd37b9fd9171ca22973d6d0a62baef4a18494f5). Thanks to juri_ at WeKan Libera.Chat IRC and xet7. - [Added s390x firewall Docs](https://github.com/wekan/wekan/commit/ec7c0e6dc3641f43b1a110d285f6ef15c146584a). Thanks to xet7. From 32733023ca07f3727f147f759b335ccdb2772a59 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 13 Jan 2026 16:30:33 +0200 Subject: [PATCH 15/32] Updated ChangeLog. --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dce2d514..5c3ed149d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,8 +14,8 @@ Newest WeKan at these platforms: Fixing other platforms In Progress. -- Node.js 14.x at https://github.com/wekan/node-v14-esm/releases/tag/v14.21.4 and https://nodejs.org/dist/latest-v14.x/ -- MongoDB 6.x and 7.x, or FerretDB/PostgreSQL https://blog.ferretdb.io/building-project-management-stack-wekan-ferretdb/ +- [Node.js 14.21.4](https://github.com/wekan/node-v14-esm/releases/tag/v14.21.4) or [Node.js 14.21.3](https://nodejs.org/dist/latest-v14.x/) +- MongoDB 6.x and 7.x, or [FerretDB2/PostgreSQL](https://github.com/wekan/wekan/blob/main/docs/Platforms/FOSS/FerretDB2-PostgreSQL.md) [Upgrade WeKan](https://wekan.fi/upgrade/) From 4a373b45e1f614ed8772e3785d70da381efddf17 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 13 Jan 2026 16:33:58 +0200 Subject: [PATCH 16/32] Updated GitHub issue templates. Part 2. Thanks to xet7 ! --- .github/ISSUE_TEMPLATE/{bug_report.md => bug_report.yml} | 0 .../ISSUE_TEMPLATE/{feature_request.md => feature_request.yml} | 0 .../ISSUE_TEMPLATE/{security-report.md => security-report.yml} | 0 .github/ISSUE_TEMPLATE/{ucs-issue.md => ucs-issue.yml} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename .github/ISSUE_TEMPLATE/{bug_report.md => bug_report.yml} (100%) rename .github/ISSUE_TEMPLATE/{feature_request.md => feature_request.yml} (100%) rename .github/ISSUE_TEMPLATE/{security-report.md => security-report.yml} (100%) rename .github/ISSUE_TEMPLATE/{ucs-issue.md => ucs-issue.yml} (100%) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/bug_report.md rename to .github/ISSUE_TEMPLATE/bug_report.yml diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/feature_request.md rename to .github/ISSUE_TEMPLATE/feature_request.yml diff --git a/.github/ISSUE_TEMPLATE/security-report.md b/.github/ISSUE_TEMPLATE/security-report.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/security-report.md rename to .github/ISSUE_TEMPLATE/security-report.yml diff --git a/.github/ISSUE_TEMPLATE/ucs-issue.md b/.github/ISSUE_TEMPLATE/ucs-issue.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/ucs-issue.md rename to .github/ISSUE_TEMPLATE/ucs-issue.yml From c740474145148f69eb415e196d2c2ebb9e0b1323 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 13 Jan 2026 16:35:28 +0200 Subject: [PATCH 17/32] Updated ChangeLog. --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c3ed149d..fb1b1cc28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,7 +69,9 @@ and adds the following updates: Thanks to juri_ at WeKan Libera.Chat IRC and xet7. - [Added s390x firewall Docs](https://github.com/wekan/wekan/commit/ec7c0e6dc3641f43b1a110d285f6ef15c146584a). Thanks to xet7. -- [Updated GitHub issue templates](https://github.com/wekan/wekan/commit/bd37b88e4d508c1f2712184a27dbbfd9df0e4c4e). +- Updated GitHub issue templates. + [Part 1](https://github.com/wekan/wekan/commit/bd37b88e4d508c1f2712184a27dbbfd9df0e4c4e), + [Part 2](https://github.com/wekan/wekan/commit/cf6e6914989a7bf1d79f8b753a0a576c54ad7580). Thanks to xet7. and fixes the following bugs: From 8a0568ce016f310f0e0425502d6e975a01e3afcd Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 13 Jan 2026 16:38:12 +0200 Subject: [PATCH 18/32] Updated GitHub issue templates. Part 3. Thanks to xet7 ! --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- .github/ISSUE_TEMPLATE/security-report.yml | 2 +- .github/ISSUE_TEMPLATE/ucs-issue.yml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 6b2b93b69..0d633d30f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,5 +1,5 @@ name: 🐛 Bug Report -description: Report a bug to help improve Wekan +description: Report a bug to help improve WeKan labels: ["bug"] body: - type: markdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 53e349829..99c26554c 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,5 +1,5 @@ name: ✨ Feature Request -description: Suggest a new feature for Wekan +description: Suggest a new feature for WeKan labels: ["Feature:Request"] body: - type: textarea diff --git a/.github/ISSUE_TEMPLATE/security-report.yml b/.github/ISSUE_TEMPLATE/security-report.yml index c08310ed2..fb4e5fbe6 100644 --- a/.github/ISSUE_TEMPLATE/security-report.yml +++ b/.github/ISSUE_TEMPLATE/security-report.yml @@ -7,7 +7,7 @@ body: value: | ## âš ī¸ IMPORTANT: Please do not report security vulnerabilities via public issues. - To protect the Wekan community, we ask that you report security bugs privately. This allows us to fix the issue before it can be exploited by malicious actors. + To protect the WeKan community, we ask that you report security bugs privately. This allows us to fix the issue before it can be exploited by malicious actors. ### How to report: Please read our **[Security Policy (SECURITY.md)](https://github.com/wekan/wekan/blob/main/SECURITY.md)** for the official reporting process and contact information. diff --git a/.github/ISSUE_TEMPLATE/ucs-issue.yml b/.github/ISSUE_TEMPLATE/ucs-issue.yml index 665ece6f1..a8358360f 100644 --- a/.github/ISSUE_TEMPLATE/ucs-issue.yml +++ b/.github/ISSUE_TEMPLATE/ucs-issue.yml @@ -1,5 +1,5 @@ name: đŸ—ŗī¸ Univention (UCS) Issue -description: Problems specifically related to the Wekan app on Univention Corporate Server +description: Problems specifically related to the WeKan app on Univention Corporate Server labels: ["UCS"] body: - type: markdown @@ -15,7 +15,7 @@ body: **Why?** Reporting there ensures that the maintainers specifically focused on the UCS environment see your request. - If you are certain this is a **core Wekan bug** that affects all platforms (Docker, Snap, etc.), please go back and use the standard [Bug Report](https://github.com/wekan/wekan/issues/new?template=bug-report.yml) template. + If you are certain this is a **core WeKan bug** that affects all platforms (Docker, Snap, etc.), please go back and use the standard [Bug Report](https://github.com/wekan/wekan/issues/new?template=bug-report.yml) template. - type: textarea id: ucs-details attributes: From a62b3c6ce059ebe94d92411a83c7a2855d366b45 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 13 Jan 2026 16:39:45 +0200 Subject: [PATCH 19/32] Updated ChangeLog. --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb1b1cc28..1ac1ecd42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,7 +71,8 @@ and adds the following updates: Thanks to xet7. - Updated GitHub issue templates. [Part 1](https://github.com/wekan/wekan/commit/bd37b88e4d508c1f2712184a27dbbfd9df0e4c4e), - [Part 2](https://github.com/wekan/wekan/commit/cf6e6914989a7bf1d79f8b753a0a576c54ad7580). + [Part 2](https://github.com/wekan/wekan/commit/cf6e6914989a7bf1d79f8b753a0a576c54ad7580), + [Part 3](https://github.com/wekan/wekan/commit/4a658dc02a770f8219669dc10bfe1077c760744f). Thanks to xet7. and fixes the following bugs: From c80d1aae4c3f3aa38c105c178eae79651e2147c3 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 13 Jan 2026 20:20:33 +0200 Subject: [PATCH 20/32] Swimlane drag button position improvements. Thanks to TDSCDMA and xet7 ! Related #6063 --- client/components/swimlanes/swimlanes.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/components/swimlanes/swimlanes.css b/client/components/swimlanes/swimlanes.css index e801654a4..6da140351 100644 --- a/client/components/swimlanes/swimlanes.css +++ b/client/components/swimlanes/swimlanes.css @@ -107,7 +107,9 @@ font-size: 22px; } .swimlane .swimlane-header-wrap .swimlane-header-handle { + position: absolute; top: calc(50% + 2px); + right: 60px; padding: 2px; font-size: clamp(16px, 3vw, 20px); transform: translateY(-50%); From 6ed8e54dacc73a9b01be3df2ba8d56f3d4ace34b Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 13 Jan 2026 20:23:08 +0200 Subject: [PATCH 21/32] Updated ChangeLog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ac1ecd42..9ed8d094e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,8 @@ and fixes the following bugs: - [Fix attachment download error with non-ASCII filenames](https://github.com/wekan/wekan/pull/6056). Thanks to brlin-tw. +- [Swimlane drag button position improvements](https://github.com/wekan/wekan/commit/376a30f8a9c5cc6b5341fda7336244ee1b9983fd). + Thanks to TDSCDMA and xet7. Thanks to above GitHub users for their contributions and translators for their translations. From ab49e09da6b8495673bc0c3d46fca58bff2dcbde Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 13 Jan 2026 21:22:39 +0200 Subject: [PATCH 22/32] Removed extra list borders. Thanks to TDSCDMA and xet7 ! Related #6063 --- client/components/boards/boardColors.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/components/boards/boardColors.css b/client/components/boards/boardColors.css index 146991d27..a0fb5f73c 100644 --- a/client/components/boards/boardColors.css +++ b/client/components/boards/boardColors.css @@ -1425,7 +1425,7 @@ THEME - Clear Blue } .board-color-clearblue .list { background: rgba(255,255,255,0.35); - margin: 10px; + margin: 10px 0; border: 0; border-radius: 14px; } @@ -2588,7 +2588,7 @@ THEME - Exodark background: #222; } .board-color-exodark .list { - margin: 10px; + margin: 10px 0; color: #fff; border-radius: 15px; background-color: #1c1c1c; From 40784b205441725e684c4ae0a19fbb87fe7d0376 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 13 Jan 2026 21:26:21 +0200 Subject: [PATCH 23/32] Updated ChangeLog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ed8d094e..4e5b967dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,6 +81,8 @@ and fixes the following bugs: Thanks to brlin-tw. - [Swimlane drag button position improvements](https://github.com/wekan/wekan/commit/376a30f8a9c5cc6b5341fda7336244ee1b9983fd). Thanks to TDSCDMA and xet7. +- [Removed extra list borders](https://github.com/wekan/wekan/commit/a4f8faa48e3fb6c617cf9c5a398bc7f85b8bae92). + Thanks to TDSCDMA and xet7. Thanks to above GitHub users for their contributions and translators for their translations. From 69e2ad1007c6e720c16d1e44fd0dec9ed2bd86ac Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 13 Jan 2026 22:24:02 +0200 Subject: [PATCH 24/32] Add back button texts to Filter, Search, Board View and MultiSelection. Thanks to audiocrush and xet7 ! Fixes #6066 --- client/components/boards/boardHeader.jade | 32 ++++++++++++++++------- client/components/main/header.css | 14 ++++++++++ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/client/components/boards/boardHeader.jade b/client/components/boards/boardHeader.jade index a5c61e12e..a85b98741 100644 --- a/client/components/boards/boardHeader.jade +++ b/client/components/boards/boardHeader.jade @@ -102,14 +102,16 @@ template(name="boardHeaderBar") a.board-header-btn.js-open-filter-view( title="{{#if Filter.isActive}}{{_ 'filter-on-desc'}}{{else}}{{_ 'filter'}}{{/if}}" - class="{{#if Filter.isActive}}emphasis{{/if}}") - | đŸ”Ŋ + class="{{#if Filter.isActive}}js-filter-active{{/if}}") + | đŸŽ›ī¸ + span {{#if Filter.isActive}}{{_ 'filter-on-desc'}}{{else}}{{_ 'filter'}}{{/if}} if Filter.isActive a.board-header-btn-close.js-filter-reset(title="{{_ 'filter-clear'}}") | ❌ a.board-header-btn.js-open-search-view(title="{{_ 'search'}}") - span.emoji-icon 🔍 + | 🔍 + span {{_ 'search'}} unless currentBoard.isTemplatesBoard a.board-header-btn.js-toggle-board-view( @@ -123,15 +125,27 @@ template(name="boardHeaderBar") | 📅 if $eq boardView 'board-view-gantt' | 📊 - + span + if $eq boardView 'board-view-swimlanes' + | {{_ 'board-view-swimlanes'}} + if $eq boardView 'board-view-lists' + | {{_ 'board-view-lists'}} + if $eq boardView 'board-view-cal' + | {{_ 'board-view-cal'}} + if $eq boardView 'board-view-gantt' + | {{_ 'board-view-gantt'}} if canModifyBoard a.board-header-btn.js-multiselection-activate( title="{{#if MultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}}" - class="{{#if MultiSelection.isActive}}emphasis{{/if}}") - | â˜‘ī¸ - if MultiSelection.isActive - a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}") - | ❌ + class="{{#if MultiSelection.isActive}}js-multiselection-active{{/if}}") + if MultiSelection.isActive + | đŸ—‚ī¸ + else + | đŸ—‚ī¸ + span {{#if MultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}} + if MultiSelection.isActive + a.board-header-btn-close.js-multiselection-reset(title="{{_ 'multi-selection-off'}}") + | ❌ .separator a.board-header-btn.js-toggle-sidebar(title="{{_ 'sidebar-open'}} {{_ 'or'}} {{_ 'sidebar-close'}}") diff --git a/client/components/main/header.css b/client/components/main/header.css index ee17d00c3..14045d259 100644 --- a/client/components/main/header.css +++ b/client/components/main/header.css @@ -78,12 +78,26 @@ #header #header-main-bar .board-header-btn .board-header-btn-close i.fa { margin: 0 6px; } +#header #header-main-bar .board-header-btn .board-header-btn-icon { + float: left; + display: block; + line-height: 28px; + color: #27ae60; + margin: 0 10px; + cursor: pointer; +} #header #header-main-bar .board-header-btn.is-active, #header #header-main-bar h1.is-clickable.is-active, #header #header-main-bar .board-header-btn:hover:not(.is-disabled), #header #header-main-bar h1.is-clickable:hover:not(.is-disabled) { background: rgba(0,0,0,0.15); } +#header #header-main-bar .board-header-btn.js-multiselection-active { + background: #1a5080; +} +#header #header-main-bar .board-header-btn.js-multiselection-active:hover { + background: #0f3a5f; +} #header #header-main-bar .separator { margin: 2px 4px; border-left: 1px solid rgba(255,255,255,0.3); From b3cb47ac47d45872088af1d99d2de8d947f9cda7 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 13 Jan 2026 22:26:45 +0200 Subject: [PATCH 25/32] Updated ChangeLog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e5b967dd..927ae87c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,8 @@ and fixes the following bugs: Thanks to TDSCDMA and xet7. - [Removed extra list borders](https://github.com/wekan/wekan/commit/a4f8faa48e3fb6c617cf9c5a398bc7f85b8bae92). Thanks to TDSCDMA and xet7. +- [Add back button texts to Filter, Search, Board View and MultiSelection](https://github.com/wekan/wekan/commit/dac7e17500de97febc7ad8f84cd1bf5edab27c52). + Thanks to audiocrush and xet7. Thanks to above GitHub users for their contributions and translators for their translations. From 69f54dddaf4a76a2a3f24c43c70e4d325cbf14ea Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 13 Jan 2026 22:35:36 +0200 Subject: [PATCH 26/32] Removed extra pipe character from UI. Thanks to xet7 ! --- client/components/sidebar/sidebar.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/components/sidebar/sidebar.jade b/client/components/sidebar/sidebar.jade index 02ef256ef..889b7a6f1 100644 --- a/client/components/sidebar/sidebar.jade +++ b/client/components/sidebar/sidebar.jade @@ -22,7 +22,7 @@ template(name="sidebar") // i.fa.fa-navicon unless isDefaultView h2 - a.js-back-home | âŦ…ī¸ + a.js-back-home âŦ…ī¸ = getViewTitle if isOpen +Template.dynamic(template=getViewTemplate) From 5b47340b76a7caf24e122c8fc708e1207290cd1e Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 13 Jan 2026 22:38:52 +0200 Subject: [PATCH 27/32] Updated ChangeLog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 927ae87c2..57f5a0b26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,6 +85,8 @@ and fixes the following bugs: Thanks to TDSCDMA and xet7. - [Add back button texts to Filter, Search, Board View and MultiSelection](https://github.com/wekan/wekan/commit/dac7e17500de97febc7ad8f84cd1bf5edab27c52). Thanks to audiocrush and xet7. +- [Removed extra pipe character from UI](https://github.com/wekan/wekan/commit/66e79d2df7ecf5526dbae360cf93352657db7fcf). + Thanks to xet7. Thanks to above GitHub users for their contributions and translators for their translations. From 6ad9770bb61824012b3078044b9cd33675b68833 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 13 Jan 2026 22:50:39 +0200 Subject: [PATCH 28/32] Changed find.sh to not search from translations, because I'm trying to find code, not translations. Thanks to xet7 ! --- find.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/find.sh b/find.sh index e23b4eba6..643185fd2 100755 --- a/find.sh +++ b/find.sh @@ -15,4 +15,4 @@ fi #find . | grep -v node_modules | grep -v .build | grep -v .meteor | grep -v .git | xargs grep --no-messages $1 | less #find . -print0 | grep -v node_modules | grep -v .build | grep -v .meteor | grep -v .git | xargs -0 grep --no-messages $1 | less -find . -type f -not -path '*/node_modules/*' -not -path '*/.build/*' -not -path '*/.meteor/*' -not -path '*/.git/*' -exec grep -I --no-messages "$1" {} + | less +find . -type f -not -path '*/node_modules/*' -not -path '*/.build/*' -not -path '*/.meteor/*' -not -path '*/.git/*' -not -path '*/imports/i18n/data/*' -exec grep -I --no-messages "$1" {} + | less From eca42f32bb10490f6967c5b18b0a0b43ec86ae04 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 13 Jan 2026 22:52:51 +0200 Subject: [PATCH 29/32] Updated ChangeLog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57f5a0b26..8b71acd44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,8 @@ and fixes the following bugs: Thanks to audiocrush and xet7. - [Removed extra pipe character from UI](https://github.com/wekan/wekan/commit/66e79d2df7ecf5526dbae360cf93352657db7fcf). Thanks to xet7. +- [Changed find.sh to not search from translations, because I'm trying to find code, not translations](https://github.com/wekan/wekan/commit/58ae2b6c6848235132308611fe3083533e120f72). + Thanks to xet7. Thanks to above GitHub users for their contributions and translators for their translations. From e89f4d260c216a75069801efff379a0f73044a36 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Wed, 14 Jan 2026 00:38:56 +0200 Subject: [PATCH 30/32] Fixed Change Avatar. Improved Admin Panel: People columns order, selected tab background color. Thanks to xet7 ! --- client/components/settings/peopleBody.jade | 63 +++++++++---------- client/components/settings/peopleBody.js | 35 +++++------ client/components/settings/settingHeader.css | 1 + client/components/settings/settingHeader.jade | 12 ++-- client/components/settings/settingHeader.js | 20 ++++++ client/components/users/userAvatar.css | 3 + client/components/users/userAvatar.jade | 20 +++--- client/components/users/userAvatar.js | 52 +++++++-------- imports/i18n/data/en.i18n.json | 2 + models/users.js | 60 ++++++++++++++++++ 10 files changed, 169 insertions(+), 99 deletions(-) create mode 100644 client/components/settings/settingHeader.js diff --git a/client/components/settings/peopleBody.jade b/client/components/settings/peopleBody.jade index dda0197d9..59b4718cc 100644 --- a/client/components/settings/peopleBody.jade +++ b/client/components/settings/peopleBody.jade @@ -135,16 +135,15 @@ template(name="peopleGeneral") thead tr th - +selectAllUser - th {{_ 'accounts-lockout-status'}} - th {{_ 'admin-people-active-status'}} + +newUserRow th {{_ 'username'}} - th {{_ 'fullname'}} - th {{_ 'admin'}} th {{_ 'email'}} + th {{_ 'admin'}} + th {{_ 'admin-people-active-status'}} + th {{_ 'accounts-lockout-status'}} th {{_ 'createdAt'}} th - +newUserRow + +selectAllUser tbody tr each user in peopleList @@ -239,22 +238,12 @@ template(name="teamRow") template(name="peopleRow") tr - if userData.loginDisabled - td - input.selectUserChkBox(type="checkbox", disabled="disabled", id="{{userData._id}}") - else - td - input.selectUserChkBox(type="checkbox", id="{{userData._id}}") - td.account-status - if isUserLocked - span.text-red.js-toggle-lock-status.emoji-icon(data-user-id=userData._id, data-is-locked="true", title="{{_ 'accounts-lockout-click-to-unlock'}}") 🔒 - else - span.text-green.js-toggle-lock-status.emoji-icon(data-user-id=userData._id, data-is-locked="false", title="{{_ 'accounts-lockout-user-unlocked'}}") 🔓 - td.account-active-status - if userData.loginDisabled - span.text-red.js-toggle-active-status(data-user-id=userData._id, data-is-active="false", title="{{_ 'admin-people-user-inactive'}}") đŸšĢ - else - span.text-green.js-toggle-active-status(data-user-id=userData._id, data-is-active="true", title="{{_ 'admin-people-user-active'}}") ✅ + td + a.edit-user + | âœī¸ + | {{_ 'edit'}} + a.more-settings-user + | ⋯ if userData.loginDisabled td.username {{ userData.username }} else if isUserLocked @@ -262,9 +251,9 @@ template(name="peopleRow") else td.username {{ userData.username }} if userData.loginDisabled - td {{ userData.profile.fullname }} + td {{ userData.emails.[0].address }} else - td {{ userData.profile.fullname }} + td {{ userData.emails.[0].address }} if userData.loginDisabled td if userData.isAdmin @@ -277,20 +266,26 @@ template(name="peopleRow") | {{_ 'yes'}} else | {{_ 'no'}} - if userData.loginDisabled - td {{ userData.emails.[0].address }} - else - td {{ userData.emails.[0].address }} + td.account-active-status + if userData.loginDisabled + span.text-red.js-toggle-active-status(data-user-id=userData._id, data-is-active="false", title="{{_ 'admin-people-user-inactive'}}") đŸšĢ + else + span.text-green.js-toggle-active-status(data-user-id=userData._id, data-is-active="true", title="{{_ 'admin-people-user-active'}}") ✅ + td.account-status + if isUserLocked + span.text-red.js-toggle-lock-status.emoji-icon(data-user-id=userData._id, data-is-locked="true", title="{{_ 'accounts-lockout-click-to-unlock'}}") 🔒 + else + span.text-green.js-toggle-lock-status.emoji-icon(data-user-id=userData._id, data-is-locked="false", title="{{_ 'accounts-lockout-user-unlocked'}}") 🔓 if userData.loginDisabled td {{ moment userData.createdAt 'LLL' }} else td {{ moment userData.createdAt 'LLL' }} - td - a.edit-user - | âœī¸ - | {{_ 'edit'}} - a.more-settings-user - | ⋯ + if userData.loginDisabled + td + input.selectUserChkBox(type="checkbox", disabled="disabled", id="{{userData._id}}") + else + td + input.selectUserChkBox(type="checkbox", id="{{userData._id}}") template(name="editOrgPopup") form diff --git a/client/components/settings/peopleBody.js b/client/components/settings/peopleBody.js index 2e3601f23..e06ef49d6 100644 --- a/client/components/settings/peopleBody.js +++ b/client/components/settings/peopleBody.js @@ -840,16 +840,7 @@ Template.editUserPopup.events({ ? user.emails[0].address.toLowerCase() : false); - Users.update(this.userId, { - $set: { - 'profile.fullname': fullname, - isAdmin: isAdmin === 'true', - loginDisabled: isActive === 'true', - authenticationMethod: authentication, - importUsernames: Users.parseImportUsernames(importUsernames), - }, - }); - + // Build user teams list let userTeamsList = userTeams.split(","); let userTeamsIdsList = userTeamsIds.split(","); let userTms = []; @@ -862,12 +853,7 @@ Template.editUserPopup.events({ } } - Users.update(this.userId, { - $set:{ - teams: userTms - } - }); - + // Build user orgs list let userOrgsList = userOrgs.split(","); let userOrgsIdsList = userOrgsIds.split(","); let userOrganizations = []; @@ -880,9 +866,20 @@ Template.editUserPopup.events({ } } - Users.update(this.userId, { - $set:{ - orgs: userOrganizations + // Update user via Meteor method (for admin to edit other users) + const updateData = { + fullname: fullname, + isAdmin: isAdmin === 'true', + loginDisabled: isActive === 'true', + authenticationMethod: authentication, + importUsernames: Users.parseImportUsernames(importUsernames), + teams: userTms, + orgs: userOrganizations, + }; + + Meteor.call('editUser', this.userId, updateData, (error) => { + if (error) { + console.error('Error updating user:', error); } }); diff --git a/client/components/settings/settingHeader.css b/client/components/settings/settingHeader.css index dbd8582e3..5b880b9f2 100644 --- a/client/components/settings/settingHeader.css +++ b/client/components/settings/settingHeader.css @@ -1,4 +1,5 @@ #header #header-main-bar .setting-header-btn { + border-radius: 3px; color: #f2f2f2; margin-left: 20px; padding-right: 10px; diff --git a/client/components/settings/settingHeader.jade b/client/components/settings/settingHeader.jade index 7ce77ba4e..9fbc89394 100644 --- a/client/components/settings/settingHeader.jade +++ b/client/components/settings/settingHeader.jade @@ -4,27 +4,27 @@ template(name="settingHeaderBar") .setting-header-btns.left if currentUser - a.setting-header-btn.settings(href="{{pathFor 'setting'}}") + a.setting-header-btn.settings(class=isSettingsActive href="{{pathFor 'setting'}}") span.emoji-icon âš™ī¸ span {{_ 'settings'}} - a.setting-header-btn.people(href="{{pathFor 'people'}}") + a.setting-header-btn.people(class=isPeopleActive href="{{pathFor 'people'}}") span.emoji-icon đŸ‘Ĩ span {{_ 'people'}} - a.setting-header-btn.informations(href="{{pathFor 'admin-reports'}}") + a.setting-header-btn.informations(class=isAdminReportsActive href="{{pathFor 'admin-reports'}}") span.emoji-icon 📋 span {{_ 'reports'}} - a.setting-header-btn.informations(href="{{pathFor 'attachments'}}") + a.setting-header-btn.informations(class=isAttachmentsActive href="{{pathFor 'attachments'}}") span.emoji-icon 📎 span {{_ 'attachments'}} - a.setting-header-btn.informations(href="{{pathFor 'translation'}}") + a.setting-header-btn.informations(class=isTranslationActive href="{{pathFor 'translation'}}") span.emoji-icon 🔤 span {{_ 'translation'}} - a.setting-header-btn.informations(href="{{pathFor 'information'}}") + a.setting-header-btn.informations(class=isInformationActive href="{{pathFor 'information'}}") span.emoji-icon â„šī¸ span {{_ 'info'}} diff --git a/client/components/settings/settingHeader.js b/client/components/settings/settingHeader.js new file mode 100644 index 000000000..673108d03 --- /dev/null +++ b/client/components/settings/settingHeader.js @@ -0,0 +1,20 @@ +Template.settingHeaderBar.helpers({ + isSettingsActive() { + return FlowRouter.getRouteName() === 'setting' ? 'active' : ''; + }, + isPeopleActive() { + return FlowRouter.getRouteName() === 'people' ? 'active' : ''; + }, + isAdminReportsActive() { + return FlowRouter.getRouteName() === 'admin-reports' ? 'active' : ''; + }, + isAttachmentsActive() { + return FlowRouter.getRouteName() === 'attachments' ? 'active' : ''; + }, + isTranslationActive() { + return FlowRouter.getRouteName() === 'translation' ? 'active' : ''; + }, + isInformationActive() { + return FlowRouter.getRouteName() === 'information' ? 'active' : ''; + }, +}); diff --git a/client/components/users/userAvatar.css b/client/components/users/userAvatar.css index 7e2b2a923..27d8993b7 100644 --- a/client/components/users/userAvatar.css +++ b/client/components/users/userAvatar.css @@ -23,6 +23,9 @@ background-color: #dbdbdb; color: #444; position: absolute; + display: flex; + align-items: center; + justify-content: center; } .member .avatar.avatar-image { object-fit: cover; diff --git a/client/components/users/userAvatar.jade b/client/components/users/userAvatar.jade index e00fc188f..652c0eedf 100644 --- a/client/components/users/userAvatar.jade +++ b/client/components/users/userAvatar.jade @@ -17,7 +17,7 @@ template(name="userAvatar") template(name="userAvatarInitials") svg.avatar.avatar-initials(viewBox="0 0 {{viewPortWidth}} 15") - text(x="50%" y="13" text-anchor="middle")= initials + text(x="50%" y="11" text-anchor="middle" dominant-baseline="middle" font-size="16")= initials template(name="orgAvatar") a.member.orgOrTeamMember(class="js-member" title="{{orgData.orgDisplayName}}") @@ -53,7 +53,7 @@ template(name="boardTeamRow") template(name="boardOrgName") svg.avatar.avatar-initials(viewBox="0 0 {{orgViewPortWidth}} 15") - text(x="50%" y="13" text-anchor="middle")= orgName + text(x="50%" y="11" text-anchor="middle" dominant-baseline="middle" font-size="16")= orgName template(name="teamAvatar") a.member.orgOrTeamMember(class="js-member" title="{{teamData.teamDisplayName}}") @@ -61,7 +61,7 @@ template(name="teamAvatar") template(name="boardTeamName") svg.avatar.avatar-initials(viewBox="0 0 {{teamViewPortWidth}} 15") - text(x="50%" y="13" text-anchor="middle")= teamName + text(x="50%" y="11" text-anchor="middle" dominant-baseline="middle" font-size="16")= teamName template(name="userPopup") .board-member-menu @@ -88,13 +88,11 @@ template(name="changeAvatarPopup") li: a.js-select-avatar .member img.avatar.avatar-image(src="{{link}}") - | {{_ 'uploaded-avatar'}} if isSelected | ✅ p.sub-name - unless isSelected - a.js-delete-avatar {{_ 'delete'}} - | - + a.js-delete-avatar {{_ 'delete'}} + | - = name li: a.js-select-initials .member @@ -102,7 +100,7 @@ template(name="changeAvatarPopup") | {{_ 'initials' }} if noAvatarUrl | ✅ - p.sub-name {{_ 'default-avatar'}} + p.sub-name {{_ 'default-avatar'}} input.hide.js-upload-avatar-input(accept="image/*;capture=camera" type="file") if Meteor.settings.public.avatarsUploadMaxSize | {{_ 'max-avatar-filesize'}} {{Meteor.settings.public.avatarsUploadMaxSize}} @@ -113,7 +111,11 @@ template(name="changeAvatarPopup") | {{_ 'invalid-file'}} button.full.js-upload-avatar | 📤 - | {{_ 'upload-avatar'}} + | {{_ 'upload-avatar' }} + +template(name="deleteAvatarPopup") + p {{_ 'delete-avatar-confirm'}} + button.js-confirm.negate.full(type="submit") {{_ 'delete'}} template(name="cardMemberPopup") .board-member-menu diff --git a/client/components/users/userAvatar.js b/client/components/users/userAvatar.js index f2db90ee3..c81ee7714 100644 --- a/client/components/users/userAvatar.js +++ b/client/components/users/userAvatar.js @@ -187,7 +187,7 @@ BlazeComponent.extendComponent({ }, uploadedAvatars() { - const ret = ReactiveCache.getAvatars({ userId: Meteor.userId() }, {}, true).each(); + const ret = ReactiveCache.getAvatars({ userId: Meteor.userId() }, {}, true); return ret; }, @@ -205,7 +205,11 @@ BlazeComponent.extendComponent({ }, setAvatar(avatarUrl) { - ReactiveCache.getCurrentUser().setAvatarUrl(avatarUrl); + Meteor.call('setAvatarUrl', avatarUrl, (err) => { + if (err) { + this.setError(err.reason || 'Error setting avatar'); + } + }); }, setError(error) { @@ -234,44 +238,30 @@ BlazeComponent.extendComponent({ uploader.start(); } }, - 'click .js-select-avatar'() { - const avatarUrl = this.currentData().link(); - this.setAvatar(avatarUrl); + 'click .js-select-avatar'(event) { + event.preventDefault(); + event.stopPropagation(); + const data = Blaze.getData(event.currentTarget); + if (data && typeof data.link === 'function') { + const avatarUrl = data.link(); + this.setAvatar(avatarUrl); + } }, - 'click .js-select-initials'() { + 'click .js-select-initials'(event) { + event.preventDefault(); + event.stopPropagation(); this.setAvatar(''); }, - 'click .js-delete-avatar'(event) { - Avatars.remove(this.currentData()._id); + 'click .js-delete-avatar': Popup.afterConfirm('deleteAvatar', function(event) { + Avatars.remove(this._id); + Popup.back(); event.stopPropagation(); - }, + }), }, ]; }, }).register('changeAvatarPopup'); -Template.cardMembersPopup.helpers({ - isCardMember() { - const card = Template.parentData(); - const cardMembers = card.getMembers(); - - return _.contains(cardMembers, this.userId); - }, - - user() { - return ReactiveCache.getUser(this.userId); - }, -}); - -Template.cardMembersPopup.events({ - 'click .js-select-member'(event) { - const card = Utils.getCurrentCard(); - const memberId = this.userId; - card.toggleMember(memberId); - event.preventDefault(); - }, -}); - Template.cardMemberPopup.helpers({ user() { return ReactiveCache.getUser(this.userId); diff --git a/imports/i18n/data/en.i18n.json b/imports/i18n/data/en.i18n.json index d48099de1..74bb4c655 100644 --- a/imports/i18n/data/en.i18n.json +++ b/imports/i18n/data/en.i18n.json @@ -281,6 +281,8 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", + "delete-avatar-confirm": "Are you sure you want to delete this avatar?", + "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", diff --git a/models/users.js b/models/users.js index f83ecd3c2..e022c36de 100644 --- a/models/users.js +++ b/models/users.js @@ -1970,10 +1970,70 @@ Meteor.methods({ Users.remove(targetUserId); return { success: true, message: 'User deleted successfully' }; }, + editUser(targetUserId, updateData) { + check(targetUserId, String); + check(updateData, Object); + + const currentUserId = Meteor.userId(); + if (!currentUserId) { + throw new Meteor.Error('not-authorized', 'User must be logged in'); + } + + const currentUser = ReactiveCache.getUser(currentUserId); + if (!currentUser) { + throw new Meteor.Error('not-authorized', 'Current user not found'); + } + + // Check if current user is admin + if (!currentUser.isAdmin) { + throw new Meteor.Error('not-authorized', 'Only administrators can edit other users'); + } + + const targetUser = ReactiveCache.getUser(targetUserId); + if (!targetUser) { + throw new Meteor.Error('user-not-found', 'Target user not found'); + } + + // Only allow updating specific fields + const updateObject = {}; + if (updateData.fullname !== undefined) { + updateObject['profile.fullname'] = updateData.fullname; + } + if (updateData.initials !== undefined) { + updateObject['profile.initials'] = updateData.initials; + } + if (updateData.isAdmin !== undefined) { + updateObject.isAdmin = updateData.isAdmin; + } + if (updateData.loginDisabled !== undefined) { + updateObject.loginDisabled = updateData.loginDisabled; + } + if (updateData.authenticationMethod !== undefined) { + updateObject.authenticationMethod = updateData.authenticationMethod; + } + if (updateData.importUsernames !== undefined) { + updateObject.importUsernames = updateData.importUsernames; + } + if (updateData.teams !== undefined) { + updateObject.teams = updateData.teams; + } + if (updateData.orgs !== undefined) { + updateObject.orgs = updateData.orgs; + } + + Users.update(targetUserId, { $set: updateObject }); + }, setListSortBy(value) { check(value, String); ReactiveCache.getCurrentUser().setListSortBy(value); }, + setAvatarUrl(avatarUrl) { + check(avatarUrl, String); + if (!this.userId) { + throw new Meteor.Error('not-logged-in', 'User must be logged in'); + } + Users.update(this.userId, { $set: { 'profile.avatarUrl': avatarUrl } }); + }, toggleBoardStar(boardId) { check(boardId, String); if (!this.userId) { From c8bdb95b3807a01619a9205d47be8b2f950f7346 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Wed, 14 Jan 2026 00:42:51 +0200 Subject: [PATCH 31/32] Updated ChangeLog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b71acd44..82b4c86ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,6 +89,8 @@ and fixes the following bugs: Thanks to xet7. - [Changed find.sh to not search from translations, because I'm trying to find code, not translations](https://github.com/wekan/wekan/commit/58ae2b6c6848235132308611fe3083533e120f72). Thanks to xet7. +- [Fixed Change Avatar. Improved Admin Panel: People columns order, selected tab background color](https://github.com/wekan/wekan/commit/07186e12a93c56555feb3b7332d43a918abe7f20). + Thanks to xet7. Thanks to above GitHub users for their contributions and translators for their translations. From 984a2dcec18fd20ebd1a5add8380d4c13d8303ba Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Wed, 14 Jan 2026 01:11:42 +0200 Subject: [PATCH 32/32] Some fixes to make WeKan working after Meteor 3 related router upgrades. Thanks to xet7 ! --- models/activities.js | 10 +++++----- server/publications/settings.js | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/models/activities.js b/models/activities.js index 626656138..87d3d5f8e 100644 --- a/models/activities.js +++ b/models/activities.js @@ -74,12 +74,12 @@ Activities.before.insert((userId, doc) => { doc.modifiedAt = doc.createdAt; }); -Activities.after.insert((userId, doc) => { - const activity = Activities._transform(doc); - RulesHelper.executeRules(activity); -}); - if (Meteor.isServer) { + Activities.after.insert((userId, doc) => { + const activity = Activities._transform(doc); + RulesHelper.executeRules(activity); + }); + // For efficiency create indexes on the date of creation, and on the date of // creation in conjunction with the card or board id, as corresponding views // are largely used in the App. See #524. diff --git a/server/publications/settings.js b/server/publications/settings.js index a8c073187..e2365d523 100644 --- a/server/publications/settings.js +++ b/server/publications/settings.js @@ -1,5 +1,4 @@ import { ReactiveCache } from '/imports/reactiveCache'; -import { Settings } from '../../models/settings'; Meteor.publish('globalwebhooks', () => { const boardId = Integrations.Const.GLOBAL_WEBHOOK_ID;