Browse Source

10.0.42 release

Former-commit-id: 7172ab91f33602cfbac280bdf47029f4b466534a
Gaudenz Alder 6 years ago
parent
commit
8a804ce33a

+ 9 - 0
ChangeLog

@@ -1,3 +1,12 @@
+16-JAN-2019: 10.0.43
+
+- Fixes possible NPE in hashValue
+
+16-JAN-2019: 10.0.42
+
+- Fixes handling of invalid files
+- Uses mxGraph 3.9.13 beta 11
+
 15-JAN-2019: 10.0.41
 
 - Adds ignored page size in checksum

+ 1 - 1
VERSION

@@ -1 +1 @@
-10.0.41
+10.0.42

File diff suppressed because it is too large
+ 5 - 4
etc/mxgraph/mxClient.js


+ 1 - 1
src/main/webapp/cache.manifest

@@ -1,7 +1,7 @@
 CACHE MANIFEST
 
 # THIS FILE WAS GENERATED. DO NOT MODIFY!
-# 01/15/2019 05:34 PM
+# 01/16/2019 04:21 PM
 
 app.html
 index.html?offline=1

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


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


File diff suppressed because it is too large
+ 613 - 610
src/main/webapp/js/atlas.min.js


+ 0 - 5
src/main/webapp/js/diagramly/App.js

@@ -477,11 +477,6 @@ App.main = function(callback, createUi)
 				// Mapping from key to URL in App.plugins
 				var t = temp.split(';');
 				
-				if (typeof window.drawDevUrl == 'undefined')
-				{
-					drawDevUrl = '';
-				}
-
 				for (var i = 0; i < t.length; i++)
 				{
 					var url = App.pluginRegistry[t[i]];

+ 98 - 88
src/main/webapp/js/diagramly/DrawioFile.js

@@ -25,6 +25,10 @@ DrawioFile = function(ui, data)
 	this.stats = {
 		joined: 0, /* number of join messages received */
 		merged: 0, /* number of calls to merge */
+		lastMerge: 0, /* details of the last successful merge */
+		lastMergeTime: 0, /* timestamp of the last call to merge */
+		lastOpenTime: 0, /* timestamp of the last call to open */
+		shadowState: 0, /* current etag hash for shadow */
 		opened: 0, /* number of calls to open */
 		closed: 0, /* number of calls to close */
 		destroyed: 0, /* number of calls to close */
@@ -176,53 +180,52 @@ DrawioFile.prototype.synchronizeFile = function(success, error)
 */
 DrawioFile.prototype.updateFile = function(success, error, abort, shadow)
 {
-	if (this.ui.getCurrentFile() != this)
+	if (abort == null || !abort())
 	{
-		if (error != null)
+		if (this.ui.getCurrentFile() != this || this.invalidChecksum)
 		{
-			error(e);
+			if (error != null)
+			{
+				error(e);
+			}
 		}
-	}
-	else if (abort == null || !abort())
-	{
-		this.getLatestVersion(mxUtils.bind(this, function(latestFile)
+		else
 		{
-			try
+			this.getLatestVersion(mxUtils.bind(this, function(latestFile)
 			{
-				if (this.ui.getCurrentFile() != this)
-				{
-					if (error != null)
-					{
-						error(e);
-					}
-				}
-				else if (abort == null || !abort())
+				try
 				{
-					if (this.ui.getCurrentFile() == this)
+					if (abort == null || !abort())
 					{
-						if (latestFile != null)
+						if (this.ui.getCurrentFile() != this || this.invalidChecksum)
 						{
-							this.mergeFile(latestFile, success, error, shadow);
+							if (error != null)
+							{
+								error(e);
+							}
 						}
 						else
 						{
-							this.reloadFile(success, error);
+							if (latestFile != null)
+							{
+								this.mergeFile(latestFile, success, error, shadow);
+							}
+							else
+							{
+								this.reloadFile(success, error);
+							}
 						}
 					}
-					else if (error != null)
+				}
+				catch (e)
+				{
+					if (error != null)
 					{
 						error(e);
 					}
 				}
-			}
-			catch (e)
-			{
-				if (error != null)
-				{
-					error(e);
-				}
-			}
-		}), error);
+			}), error);
+		}
 	}
 };
 
@@ -239,7 +242,7 @@ DrawioFile.prototype.mergeFile = function(file, success, error, diffShadow)
 		var shadow = (this.shadowPages != null) ? this.shadowPages :
 			this.ui.getPagesForNode(mxUtils.parseXml(
 			this.shadowData).documentElement);
-		this.checkShadow(shadow);
+		this.checkPages(shadow, 'mergeFile init');
 	
 		// Loads new document as shadow document
 		this.shadowPages = this.ui.getPagesForNode(
@@ -259,6 +262,9 @@ DrawioFile.prototype.mergeFile = function(file, success, error, diffShadow)
 		{
 			// Patching previous shadow to verify checksum
 			var patched = this.ui.patchPages(shadow, patches[0]);
+			this.stats.shadowState = this.ui.hashValue(file.getCurrentEtag());
+			this.checkPages(patched, 'mergeFile patched');
+			
 			var patchedDetails = {};
 			var checksum = this.ui.getHashValueForPages(patched, patchedDetails);
 			var currentDetails = {};
@@ -293,9 +299,13 @@ DrawioFile.prototype.mergeFile = function(file, success, error, diffShadow)
 				this.patch(patches,
 					(DrawioFile.LAST_WRITE_WINS) ?
 					this.backupPatch : null);
-				this.checkPages();
+				this.checkPages(this.ui.pages, 'mergeFile done');
 			}
 		}
+		else
+		{
+			this.stats.shadowState = this.ui.hashValue(file.getCurrentEtag());
+		}
 
 		this.invalidChecksum = false;
 		this.inConflictState = false;
