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