Browse Source

13.3.9 release

Gaudenz Alder 5 years ago
parent
commit
33e55238a0

+ 4 - 0
ChangeLog

@@ -1,3 +1,7 @@
+04-JUL-2020: 13.3.9
+
+- Fixes ignored autosave for native files
+
 03-JUL-2020: 13.3.8
 
 - Adds experimental file system support

+ 1 - 1
VERSION

@@ -1 +1 @@
-13.3.8
+13.3.9

+ 613 - 152
src/main/webapp/connect/confluence/connectUtils-1-4-8.js

@@ -223,7 +223,7 @@ AC.initAsync = function(baseUrl, contentId, initMacroData, config, lang)
 	}
 	
 	var ui = 'atlas';
-	var plugins = 'ac148';
+	var plugins = 'ac148;ac148cmnt';
 	
 	try
 	{
@@ -593,12 +593,22 @@ AC.initAsync = function(baseUrl, contentId, initMacroData, config, lang)
 			    			
 			    			timeoutThread = window.setTimeout(timeoutHandler, AC.timeout);
 	
-			    			AC.loadDiagram(pageId, name, revision, function(loadResp)
+			    			AC.loadDiagram(pageId, name, revision, function(loadResp, curPageId, curDiagName)
 			    			{
 					    		window.clearTimeout(timeoutThread);
 					    		
 					    		if (acceptResponse)
 						    	{
+					    			//Get current diagram information which is needed for comments
+					    			AC.getAttachmentInfo(curPageId, curDiagName, function(info)
+					    			{
+					    				AC.curDiagVer = info.version.number;
+					    				AC.curDiagId = info.id;
+					    			}, function()
+					    			{
+					    				AC.curDiagId = false;
+					    			});
+					    			
 				    				xmlReceived = loadResp;
 				    				filename = name;
 									//console.trace('DRAFT: Created', AC.draftPrefix + filename + AC.draftExtension);
@@ -1416,6 +1426,7 @@ AC.init = function(baseUrl, location, pageId, editor, diagramName, initialXml, d
 				}
 				else if (drawMsg.event == 'template')
 				{
+					AC.curDiagId = false; //New diagram, so no diagram id
 					editor.contentWindow.postMessage(JSON.stringify({action: 'spinner',
 						show: true, messageKey: 'inserting'}), '*');
 					
@@ -1919,107 +1930,17 @@ AC.init = function(baseUrl, location, pageId, editor, diagramName, initialXml, d
 	};
 };
 
-AC.loadDiagram = function (pageId, diagramName, revision, success, error, owningPageId, tryRev1, dontCheckVer) {
+AC.loadDiagram = function (pageId, diagramName, revision, success, error, owningPageId, tryRev1, dontCheckVer) 
+{
+	var curDiagName = diagramName;
+	var curPageId = pageId;
 	// TODO: Get binary
-
+	
 	//keeping the block of AP.require to minimize the number of changes!
 	{
-		//Confirm that the macro is in sync with the diagram
-		//Sometimes the diagram is saved but the macro is not updated
-		var attInfo = null;
-		var pageInfo = null;
-		
-		function confirmDiagramInSync()
-		{
-			if (attInfo == null || pageInfo == null) 
-				return;
-			
-			//TODO is this condition enough or we need to check timestamps also?
-			if (attInfo.version.number > revision 
-					&& (pageInfo.version.message == null || pageInfo.version.message.indexOf("Reverted") < 0)) 
-			{
-				AC.loadDiagram(pageId, diagramName, attInfo.version.number, success, error, owningPageId, tryRev1, true);
-				//Update the macro
-				//Custom Content version will be fixed on next save, this will not affect correctness
-				if (!AC.customContentEditMode)
-				{
-					AP.confluence.getMacroData(function (macroData) 
-			    	{
-						if (macroData != null) 
-						{
-							AP.confluence.saveMacro(
-							{
-								diagramName: macroData.diagramName,
-								diagramDisplayName: macroData.diagramDisplayName != null ? macroData.diagramDisplayName : macroData.diagramName,
-								revision: attInfo.version.number,
-								pageId: macroData.pageId,
-								custContentId: macroData.contentId || macroData.custContentId,
-								contentVer: macroData.contentVer,
-								baseUrl: macroData.baseUrl,
-								width: macroData.width,
-								height: macroData.height,
-								tbstyle: macroData.tbstyle,
-								links: macroData.links,
-								simple: macroData.simple != null ? macroData.simple : '0',
-								lbox: macroData.lbox != null ? macroData.lbox : '1',
-								zoom: macroData.zoom != null ? macroData.zoom : '1',
-								pCenter: (macroData.pCenter != null) ? macroData.pCenter : '0',
-								hiResPreview: macroData.hiResPreview,
-								inComment: AC.inComment? '1' : '0'
-							});
-						}
-			    	});
-				}
-			}
-		}
-		
-		//To avoid race we do the version check after loading the diagram in the macro 
-		var localSuccess = function()
+		var localSuccess = function(resp)
 		{
-			success.apply(this, arguments);
-			
-			//This fix contradict with copy/paste workflow where all diagrams have the same name
-			//On copy/paste diagram name must be changed
-			/*if (!dontCheckVer && revision != null)
-			{
-	            AP.request({
-	                type: 'GET',
-	                url: '/rest/api/content/' + pageId + '?expand=version',
-	                contentType: 'application/json;charset=UTF-8',
-	                success: function (resp) 
-	                {
-	                	pageInfo = JSON.parse(resp);
-	                    
-	                	confirmDiagramInSync();
-	                },
-	                error: function (resp) 
-	                {
-	                    //Ignore
-	                }
-	            });
-	
-	            AP.request({
-	                type: 'GET',
-	                url: '/rest/api/content/' + pageId + '/child/attachment?filename=' + 
-	                		encodeURIComponent(diagramName) + '&expand=version',
-	                contentType: 'application/json;charset=UTF-8',
-	                success: function (resp) 
-	                {
-	                	var tmp = JSON.parse(resp);
-	                    
-	                	if (tmp.results && tmp.results.length == 1)
-	                	{
-	                		attInfo = tmp.results[0];
-	                	}
-	                	
-	                	confirmDiagramInSync();
-	                },
-	                error: function (resp) 
-	                {
-	                    //Ignore
-	                }
-	            });
-			}*/
+			success(resp, curPageId, curDiagName);
 		}
 		
 		AP.request({
@@ -2038,6 +1959,7 @@ AC.loadDiagram = function (pageId, diagramName, revision, success, error, owning
 						error : function(resp) { //If revesion 1 failed, then try the owningPageId
 							if (owningPageId && resp.status == 404)
 							{
+								curPageId = owningPageId;
 								AP.request({
 									url: '/download/attachments/' + owningPageId + '/' + encodeURIComponent(diagramName)
 										+'?version=' + revision, //this version should exists in the original owning page
@@ -2060,6 +1982,7 @@ AC.loadDiagram = function (pageId, diagramName, revision, success, error, owning
 				}
 				else if (owningPageId && resp.status == 404) //We are at revesion 1, so try the owningPageId directly
 				{
+					curPageId = owningPageId;
 					AP.request({
 						url: '/download/attachments/' + owningPageId + '/' + encodeURIComponent(diagramName),
 						success: localSuccess,
@@ -2175,19 +2098,19 @@ AC.adjustMacroParametersDirect = function(pageId, macroData, originalBody, match
        });
 };
 
-AC.saveCustomContent = function(spaceKey, pageId, pageType, diagramName, diagramDisplayName, revision, contentId, contentVer, success, error, comments)
+AC.saveCustomContent = function(spaceKey, pageId, pageType, diagramName, diagramDisplayName, revision, contentId, contentVer, success, error, comments, reportAllErr)
 {
 	//Make sure comments are not lost
 	if (comments == null)
 	{
-		AC.getComments(contentId, function(comments)
+		AC.getOldComments(contentId, function(comments)
 		{
-			AC.saveCustomContent(spaceKey, pageId, pageType, diagramName, diagramDisplayName, revision, contentId, contentVer, success, error, comments);
+			AC.saveCustomContent(spaceKey, pageId, pageType, diagramName, diagramDisplayName, revision, contentId, contentVer, success, error, comments, reportAllErr);
 		}, 
 		//On error, whether the custom content is deleted or corrupted. It is better to proceed with saving and losing the comments than losing the diagram
 		function()
 		{
-			AC.saveCustomContent(spaceKey, pageId, pageType, diagramName, diagramDisplayName, revision, contentId, contentVer, success, error, []);
+			AC.saveCustomContent(spaceKey, pageId, pageType, diagramName, diagramDisplayName, revision, contentId, contentVer, success, error, [], reportAllErr);
 		});
 		
 		return;
@@ -2235,7 +2158,14 @@ AC.saveCustomContent = function(spaceKey, pageId, pageType, diagramName, diagram
            url:  "/rest/api/content/" + (contentId? contentId : ""),
            contentType: "application/json",
            success: success,
-           error: function(resp) {
+           error: function(resp) 
+           {
+        	   if (reportAllErr)
+    		   {
+        		   error(resp);
+        		   return;
+    		   }
+        	   
                //User can delete a custom content externally and we will get error 403 and message will contain the given id
                //Then save a new one
                var err = JSON.parse(resp.responseText);
@@ -2271,55 +2201,28 @@ AC.saveContentSearchBody = function(contentId, searchBody, success, error)
 {
 	var doSaveSearchBody = function(version)
 	{
-		var obj = {
-		    "value": searchBody
-		};
-		
-		if (version) 
+		AC.setContentProperty(contentId, 'ac:custom-content:search-body', searchBody, version, success, error);
+	};
+	
+	
+	AC.getContentProperty(contentId, 'ac:custom-content:search-body', function(resp)
+    {
+		resp = JSON.parse(resp);
+      
+		doSaveSearchBody(resp.version.number);
+    },
+    function(resp)
+    {
+    	var err = JSON.parse(resp.responseText);
+	  
+    	//if not found, create one
+		if (err.statusCode == 404)
 		{
-			obj["version"] = {
-				    "number": version + 1,
-				    "minorEdit": true
-				  };
+			doSaveSearchBody();
 		}
 		else
-		{
-			obj["key"] = "ac:custom-content:search-body";
-		}
-		
-		AP.request({
-			  url: "/rest/api/content/" + contentId + "/property" + (version? "/ac%3Acustom-content%3Asearch-body?expand=version" : ""),
-			  type: version? "PUT" : "POST",
-			  contentType: "application/json",
-			  data: JSON.stringify(obj),
-			  success: success,
-			  error: error
-		});
-	};
-	
-	AP.request({
-		  url: "/rest/api/content/" + contentId + "/property/ac%3Acustom-content%3Asearch-body?expand=version",
-		  type: "GET",
-		  contentType: "application/json",
-		  success: function(resp)
-		  {
-			  resp = JSON.parse(resp);
-              
-			  doSaveSearchBody(resp.version.number);
-		  },
-		  error: function(resp)
-		  {
-			  var err = JSON.parse(resp.responseText);
-			  
-			  //if not found, create one
-			  if (err.statusCode == 404)
-			  {
-				  doSaveSearchBody();
-			  }
-			  else
-				  error();
-		  }
-	});
+			error();
+    });
 };
 
 //TODO We can upload both the diagram and its png in one call if needed?
@@ -2572,7 +2475,515 @@ AC.getCurrentUser = function(callback, error)
 	});
 };
 
-AC.getComments = function(contentId, callback, error)
+AC.RESOLVED_MARKER = '$$RES$$ ';
+AC.REPLY_MARKER = '$$REP$$';
+AC.REPLY_MARKER_END = '$$ ';
+AC.DELETED_MARKER = '$$DELETED$$';
+AC.PREV_VERSIONS_KEY = '$$PREV_VER$$';
+AC.PREV_VERSIONS_START = '{"' + AC.PREV_VERSIONS_KEY + '": [';
+AC.PREV_VERSIONS_END = ']}';
+AC.COMMENTS_INDEX_PROP = 'commentsAttVerIndex';
+
+AC.getPrevVersionsComment = function(attId, attVer, callback, error)
+{
+	AP.request({
+        url : '/rest/api/content/' + attId + 
+        		'/child/comment?limit=200&expand=body.storage&parentVersion=' + attVer,
+        type : 'GET',
+        success : function(comments) 
+        {
+        	comments = JSON.parse(comments).results;
+        	var count = comments.length;
+        	var prevVer = [];
+        	
+        	for (var i = 0; i < comments.length; i++)
+    		{
+        		var decCntn = decodeURIComponent(comments[i].body.storage.value);
+        		
+        		if (decCntn.indexOf(AC.PREV_VERSIONS_START) == 0)
+    			{
+        			count--;
+        			
+        			try
+        			{
+        				prevVer = JSON.parse(decCntn)[AC.PREV_VERSIONS_KEY];
+        			}
+        			catch(e){} //Ignore
+    			}
+    		}
+        	
+        	if (count > 0)
+    		{
+        		prevVer.push(attVer);
+    		}
+        	
+        	callback(prevVer.length == 0? null : AC.PREV_VERSIONS_START + prevVer.join(',') +  AC.PREV_VERSIONS_END);
+        },
+        error : error
+    }); 
+};
+
+//TODO Use of globals is risky and error-prone. Find another way to get attachment id and version? 
+AC.commentsFnWrapper = function(fn, noErrCheck)
+{
+	//Wait for attId and ver to be ready
+	function wrappedFn()
+	{
+		if (AC.curDiagId == false && !noErrCheck)
+		{
+			//Call error (last argument)
+			arguments[arguments.length - 1]();
+		}
+		else if (AC.curDiagId != null)
+		{
+			fn.apply(this, arguments);
+		}
+		else
+		{
+			var fnArgs = arguments;
+			//Wait
+			setTimeout(function()
+			{
+				wrappedFn.apply(this, fnArgs);
+			}, 300);
+		}
+	}
+	
+	return wrappedFn;
+};
+
+AC.getComments = AC.commentsFnWrapper(function(attVer, checkUnresolvedOnly, success, error)
+{
+	function isResolvedComment(atlasComment)
+	{
+		if (atlasComment.children != null)
+		{
+			var lastReply = atlasComment.children.comment.results.pop();
+			
+			if (lastReply != null && decodeURIComponent(lastReply.body.storage.value).indexOf(AC.RESOLVED_MARKER) == 0)
+			{
+				return true;
+			}
+			else
+			{
+				return false;
+			}
+		}
+		else
+		{
+			return false;
+		}
+	};
+	
+	var attId = AC.curDiagId;
+	attVer = attVer || AC.curDiagVer;
+	
+	var confComments = [], remaining;
+	
+	if (attId)
+	{
+		AC.getCommentsAttVersIndex(attId, function()
+		{
+			remaining = AC.curCommentIndex.length;
+			doNextChunk();
+			indexIntegrityCheck();
+		}, function()
+		{
+			indexIntegrityCheck(function()
+			{
+				remaining = AC.curCommentIndex.length;
+				doNextChunk();
+			}, error);
+		});
+		
+		function indexIntegrityCheck(callback, error)
+		{
+			if (checkUnresolvedOnly && callback == null) return;
+				
+			AC.getAttVersWithComments(attId, attVer, function(vers, versMap)
+			{
+				var matches = 0;
+				
+				for (var i = 0; i < AC.curCommentIndex.length; i++)
+				{
+					if (versMap[AC.curCommentIndex[i]])
+					{
+						matches++;
+					}
+				}
+    			
+				if (matches != vers.length || AC.curCommentIndex.length != vers.length)
+				{
+					AC.curCommentIndex = vers;
+					AC.setCommentsAttVersIndex(attId, vers);
+				}
+				
+				if (callback)
+				{
+					callback();
+				}
+    		}, 
+    		function()
+    		{
+    			console.log('Error while checking integrity of comments index for ' + attVer); //TODO What to do when integrity call fails?
+    			
+    			if (error)
+    			{
+    				error();
+    			}
+    		});
+		};
+		
+		function doGetComments(ver, callback, error)
+		{
+			AP.request({
+		        url : '/rest/api/content/' + attId + 
+		        		'/child/comment?limit=200&expand=body.storage,version,history,children.comment.body.storage,children.comment.version,children.comment.history' + 
+		        		'&parentVersion=' + ver,
+		        type : 'GET',
+		        success : function(comments) 
+		        {
+		        	//TODO handle paging or 200 comments + 25 replies are enough?
+		        	comments = JSON.parse(comments).results;
+	
+		        	for (var i = 0; i < comments.length; i++)
+	        		{
+		        		if (checkUnresolvedOnly)
+	        			{
+		        			if (!isResolvedComment(comments[i]))
+		        			{
+		            			success(true);
+		            			return;
+		        			}
+	        			}
+		        		else
+		        		{
+			        		comments[i].attVer = ver;
+			        		confComments.push(comments[i]);
+		        		}
+	        		}
+		        	
+		        	callback();
+		        },
+		        error : error
+		    }); 
+		};
+		
+		function doNextChunk()
+		{
+			remaining--;
+			
+			if (remaining < 0)
+			{
+				success(checkUnresolvedOnly? false : confComments, AC.getSiteUrl());
+				return;
+			}
+			
+			doGetComments(AC.curCommentIndex[remaining], doNextChunk, error);
+		}
+	}
+	else
+	{
+		error({message: mxResources.get('saveDiagramFirst', null, 'Save diagram first!')});
+	}
+}, true);
+
+AC.hasUnresolvedComments = function(pageId, contentId, diagramName, callback, error) 
+{
+	AC.getOldComments(contentId, function(comments)
+	{
+		var hasOldComments = false;
+		
+		for (var i = 0; i < comments.length; i++)
+		{
+			if (comments[i].isDeleted) continue;
+			
+			hasOldComments = true;
+			
+			if (!comments[i].isResolved)
+			{
+				callback(true);
+				break;
+			}
+		}
+		
+		if (!hasOldComments)
+		{
+			//Get current diagram information which is needed for comments
+			//This call is needed since we allow calling this from viewer without using AC.loadDiagram
+			//TODO viewer needs to use AC for interaction with Confluence
+			AC.getAttachmentInfo(pageId, diagramName, function(info)
+			{
+				AC.curDiagVer = info.version.number;
+				AC.curDiagId = info.id;
+				
+				AC.getComments(null, true, callback, error);
+			}, 
+			error);
+		}
+	}, 
+	error);
+};
+
+AC.setCommentsAttVersIndex = function(attId, vers) 
+{
+	AC.setContentProperty(attId, AC.COMMENTS_INDEX_PROP, JSON.stringify(vers), AC.curCommentIndexVer, 
+	function(resp)
+	{
+		resp = JSON.parse(resp);
+		AC.curCommentIndexVer = resp.version.number;
+	}, 
+	function(){}); //Ignore errors	
+};
+
+AC.getCommentsAttVersIndex = function(attId, success, error)
+{
+	AC.getContentProperty(attId, AC.COMMENTS_INDEX_PROP, function(resp)
+	{
+		resp = JSON.parse(resp);
+		AC.curCommentIndexVer = resp.version.number;
+		
+		try
+		{
+			AC.curCommentIndex = JSON.parse(resp.value);
+			
+			if (AC.curCommentIndex.length > AC.curDiagVer)
+			{
+				AC.curCommentIndex = []; //The length of the index cannot exceed the number of the versions, so, index is corrupt
+			}
+		}
+		catch(e)
+		{
+			AC.curCommentIndex = [];
+		}
+		
+		success(AC.curCommentIndex);
+	}, function()
+	{
+		AC.curCommentIndex = [];
+		error();
+	});	
+};
+
+AC.getAttVersWithComments = function(attId, attVer, callback, error)
+{
+	var start = 1;
+	var vers = [], versMap = {};
+	
+	function checkChunk(start, end, callback, error)
+	{
+		var doneCount = 0, total = end - start + 1;
+		
+		function checkDone()
+		{
+			doneCount++;
+			
+			if (doneCount == total)
+			{
+				callback();	
+			}
+		}
+		
+		function checkVer(ver)
+		{
+			AP.request({
+		        url : '/rest/api/content/' + attId + 
+		        		'/child/comment?limit=200&parentVersion=' + ver,
+		        type : 'GET',
+		        success : function(comments) 
+		        {
+		        	//TODO handle paging or 200 comments + 25 replies are enough?
+		        	if (JSON.parse(comments).results.length > 0)
+		        	{
+		        		vers.push(ver);
+		        		versMap[ver] = true;
+		        	}
+		        	
+	        		checkDone();
+		        },
+		        error : error
+			});
+		};
+		
+		for (var i = start; i <= end; i++)
+		{
+			checkVer(i);
+		}
+	};
+	
+	function doNextChunk()
+	{
+		if (start > attVer)
+		{
+			callback(vers, versMap);
+			return;
+		}
+		
+		//Check all versions 5 at a time
+		checkChunk(start, Math.min(start + 4, attVer), doNextChunk, error);
+		start += 5;
+	}
+	
+	doNextChunk();
+};
+
+AC.addComment = AC.commentsFnWrapper(function(commentContent, success, error)
+{
+	var attId = AC.curDiagId;
+	
+	if (attId)
+	{
+		AP.request({
+	        url : '/rest/api/content',
+	        type : 'POST',
+	        data: JSON.stringify({
+            	type: 'comment',
+            	container: {
+                    "type": 'attachment',
+                    "id": attId
+                 },
+                 "body": {
+  		           "storage": {
+  		             "value": encodeURIComponent(commentContent),
+  		             "representation": "storage"
+  		           }
+  		         }
+	        }),
+	        success : function(addedComment) 
+	        {
+	        	addedComment = JSON.parse(addedComment);
+	        	success(addedComment.id, addedComment.version.number, AC.curDiagVer);
+	        	
+	        	//Add cur ver to list of versions
+	        	if (AC.curCommentIndex.indexOf(AC.curDiagVer) == -1)
+        		{
+	        		AC.curCommentIndex.push(AC.curDiagVer);
+	        		AC.setCommentsAttVersIndex(attId, AC.curCommentIndex);
+        		}
+	        },
+	        error : error,
+	        contentType: 'application/json'
+	    });
+	}
+	else
+	{
+		error({message: mxResources.get('saveDiagramFirst', null, 'Save diagram first!')});
+	}
+}, true);
+
+AC.addCommentReply = AC.commentsFnWrapper(function(parentId, parentAttVer, replyContent, doResolve, callback, error)
+{
+	var attId = AC.curDiagId;
+	
+	//We cannot add replies to comments that belong to old versions of the attachment, so, as a workaround we add a special regular comment
+	if (parentAttVer != AC.curDiagVer)
+	{
+		AC.addComment(AC.REPLY_MARKER + parentId + AC.REPLY_MARKER_END + (doResolve? AC.RESOLVED_MARKER : '') + replyContent, callback, error);
+	}
+	else
+	{
+		AP.request({
+	        url : '/rest/api/content',
+	        type : 'POST',
+	        data: JSON.stringify({
+	        	"type": 'comment',
+	        	"ancestors": [
+	        	    {
+	        	      "id": parentId
+	        	    }
+	        	],
+	        	"container": {
+	                "type": 'attachment',
+	                "id": attId
+	             },
+	             "body": {
+			           "storage": {
+			             "value": encodeURIComponent((doResolve? AC.RESOLVED_MARKER : '') + replyContent),
+			             "representation": "storage"
+			           }
+			         }
+	        }),
+	        success : function(addedReply) 
+	        {
+	        	addedReply = JSON.parse(addedReply);
+	        	callback(addedReply.id, addedReply.version.number);
+	        },
+	        error : function(xhr) 
+	        {
+	        	if (xhr.responseText.indexOf('messageKey=parent.comment.does.not.exist') > 0)
+	    		{
+	        		error({message: mxResources.get('parentCommentDel', null, 'Parent comment has been deleted. A reply cannot be added.')});
+	    		}
+	        	else 
+	        	{
+	        		error(xhr)
+	        	}
+	        },
+	        contentType: 'application/json'
+		});
+	}
+});
+
+AC.editComment = AC.commentsFnWrapper(function(id, version, newContent, success, error)
+{
+	var attId = AC.curDiagId;
+	
+	AP.request({
+        url : '/rest/api/content/' + id,
+        type : 'PUT',
+        data: JSON.stringify({
+        	"type": 'comment',
+        	"body": {
+		           "storage": {
+		             "value": encodeURIComponent(newContent),
+		             "representation": "storage"
+		           }
+		         },
+		         "container": {
+               "type": 'attachment',
+               "id": attId
+             },
+             "version": {
+ 	            "number": version + 1
+ 	         }
+        }),
+        success : function(editedComment) 
+        {
+        	editedComment = JSON.parse(editedComment);
+        	success(editedComment.version.number);
+        },
+        error : error,
+        contentType: 'application/json'
+    });
+});
+
+AC.deleteComment = function(id, version, hasReplies, success, error)
+{
+	function doDel()
+	{
+		AP.request({
+	        url : '/rest/api/content/' + id,
+	        type : 'DELETE',
+	        success : success,
+	        error : error
+	    });
+	};
+	
+	if (hasReplies)
+	{
+		//Mark as deleted if there is replies
+		AC.editComment(id, version, AC.DELETED_MARKER, function()
+		{
+			success(true);
+		}, error);
+	}
+	else
+	{
+		doDel();
+	}
+};
+
+AC.getOldComments = function(contentId, callback, error)
 {
 	if (contentId)
 	{
@@ -2739,13 +3150,41 @@ AC.getContentProperty = function(contentId, propName, success, error)
 {
 	AP.request({
 		type: 'GET',
-		url: '/rest/api/content/' + contentId + '/property/' + encodeURIComponent(propName),
+		url: '/rest/api/content/' + contentId + '/property/' + encodeURIComponent(propName) + '?expand=version',
 		contentType: 'application/json;charset=UTF-8',
 		success: success,
 		error: error
 	});
 };
 
+AC.setContentProperty = function(contentId, propName, propVal, propVersion, success, error)
+{
+	var obj = {
+		    'value': propVal
+		};
+		
+		if (propVersion) 
+		{
+			obj['version'] = {
+				    'number': propVersion + 1,
+				    'minorEdit': true
+				  };
+		}
+		else
+		{
+			obj['key'] = propName;
+		}
+		
+		AP.request({
+			  url: '/rest/api/content/' + contentId + '/property' + (propVersion? '/' + encodeURIComponent(propName) + '?expand=version' : ''),
+			  type: propVersion? 'PUT' : 'POST',
+			  contentType: 'application/json',
+			  data: JSON.stringify(obj),
+			  success: success,
+			  error: error
+		});
+};
+
 AC.getConfPageEditorVer = function(pageId, callback)
 {
 	AC.getContentProperty(pageId, 'editor', function(resp)
@@ -2909,7 +3348,12 @@ AC.remoteInvokableFns = {
 	getCustomLibraries: {isAsync: true},
 	getFileContent: {isAsync: true},
 	getCurrentUser: {isAsync: true},
+	getOldComments: {isAsync: true},
 	getComments: {isAsync: true},
+	addComment: {isAsync: true},
+	addCommentReply: {isAsync: true},
+	editComment: {isAsync: true},
+	deleteComment: {isAsync: true},
 	userCanEdit: {isAsync: true},
 	getCustomTemplates: {isAsync: true},
 	getPageInfo: {isAsync: true},
@@ -3014,3 +3458,20 @@ AC.handleRemoteInvoke = function(msg)
 		sendResponse(null, mxResources.get('invalidCallErrOccured', [e.message]));
 	}
 };
+
+//Allow loading of plugins (we need it for comments) 
+AC.plugins = [];
+
+window.Draw = new Object();
+window.Draw.loadPlugin = function(callback)
+{
+	AC.plugins.push(callback);
+};
+
+AC.loadPlugins = function(ui)
+{
+	for (var i = 0; i < AC.plugins.length; i++)
+	{
+		AC.plugins[i](ui);
+	}
+};

+ 1 - 0
src/main/webapp/connect/confluence/viewer-1-4-42.html

@@ -25,6 +25,7 @@ body {
 <script src="../new_common/cac.js" type="text/javascript"></script>
 <script src="../onedrive_common/ac.js" type="text/javascript"></script>
 <script src="../gdrive_common/gac.js" type="text/javascript"></script>
+<script src="/plugins/cConf-comments.js" type="text/javascript"></script>
 </head>
 <body>
 <script type="text/javascript" src="viewer-init.js"></script>

+ 1 - 0
src/main/webapp/connect/confluence/viewer-1-4-8.html

@@ -25,6 +25,7 @@ body {
 <script src="../new_common/cac.js" type="text/javascript"></script>
 <script src="../onedrive_common/ac.js" type="text/javascript"></script>
 <script src="../gdrive_common/gac.js" type="text/javascript"></script>
+<script src="/plugins/cConf-comments.js" type="text/javascript"></script>
 </head>
 <body>
 <script type="text/javascript">

+ 66 - 276
src/main/webapp/connect/confluence/viewer.js

@@ -474,7 +474,6 @@
 										};
 										
 										var commentsWindow = null;
-										var confUser = null;
 										
 										//Comments are only shown in lightbox mode
 										if (lightbox)
@@ -484,36 +483,25 @@
 											EditorUi.prototype.addTrees = function(){};
 									    	EditorUi.prototype.updateActionStates = function(){};
 									    	var editorUi = new EditorUi();
-											
-											editorUi.getCurrentUser = function()
-											{
-												if (confUser == null)
+									    	AC.loadPlugins(editorUi);
+									    	
+									    	//Plugins doesn't have callbacks, so we use this hack. TODO Improve this
+									    	function waitForUser()
+									    	{
+									    		if (editorUi.getCurrentUser().email == null)
+								    			{
+									    			setTimeout(waitForUser, 100);
+								    			}
+									    		else if (openComments) //Open the comments window here when the user is ready
 												{
-													AC.getCurrentUser(function(user)
-													{
-														confUser = new DrawioUser(user.id, user.email, user.displayName, user.pictureUrl);
-														
-														if (openComments) //Open the comments window here when the user is ready
-														{
-															openCommentsFunc();
-														}
-													}, function()
-													{
-														//ignore such that next call we retry
-													});
-													
-													//Return a dummy user until we have the actual user in order for UI to be populated
-													return new DrawioUser(Date.now(), null, mxResources.get('anonymous'));
+													openCommentsFunc();
 												}
-												
-												return confUser;
-											};
-											
-											//Prefetch current user 
-											editorUi.getCurrentUser();
+									    	}
+									    	
+									    	waitForUser();
 										}
 										
-										var openCommentsFunc = function()
+										var openCommentsFunc = function(e)
 										{
 											if (commentsWindow != null)
 											{
@@ -521,10 +509,21 @@
 											}
 											else
 											{
-												var confComments = null;
+												var busyIcon = null;
+												//Show busy icon
+												try
+												{
+													if (e && e.target)
+													{
+														busyIcon = document.createElement('img');
+														busyIcon.src = '/images/spin.gif';
+														e.target.parentNode.appendChild(busyIcon);
+													}
+												} catch(e){}
+												
 												var spaceKey, pageId, pageType, contentVer;
 												
-												function saveComments(comments, success, error)
+												editorUi.saveComments = function(comments, success, error)
 												{
 													AC.saveCustomContent(spaceKey, pageId, pageType, name, displayName, revision,
 			            									contentId, contentVer,
@@ -536,148 +535,39 @@
 			            										contentVer = content.version.number;
 			            										
 			            										success();
-			            									}, error, comments);
-												}
-												
-												editorUi.canComment = function()
-												{
-													return true; //We don't put restrictions on draw.io custom contents, so anyone can edit
-												};
-												
-												editorUi.commentsSupported = function()
-												{
-													return true;
-												};
-												
-												editorUi.commentsRefreshNeeded = function()
-												{
-													return false;
-												};
-
-												editorUi.commentsSaveNeeded = function()
-												{
-													return false;
-												};
-
-												
-												editorUi.canReplyToReplies = function()
-												{
-													return true;
+			            									}, error, comments, true);
 												};
 												
-												function confCommentToDrawio(cComment, pCommentId)
+												function createCommentsWindow()
 												{
-													if (cComment.isDeleted) return null; //skip deleted comments
-													
-													var comment = new DrawioComment(null, cComment.id, cComment.content, 
-															cComment.modifiedDate, cComment.createdDate, cComment.isResolved,
-															new DrawioUser(cComment.user.id, cComment.user.email,
-																	cComment.user.displayName, cComment.user.pictureUrl), pCommentId);
-													
-													for (var i = 0; cComment.replies != null && i < cComment.replies.length; i++)
-													{
-														comment.addReplyDirect(confCommentToDrawio(cComment.replies[i], cComment.id));
-													}
+													commentsWindow = new CommentsWindow(editorUi, document.body.offsetWidth - 380, 120, 300, 350);
+													commentsWindow.window.setVisible(true);
+													//Lightbox Viewer has 999 zIndex
+													commentsWindow.window.getElement().style.zIndex = 2000;
 													
-													return comment;
-												};
-														
-												editorUi.getComments = function(success, error)
-												{
-													if (confComments == null)
-													{
-														AC.getComments(contentId, function(comments, spaceKey_p, pageId_p, pageType_p, contentVer_p)
-														{
-															spaceKey = spaceKey_p; pageId = pageId_p; pageType = pageType_p; contentVer = contentVer_p;
-															
-															confComments = [];
-															
-															for (var i = 0; i < comments.length; i++)
-															{
-																var comment = confCommentToDrawio(comments[i]);
-																
-																if (comment != null) confComments.push(comment);
-															}
-															
-															success(confComments);
-														}, error);
-													}
-													else
+													if (busyIcon != null)
 													{
-														success(confComments);
+														busyIcon.parentNode.removeChild(busyIcon);
+														busyIcon = null;
 													}
 												};
-
-												editorUi.addComment = function(comment, success, error)
-												{
-													var tmpComments = JSON.parse(JSON.stringify(confComments));
-													comment.id = confUser.id + ':' + Date.now();
-													tmpComments.push(comment);
-													
-													saveComments(tmpComments, function()
-													{
-														success(comment.id);
-													}, error);
-												};
-														
-												editorUi.newComment = function(content, user)
-												{
-													return new DrawioComment(null, null, content, Date.now(), Date.now(), false, user); //remove file information
-												};
 												
-												//In Confluence, comments are part of the file (specifically custom contents), so needs to mark as changed with every change
-												DrawioComment.prototype.addReply = function(reply, success, error, doResolve, doReopen)
+												//Get current diagram information which is needed for comments
+												//TODO Viewer should use AC to load diagrams, then this won't be needed
+												AC.getAttachmentInfo(id, name, function(info)
 												{
-													reply.id = confUser.id + ':' + Date.now();
-													this.replies.push(reply);
-													var isResolved = this.isResolved;
-													
-													if (doResolve)
-													{
-														this.isResolved = true;
-													}
-													else if (doReopen)
-													{
-														this.isResolved = false;
-													}
-													
-													var tmpComments = JSON.parse(JSON.stringify(confComments));
-													this.replies.pop(); //Undo in case more changes are done before getting the reply
-													this.isResolved = isResolved;
-													
-													saveComments(tmpComments, function()
-													{
-														success(reply.id);
-													}, error);
-												};
-
-												DrawioComment.prototype.editComment = function(newContent, success, error)
-												{
-													var oldContent = this.content;
-													this.content = newContent;
-													var tmpComments = JSON.parse(JSON.stringify(confComments));
-													this.content = oldContent;
-													
-													saveComments(tmpComments, success, error);
-												};
-
-												DrawioComment.prototype.deleteComment = function(success, error)
+													AC.curDiagVer = info.version.number;
+													AC.curDiagId = info.id;
+												}, function()
 												{
-													var that = this;
-													this.isDeleted = true; //Mark as deleted since searching for this comment in the entire structure is complex. It will be cleaned in next save
-													var tmpComments = JSON.parse(JSON.stringify(confComments));
-													
-													saveComments(tmpComments, success, function(err) 
-													{
-														that.isDeleted = false;
-														error(err);
-													});
-												};
+													AC.curDiagId = false;
+												});
 												
-												commentsWindow = new CommentsWindow(editorUi, document.body.offsetWidth - 380, 120, 300, 350);
-												commentsWindow.window.setVisible(true);
-												//Lightbox Viewer has 999 zIndex
-												commentsWindow.window.getElement().style.zIndex = 2000;
+												editorUi.initComments(contentId, function(spaceKey_p, pageId_p, pageType_p, contentVer_p)
+												{
+													spaceKey = spaceKey_p; pageId = pageId_p; pageType = pageType_p; contentVer = contentVer_p;
+													createCommentsWindow();
+												}, createCommentsWindow);
 											}
 										};
 										
@@ -1200,128 +1090,28 @@
 												}
 											}
 											
-											AC.getComments(contentId, function(comments)
+											//If there are comments, show the comments icon
+											function addCommentsIcon()
 											{
-												var hasUnresolvedComments = false;
-												
-												for (var i = 0; i < comments.length; i++)
+												var commentsIcon = document.createElement('img');
+												commentsIcon.style.cssText = 'position:absolute;bottom: 5px; right: 5px;opacity: 0.25; cursor: pointer';
+												commentsIcon.setAttribute('title', mxResources.get('showComments'));
+												commentsIcon.src = Editor.commentImage;
+												commentsIcon.addEventListener('click', function() 
 												{
-													if (!comments[i].isDeleted && !comments[i].isResolved)
-													{
-														hasUnresolvedComments = true;
-														break;
-													}
-												}
-												
-												//If there are comments, show the comments icon
+													viewer.showLightbox(true);
+												});
+												container.appendChild(commentsIcon);
+											};
+											
+											AC.hasUnresolvedComments(id, contentId, name, function(hasUnresolvedComments)
+											{
 												if (hasUnresolvedComments)
 												{
-													var commentsIcon = document.createElement('img');
-													commentsIcon.style.cssText = 'position:absolute;bottom: 5px; right: 5px;opacity: 0.25; cursor: pointer';
-													commentsIcon.setAttribute('title', mxResources.get('showComments'));
-													commentsIcon.src = Editor.commentImage;
-													commentsIcon.addEventListener('click', function() 
-													{
-														viewer.showLightbox(true);
-													});
-													container.appendChild(commentsIcon);
+													addCommentsIcon();
 												}
-											}, function(){});//Nothing to do in case of an error
+											}, function(){}); //Nothing to do in case of an error
 										}
-										//Confirm that the macro is in sync with the diagram
-										//Sometimes the diagram is saved but the macro is not updated
-										var attInfo = null;
-										var pageInfo = null;
-										
-										function confirmDiagramInSync()
-										{
-											if (attInfo == null || pageInfo == null) 
-												return;
-											
-											var loadedVer = parseInt(revision);
-											
-											//TODO is this condition enough or we need to check timestamps also?
-											if (attInfo.version.number > loadedVer 
-													&& (pageInfo.version.message == null || pageInfo.version.message.indexOf("Reverted") < 0)) 
-											{
-												showDiagram(id, backupId, name, attInfo.version.number + '', links, {dontCheckVer: true}, displayName, contentId, null, null, aspects);
-												//I think updating macro here is too risky since calling confluence.getMacroData returns null
-											}
-										}
-										
-										//This fix contradict with copy/paste workflow where all diagrams have the same name
-										//On copy/paste diagram name must be changed
-										/*if (!retryParams.dontCheckVer && revision != null && revision.length > 0)
-										{
-						                    AP.request({
-						                        type: 'GET',
-						                        url: '/rest/api/content/' + id + '?expand=version',
-						                        contentType: 'application/json;charset=UTF-8',
-						                        success: function (resp) 
-						                        {
-						                        	pageInfo = JSON.parse(resp);
-						                            
-						                        	confirmDiagramInSync();
-						                        },
-						                        error: function (resp) 
-						                        {
-						                            //Ignore
-						                        }
-						                    });
-	
-						                    AP.request({
-						                        type: 'GET',
-						                        url: '/rest/api/content/' + id + '/child/attachment?filename=' + 
-						                        		encodeURIComponent(name) + '&expand=version',
-						                        contentType: 'application/json;charset=UTF-8',
-						                        success: function (resp) 
-						                        {
-						                        	var tmp = JSON.parse(resp);
-						                            
-						                        	if (tmp.results && tmp.results.length == 1)
-						                        	{
-						                        		attInfo = tmp.results[0];
-						                        	}
-						                        	
-						                        	confirmDiagramInSync();
-						                        },
-						                        error: function (resp) 
-						                        {
-						                            //Ignore
-						                        }
-						                    });
-										}*/
-										
-										//Saving the diagram to this page negates page linking feature!
-										//May be we should ask the user first or saving is not needed all together
-										/*if (retryParams.saveIt)
-										{
-								 			//Since attachment wasn't found in this page, it is better to save it to this page
-								 			//First load AC dynamically. Since AC is not needed in the viewer except for this case
-							 				var head = document.getElementsByTagName('head')[0];
-											var script = document.createElement('script');
-											script.setAttribute('data-options', 'resize:false;margin:false');
-											
-											// Main
-											script.onload = function()
-											{
-												//save diagram
-												AC.saveDiagram(retryParams.pageId, name, xml,
-												function()
-												{
-													//nothing!
-												}, 
-												function()
-												{
-													//nothing!
-												}, false, 'application/vnd.jgraph.mxfile', 'Diagram imported by Draw.io', false, false);
-												
-												//TODO save preview png
-												//This requires an editor to do the png export, may be a canvas can be used with supported browsers
-											};
-											script.src = 'connectUtils-1-4-8.js';
-											head.appendChild(script);
-										}*/
 							 		}
 								},
 								error: function (err)

File diff suppressed because it is too large
+ 245 - 243
src/main/webapp/js/app.min.js


+ 2 - 1
src/main/webapp/js/diagramly/App.js

@@ -295,7 +295,8 @@ App.startTime = new Date();
 App.pluginRegistry = {'4xAKTrabTpTzahoLthkwPNUn': '/plugins/explore.js',
 	'ex': '/plugins/explore.js', 'p1': '/plugins/p1.js',
 	'ac': '/plugins/connect.js', 'acj': '/plugins/connectJira.js',
-	'ac148': '/plugins/cConf-1-4-8.js', 'voice': '/plugins/voice.js',
+	'ac148': '/plugins/cConf-1-4-8.js', 'ac148cmnt': '/plugins/cConf-comments.js',
+	'voice': '/plugins/voice.js',
 	'tips': '/plugins/tooltips.js', 'svgdata': '/plugins/svgdata.js',
 	'electron': 'plugins/electron.js',
 	'number': '/plugins/number.js', 'sql': '/plugins/sql.js',

+ 50 - 16
src/main/webapp/js/diagramly/EditorUi.js

@@ -13005,9 +13005,19 @@
 			}
 		});
 		
+		var errWrapper = mxUtils.bind(this, function()
+		{
+	    	window.clearTimeout(timeoutThread);
+			
+			if (acceptResponse)
+			{
+				error.apply(this, arguments);
+			}
+		});
+		
 		msgMarkers = msgMarkers || {};
 		msgMarkers.callbackId = this.remoteInvokeCallbacks.length;
-		this.remoteInvokeCallbacks.push({callback: wrapper, error: error});
+		this.remoteInvokeCallbacks.push({callback: wrapper, error: errWrapper});
 		var msg = JSON.stringify({event: 'remoteInvoke', funtionName: remoteFn, functionArgs: remoteFnArgs, msgMarkers: msgMarkers});
 		
 		if (this.remoteWin != null) //remote invoke is ready
@@ -14032,6 +14042,11 @@ var CommentsWindow = function(editorUi, x, y, w, h, saveCallback)
 		mxUtils.write(commentTxtDiv, comment.content || '');
 		cdiv.appendChild(commentTxtDiv);
 		
+		if (comment.isLocked)
+		{
+			cdiv.style.opacity = '0.5';
+		}
+		
 		var actionsDiv = document.createElement('div');
 		actionsDiv.className = 'geCommentActions';
 		var actionsList = document.createElement('ul');
@@ -14133,7 +14148,7 @@ var CommentsWindow = function(editorUi, x, y, w, h, saveCallback)
 			}
 		};
 		
-		if (!readOnly && (level == 0 || canReplyToReplies))
+		if (!readOnly && !comment.isLocked && (level == 0 || canReplyToReplies))
 		{
 			addAction(mxResources.get('reply'), function()
 			{
@@ -14143,7 +14158,7 @@ var CommentsWindow = function(editorUi, x, y, w, h, saveCallback)
 		
 		var user = editorUi.getCurrentUser();
 		
-		if (user != null && user.id == comment.user.id && !readOnly)
+		if (user != null && user.id == comment.user.id && !readOnly && !comment.isLocked)
 		{
 			addAction(mxResources.get('edit'), function()
 			{
@@ -14175,25 +14190,44 @@ var CommentsWindow = function(editorUi, x, y, w, h, saveCallback)
 				{
 					showBusy(cdiv);
 					
-					comment.deleteComment(function()
+					comment.deleteComment(function(markedOnly)
 					{
-						var replies = collectReplies(comment).replies;
-						
-						for (var i = 0; i < replies.length; i++)
+						if (markedOnly === true)
 						{
-							listDiv.removeChild(replies[i]);
+							var commentTxt = cdiv.querySelector('.geCommentTxt');
+							commentTxt.innerHTML = '';
+							mxUtils.write(commentTxt, mxResources.get('msgDeleted'));
+							
+							var actions = cdiv.querySelectorAll('.geCommentAction');
+							
+							for (var i = 0; i < actions.length; i++)
+							{
+								actions[i].parentNode.removeChild(actions[i]);
+							}
+							
+							showDone(cdiv);
+							cdiv.style.opacity = '0.5';
 						}
-						
-						for (var i = 0; i < parentArr.length; i++)
+						else
 						{
-							if (parentArr[i] == comment) 
+							var replies = collectReplies(comment).replies;
+							
+							for (var i = 0; i < replies.length; i++)
 							{
-								parentArr.splice(i, 1);
-								break;
+								listDiv.removeChild(replies[i]);
 							}
+							
+							for (var i = 0; i < parentArr.length; i++)
+							{
+								if (parentArr[i] == comment) 
+								{
+									parentArr.splice(i, 1);
+									break;
+								}
+							}
+							
+							noComments.style.display = (listDiv.getElementsByTagName('div').length == 0) ? 'block' : 'none';
 						}
-						
-						noComments.style.display = (listDiv.getElementsByTagName('div').length == 0) ? 'block' : 'none';
 					}, function(err)
 					{
 						showError(cdiv);
@@ -14204,7 +14238,7 @@ var CommentsWindow = function(editorUi, x, y, w, h, saveCallback)
 			}, comment.isResolved);
 		}
 		
-		if (!readOnly && level == 0) //Resolve is a top-level action only
+		if (!readOnly && !comment.isLocked && level == 0) //Resolve is a top-level action only
 		{
 			function toggleResolve(evt)
 			{

+ 5 - 6
src/main/webapp/js/diagramly/LocalFile.js

@@ -29,7 +29,7 @@ mxUtils.extend(LocalFile, DrawioFile);
  */
 LocalFile.prototype.isAutosave = function()
 {
-	return this.fileHandle != null;
+	return this.fileHandle != null && DrawioFile.prototype.isAutosave.apply(this, arguments);
 };
 
 /**
@@ -163,12 +163,13 @@ LocalFile.prototype.saveFile = function(title, revision, success, error, useCurr
 		this.updateFileData();
 	}
 	
-	var data = this.getData();
 	var binary = this.ui.useCanvasForExport && /(\.png)$/i.test(this.getTitle());
+	this.setShadowModified(false);
+	var data = this.getData();
 	
 	var done = mxUtils.bind(this, function()
 	{
-		this.setModified(false);
+		this.setModified(this.getShadowModified());
 		this.contentChanged();
 		
 		if (success != null)
@@ -185,7 +186,6 @@ LocalFile.prototype.saveFile = function(title, revision, success, error, useCurr
 			if (!this.savingFile)
 			{
 				this.savingFileTime = new Date();
-				this.setShadowModified(false);
 				this.savingFile = true;
 				
 				var errorWrapper = mxUtils.bind(this, function(e)
@@ -211,7 +211,6 @@ LocalFile.prototype.saveFile = function(title, revision, success, error, useCurr
 								{
 									this.fileHandle.getFile().then(mxUtils.bind(this, function(desc)
 									{
-										this.setModified(this.getShadowModified());
 										this.savingFile = false;
 										this.desc = desc;
 										done();
@@ -270,7 +269,7 @@ LocalFile.prototype.saveFile = function(title, revision, success, error, useCurr
 		{
 			doSave(imageData);
 		}), error, (this.ui.getCurrentFile() != this) ?
-			this.getData() : null, p.scale, p.border);
+			data : null, p.scale, p.border);
 	}
 	else
 	{

+ 2 - 2
src/main/webapp/js/diagramly/sidebar/Sidebar-ER.js

@@ -69,12 +69,12 @@
 			}),
 			this.addEntry(dt + 'table row', function()	
 			{	
-	   			var cell = new mxCell(row.value, new mxGeometry(0, 0, 90, row.geometry.height), 'shape=partialRectangle;fillColor=none;align=left;strokeColor=none;spacingLeft=34;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;dropTarget=0;');	
+	   			var cell = new mxCell(row.value, new mxGeometry(0, 0, 90, row.geometry.height), 'shape=partialRectangle;fillColor=none;align=left;verticalAlign=middle;strokeColor=none;spacingLeft=34;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;dropTarget=0;');	
 	   			cell.vertex = true;	
 
 	   			var cell1 = sb.cloneCell(row, '');	
 	   			cell1.connectable = false;	
-	   			cell1.style = 'shape=partialRectangle;top=0;left=0;bottom=0;fillColor=none;stokeWidth=1;dashed=1;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[];portConstraint=eastwest;part=1;'	
+	   			cell1.style = 'shape=partialRectangle;top=0;left=0;bottom=0;fillColor=none;stokeWidth=1;dashed=1;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[];portConstraint=eastwest;part=1;'	
 	   			cell1.geometry.width = 30;	
 	   			cell.insert(cell1);	
 

File diff suppressed because it is too large
+ 319 - 317
src/main/webapp/js/viewer-static.min.js


File diff suppressed because it is too large
+ 319 - 317
src/main/webapp/js/viewer.min.js


+ 1 - 115
src/main/webapp/plugins/cConf-1-4-8.js

@@ -24,11 +24,9 @@ Draw.loadPlugin(function(ui)
 					{
 						ui.format.refresh();
 					}
-					
-					//Prefetch comments
-					ui.getComments(function(){}, function(){});
 				}
 				
+				ui.initComments(macroData.contentId || macroData.custContentId);
 				macroData.diagramDisplayName = data.title;
 			}
 		}
@@ -102,7 +100,6 @@ Draw.loadPlugin(function(ui)
 		if (eventName == 'export')
 		{
 			msg.macroData = macroData;
-			msg.comments = confComments;
 		}
 
 		return msg;
@@ -532,117 +529,6 @@ Draw.loadPlugin(function(ui)
 		}
 	};
 	
-	//Comments
-
-	function setModified()
-	{
-		ui.editor.setStatus(mxUtils.htmlEntities(mxResources.get('unsavedChanges')));
-		ui.editor.setModified(true);	
-	};
-	
-	var confUser = null;
-	var confComments = null;
-	
-	ui.getCurrentUser = function()
-	{
-		if (confUser == null)
-		{
-			ui.remoteInvoke('getCurrentUser', null, null, function(user)
-			{
-				confUser = new DrawioUser(user.id, user.email, user.displayName, user.pictureUrl);
-			}, function()
-			{
-				//ignore such that next call we retry
-			});
-			
-			//Return a dummy user until we have the actual user in order for UI to be populated
-			return new DrawioUser(Date.now(), null, 'Anonymous');
-		}
-		
-		return confUser;
-	};
-	
-	
-	ui.commentsSupported = function()
-	{
-		return true;
-	};
-	
-	ui.commentsRefreshNeeded = function()
-	{
-		return false;
-	};
-
-	function confCommentToDrawio(cComment, pCommentId)
-	{
-		var comment = new DrawioComment(null, cComment.id, cComment.content, 
-				cComment.modifiedDate, cComment.createdDate, cComment.isResolved,
-				new DrawioUser(cComment.user.id, cComment.user.email,
-						cComment.user.displayName, cComment.user.pictureUrl), pCommentId);
-		
-		for (var i = 0; cComment.replies != null && i < cComment.replies.length; i++)
-		{
-			comment.addReplyDirect(confCommentToDrawio(cComment.replies[i], cComment.id));
-		}
-		
-		return comment;
-	};
-			
-	ui.getComments = function(success, error)
-	{
-		if (confComments == null)
-		{
-			ui.remoteInvoke('getComments', [macroData.contentId || macroData.custContentId], null, function(comments)
-			{
-				confComments = [];
-				
-				for (var i = 0; i < comments.length; i++)
-				{
-					confComments.push(confCommentToDrawio(comments[i]));
-				}
-				
-				success(confComments);
-			}, error);
-		}
-		else
-		{
-			success(confComments);
-		}
-	};
-
-	ui.addComment = function(comment, success, error)
-	{
-		setModified();
-		success(confUser.id + ':' + Date.now()); 
-	};
-			
-	ui.newComment = function(content, user)
-	{
-		return new DrawioComment(null, null, content, Date.now(), Date.now(), false, user); //remove file information
-	};
-	
-	//In Confluence, comments are part of the file (specifically custom contents), so needs to mark as changed with every change
-	DrawioComment.prototype.addReply = function(reply, success, error, doResolve, doReopen)
-	{
-		setModified();
-		success();
-	};
-
-	DrawioComment.prototype.editComment = function(newContent, success, error)
-	{
-		setModified();
-		success();
-	};
-
-	DrawioComment.prototype.deleteComment = function(success, error)
-	{
-		setModified();
-		success();
-	};
-	
-	//Prefetch current user 
-	ui.getCurrentUser();
-	
 	//======================== Revisions ========================
 	
 	ui.isRevisionHistoryEnabled = function()

+ 406 - 0
src/main/webapp/plugins/cConf-comments.js

@@ -0,0 +1,406 @@
+/**
+ * Plugin for comments in embed mode in Confluence Connect post version 1.4.8
+ */
+Draw.loadPlugin(function(ui)
+{
+	var RESOLVED_MARKER = '$$RES$$ ';
+	var REPLY_MARKER = '$$REP$$';
+	var REPLY_MARKER_END = '$$ ';
+	var DELETED_MARKER = '$$DELETED$$';
+	
+	var confUser = null;
+	var confComments = null;
+	var commentsVer = null;
+
+	// Returns modified macro data to client
+	var uiCreateLoadMessage = ui.createLoadMessage;
+	
+	ui.createLoadMessage = function(eventName)
+	{
+		var msg = uiCreateLoadMessage.apply(this, arguments);
+		
+		if (eventName == 'export')
+		{
+			msg.comments = confComments;
+		}
+
+		return msg;
+	};
+
+	function setModified()
+	{
+		ui.editor.setStatus(mxUtils.htmlEntities(mxResources.get('unsavedChanges')));
+		ui.editor.setModified(true);	
+	};
+	
+	var origRemoteInvoke = ui.remoteInvoke;
+	
+	ui.remoteInvoke = function()
+	{
+		if (typeof AC !== 'undefined')
+		{
+			var fnName = arguments[0];
+			var fnArgs = arguments[1] || [];
+			fnArgs.push(arguments[arguments.length - 2]);
+			fnArgs.push(arguments[arguments.length - 1]);
+			AC[fnName].apply(AC, fnArgs);
+		}
+		else
+		{
+			origRemoteInvoke.apply(ui, arguments);
+		}
+	};
+	
+	ui.getCurrentUser = function()
+	{
+		if (confUser == null)
+		{
+			ui.remoteInvoke('getCurrentUser', null, null, function(user)
+			{
+				confUser = new DrawioUser(user.id, user.email, user.displayName, user.pictureUrl);
+			}, function()
+			{
+				//ignore such that next call we retry
+			});
+			
+			//Return a dummy user until we have the actual user in order for UI to be populated
+			return new DrawioUser(Date.now(), null, 'Anonymous');
+		}
+		
+		return confUser;
+	};
+	
+	
+	ui.commentsSupported = function()
+	{
+		return true;
+	};
+	
+	//Will limit ability to reply on replies to simplify retrieval in version 2
+	ui.canReplyToReplies = function()
+	{
+		return commentsVer == 1;
+	};
+	
+	ui.commentsRefreshNeeded = function()
+	{
+		return commentsVer != 1; //Refresh is needed for new format or if pre-fetch is not finished yet
+	};
+	
+	function confOldCommentToDrawio(cComment, pCommentId)
+ 	{
+        if (cComment.isDeleted) return null; //skip deleted comments
+        
+		var comment = new DrawioComment(null, cComment.id, cComment.content, 
+						cComment.modifiedDate, cComment.createdDate, cComment.isResolved,
+						new DrawioUser(cComment.user.id, cComment.user.email,
+						cComment.user.displayName, cComment.user.pictureUrl), pCommentId);
+		
+		for (var i = 0; cComment.replies != null && i < cComment.replies.length; i++)
+		{
+			comment.addReplyDirect(confOldCommentToDrawio(cComment.replies[i], cComment.id));
+		}
+		
+		return comment;
+	};
+
+	function confCommentToDrawio(atlasComment, parentId, siteUrl)
+	{
+		var user = atlasComment.history.createdBy;
+		var comment = new DrawioComment({attVer: atlasComment.attVer, ui: ui}, atlasComment.id, 
+				decodeURIComponent(atlasComment.body.storage.value), 
+				atlasComment.version.when, atlasComment.history.createdDate, false,
+				new DrawioUser(user.accountId, user.username,
+						user.displayName, siteUrl + user.profilePicture.path));
+		comment.parentId = parentId;
+		comment.version = atlasComment.version.number;
+		
+		if (comment.content == DELETED_MARKER)
+		{
+			comment.content = mxResources.get('msgDeleted');
+			comment.isLocked = true;
+		}
+
+		var replies = atlasComment.children != null ? atlasComment.children.comment.results : [];
+		
+		for (var i = 0; i < replies.length; i++)
+		{
+			var reply = confCommentToDrawio(replies[i], atlasComment.id, siteUrl);
+    		comment.addReplyDirect(reply);
+    		
+    		var isResolvedReply = reply.content.indexOf(RESOLVED_MARKER) == 0;
+    		
+    		if (isResolvedReply)
+			{
+    			reply.content = reply.content.substr(RESOLVED_MARKER.length);
+    			comment.isResolved = i == (replies.length - 1);
+			}
+		}
+		
+		return comment;
+	};
+	
+	//TODO Improve this requirement if possible
+	//This function must be called before any interaction with comments
+	//Prefetch comments (for new diagrams, this sets comments version to 2)
+	ui.initComments = function(contentId, success, error)
+	{
+		if (confComments == null)
+		{
+			ui.remoteInvoke('getOldComments', [contentId], null, function(comments, spaceKey, pageId, pageType, contentVer)
+			{
+				confComments = [];
+
+				for (var i = 0; i < comments.length; i++)
+				{
+					var comment = confOldCommentToDrawio(comments[i]);
+					
+					if (comment != null) confComments.push(comment);
+				}
+				
+				//If we have no old comments, switch to the new comments format
+				commentsVer = confComments.length == 0? 2 : 1;
+				
+				if (success)
+				{
+					success(spaceKey, pageId, pageType, contentVer);
+				}
+			}, function()
+			{
+				if (error)
+				{
+					error();
+				}
+			});
+		}
+		else if (success)
+		{
+			success(confComments);
+		}
+	};
+	
+	ui.getComments = function(success, error)
+	{
+		if (commentsVer == null)
+		{
+			error(); //User can refresh to retry, we don't have content id here to get the old comments
+		}
+		else if (commentsVer == 1)
+		{
+			success(confComments);
+		}
+		else
+		{
+			ui.remoteInvoke('getComments', [null, false], null, function(comments, siteUrl)
+			{
+				var conComments = [];
+				
+				//First pass to convert replies to old comments to regular replies
+				var commentsMap = {};
+				var oldVerReplies = [];
+				var origComments = [];
+				
+				for (var i = 0; i < comments.length; i++)
+				{
+					var cnt = decodeURIComponent(comments[i].body.storage.value);
+					
+					if (cnt.indexOf(REPLY_MARKER) == 0)
+					{
+						var end = cnt.indexOf(REPLY_MARKER_END, REPLY_MARKER.length);
+						var parentId = cnt.substring(REPLY_MARKER.length, end);
+						comments[i].body.storage.value = cnt.substring(REPLY_MARKER_END.length + end);
+						oldVerReplies.push({parentId: parentId, reply: comments[i]});
+					}
+					else
+					{
+						commentsMap[comments[i].id] = comments[i];
+						origComments.push(comments[i]);
+					}
+				}
+	
+				for (var i = 0; i < oldVerReplies.length; i++)
+				{
+					var pComment = commentsMap[oldVerReplies[i].parentId];
+					
+					if (pComment != null)
+					{
+						if (pComment.children == null) 
+						{
+							pComment.children = {comment: {results: []}};	
+						}
+						
+						pComment.children.comment.results.push(oldVerReplies[i].reply);
+					}
+				}
+				
+				for (var i = 0; i < origComments.length; i++)
+				{
+					conComments.push(confCommentToDrawio(origComments[i], null, siteUrl));
+				}
+				
+				success(conComments);
+			}, error);
+		}
+	};
+
+	ui.addComment = function(comment, success, error)
+	{
+		if (commentsVer == null)
+		{
+			error();
+		}
+		else if (commentsVer == 2)
+		{
+			ui.remoteInvoke('addComment', [comment.content], null, function(id, version, attVer)
+			{
+				comment.version = version;
+				comment.file.attVer = attVer;
+	        	success(id);
+			}, error);
+		}
+		else
+		{
+			comment.id = confUser.id + ':' + Date.now();
+
+			if (ui.saveComments != null)
+			{
+				var tmpComments = JSON.parse(JSON.stringify(confComments));
+				tmpComments.push(comment);
+					
+				ui.saveComments(tmpComments, function()
+				{
+					success(comment.id);
+				}, error);
+			}
+			else
+			{
+				setModified();
+				success(comment.id);
+			}
+		}
+	};
+			
+	ui.newComment = function(content, user)
+	{
+		return new DrawioComment(commentsVer == 2? {ui: ui} : null, null, //remove file information for old format 
+				content, Date.now(), Date.now(), false, user); 
+	};
+	
+	DrawioComment.prototype.addReply = function(reply, success, error, doResolve, doReopen)
+	{
+		if (commentsVer == null)
+		{
+			error();
+		}
+		else if (commentsVer == 2)
+		{
+			ui.remoteInvoke('addCommentReply', [this.id, this.file.attVer, reply.content, doResolve], null, function(id, version)
+			{
+				reply.version = version;
+	        	success(id);
+			}, error);
+		}
+		else
+		{
+			if (ui.saveComments != null)
+			{
+				reply.id = confUser.id + ':' + Date.now();
+				this.replies.push(reply);
+				var isResolved = this.isResolved;
+				
+				if (doResolve)
+				{
+					this.isResolved = true;
+				}
+				else if (doReopen)
+				{
+					this.isResolved = false;
+				}
+				
+				var tmpComments = JSON.parse(JSON.stringify(confComments));
+				this.replies.pop(); //Undo in case more changes are done before getting the reply
+				this.isResolved = isResolved;
+				
+				ui.saveComments(tmpComments, function()
+				{
+					success(reply.id);
+				}, error);
+			}
+			else
+			{
+				setModified();
+				success();
+			}
+		}
+	};
+
+	DrawioComment.prototype.editComment = function(newContent, success, error)
+	{
+		if (commentsVer == null)
+		{
+			error();
+		}
+		else if (commentsVer == 2)
+		{
+			var _this = this;
+			
+			ui.remoteInvoke('editComment', [this.id, this.version, newContent], null, function(version)
+			{
+				_this.version = version;
+	        	success();
+			}, error);
+		}
+		else
+		{
+			if (ui.saveComments != null)
+			{
+				var oldContent = this.content;
+				this.content = newContent;
+				var tmpComments = JSON.parse(JSON.stringify(confComments));
+				this.content = oldContent;
+				
+				ui.saveComments(tmpComments, success, error);
+			}
+			else
+			{
+				setModified();
+				success();
+			}
+		}
+	};
+
+	DrawioComment.prototype.deleteComment = function(success, error)
+	{
+		if (commentsVer == null)
+		{
+			error();
+		}
+		else if (commentsVer == 2)
+		{
+			ui.remoteInvoke('deleteComment', [this.id, this.version, this.replies != null && this.replies.length > 0], null, success, error);
+		}
+		else
+		{
+			if (ui.saveComments != null)
+			{
+				var that = this;
+				this.isDeleted = true; //Mark as deleted since searching for this comment in the entire structure is complex. It will be cleaned in next save
+				var tmpComments = JSON.parse(JSON.stringify(confComments));
+				
+				ui.saveComments(tmpComments, success, function(err) 
+				{
+					that.isDeleted = false;
+					error(err);
+				});
+			}
+			else
+			{
+				setModified();
+				success();
+			}
+		}
+	};
+	
+	//Prefetch current user 
+	ui.getCurrentUser();
+});

+ 1 - 1
src/main/webapp/service-worker.js

@@ -6,7 +6,7 @@ if (workbox)
 	workbox.precaching.precacheAndRoute([
   {
     "url": "js/app.min.js",
-    "revision": "c43c110aa92991ed5edd948538ce6370"
+    "revision": "155c9b79a191113daecc414c9a898328"
   },
   {
     "url": "js/extensions.min.js",