@@ -312,6 +322,7 @@ DrawioFile.prototype.mergeFile = function(file, success, error, diffShadow)
 	{
 		this.inConflictState = true;
 		this.invalidChecksum = true;
+		this.descriptorChanged();
 		
 		if (error != null)
 		{
@@ -345,7 +356,7 @@ DrawioFile.prototype.getAnonymizedXmlForPages = function(pages)
 			
 			if (urlParams['dev'] != '1')
 			{
-				temp = this.ui.anonymizeNode(temp);
+				temp = this.ui.anonymizeNode(temp, true);
 			}
 			
 			temp.setAttribute('id', pages[i].getId());
@@ -362,27 +373,12 @@ DrawioFile.prototype.getAnonymizedXmlForPages = function(pages)
 	return mxUtils.getPrettyXml(file);
 };
 
-/**
- * Checks if the file is empty.
- */
-DrawioFile.prototype.checkPages = function()
-{
-	if (this.ui.getCurrentFile() == this && (this.ui.pages == null ||
-		this.ui.pages.length == 0))
-	{
-		this.sendErrorReport(
-			'Pages is null or empty',
-			'ShadowPages: ' + (this.shadowPages != null) +
-			'\nShadowData: ' + (this.shadowData != null));
-	}
-};
-
 /**
  * Checks if the given shadow is valid.
  */
-DrawioFile.prototype.checkShadow = function(shadow)
+DrawioFile.prototype.checkPages = function(pages, info)
 {
-	if (shadow == null || shadow.length == 0)
+	if (this.ui.getCurrentFile() == this && (pages == null || pages.length == 0))
 	{
 		var data = (this.shadowData == null) ? 'null' :
 			this.compressReportData(
@@ -391,10 +387,11 @@ DrawioFile.prototype.checkShadow = function(shadow)
 			null, 5000);
 		
 		this.sendErrorReport(
-			'Shadow is null or empty',
-			'Shadow: ' + ((shadow != null) ? shadow.length : 'null') +
+			'Pages is null or empty',
+			'Shadow: ' + ((pages != null) ? pages.length : 'null') +
 			'\nShadowPages: ' + ((this.shadowPages != null) ?
 				this.shadowPages.length : 'null') +
+			((info != null) ? ('\nInfo: ' + info) : '') +
 			'\nShadowData: ' + data);
 	}
 };
@@ -465,7 +462,7 @@ DrawioFile.prototype.checksumError = function(error, patches, details, etag)
 				'Checksum Error',
 				((details != null) ? (details) : '') +
 				'\n\nPatches:\n' + json +
-				((remote != null) ? ('\n\nRemote:\n' + remote) : ''),
+				((remote != null) ? ('\n\nHeadRevision:\n' + remote) : ''),
 				err, 70000);
 		});
 
@@ -533,9 +530,10 @@ DrawioFile.prototype.sendErrorReport = function(title, details, error, max)
 			'\nUser=' + uid + ' (' + cid + ')' +
 			'\nPlugins=' + ((mxSettings.settings != null) ? mxSettings.getPlugins() : 'null') +
 			'\nSync=' + DrawioFile.SYNC +
+			((error != null) ? ('\nError=' + error) : '') +
 			'\n\nStats:\n' + JSON.stringify(this.stats, null, 2) +
 			((details != null) ? ('\n\n' + details) : '') +
-			'\n\nLocal:\n' + data +
+			'\n\nShadow:\n' + data +
 			'\n\nStack:\n' + stack, max);
 	}
 	catch (e)
@@ -962,6 +960,7 @@ DrawioFile.prototype.getData = function()
 DrawioFile.prototype.open = function()
 {
 	this.stats.opened++;
+	this.stats.lastOpenTime = new Date().toISOString();
 	var data = this.getData();
 	
 	if (data != null)
@@ -976,6 +975,10 @@ DrawioFile.prototype.open = function()
 			this.shadowPages = null;
 		}
 	}
+	else
+	{
+		this.sendErrorReport('Error in open', 'Data was null');
+	}
 
 	this.installListeners();
 	
@@ -1191,24 +1194,18 @@ DrawioFile.prototype.addAllSavedStatus = function(status)
 	if (this.ui.statusContainer != null && this.ui.getCurrentFile() == this)
 	{
 		status = (status != null) ? status : mxUtils.htmlEntities(mxResources.get(this.allChangesSavedKey));
+		this.ui.editor.setStatus('<div title="'+ status + '">' + status + '</div>');
+		var links = this.ui.statusContainer.getElementsByTagName('div');
 		
-		if (this.isRevisionHistorySupported())
+		if (links.length > 0 && this.isRevisionHistorySupported())
 		{
-			this.ui.editor.setStatus('<div title="'+ mxUtils.htmlEntities(mxResources.get('revisionHistory')) +
-				'" style="text-decoration:underline;cursor:pointer;">' + status + '</div>');
-			var links = this.ui.statusContainer.getElementsByTagName('div');
+			links[0].style.cursor = 'pointer';
+			links[0].style.textDecoration = 'underline';
 			
-			if (links.length > 0)
+			mxEvent.addListener(links[0], 'click', mxUtils.bind(this, function()
 			{
-				mxEvent.addListener(links[0], 'click', mxUtils.bind(this, function()
-				{
-					this.ui.actions.get('revisionHistory').funct();
-				}));
-			}
-		}
-		else
-		{
-			this.ui.editor.setStatus(status);
+				this.ui.actions.get('revisionHistory').funct();
+			}));
 		}
 	}
 };
