mirror of
https://github.com/wekan/wekan.git
synced 2025-12-25 19:58:49 +01:00
Merge branch 'whowillcare-master'
This commit is contained in:
commit
c019a68fc5
6 changed files with 849 additions and 717 deletions
|
|
@ -201,6 +201,19 @@ template(name="cardActivities")
|
|||
.activity-checklist(href="{{ card.absoluteUrl }}")
|
||||
+viewer
|
||||
= checklistItem.title
|
||||
if(currentData.timeKey)
|
||||
| {{{_ activityType }}}
|
||||
= ' '
|
||||
i(title=currentData.timeValue).activity-meta {{ moment currentData.timeValue 'LLL' }}
|
||||
if (currentData.timeOldValue)
|
||||
= ' '
|
||||
| {{{_ "previous_as" }}}
|
||||
= ' '
|
||||
i(title=currentData.timeOldValue).activity-meta {{ moment currentData.timeOldValue 'LLL' }}
|
||||
= ' @'
|
||||
else if(currentData.timeValue)
|
||||
| {{{_ activityType currentData.timeValue}}}
|
||||
|
||||
|
||||
if($eq activityType 'addComment')
|
||||
+inlinedForm(classNames='js-edit-comment')
|
||||
|
|
|
|||
1430
i18n/en.i18n.json
1430
i18n/en.i18n.json
File diff suppressed because it is too large
Load diff
|
|
@ -197,6 +197,18 @@ if (Meteor.isServer) {
|
|||
// params.label = label.name;
|
||||
// params.labelId = activity.labelId;
|
||||
//}
|
||||
if (
|
||||
(!activity.timeKey || activity.timeKey === 'dueAt') &&
|
||||
activity.timeValue
|
||||
) {
|
||||
// due time reminder
|
||||
title = 'act-withDue';
|
||||
}
|
||||
['timeValue', 'timeOldValue'].forEach(key => {
|
||||
// copy time related keys & values to params
|
||||
const value = activity[key];
|
||||
if (value) params[key] = value;
|
||||
});
|
||||
if (board) {
|
||||
const watchingUsers = _.pluck(
|
||||
_.where(board.watchers, { level: 'watching' }),
|
||||
|
|
@ -212,7 +224,6 @@ if (Meteor.isServer) {
|
|||
_.intersection(participants, trackingUsers),
|
||||
);
|
||||
}
|
||||
|
||||
Notifications.getUsers(watchers).forEach(user => {
|
||||
Notifications.notify(user, title, description, params);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1553,6 +1553,60 @@ function cardRemover(userId, doc) {
|
|||
});
|
||||
}
|
||||
|
||||
const findDueCards = days => {
|
||||
const seekDue = ($from, $to, activityType) => {
|
||||
Cards.find({
|
||||
dueAt: { $gte: $from, $lt: $to },
|
||||
}).forEach(card => {
|
||||
const username = Users.findOne(card.userId).username;
|
||||
const activity = {
|
||||
userId: card.userId,
|
||||
username,
|
||||
activityType,
|
||||
boardId: card.boardId,
|
||||
cardId: card._id,
|
||||
cardTitle: card.title,
|
||||
listId: card.listId,
|
||||
timeValue: card.dueAt,
|
||||
swimlaneId: card.swimlaneId,
|
||||
};
|
||||
Activities.insert(activity);
|
||||
});
|
||||
};
|
||||
const now = new Date(),
|
||||
aday = 3600 * 24 * 1e3,
|
||||
then = day => new Date(now.setHours(0, 0, 0, 0) + day * aday);
|
||||
seekDue(then(1), then(days), 'almostdue');
|
||||
seekDue(then(0), then(1), 'duenow');
|
||||
seekDue(then(-days), now, 'pastdue');
|
||||
};
|
||||
const addCronJob = _.debounce(
|
||||
Meteor.bindEnvironment(function findDueCardsDebounced() {
|
||||
const notifydays = parseInt(process.env.NOTIFY_DUE_DAYS, 10) || 2; // default as 2 days b4 and after
|
||||
if (!(notifydays > 0 && notifydays < 15)) {
|
||||
// notifying due is disabled
|
||||
return;
|
||||
}
|
||||
const notifyitvl = process.env.NOTIFY_DUE_ITVL; //passed in the itvl has to be a number standing for the hour of current time
|
||||
const defaultitvl = 8; // default every morning at 8am, if the passed env variable has parsing error use default
|
||||
const itvl = parseInt(notifyitvl, 10) || defaultitvl;
|
||||
const scheduler = (job => () => {
|
||||
const now = new Date();
|
||||
const hour = 3600 * 1e3;
|
||||
if (now.getHours() === itvl) {
|
||||
if (typeof job === 'function') {
|
||||
job();
|
||||
}
|
||||
}
|
||||
Meteor.setTimeout(scheduler, hour);
|
||||
})(() => {
|
||||
findDueCards(notifydays);
|
||||
});
|
||||
scheduler();
|
||||
}),
|
||||
500,
|
||||
);
|
||||
|
||||
if (Meteor.isServer) {
|
||||
// Cards are often fetched within a board, so we create an index to make these
|
||||
// queries more efficient.
|
||||
|
|
@ -1565,12 +1619,17 @@ if (Meteor.isServer) {
|
|||
// With a huge database, this result in a very slow app and high CPU on the mongodb side.
|
||||
// To correct it, add Index to parentId:
|
||||
Cards._collection._ensureIndex({ parentId: 1 });
|
||||
/*let notifydays = parseInt(process.env.NOTIFY_DUE_DAYS) || 2; // default as 2 days b4 and after
|
||||
let notifyitvl = parseInt(process.env.NOTIFY_DUE_ITVL) || 3600 * 24 * 1e3; // default interval as one day
|
||||
Meteor.call("findDueCards",notifydays,notifyitvl);*/
|
||||
Meteor.defer(() => {
|
||||
addCronJob();
|
||||
});
|
||||
});
|
||||
|
||||
Cards.after.insert((userId, doc) => {
|
||||
cardCreation(userId, doc);
|
||||
});
|
||||
|
||||
// New activity for card (un)archivage
|
||||
Cards.after.update((userId, doc, fieldNames) => {
|
||||
cardState(userId, doc, fieldNames);
|
||||
|
|
@ -1600,6 +1659,35 @@ if (Meteor.isServer) {
|
|||
cardCustomFields(userId, doc, fieldNames, modifier);
|
||||
});
|
||||
|
||||
// Add a new activity if modify time related field like dueAt startAt etc
|
||||
Cards.before.update((userId, doc, fieldNames, modifier) => {
|
||||
const dla = 'dateLastActivity';
|
||||
const fields = fieldNames.filter(name => name !== dla);
|
||||
const timingaction = ['receivedAt', 'dueAt', 'startAt', 'endAt'];
|
||||
const action = fields[0];
|
||||
if (fields.length > 0 && _.contains(timingaction, action)) {
|
||||
// add activities for user change these attributes
|
||||
const value = modifier.$set[action];
|
||||
const oldvalue = doc[action] || '';
|
||||
const activityType = `a-${action}`;
|
||||
const card = Cards.findOne(doc._id);
|
||||
const username = Users.findOne(userId).username;
|
||||
const activity = {
|
||||
userId,
|
||||
username,
|
||||
activityType,
|
||||
boardId: doc.boardId,
|
||||
cardId: doc._id,
|
||||
cardTitle: doc.title,
|
||||
timeKey: action,
|
||||
timeValue: value,
|
||||
timeOldValue: oldvalue,
|
||||
listId: card.listId,
|
||||
swimlaneId: card.swimlaneId,
|
||||
};
|
||||
Activities.insert(activity);
|
||||
}
|
||||
});
|
||||
// Remove all activities associated with a card if we remove the card
|
||||
// Remove also card_comments / checklists / attachments
|
||||
Cards.before.remove((userId, doc) => {
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@
|
|||
"prettier-eslint": "^8.8.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.1",
|
||||
"@babel/runtime": "^7.5.4",
|
||||
"bcrypt": "^3.0.2",
|
||||
"bson": "^4.0.0",
|
||||
"bunyan": "^1.8.12",
|
||||
|
|
|
|||
|
|
@ -6,12 +6,17 @@ Meteor.startup(() => {
|
|||
['card', 'list', 'oldList', 'board', 'comment'].forEach(key => {
|
||||
if (quoteParams[key]) quoteParams[key] = `"${params[key]}"`;
|
||||
});
|
||||
['timeValue', 'timeOldValue'].forEach(key => {
|
||||
if (quoteParams[key]) quoteParams[key] = `${params[key]}`;
|
||||
});
|
||||
|
||||
const lan = user.getLanguage();
|
||||
const subject = TAPi18n.__(title, params, lan); // the original function has a fault, i believe the title should be used according to original author
|
||||
const existing = user.getEmailBuffer().length > 0;
|
||||
const text = `${existing ? `\n${subject}\n` : ''}${
|
||||
params.user
|
||||
} ${TAPi18n.__(description, quoteParams, lan)}\n${params.url}`;
|
||||
|
||||
const text = `${params.user} ${TAPi18n.__(
|
||||
description,
|
||||
quoteParams,
|
||||
user.getLanguage(),
|
||||
)}\n${params.url}`;
|
||||
user.addEmailBuffer(text);
|
||||
|
||||
// unlike setTimeout(func, delay, args),
|
||||
|
|
@ -29,12 +34,11 @@ Meteor.startup(() => {
|
|||
// merge the cached content into single email and flush
|
||||
const text = texts.join('\n\n');
|
||||
user.clearEmailBuffer();
|
||||
|
||||
try {
|
||||
Email.send({
|
||||
to: user.emails[0].address.toLowerCase(),
|
||||
from: Accounts.emailTemplates.from,
|
||||
subject: TAPi18n.__('act-activity-notify', {}, user.getLanguage()),
|
||||
subject,
|
||||
text,
|
||||
});
|
||||
} catch (e) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue