mirror of
https://github.com/wekan/wekan.git
synced 2026-02-18 05:58:07 +01:00
Reverted New UI Design of WeKan v8.29 and added more fixes and performance improvements.
Thanks to xet7 !
This commit is contained in:
parent
d152d8fc1b
commit
1b8b8d2eef
196 changed files with 17659 additions and 10028 deletions
|
|
@ -392,7 +392,7 @@ if (Meteor.isServer) {
|
|||
Notifications.getUsers(watchers).forEach((user) => {
|
||||
// Skip if user is undefined or doesn't have an _id (e.g., deleted user or invalid ID)
|
||||
if (!user || !user._id) return;
|
||||
|
||||
|
||||
// Don't notify a user of their own behavior, EXCEPT for self-mentions
|
||||
const isSelfMention = (user._id === userId && title === 'act-atUserComment');
|
||||
if (user._id !== userId || isSelfMention) {
|
||||
|
|
|
|||
|
|
@ -18,44 +18,44 @@ AttachmentStorageSettings.attachSchema(
|
|||
defaultValue: STORAGE_NAME_FILESYSTEM,
|
||||
label: 'Default Storage Backend'
|
||||
},
|
||||
|
||||
|
||||
// Storage backend configuration
|
||||
storageConfig: {
|
||||
type: Object,
|
||||
optional: true,
|
||||
label: 'Storage Configuration'
|
||||
},
|
||||
|
||||
|
||||
'storageConfig.filesystem': {
|
||||
type: Object,
|
||||
optional: true,
|
||||
label: 'Filesystem Configuration'
|
||||
},
|
||||
|
||||
|
||||
'storageConfig.filesystem.enabled': {
|
||||
type: Boolean,
|
||||
defaultValue: true,
|
||||
label: 'Filesystem Storage Enabled'
|
||||
},
|
||||
|
||||
|
||||
'storageConfig.filesystem.path': {
|
||||
type: String,
|
||||
optional: true,
|
||||
label: 'Filesystem Storage Path'
|
||||
},
|
||||
|
||||
|
||||
'storageConfig.gridfs': {
|
||||
type: Object,
|
||||
optional: true,
|
||||
label: 'GridFS Configuration'
|
||||
},
|
||||
|
||||
|
||||
'storageConfig.gridfs.enabled': {
|
||||
type: Boolean,
|
||||
defaultValue: true,
|
||||
label: 'GridFS Storage Enabled'
|
||||
},
|
||||
|
||||
|
||||
// DISABLED: S3 storage configuration removed due to Node.js compatibility
|
||||
/*
|
||||
'storageConfig.s3': {
|
||||
|
|
@ -63,81 +63,81 @@ AttachmentStorageSettings.attachSchema(
|
|||
optional: true,
|
||||
label: 'S3 Configuration'
|
||||
},
|
||||
|
||||
|
||||
'storageConfig.s3.enabled': {
|
||||
type: Boolean,
|
||||
defaultValue: false,
|
||||
label: 'S3 Storage Enabled'
|
||||
},
|
||||
|
||||
|
||||
'storageConfig.s3.endpoint': {
|
||||
type: String,
|
||||
optional: true,
|
||||
label: 'S3 Endpoint'
|
||||
},
|
||||
|
||||
|
||||
'storageConfig.s3.bucket': {
|
||||
type: String,
|
||||
optional: true,
|
||||
label: 'S3 Bucket'
|
||||
},
|
||||
|
||||
|
||||
'storageConfig.s3.region': {
|
||||
type: String,
|
||||
optional: true,
|
||||
label: 'S3 Region'
|
||||
},
|
||||
|
||||
|
||||
'storageConfig.s3.sslEnabled': {
|
||||
type: Boolean,
|
||||
defaultValue: true,
|
||||
label: 'S3 SSL Enabled'
|
||||
},
|
||||
|
||||
|
||||
'storageConfig.s3.port': {
|
||||
type: Number,
|
||||
defaultValue: 443,
|
||||
label: 'S3 Port'
|
||||
},
|
||||
*/
|
||||
|
||||
|
||||
// Upload settings
|
||||
uploadSettings: {
|
||||
type: Object,
|
||||
optional: true,
|
||||
label: 'Upload Settings'
|
||||
},
|
||||
|
||||
|
||||
'uploadSettings.maxFileSize': {
|
||||
type: Number,
|
||||
optional: true,
|
||||
label: 'Maximum File Size (bytes)'
|
||||
},
|
||||
|
||||
|
||||
'uploadSettings.allowedMimeTypes': {
|
||||
type: Array,
|
||||
optional: true,
|
||||
label: 'Allowed MIME Types'
|
||||
},
|
||||
|
||||
|
||||
'uploadSettings.allowedMimeTypes.$': {
|
||||
type: String,
|
||||
label: 'MIME Type'
|
||||
},
|
||||
|
||||
|
||||
// Migration settings
|
||||
migrationSettings: {
|
||||
type: Object,
|
||||
optional: true,
|
||||
label: 'Migration Settings'
|
||||
},
|
||||
|
||||
|
||||
'migrationSettings.autoMigrate': {
|
||||
type: Boolean,
|
||||
defaultValue: false,
|
||||
label: 'Auto Migrate to Default Storage'
|
||||
},
|
||||
|
||||
|
||||
'migrationSettings.batchSize': {
|
||||
type: Number,
|
||||
defaultValue: 10,
|
||||
|
|
@ -145,7 +145,7 @@ AttachmentStorageSettings.attachSchema(
|
|||
max: 100,
|
||||
label: 'Migration Batch Size'
|
||||
},
|
||||
|
||||
|
||||
'migrationSettings.delayMs': {
|
||||
type: Number,
|
||||
defaultValue: 1000,
|
||||
|
|
@ -153,7 +153,7 @@ AttachmentStorageSettings.attachSchema(
|
|||
max: 10000,
|
||||
label: 'Migration Delay (ms)'
|
||||
},
|
||||
|
||||
|
||||
'migrationSettings.cpuThreshold': {
|
||||
type: Number,
|
||||
defaultValue: 70,
|
||||
|
|
@ -161,7 +161,7 @@ AttachmentStorageSettings.attachSchema(
|
|||
max: 90,
|
||||
label: 'CPU Threshold (%)'
|
||||
},
|
||||
|
||||
|
||||
// Metadata
|
||||
createdAt: {
|
||||
type: Date,
|
||||
|
|
@ -176,7 +176,7 @@ AttachmentStorageSettings.attachSchema(
|
|||
},
|
||||
label: 'Created At'
|
||||
},
|
||||
|
||||
|
||||
updatedAt: {
|
||||
type: Date,
|
||||
autoValue() {
|
||||
|
|
@ -186,13 +186,13 @@ AttachmentStorageSettings.attachSchema(
|
|||
},
|
||||
label: 'Updated At'
|
||||
},
|
||||
|
||||
|
||||
createdBy: {
|
||||
type: String,
|
||||
optional: true,
|
||||
label: 'Created By'
|
||||
},
|
||||
|
||||
|
||||
updatedBy: {
|
||||
type: String,
|
||||
optional: true,
|
||||
|
|
@ -207,11 +207,11 @@ AttachmentStorageSettings.helpers({
|
|||
getDefaultStorage() {
|
||||
return this.defaultStorage || STORAGE_NAME_FILESYSTEM;
|
||||
},
|
||||
|
||||
|
||||
// Check if storage backend is enabled
|
||||
isStorageEnabled(storageName) {
|
||||
if (!this.storageConfig) return false;
|
||||
|
||||
|
||||
switch (storageName) {
|
||||
case STORAGE_NAME_FILESYSTEM:
|
||||
return this.storageConfig.filesystem?.enabled !== false;
|
||||
|
|
@ -224,11 +224,11 @@ AttachmentStorageSettings.helpers({
|
|||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// Get storage configuration
|
||||
getStorageConfig(storageName) {
|
||||
if (!this.storageConfig) return null;
|
||||
|
||||
|
||||
switch (storageName) {
|
||||
case STORAGE_NAME_FILESYSTEM:
|
||||
return this.storageConfig.filesystem;
|
||||
|
|
@ -241,12 +241,12 @@ AttachmentStorageSettings.helpers({
|
|||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// Get upload settings
|
||||
getUploadSettings() {
|
||||
return this.uploadSettings || {};
|
||||
},
|
||||
|
||||
|
||||
// Get migration settings
|
||||
getMigrationSettings() {
|
||||
return this.migrationSettings || {};
|
||||
|
|
@ -268,7 +268,7 @@ if (Meteor.isServer) {
|
|||
}
|
||||
|
||||
let settings = AttachmentStorageSettings.findOne({});
|
||||
|
||||
|
||||
if (!settings) {
|
||||
// Create default settings
|
||||
settings = {
|
||||
|
|
@ -299,14 +299,14 @@ if (Meteor.isServer) {
|
|||
createdBy: this.userId,
|
||||
updatedBy: this.userId
|
||||
};
|
||||
|
||||
|
||||
AttachmentStorageSettings.insert(settings);
|
||||
settings = AttachmentStorageSettings.findOne({});
|
||||
}
|
||||
|
||||
|
||||
return settings;
|
||||
},
|
||||
|
||||
|
||||
'updateAttachmentStorageSettings'(settings) {
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('not-authorized', 'Must be logged in');
|
||||
|
|
@ -320,7 +320,7 @@ if (Meteor.isServer) {
|
|||
// Validate settings
|
||||
const schema = AttachmentStorageSettings.simpleSchema();
|
||||
schema.validate(settings);
|
||||
|
||||
|
||||
// Update settings
|
||||
const result = AttachmentStorageSettings.upsert(
|
||||
{},
|
||||
|
|
@ -332,10 +332,10 @@ if (Meteor.isServer) {
|
|||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
|
||||
'getDefaultAttachmentStorage'() {
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('not-authorized', 'Must be logged in');
|
||||
|
|
@ -344,7 +344,7 @@ if (Meteor.isServer) {
|
|||
const settings = AttachmentStorageSettings.findOne({});
|
||||
return settings ? settings.getDefaultStorage() : STORAGE_NAME_FILESYSTEM;
|
||||
},
|
||||
|
||||
|
||||
'setDefaultAttachmentStorage'(storageName) {
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('not-authorized', 'Must be logged in');
|
||||
|
|
@ -369,7 +369,7 @@ if (Meteor.isServer) {
|
|||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -857,22 +857,12 @@ Boards.helpers({
|
|||
);
|
||||
},
|
||||
|
||||
listsInSwimlane(swimlaneId) {
|
||||
return this.lists().filter(e => e.swimlaneId === swimlaneId);
|
||||
},
|
||||
|
||||
/** returns the last list
|
||||
* @returns Document the last list
|
||||
*/
|
||||
getLastList() {
|
||||
req = { boardId: this._id };
|
||||
if (this.swimlane && this.swimlane._id != this._id) {
|
||||
req.swimlaneId = this.swimlane._id;
|
||||
}
|
||||
return ReactiveCache.getList(
|
||||
req,
|
||||
{ sort: { sort: 'desc' }
|
||||
});
|
||||
const ret = ReactiveCache.getList({ boardId: this._id }, { sort: { sort: 'desc' } });
|
||||
return ret;
|
||||
},
|
||||
|
||||
nullSortLists() {
|
||||
|
|
@ -945,7 +935,8 @@ Boards.helpers({
|
|||
activeMembers(){
|
||||
// Depend on the users collection for reactivity when users are loaded
|
||||
const memberUserIds = _.pluck(this.members, 'userId');
|
||||
const dummy = Meteor.users.find({ _id: { $in: memberUserIds } }).count();
|
||||
// Use findOne with limit for reactivity trigger instead of count() which loads all users
|
||||
const dummy = Meteor.users.findOne({ _id: { $in: memberUserIds } }, { fields: { _id: 1 }, limit: 1 });
|
||||
const members = _.filter(this.members, m => m.isActive === true);
|
||||
// Group by userId to handle duplicates
|
||||
const grouped = _.groupBy(members, 'userId');
|
||||
|
|
@ -1154,7 +1145,10 @@ Boards.helpers({
|
|||
searchBoards(term) {
|
||||
check(term, Match.OneOf(String, null, undefined));
|
||||
|
||||
const query = { type: 'template-container', archived: false };
|
||||
const query = { boardId: this._id };
|
||||
query.type = 'cardType-linkedBoard';
|
||||
query.archived = false;
|
||||
|
||||
const projection = { limit: 10, sort: { createdAt: -1 } };
|
||||
|
||||
if (term) {
|
||||
|
|
@ -1163,7 +1157,7 @@ Boards.helpers({
|
|||
query.$or = [{ title: regex }, { description: regex }];
|
||||
}
|
||||
|
||||
const ret = ReactiveCache.getBoards(query, projection);
|
||||
const ret = ReactiveCache.getCards(query, projection);
|
||||
return ret;
|
||||
},
|
||||
|
||||
|
|
@ -1651,19 +1645,19 @@ Boards.helpers({
|
|||
return await Boards.updateAsync(this._id, { $set: { allowsDescriptionText } });
|
||||
},
|
||||
|
||||
async setAllowsDescriptionTextOnMinicard(allowsDescriptionTextOnMinicard) {
|
||||
async setallowsDescriptionTextOnMinicard(allowsDescriptionTextOnMinicard) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsDescriptionTextOnMinicard } });
|
||||
},
|
||||
|
||||
async setAllowsCoverAttachmentOnMinicard(allowsCoverAttachmentOnMinicard) {
|
||||
async setallowsCoverAttachmentOnMinicard(allowsCoverAttachmentOnMinicard) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsCoverAttachmentOnMinicard } });
|
||||
},
|
||||
|
||||
async setAllowsBadgeAttachmentOnMinicard(allowsBadgeAttachmentOnMinicard) {
|
||||
async setallowsBadgeAttachmentOnMinicard(allowsBadgeAttachmentOnMinicard) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsBadgeAttachmentOnMinicard } });
|
||||
},
|
||||
|
||||
async setAllowsCardSortingByNumberOnMinicard(allowsCardSortingByNumberOnMinicard) {
|
||||
async setallowsCardSortingByNumberOnMinicard(allowsCardSortingByNumberOnMinicard) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsCardSortingByNumberOnMinicard } });
|
||||
},
|
||||
|
||||
|
|
@ -1782,7 +1776,7 @@ Boards.userBoards = (
|
|||
selector.archived = archived;
|
||||
}
|
||||
if (!selector.type) {
|
||||
selector.type = { $in: ['board', 'template-container'] };
|
||||
selector.type = 'board';
|
||||
}
|
||||
|
||||
selector.$or = [
|
||||
|
|
|
|||
|
|
@ -106,53 +106,40 @@ CardComments.helpers({
|
|||
},
|
||||
|
||||
reactions() {
|
||||
const reaction = this.reaction();
|
||||
const cardCommentReactions = ReactiveCache.getCardCommentReaction({cardCommentId: this._id});
|
||||
return !!cardCommentReactions ? cardCommentReactions.reactions : [];
|
||||
},
|
||||
|
||||
reaction() {
|
||||
return cardCommentReactions = ReactiveCache.getCardCommentReaction({ cardCommentId: this._id });
|
||||
},
|
||||
|
||||
userReactions(userId) {
|
||||
const reactions = this.reactions();
|
||||
return reactions?.filter(r => r.userIds.includes(userId));
|
||||
},
|
||||
|
||||
hasUserReacted(codepoint) {
|
||||
return this.userReactions(Meteor.userId()).find(e => e.reactionCodepoint === codepoint);
|
||||
},
|
||||
|
||||
toggleReaction(reactionCodepoint) {
|
||||
if (reactionCodepoint !== sanitizeText(reactionCodepoint)) {
|
||||
return false;
|
||||
} else {
|
||||
|
||||
const cardCommentReactions = ReactiveCache.getCardCommentReaction({cardCommentId: this._id});
|
||||
const reactions = !!cardCommentReactions ? cardCommentReactions.reactions : [];
|
||||
const userId = Meteor.userId();
|
||||
const reactionDoc = this.reaction();
|
||||
const reactions = this.reactions();
|
||||
const reactionTog = reactions.find(r => r.reactionCodepoint === reactionCodepoint);
|
||||
const reaction = reactions.find(r => r.reactionCodepoint === reactionCodepoint);
|
||||
|
||||
// If no reaction is set for the codepoint, add this
|
||||
if (!reactionTog) {
|
||||
if (!reaction) {
|
||||
reactions.push({ reactionCodepoint, userIds: [userId] });
|
||||
} else {
|
||||
|
||||
// toggle user reaction upon previous reaction state
|
||||
const userHasReacted = reactionTog.userIds.includes(userId);
|
||||
const userHasReacted = reaction.userIds.includes(userId);
|
||||
if (userHasReacted) {
|
||||
reactionTog.userIds.splice(reactionTog.userIds.indexOf(userId), 1);
|
||||
if (reactionTog.userIds.length === 0) {
|
||||
reactions.splice(reactions.indexOf(reactionTog), 1);
|
||||
reaction.userIds.splice(reaction.userIds.indexOf(userId), 1);
|
||||
if (reaction.userIds.length === 0) {
|
||||
reactions.splice(reactions.indexOf(reaction), 1);
|
||||
}
|
||||
} else {
|
||||
reactionTog.userIds.push(userId);
|
||||
reaction.userIds.push(userId);
|
||||
}
|
||||
}
|
||||
|
||||
// If no reaction doc exists yet create otherwise update reaction set
|
||||
if (!!reactionDoc) {
|
||||
return CardCommentReactions.update({ _id: reactionDoc._id }, { $set: { reactions } });
|
||||
if (!!cardCommentReactions) {
|
||||
return CardCommentReactions.update({ _id: cardCommentReactions._id }, { $set: { reactions } });
|
||||
} else {
|
||||
return CardCommentReactions.insert({
|
||||
boardId: this.boardId,
|
||||
|
|
|
|||
|
|
@ -2682,21 +2682,16 @@ function cardCustomFields(userId, doc, fieldNames, modifier) {
|
|||
}
|
||||
|
||||
function cardCreation(userId, doc) {
|
||||
// For any reason some special cards also have
|
||||
// special data, e.g. linked cards who have list/swimlane ID
|
||||
// being their own ID
|
||||
const list = ReactiveCache.getList(doc.listId);
|
||||
const swim = ReactiveCache.getSwimlane(doc.listId);
|
||||
Activities.insert({
|
||||
userId,
|
||||
activityType: 'createCard',
|
||||
boardId: doc.boardId,
|
||||
listName: list?.title,
|
||||
listId: list ? doc.listId : undefined,
|
||||
listName: ReactiveCache.getList(doc.listId).title,
|
||||
listId: doc.listId,
|
||||
cardId: doc._id,
|
||||
cardTitle: doc.title,
|
||||
swimlaneName: swim?.title,
|
||||
swimlaneId: swim ? doc.swimlaneId : undefined,
|
||||
swimlaneName: ReactiveCache.getSwimlane(doc.swimlaneId).title,
|
||||
swimlaneId: doc.swimlaneId,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -103,10 +103,10 @@ export default class FileStoreStrategyFactory {
|
|||
if (!storage) {
|
||||
storage = fileObj.versions[versionName].storage;
|
||||
if (!storage) {
|
||||
if (fileObj.meta.source == "import" || Object.hasOwnProperty(fileObj.versions[versionName].meta, 'gridFsFileId')) {
|
||||
if (fileObj.meta.source == "import" || fileObj.versions[versionName].meta.gridFsFileId) {
|
||||
// uploaded by import, so it's in GridFS (MongoDB)
|
||||
storage = STORAGE_NAME_GRIDFS;
|
||||
} else if (fileObj && fileObj.versions && fileObj.versions[versionName] && fileObj.versions[versionName].meta && Object.hasOwnProperty(fileObj.versions[versionName].meta, 'pipePath')) {
|
||||
} else if (fileObj && fileObj.versions && fileObj.versions[version] && fileObj.versions[version].meta && fileObj.versions[version].meta.pipePath) {
|
||||
// DISABLED: S3 storage removed due to Node.js compatibility - fallback to filesystem
|
||||
storage = STORAGE_NAME_FILESYSTEM;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ import { mongodbDriverManager } from './mongodbDriverManager';
|
|||
|
||||
/**
|
||||
* Meteor MongoDB Integration
|
||||
*
|
||||
*
|
||||
* This module integrates the MongoDB driver manager with Meteor's
|
||||
* built-in MongoDB connection system to provide automatic driver
|
||||
* selection and version detection.
|
||||
*
|
||||
*
|
||||
* Features:
|
||||
* - Hooks into Meteor's MongoDB connection process
|
||||
* - Automatic driver selection based on detected version
|
||||
|
|
@ -58,7 +58,7 @@ class MeteorMongoIntegration {
|
|||
*/
|
||||
overrideMeteorConnection() {
|
||||
const self = this;
|
||||
|
||||
|
||||
// Override Meteor.connect if it exists
|
||||
if (typeof Meteor.connect === 'function') {
|
||||
Meteor.connect = async function(url, options) {
|
||||
|
|
@ -110,16 +110,16 @@ class MeteorMongoIntegration {
|
|||
async createCustomConnection(url, options = {}) {
|
||||
try {
|
||||
console.log('Creating custom MongoDB connection...');
|
||||
|
||||
|
||||
// Use our connection manager
|
||||
const connection = await mongodbConnectionManager.createConnection(url, options);
|
||||
|
||||
|
||||
// Store the custom connection
|
||||
this.customConnection = connection;
|
||||
|
||||
|
||||
// Create a Meteor-compatible connection object
|
||||
const meteorConnection = this.createMeteorCompatibleConnection(connection);
|
||||
|
||||
|
||||
console.log('Custom MongoDB connection created successfully');
|
||||
return meteorConnection;
|
||||
|
||||
|
|
@ -141,7 +141,7 @@ class MeteorMongoIntegration {
|
|||
// Basic connection properties
|
||||
_driver: connection,
|
||||
_name: 'custom-mongodb-connection',
|
||||
|
||||
|
||||
// Collection creation method
|
||||
createCollection: function(name, options = {}) {
|
||||
const db = connection.db();
|
||||
|
|
@ -242,7 +242,7 @@ class MeteorMongoIntegration {
|
|||
if (this.originalMongoConnect) {
|
||||
Meteor.connect = this.originalMongoConnect;
|
||||
}
|
||||
|
||||
|
||||
if (this.originalMongoCollection) {
|
||||
Mongo.Collection = this.originalMongoCollection;
|
||||
}
|
||||
|
|
@ -269,7 +269,7 @@ class MeteorMongoIntegration {
|
|||
|
||||
const db = this.customConnection.db();
|
||||
const result = await db.admin().ping();
|
||||
|
||||
|
||||
return {
|
||||
success: true,
|
||||
result,
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ import { mongodbDriverManager } from './mongodbDriverManager';
|
|||
|
||||
/**
|
||||
* MongoDB Connection Manager
|
||||
*
|
||||
*
|
||||
* This module handles MongoDB connections with automatic driver selection
|
||||
* based on detected MongoDB server version and wire protocol compatibility.
|
||||
*
|
||||
*
|
||||
* Features:
|
||||
* - Automatic driver selection based on MongoDB version
|
||||
* - Connection retry with different drivers on wire protocol errors
|
||||
|
|
@ -30,7 +30,7 @@ class MongoDBConnectionManager {
|
|||
*/
|
||||
async createConnection(connectionString, options = {}) {
|
||||
const connectionId = this.generateConnectionId(connectionString);
|
||||
|
||||
|
||||
// Check if we already have a working connection
|
||||
if (this.connections.has(connectionId)) {
|
||||
const existingConnection = this.connections.get(connectionId);
|
||||
|
|
@ -66,13 +66,13 @@ class MongoDBConnectionManager {
|
|||
for (let attempt = 0; attempt < this.retryAttempts; attempt++) {
|
||||
try {
|
||||
console.log(`Attempting MongoDB connection with driver: ${currentDriver} (attempt ${attempt + 1})`);
|
||||
|
||||
|
||||
const connection = await this.connectWithDriver(currentDriver, connectionString, options);
|
||||
|
||||
|
||||
// Record successful connection
|
||||
mongodbDriverManager.recordConnectionAttempt(
|
||||
currentDriver,
|
||||
mongodbDriverManager.detectedVersion || 'unknown',
|
||||
currentDriver,
|
||||
mongodbDriverManager.detectedVersion || 'unknown',
|
||||
true
|
||||
);
|
||||
|
||||
|
|
@ -113,9 +113,9 @@ class MongoDBConnectionManager {
|
|||
|
||||
// Record failed attempt
|
||||
mongodbDriverManager.recordConnectionAttempt(
|
||||
currentDriver,
|
||||
detectedVersion || 'unknown',
|
||||
false,
|
||||
currentDriver,
|
||||
detectedVersion || 'unknown',
|
||||
false,
|
||||
error
|
||||
);
|
||||
|
||||
|
|
@ -204,7 +204,7 @@ class MongoDBConnectionManager {
|
|||
async closeAllConnections() {
|
||||
let closedCount = 0;
|
||||
const connectionIds = Array.from(this.connections.keys());
|
||||
|
||||
|
||||
for (const connectionId of connectionIds) {
|
||||
if (await this.closeConnection(connectionId)) {
|
||||
closedCount++;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ import { Meteor } from 'meteor/meteor';
|
|||
|
||||
/**
|
||||
* MongoDB Driver Manager
|
||||
*
|
||||
*
|
||||
* This module provides automatic MongoDB version detection and driver selection
|
||||
* to support MongoDB versions 3.0 through 8.0 with compatible Node.js drivers.
|
||||
*
|
||||
*
|
||||
* Features:
|
||||
* - Automatic MongoDB version detection from wire protocol errors
|
||||
* - Dynamic driver selection based on detected version
|
||||
|
|
@ -113,7 +113,7 @@ class MongoDBDriverManager {
|
|||
}
|
||||
|
||||
const errorMessage = error.message.toLowerCase();
|
||||
|
||||
|
||||
// Check specific version patterns
|
||||
for (const [version, patterns] of Object.entries(VERSION_ERROR_PATTERNS)) {
|
||||
for (const pattern of patterns) {
|
||||
|
|
|
|||
|
|
@ -61,10 +61,10 @@ export function cleanFileUrl(url, type) {
|
|||
|
||||
// Remove any domain, port, or protocol from the URL
|
||||
let cleanUrl = url;
|
||||
|
||||
|
||||
// Remove protocol and domain
|
||||
cleanUrl = cleanUrl.replace(/^https?:\/\/[^\/]+/, '');
|
||||
|
||||
|
||||
// Remove ROOT_URL pathname if present
|
||||
if (Meteor.isServer && process.env.ROOT_URL) {
|
||||
try {
|
||||
|
|
@ -79,7 +79,7 @@ export function cleanFileUrl(url, type) {
|
|||
|
||||
// Normalize path separators
|
||||
cleanUrl = cleanUrl.replace(/\/+/g, '/');
|
||||
|
||||
|
||||
// Ensure URL starts with /
|
||||
if (!cleanUrl.startsWith('/')) {
|
||||
cleanUrl = '/' + cleanUrl;
|
||||
|
|
@ -176,13 +176,13 @@ export function getAllPossibleUrls(fileId, type) {
|
|||
}
|
||||
|
||||
const urls = [];
|
||||
|
||||
|
||||
// Primary URL
|
||||
urls.push(generateUniversalFileUrl(fileId, type));
|
||||
|
||||
|
||||
// Fallback URL
|
||||
urls.push(generateFallbackUrl(fileId, type));
|
||||
|
||||
|
||||
// Legacy URLs for backward compatibility
|
||||
if (type === 'attachment') {
|
||||
urls.push(`/cfs/files/attachments/${fileId}`);
|
||||
|
|
|
|||
|
|
@ -26,11 +26,11 @@ export function isValidBoolean(value) {
|
|||
*/
|
||||
export function getValidatedNumber(key, boardId, itemId, defaultValue, min, max) {
|
||||
if (typeof localStorage === 'undefined') return defaultValue;
|
||||
|
||||
|
||||
try {
|
||||
const stored = localStorage.getItem(key);
|
||||
if (!stored) return defaultValue;
|
||||
|
||||
|
||||
const data = JSON.parse(stored);
|
||||
if (data[boardId] && typeof data[boardId][itemId] === 'number') {
|
||||
const value = data[boardId][itemId];
|
||||
|
|
@ -41,7 +41,7 @@ export function getValidatedNumber(key, boardId, itemId, defaultValue, min, max)
|
|||
} catch (e) {
|
||||
console.warn(`Error reading ${key} from localStorage:`, e);
|
||||
}
|
||||
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
|
|
@ -50,22 +50,22 @@ export function getValidatedNumber(key, boardId, itemId, defaultValue, min, max)
|
|||
*/
|
||||
export function setValidatedNumber(key, boardId, itemId, value, min, max) {
|
||||
if (typeof localStorage === 'undefined') return false;
|
||||
|
||||
|
||||
// Validate value
|
||||
if (typeof value !== 'number' || isNaN(value) || !isFinite(value) || value < min || value > max) {
|
||||
console.warn(`Invalid value for ${key}:`, value);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const stored = localStorage.getItem(key);
|
||||
const data = stored ? JSON.parse(stored) : {};
|
||||
|
||||
|
||||
if (!data[boardId]) {
|
||||
data[boardId] = {};
|
||||
}
|
||||
data[boardId][itemId] = value;
|
||||
|
||||
|
||||
localStorage.setItem(key, JSON.stringify(data));
|
||||
return true;
|
||||
} catch (e) {
|
||||
|
|
@ -79,11 +79,11 @@ export function setValidatedNumber(key, boardId, itemId, value, min, max) {
|
|||
*/
|
||||
export function getValidatedBoolean(key, boardId, itemId, defaultValue) {
|
||||
if (typeof localStorage === 'undefined') return defaultValue;
|
||||
|
||||
|
||||
try {
|
||||
const stored = localStorage.getItem(key);
|
||||
if (!stored) return defaultValue;
|
||||
|
||||
|
||||
const data = JSON.parse(stored);
|
||||
if (data[boardId] && typeof data[boardId][itemId] === 'boolean') {
|
||||
return data[boardId][itemId];
|
||||
|
|
@ -91,7 +91,7 @@ export function getValidatedBoolean(key, boardId, itemId, defaultValue) {
|
|||
} catch (e) {
|
||||
console.warn(`Error reading ${key} from localStorage:`, e);
|
||||
}
|
||||
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
|
|
@ -100,22 +100,22 @@ export function getValidatedBoolean(key, boardId, itemId, defaultValue) {
|
|||
*/
|
||||
export function setValidatedBoolean(key, boardId, itemId, value) {
|
||||
if (typeof localStorage === 'undefined') return false;
|
||||
|
||||
|
||||
// Validate value
|
||||
if (typeof value !== 'boolean') {
|
||||
console.warn(`Invalid boolean value for ${key}:`, value);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const stored = localStorage.getItem(key);
|
||||
const data = stored ? JSON.parse(stored) : {};
|
||||
|
||||
|
||||
if (!data[boardId]) {
|
||||
data[boardId] = {};
|
||||
}
|
||||
data[boardId][itemId] = value;
|
||||
|
||||
|
||||
localStorage.setItem(key, JSON.stringify(data));
|
||||
return true;
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -468,21 +468,21 @@ Meteor.methods({
|
|||
|
||||
enableSoftLimit(listId) {
|
||||
check(listId, String);
|
||||
|
||||
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('not-authorized', 'You must be logged in.');
|
||||
}
|
||||
|
||||
|
||||
const list = ReactiveCache.getList(listId);
|
||||
if (!list) {
|
||||
throw new Meteor.Error('list-not-found', 'List not found');
|
||||
}
|
||||
|
||||
|
||||
const board = ReactiveCache.getBoard(list.boardId);
|
||||
if (!board || !board.hasAdmin(this.userId)) {
|
||||
throw new Meteor.Error('not-authorized', 'You must be a board admin to modify WIP limits.');
|
||||
}
|
||||
|
||||
|
||||
list.toggleSoftLimit(!list.getWipLimit('soft'));
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -139,17 +139,33 @@ if (Meteor.isServer) {
|
|||
|
||||
LockoutSettings.helpers({
|
||||
getKnownConfig() {
|
||||
// Fetch all settings in one query instead of 3 separate queries
|
||||
const settings = LockoutSettings.find({
|
||||
_id: { $in: ['known-failuresBeforeLockout', 'known-lockoutPeriod', 'known-failureWindow'] }
|
||||
}, { fields: { _id: 1, value: 1 } }).fetch();
|
||||
|
||||
const settingsMap = {};
|
||||
settings.forEach(s => { settingsMap[s._id] = s.value; });
|
||||
|
||||
return {
|
||||
failuresBeforeLockout: LockoutSettings.findOne('known-failuresBeforeLockout')?.value || 3,
|
||||
lockoutPeriod: LockoutSettings.findOne('known-lockoutPeriod')?.value || 60,
|
||||
failureWindow: LockoutSettings.findOne('known-failureWindow')?.value || 15
|
||||
failuresBeforeLockout: settingsMap['known-failuresBeforeLockout'] || 3,
|
||||
lockoutPeriod: settingsMap['known-lockoutPeriod'] || 60,
|
||||
failureWindow: settingsMap['known-failureWindow'] || 15
|
||||
};
|
||||
},
|
||||
getUnknownConfig() {
|
||||
// Fetch all settings in one query instead of 3 separate queries
|
||||
const settings = LockoutSettings.find({
|
||||
_id: { $in: ['unknown-failuresBeforeLockout', 'unknown-lockoutPeriod', 'unknown-failureWindow'] }
|
||||
}, { fields: { _id: 1, value: 1 } }).fetch();
|
||||
|
||||
const settingsMap = {};
|
||||
settings.forEach(s => { settingsMap[s._id] = s.value; });
|
||||
|
||||
return {
|
||||
failuresBeforeLockout: LockoutSettings.findOne('unknown-failuresBeforeLockout')?.value || 3,
|
||||
lockoutPeriod: LockoutSettings.findOne('unknown-lockoutPeriod')?.value || 60,
|
||||
failureWindow: LockoutSettings.findOne('unknown-failureWindow')?.value || 15
|
||||
failuresBeforeLockout: settingsMap['unknown-failuresBeforeLockout'] || 3,
|
||||
lockoutPeriod: settingsMap['unknown-lockoutPeriod'] || 60,
|
||||
failureWindow: settingsMap['unknown-failureWindow'] || 15
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ Swimlanes.helpers({
|
|||
myLists() {
|
||||
// Return per-swimlane lists: provide lists specific to this swimlane
|
||||
return ReactiveCache.getLists(
|
||||
{
|
||||
{
|
||||
boardId: this.boardId,
|
||||
swimlaneId: this._id,
|
||||
archived: false
|
||||
|
|
@ -690,7 +690,7 @@ Swimlanes.helpers({
|
|||
hasMovedFromOriginalPosition() {
|
||||
const history = this.getOriginalPosition();
|
||||
if (!history) return false;
|
||||
|
||||
|
||||
return history.originalPosition.sort !== this.sort;
|
||||
},
|
||||
|
||||
|
|
@ -700,7 +700,7 @@ Swimlanes.helpers({
|
|||
getOriginalPositionDescription() {
|
||||
const history = this.getOriginalPosition();
|
||||
if (!history) return 'No original position data';
|
||||
|
||||
|
||||
return `Original position: ${history.originalPosition.sort || 0}`;
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -155,9 +155,9 @@ UserPositionHistory.helpers({
|
|||
getDescription() {
|
||||
const entityName = this.entityType;
|
||||
const action = this.actionType;
|
||||
|
||||
|
||||
let desc = `${action} ${entityName}`;
|
||||
|
||||
|
||||
if (this.actionType === 'move') {
|
||||
if (this.previousListId && this.newListId && this.previousListId !== this.newListId) {
|
||||
desc += ' to different list';
|
||||
|
|
@ -167,7 +167,7 @@ UserPositionHistory.helpers({
|
|||
desc += ' position';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return desc;
|
||||
},
|
||||
|
||||
|
|
@ -201,7 +201,7 @@ UserPositionHistory.helpers({
|
|||
}
|
||||
|
||||
const userId = this.userId;
|
||||
|
||||
|
||||
switch (this.entityType) {
|
||||
case 'card': {
|
||||
const card = ReactiveCache.getCard(this.entityId);
|
||||
|
|
@ -211,7 +211,7 @@ UserPositionHistory.helpers({
|
|||
const swimlaneId = this.previousSwimlaneId || card.swimlaneId;
|
||||
const listId = this.previousListId || card.listId;
|
||||
const sort = this.previousSort !== undefined ? this.previousSort : card.sort;
|
||||
|
||||
|
||||
Cards.update(card._id, {
|
||||
$set: {
|
||||
boardId,
|
||||
|
|
@ -228,7 +228,7 @@ UserPositionHistory.helpers({
|
|||
if (list) {
|
||||
const sort = this.previousSort !== undefined ? this.previousSort : list.sort;
|
||||
const swimlaneId = this.previousSwimlaneId || list.swimlaneId;
|
||||
|
||||
|
||||
Lists.update(list._id, {
|
||||
$set: {
|
||||
sort,
|
||||
|
|
@ -242,7 +242,7 @@ UserPositionHistory.helpers({
|
|||
const swimlane = ReactiveCache.getSwimlane(this.entityId);
|
||||
if (swimlane) {
|
||||
const sort = this.previousSort !== undefined ? this.previousSort : swimlane.sort;
|
||||
|
||||
|
||||
Swimlanes.update(swimlane._id, {
|
||||
$set: {
|
||||
sort,
|
||||
|
|
@ -255,7 +255,7 @@ UserPositionHistory.helpers({
|
|||
const checklist = ReactiveCache.getChecklist(this.entityId);
|
||||
if (checklist) {
|
||||
const sort = this.previousSort !== undefined ? this.previousSort : checklist.sort;
|
||||
|
||||
|
||||
Checklists.update(checklist._id, {
|
||||
$set: {
|
||||
sort,
|
||||
|
|
@ -270,7 +270,7 @@ UserPositionHistory.helpers({
|
|||
if (item) {
|
||||
const sort = this.previousSort !== undefined ? this.previousSort : item.sort;
|
||||
const checklistId = this.previousState?.checklistId || item.checklistId;
|
||||
|
||||
|
||||
ChecklistItems.update(item._id, {
|
||||
$set: {
|
||||
sort,
|
||||
|
|
@ -348,20 +348,20 @@ if (Meteor.isServer) {
|
|||
* Cleanup old history entries (keep last 1000 per user per board)
|
||||
*/
|
||||
UserPositionHistory.cleanup = function() {
|
||||
const users = Meteor.users.find({}).fetch();
|
||||
|
||||
const users = Meteor.users.find({}, { fields: { _id: 1 } }).fetch();
|
||||
|
||||
users.forEach(user => {
|
||||
const boards = Boards.find({ 'members.userId': user._id }).fetch();
|
||||
|
||||
const boards = Boards.find({ 'members.userId': user._id }, { fields: { _id: 1 } }).fetch();
|
||||
|
||||
boards.forEach(board => {
|
||||
const history = UserPositionHistory.find(
|
||||
{ userId: user._id, boardId: board._id, isCheckpoint: { $ne: true } },
|
||||
{ sort: { createdAt: -1 }, limit: 1000 }
|
||||
).fetch();
|
||||
|
||||
|
||||
if (history.length >= 1000) {
|
||||
const oldestToKeep = history[999].createdAt;
|
||||
|
||||
|
||||
// Remove entries older than the 1000th entry (except checkpoints)
|
||||
UserPositionHistory.remove({
|
||||
userId: user._id,
|
||||
|
|
@ -391,11 +391,11 @@ Meteor.methods({
|
|||
'userPositionHistory.createCheckpoint'(boardId, checkpointName) {
|
||||
check(boardId, String);
|
||||
check(checkpointName, String);
|
||||
|
||||
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('not-authorized', 'Must be logged in');
|
||||
}
|
||||
|
||||
|
||||
// Create a checkpoint entry
|
||||
return UserPositionHistory.insert({
|
||||
userId: this.userId,
|
||||
|
|
@ -413,27 +413,27 @@ Meteor.methods({
|
|||
|
||||
'userPositionHistory.undo'(historyId) {
|
||||
check(historyId, String);
|
||||
|
||||
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('not-authorized', 'Must be logged in');
|
||||
}
|
||||
|
||||
|
||||
const history = UserPositionHistory.findOne({ _id: historyId, userId: this.userId });
|
||||
if (!history) {
|
||||
throw new Meteor.Error('not-found', 'History entry not found');
|
||||
}
|
||||
|
||||
|
||||
return history.undo();
|
||||
},
|
||||
|
||||
'userPositionHistory.getRecent'(boardId, limit = 50) {
|
||||
check(boardId, String);
|
||||
check(limit, Number);
|
||||
|
||||
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('not-authorized', 'Must be logged in');
|
||||
}
|
||||
|
||||
|
||||
return UserPositionHistory.find(
|
||||
{ userId: this.userId, boardId },
|
||||
{ sort: { createdAt: -1 }, limit: Math.min(limit, 100) }
|
||||
|
|
@ -442,11 +442,11 @@ Meteor.methods({
|
|||
|
||||
'userPositionHistory.getCheckpoints'(boardId) {
|
||||
check(boardId, String);
|
||||
|
||||
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('not-authorized', 'Must be logged in');
|
||||
}
|
||||
|
||||
|
||||
return UserPositionHistory.find(
|
||||
{ userId: this.userId, boardId, isCheckpoint: true },
|
||||
{ sort: { createdAt: -1 } }
|
||||
|
|
@ -455,21 +455,21 @@ Meteor.methods({
|
|||
|
||||
'userPositionHistory.restoreToCheckpoint'(checkpointId) {
|
||||
check(checkpointId, String);
|
||||
|
||||
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('not-authorized', 'Must be logged in');
|
||||
}
|
||||
|
||||
const checkpoint = UserPositionHistory.findOne({
|
||||
_id: checkpointId,
|
||||
|
||||
const checkpoint = UserPositionHistory.findOne({
|
||||
_id: checkpointId,
|
||||
userId: this.userId,
|
||||
isCheckpoint: true,
|
||||
});
|
||||
|
||||
|
||||
if (!checkpoint) {
|
||||
throw new Meteor.Error('not-found', 'Checkpoint not found');
|
||||
}
|
||||
|
||||
|
||||
// Find all changes after this checkpoint and undo them in reverse order
|
||||
const changesToUndo = UserPositionHistory.find(
|
||||
{
|
||||
|
|
@ -480,7 +480,7 @@ Meteor.methods({
|
|||
},
|
||||
{ sort: { createdAt: -1 } }
|
||||
).fetch();
|
||||
|
||||
|
||||
let undoneCount = 0;
|
||||
changesToUndo.forEach(change => {
|
||||
try {
|
||||
|
|
@ -492,7 +492,7 @@ Meteor.methods({
|
|||
console.warn('Failed to undo change:', change._id, e);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return { undoneCount, totalChanges: changesToUndo.length };
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -615,6 +615,15 @@ Users.attachSchema(
|
|||
allowedValues: ['YYYY-MM-DD', 'DD-MM-YYYY', 'MM-DD-YYYY'],
|
||||
defaultValue: 'YYYY-MM-DD',
|
||||
},
|
||||
'profile.zoomLevel': {
|
||||
/**
|
||||
* User-specified zoom level for board view (1.0 = 100%, 1.5 = 150%, etc.)
|
||||
*/
|
||||
type: Number,
|
||||
defaultValue: 1.0,
|
||||
min: 0.5,
|
||||
max: 3.0,
|
||||
},
|
||||
'profile.mobileMode': {
|
||||
/**
|
||||
* User-specified mobile/desktop mode toggle
|
||||
|
|
@ -833,6 +842,7 @@ Users.safeFields = {
|
|||
'profile.fullname': 1,
|
||||
'profile.avatarUrl': 1,
|
||||
'profile.initials': 1,
|
||||
'profile.zoomLevel': 1,
|
||||
'profile.mobileMode': 1,
|
||||
'profile.GreyIcons': 1,
|
||||
orgs: 1,
|
||||
|
|
@ -1772,6 +1782,18 @@ Users.helpers({
|
|||
current[boardId][swimlaneId] = !!collapsed;
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.collapsedSwimlanes': current } });
|
||||
},
|
||||
|
||||
async setZoomLevel(level) {
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.zoomLevel': level } });
|
||||
},
|
||||
|
||||
async setMobileMode(enabled) {
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.mobileMode': enabled } });
|
||||
},
|
||||
|
||||
async setCardZoom(level) {
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.cardZoom': level } });
|
||||
},
|
||||
});
|
||||
|
||||
Meteor.methods({
|
||||
|
|
@ -1970,7 +1992,7 @@ Meteor.methods({
|
|||
check(spaceId, String);
|
||||
if (!this.userId) throw new Meteor.Error('not-logged-in');
|
||||
|
||||
const user = Users.findOne(this.userId);
|
||||
const user = Users.findOne(this.userId, { fields: { 'profile.boardWorkspaceAssignments': 1 } });
|
||||
const assignments = user.profile?.boardWorkspaceAssignments || {};
|
||||
assignments[boardId] = spaceId;
|
||||
|
||||
|
|
@ -1984,7 +2006,7 @@ Meteor.methods({
|
|||
check(boardId, String);
|
||||
if (!this.userId) throw new Meteor.Error('not-logged-in');
|
||||
|
||||
const user = Users.findOne(this.userId);
|
||||
const user = Users.findOne(this.userId, { fields: { 'profile.boardWorkspaceAssignments': 1 } });
|
||||
const assignments = user.profile?.boardWorkspaceAssignments || {};
|
||||
delete assignments[boardId];
|
||||
|
||||
|
|
@ -2001,11 +2023,9 @@ Meteor.methods({
|
|||
const user = ReactiveCache.getCurrentUser();
|
||||
user.toggleFieldsGrid(user.hasCustomFieldsGrid());
|
||||
},
|
||||
/* #FIXME not sure about what I'm doing here, but this methods call an async method AFAIU.
|
||||
not making it wait to it creates flickering and multiple renderings on client side. */
|
||||
async toggleCardMaximized() {
|
||||
toggleCardMaximized() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
await user.toggleCardMaximized(user.hasCardMaximized());
|
||||
user.toggleCardMaximized(user.hasCardMaximized());
|
||||
},
|
||||
setCardCollapsed(value) {
|
||||
check(value, Boolean);
|
||||
|
|
@ -2016,10 +2036,6 @@ Meteor.methods({
|
|||
const user = ReactiveCache.getCurrentUser();
|
||||
user.toggleLabelText(user.hasHiddenMinicardLabelText());
|
||||
},
|
||||
toggleShowWeekOfYear() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
user.toggleShowWeekOfYear(user.isShowWeekOfYear());
|
||||
},
|
||||
toggleRescueCardDescription() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
user.toggleRescueCardDescription(user.hasRescuedCardDescription());
|
||||
|
|
@ -2100,7 +2116,7 @@ Meteor.methods({
|
|||
check(height, Number);
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
if (user) {
|
||||
user.setSwimlaneHeightToStorage(boardId, swimlaneId, parseInt(height));
|
||||
user.setSwimlaneHeightToStorage(boardId, swimlaneId, height);
|
||||
}
|
||||
// For non-logged-in users, the client-side code will handle localStorage
|
||||
},
|
||||
|
|
@ -2117,6 +2133,11 @@ Meteor.methods({
|
|||
}
|
||||
// For non-logged-in users, the client-side code will handle localStorage
|
||||
},
|
||||
setZoomLevel(level) {
|
||||
check(level, Number);
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
user.setZoomLevel(level);
|
||||
},
|
||||
setMobileMode(enabled) {
|
||||
check(enabled, Boolean);
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
|
|
@ -3016,7 +3037,7 @@ if (Meteor.isServer) {
|
|||
// get all boards where the user is member of
|
||||
let boards = ReactiveCache.getBoards(
|
||||
{
|
||||
type: {$in: ['board', 'template-container']},
|
||||
type: 'board',
|
||||
'members.userId': req.userId,
|
||||
},
|
||||
{
|
||||
|
|
@ -3060,7 +3081,9 @@ if (Meteor.isServer) {
|
|||
Authentication.checkUserId(req.userId);
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: Meteor.users.find({}).map(function (doc) {
|
||||
data: Meteor.users.find({}, {
|
||||
fields: { _id: 1, username: 1 }
|
||||
}).map(function (doc) {
|
||||
return {
|
||||
_id: doc._id,
|
||||
username: doc.username,
|
||||
|
|
@ -3102,7 +3125,7 @@ if (Meteor.isServer) {
|
|||
// get all boards where the user is member of
|
||||
let boards = ReactiveCache.getBoards(
|
||||
{
|
||||
type: { $in: ['board', 'template-container'] },
|
||||
type: 'board',
|
||||
'members.userId': id,
|
||||
},
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue