1/** 2 * Plugin for comments in embed mode in Confluence Connect post version 1.4.8 3 */ 4Draw.loadPlugin(function(ui) 5{ 6 var RESOLVED_MARKER = '$$RES$$ '; 7 var REPLY_MARKER = '$$REP$$'; 8 var REPLY_MARKER_END = '$$ '; 9 var DELETED_MARKER = '$$DELETED$$'; 10 11 var confUser = null; 12 var confComments = null; 13 var commentsVer = null; 14 15 // Returns modified macro data to client 16 var uiCreateLoadMessage = ui.createLoadMessage; 17 18 ui.createLoadMessage = function(eventName) 19 { 20 var msg = uiCreateLoadMessage.apply(this, arguments); 21 22 if (eventName == 'export') 23 { 24 msg.comments = confComments; 25 } 26 27 return msg; 28 }; 29 30 function setModified() 31 { 32 ui.editor.setStatus(mxUtils.htmlEntities(mxResources.get('unsavedChanges'))); 33 ui.editor.setModified(true); 34 }; 35 36 var origRemoteInvoke = ui.remoteInvoke; 37 38 ui.remoteInvoke = function() 39 { 40 if (typeof AC !== 'undefined') 41 { 42 var fnName = arguments[0]; 43 var fnArgs = arguments[1] || []; 44 fnArgs.push(arguments[arguments.length - 2]); 45 fnArgs.push(arguments[arguments.length - 1]); 46 AC[fnName].apply(AC, fnArgs); 47 } 48 else 49 { 50 origRemoteInvoke.apply(ui, arguments); 51 } 52 }; 53 54 ui.getCurrentUser = function() 55 { 56 if (confUser == null) 57 { 58 ui.remoteInvoke('getCurrentUser', null, null, function(user) 59 { 60 confUser = new DrawioUser(user.id, user.email, user.displayName, user.pictureUrl); 61 }, function() 62 { 63 //ignore such that next call we retry 64 }); 65 66 //Return a dummy user until we have the actual user in order for UI to be populated 67 return new DrawioUser(Date.now(), null, 'Anonymous'); 68 } 69 70 return confUser; 71 }; 72 73 74 ui.commentsSupported = function() 75 { 76 return true; 77 }; 78 79 //Will limit ability to reply on replies to simplify retrieval in version 2 80 ui.canReplyToReplies = function() 81 { 82 return commentsVer == 1; 83 }; 84 85 ui.commentsRefreshNeeded = function() 86 { 87 return commentsVer != 1; //Refresh is needed for new format or if pre-fetch is not finished yet 88 }; 89 90 function confOldCommentToDrawio(cComment, pCommentId) 91 { 92 if (cComment.isDeleted) return null; //skip deleted comments 93 94 var comment = new DrawioComment(null, cComment.id, cComment.content, 95 cComment.modifiedDate, cComment.createdDate, cComment.isResolved, 96 new DrawioUser(cComment.user.id, cComment.user.email, 97 cComment.user.displayName, cComment.user.pictureUrl), pCommentId); 98 99 for (var i = 0; cComment.replies != null && i < cComment.replies.length; i++) 100 { 101 comment.addReplyDirect(confOldCommentToDrawio(cComment.replies[i], cComment.id)); 102 } 103 104 return comment; 105 }; 106 107 function confCommentToDrawio(atlasComment, parentId, siteUrl) 108 { 109 var user = atlasComment.history.createdBy; 110 var comment = new DrawioComment({attVer: atlasComment.attVer, ui: ui}, atlasComment.id, 111 decodeURIComponent(atlasComment.body.storage.value), 112 atlasComment.version.when, atlasComment.history.createdDate, false, 113 new DrawioUser(user.accountId, user.username, 114 user.displayName, siteUrl + user.profilePicture.path)); 115 comment.parentId = parentId; 116 comment.version = atlasComment.version.number; 117 118 if (comment.content == DELETED_MARKER) 119 { 120 comment.content = mxResources.get('msgDeleted'); 121 comment.isLocked = true; 122 } 123 124 var replies = atlasComment.children != null ? atlasComment.children.comment.results : []; 125 126 for (var i = 0; i < replies.length; i++) 127 { 128 var reply = confCommentToDrawio(replies[i], atlasComment.id, siteUrl); 129 comment.addReplyDirect(reply); 130 131 var isResolvedReply = reply.content.indexOf(RESOLVED_MARKER) == 0; 132 133 if (isResolvedReply) 134 { 135 reply.content = reply.content.substr(RESOLVED_MARKER.length); 136 comment.isResolved = i == (replies.length - 1); 137 } 138 } 139 140 return comment; 141 }; 142 143 //TODO Improve this requirement if possible 144 //This function must be called before any interaction with comments 145 //Prefetch comments (for new diagrams, this sets comments version to 2) 146 ui.initComments = function(contentId, success, error) 147 { 148 if (confComments == null) 149 { 150 ui.remoteInvoke('getOldComments', [contentId], null, function(comments, spaceKey, pageId, pageType, contentVer) 151 { 152 confComments = []; 153 154 for (var i = 0; i < comments.length; i++) 155 { 156 var comment = confOldCommentToDrawio(comments[i]); 157 158 if (comment != null) confComments.push(comment); 159 } 160 161 //If we have no old comments, switch to the new comments format 162 commentsVer = confComments.length == 0? 2 : 1; 163 164 if (success) 165 { 166 success(spaceKey, pageId, pageType, contentVer); 167 } 168 }, function() 169 { 170 if (error) 171 { 172 error(); 173 } 174 }); 175 } 176 else if (success) 177 { 178 success(confComments); 179 } 180 }; 181 182 ui.getComments = function(success, error) 183 { 184 if (commentsVer == null) 185 { 186 error(); //User can refresh to retry, we don't have content id here to get the old comments 187 } 188 else if (commentsVer == 1) 189 { 190 success(confComments); 191 } 192 else 193 { 194 ui.remoteInvoke('getComments', [null, false], null, function(comments, siteUrl) 195 { 196 var conComments = []; 197 198 //First pass to convert replies to old comments to regular replies 199 var commentsMap = {}; 200 var oldVerReplies = []; 201 var origComments = []; 202 203 for (var i = 0; i < comments.length; i++) 204 { 205 var cnt = decodeURIComponent(comments[i].body.storage.value); 206 207 if (cnt.indexOf(REPLY_MARKER) == 0) 208 { 209 var end = cnt.indexOf(REPLY_MARKER_END, REPLY_MARKER.length); 210 var parentId = cnt.substring(REPLY_MARKER.length, end); 211 comments[i].body.storage.value = cnt.substring(REPLY_MARKER_END.length + end); 212 oldVerReplies.push({parentId: parentId, reply: comments[i]}); 213 } 214 else 215 { 216 commentsMap[comments[i].id] = comments[i]; 217 origComments.push(comments[i]); 218 } 219 } 220 221 for (var i = 0; i < oldVerReplies.length; i++) 222 { 223 var pComment = commentsMap[oldVerReplies[i].parentId]; 224 225 if (pComment != null) 226 { 227 if (pComment.children == null) 228 { 229 pComment.children = {comment: {results: []}}; 230 } 231 232 pComment.children.comment.results.push(oldVerReplies[i].reply); 233 } 234 } 235 236 for (var i = 0; i < origComments.length; i++) 237 { 238 conComments.push(confCommentToDrawio(origComments[i], null, siteUrl)); 239 } 240 241 success(conComments); 242 }, error); 243 } 244 }; 245 246 ui.addComment = function(comment, success, error) 247 { 248 if (commentsVer == null || !comment.content) 249 { 250 error(); 251 } 252 else if (commentsVer == 2) 253 { 254 ui.remoteInvoke('addComment', [comment.content], null, function(id, version, attVer) 255 { 256 comment.version = version; 257 comment.file.attVer = attVer; 258 success(id); 259 }, error); 260 } 261 else 262 { 263 comment.id = confUser.id + ':' + Date.now(); 264 265 if (ui.saveComments != null) 266 { 267 var tmpComments = JSON.parse(JSON.stringify(confComments)); 268 tmpComments.push(comment); 269 270 ui.saveComments(tmpComments, function() 271 { 272 success(comment.id); 273 }, error); 274 } 275 else 276 { 277 setModified(); 278 success(comment.id); 279 } 280 } 281 }; 282 283 ui.newComment = function(content, user) 284 { 285 return new DrawioComment(commentsVer == 2? {ui: ui} : null, null, //remove file information for old format 286 content, Date.now(), Date.now(), false, user); 287 }; 288 289 DrawioComment.prototype.addReply = function(reply, success, error, doResolve, doReopen) 290 { 291 if (commentsVer == null || !reply.content) 292 { 293 error(); 294 } 295 else if (commentsVer == 2) 296 { 297 ui.remoteInvoke('addCommentReply', [this.id, this.file.attVer, reply.content, doResolve], null, function(id, version) 298 { 299 reply.version = version; 300 success(id); 301 }, error); 302 } 303 else 304 { 305 if (ui.saveComments != null) 306 { 307 reply.id = confUser.id + ':' + Date.now(); 308 this.replies.push(reply); 309 var isResolved = this.isResolved; 310 311 if (doResolve) 312 { 313 this.isResolved = true; 314 } 315 else if (doReopen) 316 { 317 this.isResolved = false; 318 } 319 320 var tmpComments = JSON.parse(JSON.stringify(confComments)); 321 this.replies.pop(); //Undo in case more changes are done before getting the reply 322 this.isResolved = isResolved; 323 324 ui.saveComments(tmpComments, function() 325 { 326 success(reply.id); 327 }, error); 328 } 329 else 330 { 331 setModified(); 332 success(); 333 } 334 } 335 }; 336 337 DrawioComment.prototype.editComment = function(newContent, success, error) 338 { 339 if (commentsVer == null) 340 { 341 error(); 342 } 343 else if (commentsVer == 2) 344 { 345 var _this = this; 346 347 ui.remoteInvoke('editComment', [this.id, this.version, newContent], null, function(version) 348 { 349 _this.version = version; 350 success(); 351 }, error); 352 } 353 else 354 { 355 if (ui.saveComments != null) 356 { 357 var oldContent = this.content; 358 this.content = newContent; 359 var tmpComments = JSON.parse(JSON.stringify(confComments)); 360 this.content = oldContent; 361 362 ui.saveComments(tmpComments, success, error); 363 } 364 else 365 { 366 setModified(); 367 success(); 368 } 369 } 370 }; 371 372 DrawioComment.prototype.deleteComment = function(success, error) 373 { 374 if (commentsVer == null) 375 { 376 error(); 377 } 378 else if (commentsVer == 2) 379 { 380 ui.remoteInvoke('deleteComment', [this.id, this.version, this.replies != null && this.replies.length > 0], null, success, error); 381 } 382 else 383 { 384 if (ui.saveComments != null) 385 { 386 var that = this; 387 this.isDeleted = true; //Mark as deleted since searching for this comment in the entire structure is complex. It will be cleaned in next save 388 var tmpComments = JSON.parse(JSON.stringify(confComments)); 389 390 ui.saveComments(tmpComments, success, function(err) 391 { 392 that.isDeleted = false; 393 error(err); 394 }); 395 } 396 else 397 { 398 setModified(); 399 success(); 400 } 401 } 402 }; 403 404 //Prefetch current user 405 ui.getCurrentUser(); 406}); 407