forked from men232/plus-for-trello
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsharedsync.js
More file actions
316 lines (270 loc) · 14.5 KB
/
sharedsync.js
File metadata and controls
316 lines (270 loc) · 14.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
/// <reference path="intellisense.js" />
//dont use const in this file, as mobile supports older browsers
var g_deletedUserIdPrefix = "deleted"; //prefix for user id and user (when making up a username on deleted users)
var PREFIX_ERROR_SE_COMMENT = "[error: "; //always use this to prefix error SE rows.
var g_prefixCustomUserId = "customUser:";
var g_dateMinCommentSE = new Date(2013, 6, 30); //exclude S/E before this (regular users didnt have this available back then), excludes my testing data from spent backend
var g_dateMinCommentSEWithDateOverBackend = new Date(2014, 11, 3); //S/E with -xd will be ignored on x<-2 for non spent-backend admins, like the backend used to do
var g_dateMinCommentSERelaxedFormat = new Date(2014, 11, 9);
var g_dateMinTransferInPast = new Date(2017, 8, 15); //REVIEW cardtransfer
//regex is easy to break. check well your changes. consider newlines in comments. NOTE command could also be in the note.
//For historical reasons, the command here only covers ^resetsync without 0/0. later we detect other commands.
// users days spent command / estimate spaces note
var g_regexSEFull = new RegExp("^((\\s*@\\w+\\s+)*)((-[0-9]+)[dD]\\s+)?(([+-]?[0-9]*[.:]?[0-9]*)|(\\^[a-zA-Z]+))?\\s*(/?)\\s*([+-]?[0-9]*[.:]?[0-9]*)?(\\s*)(\\s[\\s\\S]*)?$");
function readTrelloCommentDataFromAction(action, rgKeywords, alldata, usersMap, idMemberMapByName) {
var tableRet = [];
var id = action.id;
var from = null;
var memberCreator = action.memberCreator; //may be undefined
if (usersMap && action.idMemberCreator) { //should be set always. but just in case handle it
var cached = usersMap[action.idMemberCreator];
if (cached)
memberCreator = cached; //Trello renames all actions users when a user is renamed but not when deleted.
}
if (memberCreator && memberCreator.username)
from = memberCreator.username;
else
from = g_deletedUserIdPrefix + (action.idMemberCreator || "unknown"); //keep the username as a regex word
from = from.toLowerCase(); //shouldnt be necessary but just in case
var idCardShort = alldata.cardsByLong[action.data.card.id];
var cardObj = alldata.cards[idCardShort];
var idBoardShort = (cardObj || {}).idBoard; //this one is more up to date than the one in the action
if (!idBoardShort || !idCardShort || idBoardShort == IDBOARD_UNKNOWN) {
//idBoardShort can be unknown. ignore those.
if (!idBoardShort || !idCardShort)
logPlusError("error: unexpected card comment from unknown board/card");
return tableRet;
}
if (!alldata.boards[idBoardShort]) {
//review zig: this happens rarely. the card could have moved to a board that the user no longer has access, but if so the comments should have moved there too.
//might be timing-related to trello db.
//if the board is not mapped by plus, this card comment should be processed when the user becomes a member or on next sync.
return tableRet;
//logPlusError("error: idBoardShort:" + idBoardShort + " action:" + JSON.stringify(action) + " cardObj:" + JSON.stringify(cardObj));
//assert(false);
}
var strBoard = alldata.boards[idBoardShort].name;
var strCard = cardObj.name;
var textNotifyOrig = action.data.text.trim();
var date = new Date(action.date); //convert from ISO-8601 to js date
if (date < g_dateMinCommentSE || rgKeywords.length == 0)
return tableRet;
var cardTitle = action.data.card.name;
var bRecurring = cardTitle.indexOf(TAG_RECURRING_CARD) >= 0;
rgKeywords.every(function (keywordParam) {
var bPlusBoardCommand = false;
var keyword = keywordParam.toLowerCase();
var txtPre = keyword + " ";
var i = textNotifyOrig.toLowerCase().indexOf(txtPre);
if (i < 0 || (i > 0 && textNotifyOrig.charAt(i - 1) != " ")) //whole word keyword
return true; //continue
var textNotify = textNotifyOrig.substr(txtPre.length + i).trim(); //remove keyword
var idForSs = "" + id; //clone it
var parseResults = matchCommentParts(textNotify, date, bRecurring, from);
var comment = "";
function pushErrorObj(strErr) {
if (tableRet.length != 0)
return;
var obj = makeHistoryRowObject(date, idCardShort, idBoardShort, strBoard, strCard, from, 0, 0, PREFIX_ERROR_SE_COMMENT + strErr + "] " + replaceBrackets(textNotify), idForSs, keyword);
obj.bError = true;
tableRet.push(obj);
}
if (!parseResults) {
pushErrorObj("bad format");
return true; //continue
}
if (i > 0) {
if (date > g_dateMinCommentSEWithDateOverBackend) {
pushErrorObj("keyword not at start");
return true;
}
//allow legacy S/E entry format for old spent backend rows
}
var s = 0;
var e = 0;
comment = parseResults.comment;
if (parseResults.strSpent)
s = parseFixedFloat(parseResults.strSpent, false);
if (parseResults.strEstimate)
e = parseFixedFloat(parseResults.strEstimate, false);
var bETransfer = false;
var idCardFromTransfer = null;
if (parseResults.strCommand) {
//note before v3.2.13 there were "board commands", (markboard, unmarkboard) no longer used
bPlusBoardCommand = (parseResults.strCommand.indexOf("markboard") == 1 || parseResults.strCommand.indexOf("unmarkboard") == 1);
function failCommand() {
pushErrorObj("bad command format");
}
if (s != 0) {
failCommand();
return true; //continue
}
if (bPlusBoardCommand || parseResults.strCommand.indexOf(PLUSCOMMAND_RESET) == 0) {
if (e != 0 || parseResults.rgUsers.length > 0 || parseResults.days) {
failCommand();
return true; //continue
}
comment = "[" + parseResults.strCommand + " command] " + comment; //keep command in history row for traceability
} else {
if (parseResults.strCommand.indexOf(PLUSCOMMAND_ETRANSFER) == 0) {
bETransfer = true;
if (parseResults.days && date < g_dateMinTransferInPast) {
failCommand();
return true; //continue
}
//note: do not yet modify the comment. we include the command later only on the first history row
if (e < 0 || parseResults.rgUsers.length != 2) {
failCommand();
return true; //continue
}
if (false) {
const prefixFromCard = PLUSCOMMAND_ETRANSFER + PLUSCOMMAND_ETRANSFER_FROMCARD;
if (parseResults.strCommand.indexOf(prefixFromCard) == 0) {
idCardFromTransfer = parseResults.strCommand.substring(prefixFromCard.length).split(" ")[0]; //first word
if (!idCardFromTransfer) {
failCommand();
return true; //continue
}
}
}
} else {
failCommand();
return true; //continue
}
}
}
var deltaDias = parseResults.days;
var deltaParsed = 0;
if (deltaDias) {
deltaParsed = parseInt(deltaDias, 10) || 0;
if (deltaParsed > 0 || deltaParsed < g_dDaysMinimum) { //sane limits
//note this is really not possible to enter here because the parser guarantees that deltaParsed will be negative
pushErrorObj("bad d");
return true; //continue
}
var deltaMin = (keyword == "@tareocw" ? -2 : -10);
//support spent backend legacy rules for legacy rows
if (deltaParsed < deltaMin && date < g_dateMinCommentSEWithDateOverBackend) {
if (true) {
pushErrorObj("bad d for legacy entry"); //used to say "bad d for non-admin"
return true; //continue
}
}
date.setDate(date.getDate() + deltaParsed);
}
var rgUsersProcess = parseResults.rgUsers; //NOTE: >1 when reporting multiple users on a single comment
var iRowPush = 0;
if (rgUsersProcess.length == 0)
rgUsersProcess.push(from);
tableRet = []; //remove possible previous errors (when another keyword before matched partially and failed)
for (iRowPush = 0; iRowPush < rgUsersProcess.length; iRowPush++) {
var idForSsUse = idForSs;
var commentPush = appendCommentBracketInfo(deltaParsed, comment, from, rgUsersProcess, iRowPush, bETransfer);
var datePush = date;
if (iRowPush > 0)
idForSsUse = idForSs + SEP_IDHISTORY_MULTI + iRowPush;
if (action.idPostfix)
idForSsUse = idForSsUse + action.idPostfix;
var userCur = rgUsersProcess[iRowPush];
if (idMemberMapByName && userCur != from) {
//update usersMap to fake users that may not be real users
//note checking for prefix g_deletedUserIdPrefix fails if user actually starts with "deleted", but its not a real scenario
if (!idMemberMapByName[userCur] && userCur.indexOf(g_deletedUserIdPrefix) != 0) { //review zig duplicated. consolidate
var idMemberFake = g_prefixCustomUserId + userCur;
usersMap[idMemberFake] = { dateSzLastTrello: action.date, bEdited: true, idMemberCreator: idMemberFake, username: userCur };
idMemberMapByName[userCur] = idMemberFake;
}
}
var bSpecialETransferFrom = (bETransfer && iRowPush === 0);
//note that for transfers both are entered with the same date. code should sort by date,rowid to get the right timeline
var idCardForRow = idCardShort;
if (bPlusBoardCommand)
idCardForRow = ID_PLUSBOARDCOMMAND;
else if (bSpecialETransferFrom && idCardFromTransfer)
idCardForRow = idCardFromTransfer;
var obj = makeHistoryRowObject(datePush, idCardForRow, idBoardShort, strBoard, strCard, userCur, s, e, commentPush, idForSsUse, keyword);
if (idCardForRow == ID_PLUSBOARDCOMMAND)
obj.idCardOrig = idCardShort; //to restore in case the row causes an error at history commit time.
obj.bError = false;
if (bETransfer || bRecurring)
obj.bENew = true;
if (parseResults.strCommand)
obj.command = parseResults.strCommand.substring(1); //removes ^
if (bSpecialETransferFrom) {
assert(e >= 0);
obj.est = -obj.est;
}
tableRet.push(obj);
}
return false; //stop
}); //end every keyword
return tableRet;
}
function matchCommentParts(text, date, bRecurringCard, userFrom) {
//? is used to force non-greedy
var i_users = 1;
var i_days = 4;
var i_spent = 6;
var i_command = 7;
var i_sep = 8;
var i_est = 9;
var i_spacesPreComment = 10;
var i_note = 11;
var preComment = "";
var rgResults = g_regexSEFull.exec(text);
if (rgResults == null)
return null;
//standarize regex quirks
rgResults[i_users] = rgResults[i_users] || "";
rgResults[i_command] = (rgResults[i_command] || "").toLowerCase();
rgResults[i_est] = rgResults[i_est] || "";
rgResults[i_note] = (rgResults[i_note] || ""); //note there is no limit. The user could in theory add millions of characters here.
rgResults[i_note].split("\n")[0]; //note is up to newline if any
assert(rgResults[i_command] == "" || (rgResults[i_command].length > 0 && rgResults[i_command].charAt(0) == PREFIX_PLUSCOMMAND));
if (!rgResults[i_sep]) { //separator
if (date && date < g_dateMinCommentSERelaxedFormat) {
console.log("S/E legacy row with new format ignored " + date);
return null;
}
if (rgResults[i_est]) {
//when no separator, assume there is only spent. add any possible E matches to the comment (in case note started with a number)
rgResults[i_note] = rgResults[i_est] + rgResults[i_spacesPreComment] + rgResults[i_note];
rgResults[i_est] = "";
rgResults[i_spacesPreComment] = "";
preComment = "[warning: possibly missing /] ";
}
if (rgResults[i_est].length == 0 && bRecurringCard) { //special case for recurring cards
rgResults[i_est] = rgResults[i_spent];
}
}
rgResults[i_note] = rgResults[i_note].trim();
if (!rgResults[i_command] && rgResults[i_note].indexOf(PREFIX_PLUSCOMMAND) == 0) {
//for historical compatibility reasons, command in regex is only parsed when no S/E (resetsync case), but we need to manually parse it out of the comment otherwise.
var words = rgResults[i_note].split(" ");
rgResults[i_command] = words[0];
rgResults[i_note] = rgResults[i_note].substring(words[0].length).trim();
}
var ret = {};
var users = rgResults[i_users].trim();
var rgUsers = [];
if (users.length > 0) {
var listUsers = users.split("@");
var i = 0;
for (; i < listUsers.length; i++) {
var item = listUsers[i].trim().toLowerCase();
if (item.length != 0) {
item = item.toLowerCase();
if (item == g_strUserMeOption)
item = userFrom; //allow @me shortcut (since trello wont autocomplete the current user)
rgUsers.push(item);
}
}
}
ret.rgUsers = rgUsers;
ret.days = rgResults[i_days] || "";
ret.strSpent = rgResults[i_spent] || "";
ret.strEstimate = rgResults[i_est] || "";
ret.strCommand = rgResults[i_command] || "";
ret.comment = preComment + replaceBrackets(rgResults[i_note] || "");
return ret;
}