@@ -1222,8 +1219,10 @@ DrawioFile.prototype.addUnsavedStatus = function(err)
 	{
 		if (err instanceof Error && err.message != null && err.message != '')
 		{
-			this.ui.editor.setStatus('<div class="geStatusAlert" style="overflow:hidden;">' +
-				mxUtils.htmlEntities(mxResources.get('unsavedChanges')) +
+			var status = mxUtils.htmlEntities(mxResources.get('unsavedChanges'));
+			
+			this.ui.editor.setStatus('<div title="'+ status +
+				'" class="geStatusAlert" style="overflow:hidden;">' + status +
 				' (' + mxUtils.htmlEntities(err.message) + ')</div>');
 		}
 		else
@@ -1247,10 +1246,12 @@ DrawioFile.prototype.addUnsavedStatus = function(err)
 			{
 				msg = msg.substring(0, 60) + '...';
 			}
-			
-			this.ui.editor.setStatus('<div class="geStatusAlert" style="cursor:pointer;overflow:hidden;">' +
-				mxUtils.htmlEntities(mxResources.get('unsavedChangesClickHereToSave')) +
-				((msg != null && msg != '') ? ' (' + mxUtils.htmlEntities(msg) + ')' : '') + '</div>');
+
+			var status = mxUtils.htmlEntities(mxResources.get('unsavedChangesClickHereToSave')) +
+				((msg != null && msg != '') ? ' (' + mxUtils.htmlEntities(msg) + ')' : '');
+			this.ui.editor.setStatus('<div title="'+ status +
+				'" class="geStatusAlert" style="cursor:pointer;overflow:hidden;">' +
+				status + '</div>');
 			
 			// Installs click handler for saving
 			var links = this.ui.statusContainer.getElementsByTagName('div');
@@ -1265,8 +1266,11 @@ DrawioFile.prototype.addUnsavedStatus = function(err)
 			}
 			else
 			{
-				this.ui.editor.setStatus('<div class="geStatusAlert" style="overflow:hidden;">' +
-					mxUtils.htmlEntities(mxResources.get('unsavedChanges')) + '</div>');
+				var status = mxUtils.htmlEntities(mxResources.get('unsavedChanges'));
+				
+				this.ui.editor.setStatus('<div title="'+ status +
+					'" class="geStatusAlert" style="overflow:hidden;">' + status +
+					' (' + mxUtils.htmlEntities(err.message) + ')</div>');
 			}
 		}
 	}
@@ -1278,13 +1282,13 @@ DrawioFile.prototype.addUnsavedStatus = function(err)
  */
 DrawioFile.prototype.addConflictStatus = function(fn, message)
 {
-	if (this.invalidChecksum)
+	if (this.invalidChecksum && message == null)
 	{
 		message = mxResources.get('checksum');
 	}
 
 	this.setConflictStatus(mxUtils.htmlEntities(mxResources.get('fileChangedSync')) +
-			((message != null && message != '') ? ' (' + mxUtils.htmlEntities(message) + ')' : ''));
+		((message != null && message != '') ? ' (' + mxUtils.htmlEntities(message) + ')' : ''));
 	this.ui.spinner.stop();
 	this.clearAutosave();
 
@@ -1312,17 +1316,22 @@ DrawioFile.prototype.addConflictStatus = function(fn, message)
  */
 DrawioFile.prototype.setConflictStatus = function(message)
 {
-	this.ui.editor.setStatus('<div class="geStatusAlert geBlink" style="cursor:pointer;overflow:hidden;">' + message +
-		' <a href="https://desk.draw.io/support/solutions/articles/16000087947" target="_blank"><img border="0" ' +
-		'title="' + mxUtils.htmlEntities(mxResources.get('help')) + '" style="margin-left:2px;cursor:help;' +
-		'opacity:0.5;width:16px;height:16px;" valign="bottom" src="' + Editor.helpImage + '" style=""/></a></div>');
+	this.ui.editor.setStatus('<div title="'+ message + '" class="geStatusAlert geBlink" style="cursor:pointer;overflow:hidden;">' +
+		message + ' <a href="https://desk.draw.io/support/solutions/articles/16000087947" target="_blank"><img border="0" ' +
+		'style="margin-left:2px;cursor:help;opacity:0.5;width:16px;height:16px;" valign="bottom" src="' + Editor.helpImage +
+		'" style=""/></a></div>');
 };
 
 /**
  * Shows a conflict dialog to the user.
  */
-DrawioFile.prototype.showRefreshDialog = function(success, error)
+DrawioFile.prototype.showRefreshDialog = function(success, error, message)
 {
+	if (message == null)
+	{
+		message = mxResources.get('checksum');
+	}
+	
 	if (this.ui.editor.isChromelessView() && !this.ui.editor.editable)
 	{
 		this.ui.alert(mxResources.get('fileChangedSync'), mxUtils.bind(this, function()
@@ -1336,9 +1345,9 @@ DrawioFile.prototype.showRefreshDialog = function(success, error)
 		this.addConflictStatus(mxUtils.bind(this, function()
 		{
 			this.showRefreshDialog(success, error);
-		}));
+		}), message);
 		
-		this.ui.showError(mxResources.get('error') + ' (' + mxResources.get('checksum') + ')',
+		this.ui.showError(mxResources.get('error') + ' (' + message + ')',
 			mxResources.get('fileChangedSyncDialog'),
 			mxResources.get('makeCopy'), mxUtils.bind(this, function()
 		{
@@ -1583,7 +1592,7 @@ DrawioFile.prototype.handleConflictError = function(err, manual)
 	}
 	else if (this.invalidChecksum)
 	{
-		this.showRefreshDialog(success, error);
+		this.showRefreshDialog(success, error, this.getErrorMessage(err));
 	}
 	else if (manual)
 	{
@@ -1648,7 +1657,7 @@ DrawioFile.prototype.fileSaved = function(savedData, lastDesc, success, error)
 		this.stats.fileSaved++;
 		this.inConflictState = false;
 		this.invalidChecksum = false;
-		this.checkPages();
+		this.checkPages(this.ui.pages, 'fileSaved');
 		
 		if (this.sync == null)
 		{
@@ -1671,6 +1680,7 @@ DrawioFile.prototype.fileSaved = function(savedData, lastDesc, success, error)
 	{
 		this.inConflictState = true;
 		this.invalidChecksum = true;
+		this.descriptorChanged();
 		
 		if (error != null)
 		{
@@ -1681,7 +1691,7 @@ DrawioFile.prototype.fileSaved = function(savedData, lastDesc, success, error)
 		{
 			this.sendErrorReport('Error in fileSaved',
 				'SavedData:\n' + this.compressReportData(
-				this.ui.anonymizeString(savedData), 25000), e);
+				this.ui.anonymizeString(savedData), 5000), e);
 		}
 		catch (e2)
 		{

+ 213 - 170
src/main/webapp/js/diagramly/DrawioFileSync.js

@@ -109,7 +109,7 @@ DrawioFileSync = function(file)
 		this.lastActivity = new Date();
 
 		if (this.enabled && !this.file.inConflictState &&
-			!this.redirectDialogShowing)
+			!this.file.redirectDialogShowing)
 		{
 			try
 			{
@@ -434,7 +434,7 @@ DrawioFileSync.prototype.updateStatus = function()
 	
 	if (!this.file.isModified() && !this.file.inConflictState &&
 		this.file.autosaveThread == null && !this.file.savingFile &&
-		!this.redirectDialogShowing)
+		!this.file.redirectDialogShowing)
 	{
 		if (this.enabled && this.ui.statusContainer != null)
 		{
@@ -459,31 +459,25 @@ DrawioFileSync.prototype.updateStatus = function()
 
 			var label = mxResources.get('lastChange', [str]);
 			
-			this.ui.editor.setStatus('<div style="display:inline-block;">' + mxUtils.htmlEntities(label)  + '</div>' +
-				((msg != null) ? ' <span style="opacity:0;">(' + msg + ')</span>' : '') +
+			this.ui.editor.setStatus('<div title="'+ mxUtils.htmlEntities(label) +
+				'" style="display:inline-block;">' + mxUtils.htmlEntities(label)  + '</div>' +
+				((msg != null) ? ' <span style="opacity:0;" title="' + mxUtils.htmlEntities(msg) +
+				'">(' + mxUtils.htmlEntities(msg) + ')</span>' : '') +
 				(this.file.isEditable() ? '' : '<div class="geStatusAlert" style="margin-left:8px;display:inline-block;">' +
 					mxUtils.htmlEntities(mxResources.get('readOnly')) + '</div>') +
 				(this.isConnected() ? '' : '<div class="geStatusAlert geBlink" style="margin-left:8px;display:inline-block;">' +
 					mxUtils.htmlEntities(mxResources.get('disconnected')) + '</div>'));
 			var links = this.ui.statusContainer.getElementsByTagName('div');
 			
-			if (links.length > 0)
+			if (links.length > 0 && history)
 			{
-				if (history)
-				{
-					links[0].style.cursor = 'pointer';
-					links[0].style.textDecoration = 'underline';
-					links[0].setAttribute('title', mxResources.get('revisionHistory'));
-					
-					mxEvent.addListener(links[0], 'click', mxUtils.bind(this, function()
-					{
-						this.ui.actions.get('revisionHistory').funct();
-					}));
-				}
-				else
+				links[0].style.cursor = 'pointer';
+				links[0].style.textDecoration = 'underline';
+				
+				mxEvent.addListener(links[0], 'click', mxUtils.bind(this, function()
 				{
-					links[0].setAttribute('title', label);
-				}
+					this.ui.actions.get('revisionHistory').funct();
+				}));
 			}
 			
 			// Fades in/out last message
@@ -590,30 +584,43 @@ DrawioFileSync.prototype.handleMessageData = function(data)
 	}
 };
 
+/**
+ * Adds the listener for automatically saving the diagram for local changes.
+ */
+DrawioFileSync.prototype.isValidState = function()
+{
+	return this.ui.getCurrentFile() == this.file &&
+		this.file.sync == this && !this.file.invalidChecksum &&
+		!this.file.redirectDialogShowing;
+};
+
 /**
  * Adds the listener for automatically saving the diagram for local changes.
  */
 DrawioFileSync.prototype.fileChangedNotify = function()
 {
-	if (this.file.savingFile)
+	if (this.isValidState())
 	{
-		this.remoteFileChanged = true;
-	}
-	else
-	{
-		// It's possible that a request never returns so override
-		// existing requests and abort them when they are active
-		var thread = this.fileChanged(mxUtils.bind(this, function(err)
+		if (this.file.savingFile)
 		{
-			this.updateStatus();
-		}),
-			mxUtils.bind(this, function(err)
-		{
-			this.file.handleFileError(err);
-		}), mxUtils.bind(this, function()
+			this.remoteFileChanged = true;
+		}
+		else
 		{
-			return !this.file.savingFile && this.notifyThread != thread;
-		}));
+			// It's possible that a request never returns so override
+			// existing requests and abort them when they are active
+			var thread = this.fileChanged(mxUtils.bind(this, function(err)
+			{
+				this.updateStatus();
+			}),
+				mxUtils.bind(this, function(err)
+			{
+				this.file.handleFileError(err);
+			}), mxUtils.bind(this, function()
+			{
+				return !this.file.savingFile && this.notifyThread != thread;
+			}));
+		}
 	}
 };
 
@@ -624,22 +631,37 @@ DrawioFileSync.prototype.fileChanged = function(success, error, abort)
 {
 	var thread = window.setTimeout(mxUtils.bind(this, function()
 	{
-		if (this.ui.getCurrentFile() != this.file ||
-			this.file.sync != this)
+		if (abort == null || !abort())
 		{
-			if (error != null)
+			if (!this.isValidState())
 			{
-				error();
+				if (error != null)
+				{
+					error();
+				}
 			}
-		}
-		else if (abort == null || !abort())
-		{
-			this.file.loadPatchDescriptor(mxUtils.bind(this, function(desc)
+			else
 			{
-				this.catchup(this.file.getDescriptorEtag(desc),
-					this.file.getDescriptorSecret(desc),
-					success, error, abort);
-			}), error);
+				this.file.loadPatchDescriptor(mxUtils.bind(this, function(desc)
+				{
+					if (abort == null || !abort())
+					{
+						if (!this.isValidState())
+						{
+							if (error != null)
+							{
+								error();
+							}
+						}
+						else
+						{
+							this.catchup(this.file.getDescriptorEtag(desc),
+								this.file.getDescriptorSecret(desc),
+								success, error, abort);
+						}
+					}
+				}), error);
+			}
 		}
 	}), 0);
 	
@@ -689,57 +711,35 @@ DrawioFileSync.prototype.updateDescriptor = function(desc)
  */
 DrawioFileSync.prototype.catchup = function(etag, secret, success, error, abort)
 {
-	var current = this.file.getCurrentEtag();
-
-	if (current == etag)
+	if (abort == null || !abort())
 	{
-		if (success != null)
-		{
-			success();
-		}
-	}
-	else if (this.ui.getCurrentFile() != this.file ||
-		this.file.sync != this)
-	{
-		if (error != null)
-		{
-			error();
-		}
-	}
-	else if (abort == null || !abort())
-	{
-		// Cache entry may not have been uploaded to cache before new
-		// etag is visible to client so retry once after cache miss
-		var cacheReadyRetryCount = 0;
-		var failed = false;
-		
-		var doCatchup = mxUtils.bind(this, function()
+		var current = this.file.getCurrentEtag();
+	
+		if (current == etag)
 		{
-			// Ignores patch if shadow has changed
-			if (current != this.file.getCurrentEtag())
+			if (success != null)
 			{
-				if (success != null)
-				{
-					success();
-				}
+				success();
 			}
-			else if (this.ui.getCurrentFile() != this.file ||
-				this.file.sync != this)
+		}
+		else if (!this.isValidState())
+		{
+			if (error != null)
 			{
-				if (error != null)
-				{
-					error();
-				}
+				error();
 			}
-			else if (abort == null || !abort())
+		}
+		else
+		{
+			// Cache entry may not have been uploaded to cache before new
+			// etag is visible to client so retry once after cache miss
+			var cacheReadyRetryCount = 0;
+			var failed = false;
+			
+			var doCatchup = mxUtils.bind(this, function()
 			{
-				mxUtils.get(this.cacheUrl + '?id=' + encodeURIComponent(this.channelId) +
-					'&from=' + encodeURIComponent(current) + '&to=' + encodeURIComponent(etag) +
-					((secret != null) ? '&secret=' + encodeURIComponent(secret) : ''),
-					mxUtils.bind(this, function(req)
+				if (abort == null || !abort())
 				{
-					this.file.stats.bytesReceived += req.getText().length;	
-					
 					// Ignores patch if shadow has changed
 					if (current != this.file.getCurrentEtag())
 					{
@@ -748,105 +748,133 @@ DrawioFileSync.prototype.catchup = function(etag, secret, success, error, abort)
 							success();
 						}
 					}
-					else if (this.ui.getCurrentFile() != this.file ||
-						this.file.sync != this)
+					else if (!this.isValidState())
 					{
 						if (error != null)
 						{
 							error();
 						}
 					}
-					else if (abort == null || !abort())
+					else
 					{
-						var checksum = null;
-						var details = [];
-						var temp = [];
-				
-						if (req.getStatus() >= 200 && req.getStatus() <= 299 &&
-							req.getText().length > 0)
+						mxUtils.get(this.cacheUrl + '?id=' + encodeURIComponent(this.channelId) +
+							'&from=' + encodeURIComponent(current) + '&to=' + encodeURIComponent(etag) +
+							((secret != null) ? '&secret=' + encodeURIComponent(secret) : ''),
+							mxUtils.bind(this, function(req)
 						{
-							try
+							this.file.stats.bytesReceived += req.getText().length;	
+							
+							if (abort == null || !abort())
 							{
-								var result = JSON.parse(req.getText());
-								
-								if (result != null && result.length > 0)
+								// Ignores patch if shadow has changed
+								if (current != this.file.getCurrentEtag())
+								{
+									if (success != null)
+									{
+										success();
+									}
+								}
+								else if (!this.isValidState())
 								{
-									for (var i = 0; i < result.length; i++)
+									if (error != null)
 									{
-										var value = this.stringToObject(result[i]);
-										
-										if (value.v > DrawioFileSync.PROTOCOL)
+										error();
+									}
+								}
+								else
+								{
+									var checksum = null;
+									var details = [];
+									var temp = [];
+							
+									if (req.getStatus() >= 200 && req.getStatus() <= 299 &&
+										req.getText().length > 0)
+									{
+										try
 										{
-											failed = true;
-											temp = [];
-											break;
+											var result = JSON.parse(req.getText());
+											
+											if (result != null && result.length > 0)
+											{
+												for (var i = 0; i < result.length; i++)
+												{
+													var value = this.stringToObject(result[i]);
+													
+													if (value.v > DrawioFileSync.PROTOCOL)
+													{
+														failed = true;
+														temp = [];
+														break;
+													}
+													else if (value.v === DrawioFileSync.PROTOCOL &&
+														value.d != null)
+													{
+														checksum = value.d.checksum;
+														temp.push(value.d.patch);
+														
+														if (value.d.details != null)
+														{
+															value.d.details.checksum = checksum;
+															details.push(JSON.stringify(value.d.details));
+														}
+													}
+													else
+													{
+														failed = true;
+														temp = [];
+														break;
+													}
+												}
+											}
 										}
-										else if (value.v === DrawioFileSync.PROTOCOL &&
-											value.d != null)
+										catch (e)
 										{
-											checksum = value.d.checksum;
-											temp.push(value.d.patch);
+											temp = [];
 											
-											if (value.d.details != null)
+											if (window.console != null && urlParams['test'] == '1')
 											{
-												value.d.details.checksum = checksum;
-												details.push(JSON.stringify(value.d.details));
+												console.log(e);
 											}
 										}
+									}
+									
+									try
+									{
+										if (temp.length > 0)
+										{
+											this.file.stats.cacheHits++;
+											this.merge(temp, checksum, etag, success, error, abort, details);
+										}
+										// Retries if cache entry was not yet there
+										else if (cacheReadyRetryCount <= this.maxCacheReadyRetries &&
+											!failed && req.getStatus() != 401)
+										{
+											cacheReadyRetryCount++;
+											this.file.stats.cacheMiss++;
+											window.setTimeout(doCatchup, (cacheReadyRetryCount + 1) * this.cacheReadyDelay);
+										}
 										else
 										{
-											failed = true;
-											temp = [];
-											break;
+											this.file.stats.cacheFail++;
+											this.reload(success, error, abort);
+										}
+									}
+									catch (e)
+									{
+										if (error != null)
+										{
+											error(e);
 										}
 									}
 								}
 							}
-							catch (e)
-							{
-								temp = [];
-								
-								if (window.console != null && urlParams['test'] == '1')
-								{
-									console.log(e);
-								}
-							}
-						}
-						
-						try
-						{
-							if (temp.length > 0)
-							{
-								this.file.stats.cacheHits++;
-								this.merge(temp, checksum, etag, success, error, abort, details);
-							}
-							// Retries if cache entry was not yet there
-							else if (cacheReadyRetryCount <= this.maxCacheReadyRetries &&
-								!failed && req.getStatus() != 401)
-							{
-								cacheReadyRetryCount++;
-								this.file.stats.cacheMiss++;
-								window.setTimeout(doCatchup, (cacheReadyRetryCount + 1) * this.cacheReadyDelay);
-							}
-							else
-							{
-								this.file.stats.cacheFail++;
-								this.reload(success, error, abort);
-							}
-						}
-						catch (e)
-						{
-							if (error != null)
-							{
-								error(e);
-							}
-						}
+						}));
 					}
-				}));
-			}
-		});
-		
-		window.setTimeout(doCatchup, this.cacheReadyDelay);
+				}
+			});
+			
+			window.setTimeout(doCatchup, this.cacheReadyDelay);
+		}
 	}
 };
 
@@ -886,7 +914,7 @@ DrawioFileSync.prototype.merge = function(patches, checksum, etag, success, erro
 		this.file.shadowPages = (this.file.shadowPages != null) ?
 			this.file.shadowPages : this.ui.getPagesForNode(
 			mxUtils.parseXml(this.file.shadowData).documentElement)
-		this.file.checkShadow(this.file.shadowPages);
+		this.file.checkPages(this.file.shadowPages, 'merge init');
 		
 		// Creates a patch for backup if the checksum fails
 		this.file.backupPatch = (this.file.isModified()) ?
@@ -901,6 +929,8 @@ DrawioFileSync.prototype.merge = function(patches, checksum, etag, success, erro
 				this.file.shadowPages = this.ui.patchPages(this.file.shadowPages, patches[i]);
 			}
 			
+			this.file.stats.shadowState = this.ui.hashValue(etag);
+			this.file.checkPages(this.file.shadowPages, 'merge patched');
 			var currentDetails = {};
 			var current = (checksum != null) ? this.ui.getHashValueForPages(
 				this.file.shadowPages, currentDetails) : null;
@@ -945,20 +975,24 @@ DrawioFileSync.prototype.merge = function(patches, checksum, etag, success, erro
 			}
 			else
 			{
-				this.file.stats.lastMerge = details; 
-				
 				// Patches the current document
 				this.file.patch(patches,
 					(DrawioFile.LAST_WRITE_WINS) ?
 					this.file.backupPatch : null);
 			}
 		}
+		else
+		{
+			this.file.stats.shadowState = this.ui.hashValue(etag);
+		}
 
+		this.file.stats.lastMergeTime = new Date().toISOString();
+		this.file.stats.lastMerge = details; 
 		this.file.invalidChecksum = false;
 		this.file.inConflictState = false;
 		this.file.setCurrentEtag(etag);
 		this.file.backupPatch = null;
-		this.file.checkPages();
+		this.file.checkPages(this.ui.pages, 'merge done');
 		
 		if (success != null)
 		{
@@ -969,6 +1003,7 @@ DrawioFileSync.prototype.merge = function(patches, checksum, etag, success, erro
 	{
 		this.file.inConflictState = true;
 		this.file.invalidChecksum = true;
+		this.file.descriptorChanged();
 		
 		if (error != null)
 		{
@@ -977,8 +1012,16 @@ DrawioFileSync.prototype.merge = function(patches, checksum, etag, success, erro
 		
 		try
 		{
+			var from = this.ui.hashValue(this.file.getCurrentEtag());
+			var to = this.ui.hashValue(etag);
+			
 			this.file.sendErrorReport('Error in merge',
-				'Patches:\n' + this.file.compressReportData(
+				'From: ' + from +
+				'\nTo: ' + to +
+				((details != null && details.length > 0) ? ('\nDetails: ' +
+					details.join(', ')) : '') +
+				'\nChecksum: ' + checksum +
+				'\nPatches:\n' + this.file.compressReportData(
 					JSON.stringify(patches, null, 2)), e);
 		}
 		catch (e2)
@@ -1053,7 +1096,7 @@ DrawioFileSync.prototype.fileSaved = function(pages, lastDesc, success, error)
 	this.resetUpdateStatusThread();
 	this.catchupRetryCount = 0;
 	
-	if (!this.ui.isOffline() && !this.file.inConflictState && !this.redirectDialogShowing)
+	if (!this.ui.isOffline() && !this.file.inConflictState && !this.file.redirectDialogShowing)
 	{
 		this.start();
 

+ 4 - 0
src/main/webapp/js/diagramly/Editor.js

@@ -4019,7 +4019,11 @@ var ErrorDialog = function(editorUi, title, message, buttonText, fn, retry, butt
 		hd.style.marginBottom = '16px';
 		hd.style.borderBottom = '1px solid #c0c0c0';
 		hd.style.color = 'gray';
+		hd.style.whiteSpace = 'nowrap';
+		hd.style.textOverflow = 'ellipsis';
+		hd.style.overflow = 'hidden';
 		mxUtils.write(hd, title);
+		hd.setAttribute('title', title);
 		div.appendChild(hd);
 	}
 

+ 8 - 8
src/main/webapp/js/diagramly/EditorUi.js

@@ -937,7 +937,7 @@
 	/**
 	 * Removes any values, styles and geometries from the given XML node.
 	 */
-	EditorUi.prototype.anonymizeString = function(text)
+	EditorUi.prototype.anonymizeString = function(text, zeros)
 	{
 		var result = [];
 		
@@ -951,7 +951,7 @@
 			}
 			else if (!isNaN(parseInt(c)))
 			{
-				result.push(Math.round(Math.random() * 9));
+				result.push((zeros) ? '0' : Math.round(Math.random() * 9));
 			}
 			else if (c.toLowerCase() != c)
 			{
@@ -1083,7 +1083,7 @@
 	/**
 	 * Removes any values, styles and geometries from the given XML node.
 	 */
-	EditorUi.prototype.anonymizeAttributes = function(node)
+	EditorUi.prototype.anonymizeAttributes = function(node, zeros)
 	{
 		if (node.attributes != null)
 		{
@@ -1092,7 +1092,7 @@
 				if (node.attributes[i].name != 'as')
 				{
 					node.setAttribute(node.attributes[i].name,
-						this.anonymizeString(node.attributes[i].value));
+						this.anonymizeString(node.attributes[i].value, zeros));
 				}
 			}
 		}
@@ -1101,7 +1101,7 @@
 		{
 			for (var i = 0; i < node.childNodes.length; i++)
 			{
-				this.anonymizeAttributes(node.childNodes[i]);
+				this.anonymizeAttributes(node.childNodes[i], zeros);
 			}
 		}
 	};
@@ -1109,7 +1109,7 @@
 	/**
 	 * Removes any values, styles and geometries from the given XML node.
 	 */
-	EditorUi.prototype.anonymizeNode = function(node)
+	EditorUi.prototype.anonymizeNode = function(node, zeros)
 	{
 		var nodes = node.getElementsByTagName('mxCell');
 		
@@ -1137,7 +1137,7 @@
 		
 		for (var i = 0; i < geos.length; i++)
 		{
-			this.anonymizeAttributes(geos[i]);
+			this.anonymizeAttributes(geos[i], zeros);
 		}
 		
 		return node;
@@ -2455,7 +2455,7 @@
 		var hash = 0;
 		
 		// Checks for XML nodes
-		if (typeof obj === 'object' && typeof obj.nodeType === 'number' &&
+		if (obj != null && typeof obj === 'object' && typeof obj.nodeType === 'number' &&
 			typeof obj.nodeName === 'string' && typeof obj.getAttribute === 'function')
 		{
 			if (obj.nodeName != null)

+ 26 - 2
src/main/webapp/js/diagramly/Menus.js

@@ -977,10 +977,34 @@
 					{
 						try
 						{
-							var pages = editorUi.getPagesForNode(mxUtils.parseXml(
-								newValue).documentElement, 'mxGraphModel');
+							var doc = mxUtils.parseXml(newValue);
+							var pages = editorUi.getPagesForNode(doc.documentElement, 'mxGraphModel');
 							var checksum = editorUi.getHashValueForPages(pages);
 							console.log('checksum', pages, checksum);
+							
+							// Checks for duplicates
+							var all = doc.getElementsByTagName('*');
+							var allIds = {};
+							var dups = {};
+							
+							for (var i = 0; i < all.length; i++)
+							{
+								var el = all[i];
+								
+								if (allIds[el.id] == null)
+								{
+									allIds[el.id] = el.id;
+								}
+								else
+								{
+									dups[el.id] = el.id;
+								}
+							}
+							
+							if (dups.length > 0)
+							{
+								console.log('duplicates', dups);
+							}
 						}
 						catch (e)
 						{

+ 13 - 6
src/main/webapp/js/diagramly/Minimal.js

@@ -1191,7 +1191,8 @@ EditorUi.initMinimalTheme = function()
 		menubar.appendChild(ui.statusContainer);
 
 		ui.buttonContainer = document.createElement('div');
-		ui.buttonContainer.style.cssText = 'position:absolute;right:40px;top:12px;white-space:nowrap;';
+		ui.buttonContainer.style.cssText = 'position:absolute;right:0px;padding-right:34px;top:10px;' +
+			'white-space:nowrap;padding-top:2px;background-color:inherit;';
 		menubar.appendChild(ui.buttonContainer);
 		
 		// Container for the user element
@@ -1293,7 +1294,7 @@ EditorUi.initMinimalTheme = function()
         	
         	before = menubar.firstChild;
 	        iw = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
-	        var small = iw < 900;
+	        var small = iw < 1000;
 	 
 	        if (!small)
 	        {
@@ -1357,7 +1358,7 @@ EditorUi.initMinimalTheme = function()
 	        var langMenu = ui.menus.get('language');
 
 			if (langMenu != null && !mxClient.IS_CHROMEAPP &&
-				!EditorUi.isElectronApp && iw >= 540)
+				!EditorUi.isElectronApp && iw >= 600)
 			{
 				if (langMenuElt == null)
 				{
@@ -1373,17 +1374,23 @@ EditorUi.initMinimalTheme = function()
 		        	elt.style.width = '24px';
 					elt.style.zIndex = '1';
 					elt.style.top = '11px';
-					elt.style.right = '14px';
+					elt.style.right = '8px';
 					elt.style.cursor = 'pointer';
 					menubar.appendChild(elt);
 					langMenuElt = elt;
 				}
 				
-				ui.buttonContainer.style.right = '40px';
+				ui.buttonContainer.style.paddingRight = '34px';
 			}
 			else
 			{
-				ui.buttonContainer.style.right = '14px';
+				ui.buttonContainer.style.paddingRight = '4px';
+				
+				if (langMenuElt != null)
+				{
+					langMenuElt.parentNode.removeChild(langMenuElt);
+					langMenuElt = null;
+				}
 			}
         };
         

+ 65 - 51
src/main/webapp/js/diagramly/Pages.js

@@ -717,30 +717,37 @@ EditorUi.prototype.updatePageRoot = function(page)
  */
 EditorUi.prototype.selectPage = function(page, quiet, viewState)
 {
-	if (this.editor.graph.isEditing())
+	try
 	{
-		this.editor.graph.stopEditing(false);
-	}
-	
-	quiet = (quiet != null) ? quiet : false;
-	this.editor.graph.isMouseDown = false;
-	this.editor.graph.reset();
-	
-	var edit = this.editor.graph.model.createUndoableEdit();
-	
-	// Special flag to bypass autosave for this edit
-	edit.ignoreEdit = true;
-
-	var change = new SelectPage(this, page, viewState);
-	change.execute();
-	edit.add(change);
-	edit.notify();
-	
-	this.editor.graph.tooltipHandler.hide();
+		if (this.editor.graph.isEditing())
+		{
+			this.editor.graph.stopEditing(false);
+		}
+		
+		quiet = (quiet != null) ? quiet : false;
+		this.editor.graph.isMouseDown = false;
+		this.editor.graph.reset();
+		
+		var edit = this.editor.graph.model.createUndoableEdit();
+		
+		// Special flag to bypass autosave for this edit
+		edit.ignoreEdit = true;
 	
-	if (!quiet)
+		var change = new SelectPage(this, page, viewState);
+		change.execute();
+		edit.add(change);
+		edit.notify();
+		
+		this.editor.graph.tooltipHandler.hide();
+		
+		if (!quiet)
+		{
+			this.editor.graph.model.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+		}
+	}
+	catch (e)
 	{
-		this.editor.graph.model.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+		this.handleError(e);
 	}
 };
 
@@ -836,51 +843,58 @@ EditorUi.prototype.createPageName = function()
  */
 EditorUi.prototype.removePage = function(page)
 {
-	var graph = this.editor.graph;
-	var tmp = mxUtils.indexOf(this.pages, page);
-	
-	if (graph.isEnabled() && tmp >= 0)
+	try
 	{
-		if (this.editor.graph.isEditing())
-		{
-			this.editor.graph.stopEditing(false);
-		}
+		var graph = this.editor.graph;
+		var tmp = mxUtils.indexOf(this.pages, page);
 		
-		graph.model.beginUpdate();
-		try
+		if (graph.isEnabled() && tmp >= 0)
 		{
-			var next = this.currentPage;
+			if (this.editor.graph.isEditing())
+			{
+				this.editor.graph.stopEditing(false);
+			}
 			
-			if (next == page && this.pages.length > 1)
+			graph.model.beginUpdate();
+			try
 			{
-				if (tmp == this.pages.length - 1)
+				var next = this.currentPage;
+				
+				if (next == page && this.pages.length > 1)
 				{
-					tmp--;
+					if (tmp == this.pages.length - 1)
+					{
+						tmp--;
+					}
+					else
+					{
+						tmp++;
+					}
+					
+					next = this.pages[tmp];
 				}
-				else
+				else if (this.pages.length <= 1)
 				{
-					tmp++;
+					// Removes label with incorrect page number to force
+					// default page name which is OK for a single page
+					next = this.insertPage();
+					graph.model.execute(new RenamePage(this, next,
+						mxResources.get('pageWithNumber', [1])));
 				}
 				
-				next = this.pages[tmp];
+				// Uses model to fire event to trigger autosave
+				graph.model.execute(new ChangePage(this, page, next));
 			}
-			else if (this.pages.length <= 1)
+			finally
 			{
-				// Removes label with incorrect page number to force
-				// default page name which is OK for a single page
-				next = this.insertPage();
-				graph.model.execute(new RenamePage(this, next,
-					mxResources.get('pageWithNumber', [1])));
+				graph.model.endUpdate();
 			}
-			
-			// Uses model to fire event to trigger autosave
-			graph.model.execute(new ChangePage(this, page, next));
-		}
-		finally
-		{
-			graph.model.endUpdate();
 		}
 	}
+	catch (e)
+	{
+		this.handleError(e);
+	}
 	
 	return page;
 };

File diff suppressed because it is too large
+ 6 - 5
src/main/webapp/js/embed-static.min.js


File diff suppressed because it is too large
+ 6 - 5
src/main/webapp/js/reader.min.js


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