Просмотр исходного кода

6.2.0 release

Former-commit-id: c61cdfff95b616ca671c3b58963cd6e8f2cfe938
Gaudenz Alder 8 лет назад
Родитель
Сommit
0006b5eae2
44 измененных файлов с 21526 добавлено и 21752 удалено
  1. 11 0
      ChangeLog
  2. 1 1
      VERSION
  3. 1 4
      etc/build/build.xml
  4. BIN
      etc/build/compiler.jar
  5. 647 650
      etc/mxgraph/mxClient.js
  6. 3 10
      src/com/mxgraph/online/GitHubServlet.java
  7. 1 1
      war/cache.manifest
  8. 27 3
      war/dropbox.html
  9. 34 80
      war/electron.js
  10. 2 1
      war/export2.html
  11. 8 2
      war/github.html
  12. 7290 7639
      war/js/app.min.js
  13. 1459 1469
      war/js/atlas-viewer.min.js
  14. 6121 6370
      war/js/atlas.min.js
  15. 171 44
      war/js/diagramly/App.js
  16. 1 1
      war/js/diagramly/Devel.js
  17. 76 10
      war/js/diagramly/Dialogs.js
  18. 139 0
      war/js/diagramly/DrawioClient.js
  19. 39 29
      war/js/diagramly/DriveClient.js
  20. 298 192
      war/js/diagramly/DropboxClient.js
  21. 1 1
      war/js/diagramly/DropboxFile.js
  22. 81 56
      war/js/diagramly/EditorUi.js
  23. 19 12
      war/js/diagramly/ElectronApp.js
  24. 140 202
      war/js/diagramly/GitHubClient.js
  25. 2 2
      war/js/diagramly/GraphViewer.js
  26. 6 0
      war/js/diagramly/Init.js
  27. 30 29
      war/js/diagramly/Menus.js
  28. 10 10
      war/js/diagramly/OneDriveClient.js
  29. 10 3
      war/js/diagramly/sidebar/Sidebar.js
  30. 0 4
      war/js/dropbox/dropbox.min.js
  31. 0 0
      war/js/dropbox/dropbox.min.map
  32. 916 918
      war/js/embed-static.min.js
  33. 20 20
      war/js/embed.min.js
  34. 164 176
      war/js/extensions.min.js
  35. 5 0
      war/js/mxgraph/EditorUi.js
  36. 30 8
      war/js/mxgraph/Graph.js
  37. 0 1
      war/js/mxgraph/Shapes.js
  38. 896 898
      war/js/reader.min.js
  39. 1415 1441
      war/js/shapes.min.js
  40. 1451 1459
      war/js/viewer.min.js
  41. 1 1
      war/onedrive.html
  42. 0 1
      war/shapes/mxArchiMate.js
  43. 0 2
      war/shapes/mxArchiMate3.js
  44. 0 2
      war/shapes/pid2/mxPidValves.js

+ 11 - 0
ChangeLog

@@ -1,3 +1,14 @@
+22-FEB-2017: 6.2.0
+
+- Fixes encoding errors in GitHub client
+- Checks size in GitHub and Dropbox
+- Fixes initial SVG file contents
+- Adds target=frame URL parameter
+- Moves cookies to local storage
+- Adds recent files in splash
+- Uses mxGraph 3.7.0.2 beta 5
+- Uses Dropbox API v2
+
 17-FEB-2017: 6.1.2
 
 - Uses semantic versioning

+ 1 - 1
VERSION

@@ -1 +1 @@
-6.1.2
+6.2.0

+ 1 - 4
etc/build/build.xml

@@ -306,16 +306,13 @@
 				<file name="Graph-Stylesheet.js" />
 			</sources>
 
-			<sources dir="${war.dir}/js/dropbox">
-				<file name="dropbox.min.js" />
-			</sources>
-
 			<sources dir="${war.dir}/js/diagramly/util">
 				<file name="mxAsyncCanvas.js" />
 				<file name="mxJsCanvas.js" />
 			</sources>
 
 			<sources dir="${war.dir}/js/diagramly">
+				<file name="DrawioClient.js" />
 				<file name="DrawioUser.js" />
 				<file name="UrlLibrary.js" />
 				<file name="DriveRealtime.js" />

BIN
etc/build/compiler.jar


Разница между файлами не показана из-за своего большого размера
+ 647 - 650
etc/mxgraph/mxClient.js


+ 3 - 10
src/com/mxgraph/online/GitHubServlet.java

@@ -121,11 +121,6 @@ public class GitHubServlet extends HttpServlet
 			wr.flush();
 			wr.close();
 
-			int responseCode = con.getResponseCode();
-			System.out.println("\nSending 'POST' request to URL : " + url);
-			System.out.println("Post parameters : " + urlParameters);
-			System.out.println("Response Code : " + responseCode);
-
 			BufferedReader in = new BufferedReader(
 					new InputStreamReader(con.getInputStream()));
 			String inputLine;
@@ -136,10 +131,9 @@ public class GitHubServlet extends HttpServlet
 				res.append(inputLine);
 			}
 			in.close();
-
-			//print result
-			System.out.println(res.toString());
-
+			
+			response.setStatus(con.getResponseCode());
+			
 			OutputStream out = response.getOutputStream();
 
 			// Creates XML for stencils
@@ -148,7 +142,6 @@ public class GitHubServlet extends HttpServlet
 			// Writes JavaScript and adds function call with
 			// stylesheet and stencils as arguments 
 			writer.println(res.toString());
-			response.setStatus(HttpServletResponse.SC_OK);
 
 			writer.flush();
 			writer.close();

+ 1 - 1
war/cache.manifest

@@ -1,7 +1,7 @@
 CACHE MANIFEST
 
 # THIS FILE WAS GENERATED. DO NOT MODIFY!
-# 02/17/2017 07:08 AM
+# 02/22/2017 02:03 PM
 
 app.html
 index.html?offline=1

+ 27 - 3
war/dropbox.html

@@ -4,9 +4,33 @@
 		<title></title>
 	</head>
 	<body>
-		<script src="js/dropbox/dropbox.min.js"></script>
-		<script type="text/javascript">
-			Dropbox.AuthDriver.Popup.oauthReceiver();
+		<script>
+			try
+			{
+				if (window.opener != null && window.opener.onDropboxCallback != null)
+				{
+ 					var hash = window.location.hash;
+ 					var i1 = hash.indexOf('access_token=');
+ 					var token = null;
+ 					
+ 					if (i1 >= 0)
+ 					{
+ 						var i2 = hash.indexOf('&', i1 + 13);
+ 						
+ 						if (i2 > i1)
+ 						{
+ 							token = hash.substring(i1 + 13, i2);
+ 						}
+ 					}
+
+					// Continues execution of main program flow
+					window.opener.onDropboxCallback(token, window);
+				}
+			}
+			catch (e)
+			{
+				// ignore
+			}
 		</script>
 	</body>
 </html>

+ 34 - 80
war/electron.js

@@ -1,107 +1,61 @@
-const fs = require('fs')
-const path = require('path')
-const electron = require('electron')
-const ipcMain = electron.ipcMain
-const dialog = electron.dialog
-const app = electron.app
-const BrowserWindow = electron.BrowserWindow
+const electron = require('electron');
+const remote = electron.remote;
+const dialog = electron.dialog;
+var fs = require('fs');
+var path = require('path');
 
-let windowsRegistry = []
+// Module to control application life.
+const app = electron.app;
+// Module to create native browser window.
+const BrowserWindow = electron.BrowserWindow;
 
-function createWindow (opt = {}) {
-	let options = Object.assign({
-		width: 1600,
-		height: 1200,
-		'web-security': false,
-	}, opt)
+// Keep a global reference of the window object, if you don't, the window will
+// be closed automatically when the JavaScript object is garbage collected.
+let mainWindow
 
-	let mainWindow = new BrowserWindow(options)
-	windowsRegistry.push(mainWindow)
-
-	console.log('createWindow', opt)
+function createWindow()
+{
+	// Create the browser window.
+	mainWindow = new BrowserWindow({width: 1600, height: 1200, "web-security" : false});
 
 	// and load the index.html of the app.
-	mainWindow.loadURL(
-		`file://${__dirname}/index.html?dev=1&test=1&db=0&gapi=0&od=0&analytics=0&picker=0&mode=device&browser=0&p=electron`)
+	mainWindow.loadURL(`file://${__dirname}/index.html?dev=1&test=1&db=0&gapi=0&od=0&analytics=0&picker=0&mode=device&browser=0&p=electron`);
 
 	// Open the DevTools.
-	mainWindow.webContents.openDevTools()
-
-	mainWindow.on('close', (event/*:WindowEvent*/) => {
-		const win = event.sender
-		const index = windowsRegistry.indexOf(win)
-		console.log('Window on close idx:%d', index)
-		const contents = win.webContents
-		if (contents != null) {
-			contents.executeJavaScript(`global.__emt_isModified()`, true,
-				isModified => {
-					console.log('__emt_isModified', isModified)
-					if (isModified) {
-						var choice = dialog.showMessageBox(
-							win,
-							{
-								type: 'question',
-								buttons: ['Yes', 'No'],
-								title: 'Confirm',
-								message: 'All Changes will be lost' //mxResources.get('allChangesLost')
-							})
-						if (choice === 0) {
-							win.destroy()
-						}
-					} else {
-						win.destroy()
-					}
-				})
-			event.preventDefault()
-		}
-	})
+	mainWindow.webContents.openDevTools();
 
 	// Emitted when the window is closed.
-	mainWindow.on('closed', (event/*:WindowEvent*/) => {
-		const index = windowsRegistry.indexOf(event.sender)
-		console.log('Window closed idx:%d', index)
-		windowsRegistry.splice(index, 1)
-	})
-
-	return mainWindow.id
+	mainWindow.on('closed', function()
+	{
+	    // Dereference the window object, usually you would store windows
+	    // in an array if your app supports multi windows, this is the time
+	    // when you should delete the corresponding element.
+	    mainWindow = null;
+	});
 }
 
 // This method will be called when Electron has finished
 // initialization and is ready to create browser windows.
 // Some APIs can only be used after this event occurs.
-app.on('ready', e => {
-	//asynchronous
-	ipcMain.on('asynchronous-message', (event, arg) => {
-		console.log(arg)  // prints "ping"
-		event.sender.send('asynchronous-reply', 'pong')
-	})
-	//synchronous
-	ipcMain.on('winman', (event, arg) => {
-		console.log('ipcMain.on winman', arg)
-		if (arg.action === 'newfile') {
-			event.returnValue = createWindow(arg.opt)
-			return
-		}
-		event.returnValue = 'pong'
-	})
-	createWindow()
-})
+app.on('ready', createWindow)
 
 // Quit when all windows are closed.
-app.on('window-all-closed', function () {
-	console.log('window-all-closed', windowsRegistry.length)
+app.on('window-all-closed', function()
+{
 	// On OS X it is common for applications and their menu bar
 	// to stay active until the user quits explicitly with Cmd + Q
-	if (process.platform !== 'darwin') {
+	if (process.platform !== 'darwin')
+	{
 		app.quit()
 	}
 })
 
-app.on('activate', function () {
-	console.log('app on activate', windowsRegistry.length)
+app.on('activate', function()
+{
 	// On OS X it's common to re-create a window in the app when the
 	// dock icon is clicked and there are no other windows open.
-	if (windowsRegistry.length === 0) {
+	if (mainWindow === null)
+	{
 		createWindow()
 	}
 })

+ 2 - 1
war/export2.html

@@ -31,6 +31,7 @@
 			data.border = parseInt(data.border) || 0;
 			data.w = parseFloat(data.w) || 0;
 			data.h = parseFloat(data.h) || 0;
+			data.scale = parseFloat(data.scale) || 1;
 			
 			// Parses XML
 			var xmlDoc = mxUtils.parseXml(data.xml);
@@ -201,7 +202,7 @@
 				}
 				else
 				{
-					graph.view.setTranslate(Math.floor(data.border - b.x),
+					graph.view.scaleAndTranslate(data.scale, Math.floor(data.border - b.x),
 						Math.floor(data.border - b.y));
 				}
 			}

+ 8 - 2
war/github.html

@@ -7,10 +7,16 @@
 		<script>
 			try
 			{
-				if (window.opener != null)
+				if (window.opener != null && window.opener.onGitHubCallback != null)
 				{
 					var search = window.location.search;
-					var code = search.substring(search.indexOf('code=') + 5);
+					var idx1 = search.indexOf('code=');
+					var code = null;
+					
+					if (idx1 >= 0)
+					{
+						code = search.substring(idx1 + 5);
+					}
 
 					// Continues execution of main program flow
 					window.opener.onGitHubCallback(code, window);

Разница между файлами не показана из-за своего большого размера
+ 7290 - 7639
war/js/app.min.js


Разница между файлами не показана из-за своего большого размера
+ 1459 - 1469
war/js/atlas-viewer.min.js


Разница между файлами не показана из-за своего большого размера
+ 6121 - 6370
war/js/atlas.min.js


+ 171 - 44
war/js/diagramly/App.js

@@ -28,7 +28,16 @@ App = function(editor, container, lightbox)
 	// Global helper method to deal with popup blockers
 	window.openWindow = mxUtils.bind(this, function(url, pre, fallback)
 	{
-		var wnd = window.open(url);
+		var wnd = null;
+		
+		try
+		{
+			wnd = window.open(url);
+		}
+		catch (e)
+		{
+			// ignore
+		}
 		
 		if (wnd == null || wnd === undefined)
 		{
@@ -168,7 +177,12 @@ App.getStoredMode = function()
 {
 	var mode = null;
 	
-	if (typeof(Storage) != 'undefined')
+	if (mode == null && isLocalStorage)
+	{
+		mode = localStorage.getItem('.mode');
+	}
+	
+	if (mode == null && typeof(Storage) != 'undefined')
 	{
 		var cookies = document.cookie.split(";");
 		
@@ -183,6 +197,15 @@ App.getStoredMode = function()
 				break;
 			}
 		}
+		
+		if (mode != null && isLocalStorage)
+		{
+			// Moves to local storage
+			var expiry = new Date();
+			expiry.setYear(expiry.getFullYear() - 1);
+			document.cookie = 'MODE=; expires=' + expiry.toUTCString();
+			localStorage.setItem('.mode', mode);
+		}
 	}
 	
 	return mode;
@@ -258,7 +281,11 @@ App.getStoredMode = function()
 						if (App.mode == App.MODE_DROPBOX || (window.location.hash != null &&
 							window.location.hash.substring(0, 2) == '#D'))
 						{
-							mxscript('https://www.dropbox.com/static/api/1/dropins.js', null, 'dropboxjs', App.DROPBOX_APPKEY);
+							mxscript('https://unpkg.com/dropbox/dist/Dropbox-sdk.min.js');
+							
+							// Must load this after the dropbox SDK since they use the same namespace
+							mxscript('https://www.dropbox.com/static/api/2/dropins.js',
+									null, 'dropboxjs', App.DROPBOX_APPKEY);
 						}
 						else if (urlParams['chrome'] == '0')
 						{
@@ -558,13 +585,19 @@ App.main = function(callback)
 			// Loads dropbox for all browsers but IE8 and below (no CORS) if not disabled or if enabled and in embed mode
 			// KNOWN: Picker does not work in IE11 (https://dropbox.zendesk.com/requests/1650781)
 			if (typeof window.DropboxClient === 'function' &&
-				(window.Dropbox != null && typeof Dropbox.choose === 'undefined' &&
-				window.DrawDropboxClientCallback != null &&
+				(window.Dropbox == null && window.DrawDropboxClientCallback != null &&
 				(((urlParams['embed'] != '1' && urlParams['db'] != '0') ||
 				(urlParams['embed'] == '1' && urlParams['db'] == '1')) &&
 				isSvgBrowser && (document.documentMode == null || document.documentMode > 9))))
 			{
-				mxscript('https://www.dropbox.com/static/api/1/dropins.js', window.DrawDropboxClientCallback, 'dropboxjs', App.DROPBOX_APPKEY);
+				mxscript('https://unpkg.com/dropbox/dist/Dropbox-sdk.min.js', function()
+				{
+					// Must load this after the dropbox SDK since they use the same namespace
+					mxscript('https://www.dropbox.com/static/api/2/dropins.js', function()
+					{
+						DrawDropboxClientCallback();
+					}, 'dropboxjs', App.DROPBOX_APPKEY);
+				});
 			}
 			// Disables client
 			else if (typeof window.Dropbox === 'undefined' || typeof window.Dropbox.choose === 'undefined')
@@ -1251,7 +1284,7 @@ App.prototype.updateActionStates = function()
 };
 
 /**
- * Sets the onbeforeunload for the application
+ * Updates draft in local storage
  */
 App.prototype.updateDraft = function()
 {
@@ -1262,7 +1295,7 @@ App.prototype.updateDraft = function()
 };
 
 /**
- * Sets the onbeforeunload for the application
+ * Returns the draft in local storage
  */
 App.prototype.getDraft = function()
 {
@@ -1288,7 +1321,7 @@ App.prototype.getDraft = function()
 };
 
 /**
- * Sets the onbeforeunload for the application
+ * Adds the specified entry to the recent file list in local storage
  */
 App.prototype.addRecent = function(entry)
 {
@@ -1321,7 +1354,7 @@ App.prototype.addRecent = function(entry)
 };
 
 /**
- * Sets the onbeforeunload for the application
+ * Returns the recent file list from local storage
  */
 App.prototype.getRecent = function()
 {
@@ -1346,7 +1379,7 @@ App.prototype.getRecent = function()
 };
 
 /**
- * Sets the onbeforeunload for the application
+ * Clears the recent file list in local storage
  */
 App.prototype.resetRecent = function(entry)
 {
@@ -1364,7 +1397,7 @@ App.prototype.resetRecent = function(entry)
 };
 
 /**
- * Sets the onbeforeunload for the application
+ * Clears the draft save in local storage
  */
 App.prototype.removeDraft = function()
 {
@@ -1636,11 +1669,18 @@ App.prototype.createBackground = function()
 			Editor.useLocalStorage = this.mode == App.MODE_BROWSER;
 		}
 		
-		if (typeof(Storage) != 'undefined' && remember)
+		if (remember)
 		{
-			var expiry = new Date();
-			expiry.setYear(expiry.getFullYear() + 1);
-			document.cookie = 'MODE=' + mode + '; expires=' + expiry.toUTCString();
+			if (isLocalStorage)
+			{
+				localStorage.setItem('.mode', mode);
+			}
+			else if (typeof(Storage) != 'undefined')
+			{
+				var expiry = new Date();
+				expiry.setYear(expiry.getFullYear() + 1);
+				document.cookie = 'MODE=' + mode + '; expires=' + expiry.toUTCString();
+			}
 		}
 		
 		if (this.appIcon != null)
@@ -1730,7 +1770,11 @@ App.prototype.appIconClicked = function(evt)
  */
 App.prototype.clearMode = function()
 {
-	if (typeof(Storage) != 'undefined')
+	if (isLocalStorage)
+	{
+		localStorage.removeItem('.mode');
+	}
+	else if (typeof(Storage) != 'undefined')
 	{
 		var expiry = new Date();
 		expiry.setYear(expiry.getFullYear() - 1);
@@ -2169,6 +2213,9 @@ App.prototype.start = function()
 							title = this.defaultFilename;
 						}
 						
+						var serviceCount = this.getServiceCount(true);
+						var rowLimit = (serviceCount <= 4) ? 4 : 3;
+						
 						var dlg = new CreateDialog(this, title, mxUtils.bind(this, function(filename, mode)
 						{
 							if (mode == null)
@@ -2183,8 +2230,8 @@ App.prototype.start = function()
 							{
 								this.createFile(filename, this.getFileData(true), null, mode);
 							}
-						}), null, null, null, null, urlParams['browser'] == '1', null, null, true);
-						this.showDialog(dlg.container, 380, (this.getServiceCount(true) < 4) ? 270 : 390,
+						}), null, null, null, null, urlParams['browser'] == '1', null, null, true, rowLimit);
+						this.showDialog(dlg.container, 380, (serviceCount > rowLimit) ? 390 : 270,
 							true, false, mxUtils.bind(this, function(cancel)
 						{
 							if (cancel && this.getCurrentFile() == null)
@@ -2802,6 +2849,7 @@ App.prototype.saveFile = function(forceDialog)
 			var prev = this.mode;
 			
 			var serviceCount = this.getServiceCount(true);
+			var rowLimit = (serviceCount <= 4) ? 4 : 3;
 			
 			var dlg = new CreateDialog(this, filename, mxUtils.bind(this, function(name, mode)
 			{
@@ -2836,7 +2884,7 @@ App.prototype.saveFile = function(forceDialog)
 								name.indexOf('.') < 0, /(\.svg)$/i.test(name),
 								/(\.html)$/i.test(name)), null, mode, done,
 								this.mode == null, folderId);
-						}));
+						}), mode !== App.MODE_GITHUB);
 					}
 					else if (mode != null)
 					{
@@ -2847,8 +2895,10 @@ App.prototype.saveFile = function(forceDialog)
 			{
 				this.hideDialog();
 			}), mxResources.get('saveAs'), mxResources.get('download'), null, null, allowTab,
-				(this.isOffline()) ? null : 'https://support.draw.io/questions/9338901', true);
-			this.showDialog(dlg.container, 460, (serviceCount < 4) ? 270 : 390, true, true);
+				(this.isOffline()) ? null :
+				'https://desk.draw.io/support/solutions/articles/16000042485-what-file-extensions-are-supported-',
+				true, rowLimit);
+			this.showDialog(dlg.container, 460, (serviceCount > rowLimit) ? 390 : 270, true, true);
 			dlg.init();
 		}
 	}
@@ -3060,17 +3110,89 @@ App.prototype.fileCreated = function(file, libs, replace, done)
 	// to save the file again since it needs the newly created file ID for redirecting in HTML
 	if (this.spinner.spin(document.body, mxResources.get('inserting')))
 	{
-		var redirect = window.location.protocol + '//' + window.location.hostname + url;
-		var prev = this.getFileData(true);
 		var data = file.getData();
+		var dataNode = (data.length > 0) ? this.editor.extractGraphModel(
+			mxUtils.parseXml(data).documentElement, true) : null;
+		var redirect = window.location.protocol + '//' + window.location.hostname + url;
+		var graph = null;
 		
-		// Updates display to fetch graphical output for graphics file formats (eg. svg)
-		this.setFileData(data);
-		file.setData(this.createFileData(this.getXmlFileData(), null, file, redirect));
+		// Handles special case where SVG files need a rendered graph to be saved
+		if (!/\.svg$/i.test(name) && dataNode != null)
+		{
+			graph = this.createTemporaryGraph(this.editor.graph.getStylesheet());
+
+			document.body.appendChild(graph.container);
+			node = dataNode;
+			
+			if (node != null)
+			{
+				var diagramNode = null;
+				
+				if (node.nodeName == 'diagram')
+				{
+					diagramNode = node;
+				}
+				else if (node.nodeName == 'mxfile')
+				{
+					var diagrams = node.getElementsByTagName('diagram');
 
-		// Restores display for current diagram
-		this.setFileData(prev);
+					if (diagrams.length > 0)
+					{
+						diagramNode = diagrams[0];
+						var graphGetGlobalVariable = graph.getGlobalVariable;
+						
+						graph.getGlobalVariable = function(name)
+						{
+							if (name == 'page')
+							{
+								return diagramNode.getAttribute('name') || mxResources.get('pageWithNumber', [1])
+							}
+							else if (name == 'pagenumber')
+							{
+								return 1;
+							}
+							
+							return graphGetGlobalVariable.apply(this, arguments);
+						};
+					}
+				}
+				
+				if (diagramNode != null)
+				{
+					var tmp = graph.decompress(mxUtils.getTextContent(diagramNode));
+					
+					if (tmp != null && tmp.length > 0)
+					{
+						node = mxUtils.parseXml(tmp).documentElement;
+					}
+				}
+			}
+			
+			// Hack to decode XML into temp graph via editor
+			var prev = this.editor.graph;
+			
+			try
+			{
+				this.editor.graph = graph;
+				this.editor.setGraphXml(node);	
+			}
+			catch (e)
+			{
+				// ignore
+			}
+			finally
+			{
+				this.editor.graph = prev;
+			}
+		}
 		
+		file.setData(this.createFileData(dataNode, graph, file, redirect));
+
+		if (graph != null)
+		{
+			graph.container.parentNode.removeChild(graph.container);
+		}
+
 		var complete = mxUtils.bind(this, function()
 		{
 			this.spinner.stop();
@@ -3161,8 +3283,8 @@ App.prototype.loadFile = function(id, sameWindow, file)
 				{
 					this.handleError({message: mxResources.get('serviceUnavailableOrBlocked')}, mxResources.get('errorLoadingFile'), mxUtils.bind(this, function()
 					{
-						var file = this.getCurrentFile();
-						window.location.hash = (file != null) ? file.getHash() : '';
+						var tempFile = this.getCurrentFile();
+						window.location.hash = (tempFile != null) ? tempFile.getHash() : '';
 					}));
 				}
 				else
@@ -3185,8 +3307,8 @@ App.prototype.loadFile = function(id, sameWindow, file)
 					{
 						this.handleError(e, mxResources.get('errorLoadingFile'), mxUtils.bind(this, function()
 						{
-							var file = this.getCurrentFile();
-							window.location.hash = (file != null) ? file.getHash() : '';
+							var tempFile = this.getCurrentFile();
+							window.location.hash = (tempFile != null) ? tempFile.getHash() : '';
 						}));
 					}
 				}
@@ -3208,13 +3330,13 @@ App.prototype.loadFile = function(id, sameWindow, file)
 					data = this.editor.graph.decompress(data);
 				}
 				
-				var file = new LocalFile(this, data, (urlParams['title'] != null) ?
+				var tempFile = new LocalFile(this, data, (urlParams['title'] != null) ?
 					decodeURIComponent(urlParams['title']) : this.defaultFilename, true);
-				file.getHash = function()
+				tempFile.getHash = function()
 				{
 					return id;
 				};
-				this.fileLoaded(file);
+				this.fileLoaded(tempFile);
 			}
 			else if (id.charAt(0) == 'U')
 			{
@@ -3252,14 +3374,14 @@ App.prototype.loadFile = function(id, sameWindow, file)
 							}
 						}
 						
-						var file = new LocalFile(this, text, (urlParams['title'] != null) ?
+						var tempFile = new LocalFile(this, text, (urlParams['title'] != null) ?
 							decodeURIComponent(urlParams['title']) : filename, true);
-						file.getHash = function()
+						tempFile.getHash = function()
 						{
 							return id;
 						};
 						
-						if (!this.fileLoaded(file))
+						if (!this.fileLoaded(tempFile))
 						{
 							// Fallback for non-public Google Drive diagrams
 							if (url.substring(0, 31) == 'https://drive.google.com/uc?id=' &&
@@ -3395,6 +3517,10 @@ App.prototype.getLibraryStorageHint = function(file)
 	{
 		tip += ' (' + mxResources.get('googleDrive') + ')';
 	}
+	else if (file.constructor == GitHubLibrary)
+	{
+		tip += ' (' + mxResources.get('github') + ')';
+	}
 	else if (file.constructor == DropboxLibrary)
 	{
 		tip += ' (' + mxResources.get('dropbox') + ')';
@@ -3949,10 +4075,11 @@ App.prototype.showAuthDialog = function(peer, showRememberOption, fn)
 		{
 			if (fn != null)
 			{
-				fn(remember, function()
+				fn(remember, mxUtils.bind(this, function()
 				{
+					this.hideDialog();
 					resume();
-				});
+				}));
 			}
 		}
 		catch (e)
@@ -4053,7 +4180,7 @@ App.prototype.convertFile = function(url, filename, mimeType, extension, success
 						success(new LocalFile(this, data, filename, true));
 					}
 				}
-				else if (Graph.fileSupport && new XMLHttpRequest().upload)
+				else if (Graph.fileSupport && new XMLHttpRequest().upload && this.isRemoteFileFormat(data, url))
 				{
 					this.parseFile(new Blob([data], {type: 'application/octet-stream'}), mxUtils.bind(this, function(xhr)
 					{
@@ -4070,9 +4197,9 @@ App.prototype.convertFile = function(url, filename, mimeType, extension, success
 						}
 					}), filename);
 				}
-				else if (error != null)
+				else
 				{
-					error({message: mxResources.get('errorLoadingFile')});
+					success(new LocalFile(this, data, name, true));
 				}
 			}
 			catch (e)

+ 1 - 1
war/js/diagramly/Devel.js

@@ -11,7 +11,6 @@ mxscript(drawDevUrl + 'js/spin/spin.min.js');
 mxscript(drawDevUrl + 'js/deflate/pako.min.js');
 mxscript(drawDevUrl + 'js/deflate/base64.js');
 mxscript(drawDevUrl + 'js/sanitizer/sanitizer.min.js');
-mxscript(drawDevUrl + 'js/dropbox/dropbox.min.js');
 
 // Uses grapheditor from devhost
 mxscript(geBasePath +'/Editor.js');
@@ -73,6 +72,7 @@ mxscript(drawDevUrl + 'js/diagramly/EditorUi.js');
 mxscript(drawDevUrl + 'js/diagramly/Settings.js');
 
 // Excluded in base.min.js
+mxscript(drawDevUrl + 'js/diagramly/DrawioClient.js');
 mxscript(drawDevUrl + 'js/diagramly/DrawioUser.js');
 mxscript(drawDevUrl + 'js/diagramly/UrlLibrary.js');
 mxscript(drawDevUrl + 'js/diagramly/DriveRealtime.js');

+ 76 - 10
war/js/diagramly/Dialogs.js

@@ -9,6 +9,8 @@ var StorageDialog = function(editorUi, fn, rowLimit)
 	var div = document.createElement('div');
 	div.style.textAlign = 'center';
 	div.style.whiteSpace = 'nowrap';
+	div.style.paddingTop = '0px';
+	div.style.paddingBottom = '20px';
 	
 	var elt = editorUi.addLanguageMenu(div);
 	var bottom = '28px';
@@ -82,7 +84,7 @@ var StorageDialog = function(editorUi, fn, rowLimit)
 
 	buttons.style.border = '1px solid #d3d3d3';
 	buttons.style.borderWidth = '1px 0px 1px 0px';
-	buttons.style.padding = '26px 0px 12px 0px';
+	buttons.style.padding = '18px 0px 18px 0px';
 
 	var cb = document.createElement('input');
 	cb.setAttribute('type', 'checkbox');
@@ -212,7 +214,7 @@ var StorageDialog = function(editorUi, fn, rowLimit)
 	hd.style.fontSize = '16pt';
 	hd.style.padding = '0px';
 	hd.style.paddingTop = '4px';
-	hd.style.paddingBottom = '20px';
+	hd.style.paddingBottom = '16px';
 	hd.style.margin = '0px';
 	hd.style.color = 'gray';
 	mxUtils.write(hd, mxResources.get('saveDiagramsTo') + ':');
@@ -251,7 +253,8 @@ var StorageDialog = function(editorUi, fn, rowLimit)
 	div.appendChild(buttons);
 
 	var p2 = document.createElement('p');
-	p2.style.paddingTop = '10px';
+	p2.style.marginTop = '12px';
+	p2.style.marginBottom = '10px';
 	p2.appendChild(cb);
 	
 	var span = document.createElement('span');
@@ -259,6 +262,62 @@ var StorageDialog = function(editorUi, fn, rowLimit)
 	span.style.fontSize = '12px';
 	mxUtils.write(span, ' ' + mxResources.get('rememberThisSetting'));
 	p2.appendChild(span);
+	mxUtils.br(p2);
+
+	var recent = editorUi.getRecent();
+
+	if (recent != null && recent.length > 0)
+	{
+		var recentSelect = document.createElement('select');
+		recentSelect.style.marginTop = '14px';
+		recentSelect.style.width = '140px';
+
+		var titleOption = document.createElement('option');
+		titleOption.setAttribute('value', '');
+		titleOption.setAttribute('selected', 'selected');
+		titleOption.style.textAlign = 'center';
+		mxUtils.write(titleOption, mxResources.get('openRecent') + '...');
+		recentSelect.appendChild(titleOption);
+		
+		for (var i = 0; i < recent.length; i++)
+		{
+			(function(entry)
+			{
+				var modeKey = entry.mode;
+				
+				// Google and oneDrive use different keys
+				if (modeKey == App.MODE_GOOGLE)
+				{
+					modeKey = 'googleDrive';
+				}
+				else if (modeKey == App.MODE_ONEDRIVE)
+				{
+					modeKey = 'oneDrive';
+				}
+				
+				var entryOption = document.createElement('option');
+				entryOption.setAttribute('value', entry.id);
+				mxUtils.write(entryOption, entry.title + ' (' + mxResources.get(modeKey) + ')');
+				recentSelect.appendChild(entryOption);
+			})(recent[i]);
+		}
+
+		p2.appendChild(recentSelect);
+		
+		mxEvent.addListener(recentSelect, 'change', function(evt)
+		{
+			if (recentSelect.value != '')
+			{
+				editorUi.loadFile(recentSelect.value);
+			}
+		});
+	}
+	else
+	{
+		p2.style.marginTop = '20px';
+		buttons.style.padding = '30px 0px 26px 0px';
+	}
+	
 	buttons.appendChild(p2);
 	
 	mxEvent.addListener(span, 'click', function(evt)
@@ -850,7 +909,8 @@ var EmbedDialog = function(editorUi, result, timeout, ignoreSize, previewFn)
 						{
 							window.setTimeout(mxUtils.bind(this, function()
 							{
-								if (win != null && win.location.href != value)
+								if (win != null && win.location.href != null &&
+									win.location.href.substring(0, 8) != value.substring(0, 8))
 								{
 									win.close();
 									editorUi.handleError({message: mxResources.get('drawingTooLarge')});
@@ -1509,8 +1569,11 @@ var CreateGraphDialog = function(editorUi, title, type)
 		{
 			editorUi.confirm(mxResources.get('areYouSure'), function()
 			{
-				graph.destroy();
-				container.parentNode.removeChild(container);
+				if (container.parentNode != null)
+				{
+					graph.destroy();
+					container.parentNode.removeChild(container);
+				}
 		
 				editorUi.hideDialog();
 			});
@@ -1538,8 +1601,12 @@ var CreateGraphDialog = function(editorUi, title, type)
 			editorUi.editor.graph.scrollRectToVisible(temp);
 			editorUi.editor.graph.setSelectionCells(cells);
 			
-			graph.destroy();
-			container.parentNode.removeChild(container);
+			if (container.parentNode != null)
+			{
+				graph.destroy();
+				container.parentNode.removeChild(container);
+			}
+			
 			editorUi.hideDialog();
 		});
 		
@@ -6525,7 +6592,6 @@ var AuthDialog = function(editorUi, peer, showRememberOption, fn)
 	
 	var button = mxUtils.button(mxResources.get('authorize'), function()
 	{
-		editorUi.hideDialog(false);
 		fn(cb.checked);
 	});
 
@@ -8125,7 +8191,7 @@ var EditShapeDialog = function(editorUi, cell, title, w, h)
 	{
 		var helpBtn = mxUtils.button(mxResources.get('help'), function()
 		{
-			window.open('https://support.draw.io/display/DO/Editing+Shapes');
+			window.open('https://desk.draw.io/support/solutions/articles/16000052874-how-to-create-and-edit-shapes-');
 		});
 		
 		helpBtn.className = 'geBtn';

+ 139 - 0
war/js/diagramly/DrawioClient.js

@@ -0,0 +1,139 @@
+/**
+ * Copyright (c) 2006-2017, JGraph Ltd
+ * Copyright (c) 2006-2017, Gaudenz Alder
+ */
+DrawioClient = function(editorUi, cookieName)
+{
+	mxEventSource.call(this);
+
+	this.ui = editorUi;
+	this.cookieName = cookieName;
+	this.token = this.getPersistentToken();
+};
+
+// Extends mxEventSource
+mxUtils.extend(DrawioClient, mxEventSource);
+
+/**
+ * Token for the current user.
+ */
+DrawioClient.prototype.token = null;
+
+/**
+ * Token for the current user.
+ */
+DrawioClient.prototype.user = null;
+
+/**
+ * Authorizes the client, gets the userId and calls <open>.
+ */
+DrawioClient.prototype.setUser = function(user)
+{
+	this.user = user;
+	this.fireEvent(new mxEventObject('userChanged'));
+};
+
+/**
+ * Authorizes the client, gets the userId and calls <open>.
+ */
+DrawioClient.prototype.getUser = function()
+{
+	return this.user;
+};
+
+/**
+ * 
+ */
+DrawioClient.prototype.clearPersistentToken = function()
+{
+	if (isLocalStorage)
+	{
+		localStorage.removeItem('.' + this.cookieName);
+	}
+	else if (typeof(Storage) != 'undefined')
+	{
+		var expiration = new Date();
+		expiration.setYear(expiration.getFullYear() - 1);
+		document.cookie = this.cookieName + '=; expires=' + expiration.toUTCString();
+	}
+};
+
+/**
+ * Authorizes the client, gets the userId and calls <open>.
+ */
+DrawioClient.prototype.getPersistentToken = function()
+{
+	var token = null;
+	
+	if (isLocalStorage)
+	{
+		token = localStorage.getItem('.' + this.cookieName);
+	}
+	
+	if (token == null && typeof(Storage) != 'undefined')
+	{
+		var cookies = document.cookie;
+		var name = this.cookieName + '=';
+		var start = cookies.indexOf(name);
+	
+		if (start >= 0)
+		{
+			start += name.length;
+			var end = cookies.indexOf(';', start);
+		    
+			if (end < 0)
+			{
+				end = cookies.length;
+			}
+			else
+			{
+				postCookie = cookies.substring(end);
+		    }
+	
+			var value = cookies.substring(start, end);
+			token = (value.length > 0) ? value : null;
+			
+			if (token != null && isLocalStorage)
+			{
+				// Moves to local storage
+				var expiry = new Date();
+				expiry.setYear(expiry.getFullYear() - 1);
+				document.cookie = name + '; expires=' + expiry.toUTCString();
+				localStorage.setItem('.' + this.cookieName, token);
+			}
+		}
+	}
+	
+	return token;
+};
+
+/**
+ * Authorizes the client, gets the userId and calls <open>.
+ */
+DrawioClient.prototype.setPersistentToken = function(token)
+{
+	if (token != null)
+	{
+		if (isLocalStorage)
+		{
+			localStorage.setItem('.' + this.cookieName, token);
+		}
+		else if (typeof(Storage) != 'undefined')
+		{
+			var expiration = new Date();
+			expiration.setYear(expiration.getFullYear() + 10);
+			var cookie = this.cookieName + '=' + token +'; path=/; expires=' + expiration.toUTCString();
+	
+			if (document.location.protocol.toLowerCase() == 'https')
+			{
+				cookie = cookie + ';secure';
+			}
+	
+			document.cookie = cookie;
+		}
+	}
+	else
+	{
+		this.clearPersistentToken();
+	}
+};

+ 39 - 29
war/js/diagramly/DriveClient.js

@@ -159,22 +159,24 @@ DriveClient.prototype.getUser = function()
  */
 DriveClient.prototype.setUserId = function(userId, remember)
 {
-	if (typeof(Storage) != 'undefined')
+	if (remember)
 	{
-		try
+		if (isLocalStorage)
 		{
-			sessionStorage.setItem('GUID', userId);
-			
-			if (remember)
+			localStorage.setItem('.guid', userId);
+		}
+		else if (typeof(Storage) != 'undefined')
+		{
+			try
 			{
 				var expiry = new Date();
 				expiry.setYear(expiry.getFullYear() + 1);
 				document.cookie = 'GUID=' + userId + '; expires=' + expiry.toUTCString();
 			}
-		}
-		catch (e)
-		{
-			// any errors for storing the user ID can be safely ignored
+			catch (e)
+			{
+				// any errors for storing the user ID can be safely ignored
+			}
 		}
 	}
 };
@@ -184,10 +186,12 @@ DriveClient.prototype.setUserId = function(userId, remember)
  */
 DriveClient.prototype.clearUserId = function()
 {
-	if (typeof(Storage) != 'undefined')
+	if (isLocalStorage)
+	{
+		localStorage.removeItem('.guid');
+	}
+	else if (typeof(Storage) != 'undefined')
 	{
-		sessionStorage.removeItem('GUID');
-
 		var expiry = new Date();
 		expiry.setYear(expiry.getFullYear() - 1);
 		document.cookie = 'GUID=; expires=' + expiry.toUTCString();
@@ -205,30 +209,36 @@ DriveClient.prototype.getUserId = function()
 	{
 		uid = this.user.id;
 	}
-
-	if (typeof(Storage) != 'undefined')
+	
+	if (uid == null && isLocalStorage)
 	{
-		if (uid == null)
-		{
-			uid = sessionStorage.getItem('GUID');
-		}
+		uid = localStorage.getItem('.guid');
+	}
+	
+	if (uid == null	&& typeof(Storage) != 'undefined')
+	{
+		var cookies = document.cookie.split(";");
 		
-		if (uid == null)
+		for (var i = 0; i < cookies.length; i++)
 		{
-			var cookies = document.cookie.split(";");
+			// Removes spaces around cookie
+			var cookie = mxUtils.trim(cookies[i]);
 			
-			for (var i = 0; i < cookies.length; i++)
+			if (cookie.substring(0, 5) == 'GUID=')
 			{
-				// Removes spaces around cookie
-				var cookie = mxUtils.trim(cookies[i]);
-				
-				if (cookie.substring(0, 5) == 'GUID=')
-				{
-					uid = cookie.substring(5);
-					break;
-				}
+				uid = cookie.substring(5);
+				break;
 			}
 		}
+		
+		if (uid != null && isLocalStorage)
+		{
+			// Moves to local storage
+			var expiry = new Date();
+			expiry.setYear(expiry.getFullYear() - 1);
+			document.cookie = 'GUID=; expires=' + expiry.toUTCString();
+			localStorage.setItem('.guid', uid);
+		}
 	}
 	
 	return uid;

+ 298 - 192
war/js/diagramly/DropboxClient.js

@@ -4,40 +4,14 @@
  */
 DropboxClient = function(editorUi)
 {
-	mxEventSource.call(this);
+	DrawioClient.call(this, editorUi, 'dbauth');
 	
-	/**
-	 * Holds the x-coordinate of the point.
-	 * @type number
-	 * @default 0
-	 */
-	this.ui = editorUi;
-
-	/**
-	 * Holds the x-coordinate of the point.
-	 * @type number
-	 * @default 0
-	 */
-	this.client = new Dropbox.Client(
-	{
-		key: App.DROPBOX_APPKEY,
-		sandbox: true
-	});
-	
-	/**
-	 * Holds the x-coordinate of the point.
-	 * @type number
-	 * @default 0
-	 */
-	this.client.authDriver(new Dropbox.AuthDriver.Popup(
-	{
-		rememberUser: true,
-		receiverUrl: 'https://' + window.location.host + '/dropbox.html'
-	}));
+	this.client = new Dropbox({clientId: App.DROPBOX_APPKEY});
+	this.client.setAccessToken(this.token);
 };
 
-// Extends mxEventSource
-mxUtils.extend(DropboxClient, mxEventSource);
+// Extends DrawioClient
+mxUtils.extend(DropboxClient, DrawioClient);
 
 /**
  * FIXME: How to find name of app folder for current user. The Apps part of the
@@ -53,60 +27,72 @@ DropboxClient.prototype.extension = '.html';
 /**
  * Executes the first step for connecting to Google Drive.
  */
-DriveClient.prototype.maxRetries = 4;
-
-/**
- * Executes the first step for connecting to Google Drive.
- */
-DropboxClient.prototype.user = null;
+DropboxClient.prototype.writingFile = false;
 
 /**
  * Executes the first step for connecting to Google Drive.
  */
-DropboxClient.prototype.writingFile = false;
+DropboxClient.prototype.maxRetries = 4;
 
 /**
  * Authorizes the client, gets the userId and calls <open>.
  */
 DropboxClient.prototype.logout = function()
 {
-	this.client.signOut(mxUtils.bind(this, function()
+	this.client.authTokenRevoke().then(mxUtils.bind(this, function()
 	{
+		this.client.setAccessToken(null);
+		this.clearPersistentToken();
 		this.setUser(null);
+		this.token = null;
 	}));
 };
 
-/**
- * Authorizes the client, gets the userId and calls <open>.
- */
-DropboxClient.prototype.setUser = function(user)
-{
-	this.user = user;
-	this.fireEvent(new mxEventObject('userChanged'));
-};
-
-/**
- * Authorizes the client, gets the userId and calls <open>.
- */
-DropboxClient.prototype.getUser = function()
-{
-	return this.user;
-};
-
 /**
  * Checks if the client is authorized and calls the next step.
  */
-DropboxClient.prototype.updateUser = function(success, error, remember)
+DropboxClient.prototype.updateUser = function(success)
 {
-	this.client.getUserInfo(null, mxUtils.bind(this, function(error, info)
+	var acceptResponse = true;
+	
+	var timeoutThread = window.setTimeout(mxUtils.bind(this, function()
 	{
-		if (error == null)
+		acceptResponse = false;
+		this.ui.handleError({code: App.ERROR_TIMEOUT});
+	}), this.ui.timeout);
+	
+	var promise = this.client.usersGetCurrentAccount();
+	promise.then(mxUtils.bind(this, function(response)
+	{
+    	window.clearTimeout(timeoutThread);
+    	
+    	if (acceptResponse)
+    	{
+			this.setUser(new DrawioUser(response.account_id, response.email, response.name.display_name));
+			
+			if (success != null)
+			{
+				success();
+			}
+    	}
+	}));
+	// Workaround for IE8/9 support with catch function
+	promise['catch'](mxUtils.bind(this, function(err)
+	{
+		if (err != null && err.status === 401)
 		{
-			this.setUser(new DrawioUser(info.uid, info.email, info.name));
+			this.client.setAccessToken(null);
+			this.execute(success);
 		}
 		else
 		{
-			this.setUser(null);
+	    	window.clearTimeout(timeoutThread);
+	    	
+	    	if (acceptResponse)
+	    	{
+	    		this.setUser(null);
+	    		this.ui.handleError(err);
+	    	}
 		}
 	}));
 };
@@ -116,50 +102,36 @@ DropboxClient.prototype.updateUser = function(success, error, remember)
  */
 DropboxClient.prototype.execute = function(fn)
 {
-	if (this.client.isAuthenticated())
+	if (this.client.getAccessToken() != null)
 	{
-		fn();
+		if (this.user == null)
+		{
+			this.updateUser(fn);
+		}
+		else
+		{
+			fn();
+		}
 	}
 	else
 	{
-		this.authorize(false, mxUtils.bind(this, function(error, client)
+		this.ui.showAuthDialog(this, false, mxUtils.bind(this, function(remember, success)
 		{
-			if (error != null)
-			{
-				this.ui.handleError(error);
-			}
-			else
+			this.authenticate(mxUtils.bind(this, function()
 			{
-				if (this.client.isAuthenticated())
+				if (success != null)
 				{
-					this.updateUser();
-					fn();
+					success();
 				}
-				else
+
+				if (this.client.getAccessToken() != null)
 				{
-					this.ui.showAuthDialog(this, false, mxUtils.bind(this, function(remember, success)
+					this.updateUser(fn, mxUtils.bind(this, function(err)
 					{
-						this.authorize(true, mxUtils.bind(this, function(error2, client)
-						{
-							if (error2 != null)
-							{
-								this.ui.handleError(error2);
-							}
-							else if (this.client.isAuthenticated())
-							{
-								this.updateUser();
-								
-								if (success != null)
-								{
-									success();
-								}
-
-								fn();
-							}
-						}));
+						this.ui.handleError(e);
 					}));
 				}
-			}
+			}));
 		}));
 	}
 };
@@ -167,22 +139,39 @@ DropboxClient.prototype.execute = function(fn)
 /**
  * Authorizes the client, gets the userId and calls <open>.
  */
-DropboxClient.prototype.authorize = function(interactive, fn)
+DropboxClient.prototype.authenticate = function(fn)
 {
-	this.client.authenticate({interactive: interactive}, mxUtils.bind(this, function(error, client)
+	window.open(this.client.getAuthenticationUrl('https://' +
+		window.location.host + '/dropbox.html'), 'oauth');
+	
+	window.onDropboxCallback = mxUtils.bind(this, function(token, authWindow)
 	{
-		if (error != null)
+		try
 		{
-			if (window.console != null)
+			window.onDropboxCallback = null;
+	
+			if (authWindow != null)
+			{
+				authWindow.close();
+			}
+			
+			if (token == null)
 			{
-				console.log(error);
+				this.ui.hideDialog();
+				this.ui.handleError({message: mxResources.get('cannotLogin')});
+			}
+			else
+			{
+				this.client.setAccessToken(token);
+				this.setPersistentToken(token);
+				fn();
 			}
 		}
-		else
+		catch (e)
 		{
-			fn();
+			this.ui.handleError(e);
 		}
-	}));
+	});
 };
 
 /**
@@ -213,51 +202,18 @@ DropboxClient.prototype.getFile = function(path, success, error, asLibrary)
 			}
 			else
 			{
-				var acceptResponse = true;
-				
-				var timeoutThread = window.setTimeout(mxUtils.bind(this, function()
-				{
-					acceptResponse = false;
-					error({code: App.ERROR_TIMEOUT, retry: fn});
-				}), this.ui.timeout);
-				
-				var options = null;
+				var arg = {path: '/' + path};
 				
 				if (urlParams['rev'] != null)
 				{
-					options = {versionTag: urlParams['rev']};
+					arg.rev = urlParams['rev'];
 				}
 				
-				this.client.readFile('/' + path, options, mxUtils.bind(this, function(err, data, stat)
+				this.readFile(arg, mxUtils.bind(this, function(data, response)
 				{
-					try
-					{
-				    	window.clearTimeout(timeoutThread);
-				    	
-				    	if (acceptResponse)
-				    	{
-							if (err != null)
-							{
-								error(err)
-							}
-							else
-							{
-								if (asLibrary)
-								{
-									success(new DropboxLibrary(this.ui, data, stat));
-								}
-								else
-								{
-									success(new DropboxFile(this.ui, data, stat));
-								}
-							}
-				    	}
-					}
-					catch (e)
-					{
-						error(e);
-					}
-				}));
+		    		success((asLibrary) ? new DropboxLibrary(this.ui, data, response) :
+		    			new DropboxFile(this.ui, data, response));
+				}), error);
 			}
 		}));
 	});
@@ -271,25 +227,107 @@ DropboxClient.prototype.getFile = function(path, success, error, asLibrary)
  * @param {number} dx X-coordinate of the translation.
  * @param {number} dy Y-coordinate of the translation.
  */
-DropboxClient.prototype.checkExists = function(filename, fn)
+DropboxClient.prototype.readFile = function(arg, success, error)
 {
-	this.client.stat(filename, mxUtils.bind(this, function(err, stat)
+	var acceptResponse = true;
+	
+	var timeoutThread = window.setTimeout(mxUtils.bind(this, function()
+	{
+		acceptResponse = false;
+		error({code: App.ERROR_TIMEOUT});
+	}), this.ui.timeout);
+
+	// TODO: Find catch for file does not exist in filesDownload below
+	// This will have significant impact on performance!
+	// LATER: Catch "file not found" error without metadata check?
+	this.checkExists(arg.path.substring(1), mxUtils.bind(this, function(checked, exists)
 	{
-		if ((err != null && err.status == 404) || (stat != null && stat.isRemoved))
+    	window.clearTimeout(timeoutThread);
+	    
+    	if (acceptResponse)
+    	{
+			if (!exists)
+			{
+				error({message: mxResources.get('fileNotFound')});
+			}
+			else
+			{
+	    		timeoutThread = window.setTimeout(mxUtils.bind(this, function()
+				{
+					acceptResponse = false;
+					error({code: App.ERROR_TIMEOUT});
+				}), this.ui.timeout);
+	    		
+				var promise = this.client.filesDownload(arg);
+				promise.then(mxUtils.bind(this, function(response)
+				{
+			    	window.clearTimeout(timeoutThread);
+				    
+			    	if (acceptResponse)
+			    	{
+						try
+						{
+							var reader = new FileReader();
+							
+							reader.onload = mxUtils.bind(this, function(event)
+							{
+								success(reader.result, response);
+							});
+							
+							reader.readAsText(response.fileBlob);
+						}
+						catch (e)
+						{
+							error(e);
+						}
+			    	}
+				}));
+				// Workaround for IE8/9 support with catch function
+				promise['catch'](function(err)
+				{
+			    	window.clearTimeout(timeoutThread);
+				    
+			    	if (acceptResponse)
+			    	{
+			    		error(e);
+			    	}
+				});
+			}
+    	}
+	}), true);
+};
+
+/**
+ * Translates this point by the given vector.
+ * 
+ * @param {number} dx X-coordinate of the translation.
+ * @param {number} dy Y-coordinate of the translation.
+ */
+DropboxClient.prototype.checkExists = function(filename, fn, noConfirm)
+{
+	var promise = this.client.filesGetMetadata({path: '/' + filename.toLowerCase(), include_deleted: false});
+	promise.then(mxUtils.bind(this, function(response)
+	{
+		if (noConfirm)
 		{
-			fn(true);
+			fn(false, true, response);
 		}
 		else
 		{
 			this.ui.confirm(mxResources.get('replaceIt', [filename]), function()
 			{
-				fn(true);
+				fn(true, true, response);
 			}, function()
 			{
-				fn(false);
+				fn(false, true, response);
 			});
 		}
 	}));
+	// Workaround for IE8/9 support with catch function
+	promise['catch'](function(err)
+	{
+		fn(true, false);
+	});
 };
 
 /**
@@ -300,37 +338,73 @@ DropboxClient.prototype.checkExists = function(filename, fn)
  */
 DropboxClient.prototype.renameFile = function(file, filename, success, error)
 {
-	if (file != null && filename != null)
+	if (/[\\\/:\?\*"\|]/.test(filename))
 	{
-		// Checks if file exists
-		this.execute(mxUtils.bind(this, function()
+		error({message: mxResources.get('dropboxCharsNotAllowed')});
+	}
+	else
+	{
+		// Appends working directory of source file
+		if (file != null && filename != null)
+		{
+			var path = file.stat.path_display.substring(1);
+			var idx = path.lastIndexOf('/');
+			
+			if (idx > 0)
+			{
+				filename = path.substring(0, idx + 1) + filename;
+			}
+		}
+		
+		if (file != null && filename != null && file.stat.path_lower.substring(1) !== filename.toLowerCase())
 		{
-			this.checkExists(filename, mxUtils.bind(this, function(checked)
+			// Checks if file exists
+			this.execute(mxUtils.bind(this, function()
 			{
-				if (checked)
+				this.checkExists(filename, mxUtils.bind(this, function(checked, exists, response)
 				{
-					// Uses write and remove because move does not allow overwriting an existing target
-					this.writeFile(filename, file.getData(), mxUtils.bind(this, function(stat)
+					if (checked)
 					{
-						this.client.remove(file.getTitle(), function(err2, stat2)
+						var thenHandler = mxUtils.bind(this, function(deleteResponse)
 						{
-							if (err2 != null)
-							{
-								error(err2)
-							}
-							else
+							// Uses write and remove because move does not allow overwriting an existing target
+							var move = this.client.filesMove({from_path: file.stat.path_display, to_path: '/' +
+								filename, autorename: false});
+							move.then(mxUtils.bind(this, function(response)
 							{
-								success(stat);
-							}
+								success(response);
+							}))
+							// Workaround for IE8/9 support with catch function
+							move['catch'](error);
 						});
-					}), error);
-				}
-				else
-				{
-					error();
-				}
+						
+						// API fails on same name with different upper-/lowercase
+						if (!exists || response.path_lower.substring(1) === filename.toLowerCase())
+						{
+							thenHandler();
+						}
+						else
+						{
+							// Deletes file first to avoid conflict in filesMove (non-atomic)
+							var promise = this.client.filesDelete({path: '/' + filename.toLowerCase()});
+							promise.then(thenHandler);
+							// Workaround for IE8/9 support with catch function
+							promise['catch'](error);
+						}
+					}
+					else
+					{
+						error();
+					}
+				}));
 			}));
-		}));
+		}
+		else
+		{
+			// Same name with different upper-/lowercase
+			// is not supported by Dropbox API
+			error({message: mxResources.get('invalidName')});
+		}
 	}
 };
 
@@ -410,11 +484,27 @@ DropboxClient.prototype.writeFile = function(filename, data, success, error)
 			error({message: mxResources.get('dropboxCharsNotAllowed')});
 		}
 	}
-	else if (!this.writingFile)
+	else if (data.length >= 150000000 /*150MB*/)
+	{
+		if (error != null)
+		{
+			error({message: mxResources.get('drawingTooLarge') + ' (' +
+				this.ui.formatFileSize(data.length) + ' / 150 MB)'});
+		}
+	}
+	else if (this.writingFile)
+	{
+		if (error != null)
+		{
+			error({code: App.ERROR_BUSY});
+		}
+	}
+	else
 	{
+		this.writingFile = true;
+		
 		var acceptResponse = true;
 		var timeoutThread = null;
-		this.writingFile = true;
 		var retryCount = 0;
 		
 		// Cancels any pending requests
@@ -441,45 +531,61 @@ DropboxClient.prototype.writeFile = function(filename, data, success, error)
 				}
 			}), this.ui.timeout);
 			
-			this.client.writeFile(filename, data, mxUtils.bind(this, function(err, stat)
+			var promise = this.client.filesUpload({path: '/' + filename, mode: {'.tag': 'overwrite'},
+				contents: new Blob([data], {type: 'text/plain'})});
+			promise.then(mxUtils.bind(this, function(response)
 			{
 		    	window.clearTimeout(timeoutThread);
-		    
+			    
 		    	if (acceptResponse)
 		    	{
-					if (err != null)
+					this.writingFile = false;
+					
+					try
 					{
-						if (retryCount < this.maxRetries)
+						if (success != null)
 						{
-							retryCount++;
-							var jitter = 1 + 0.1 * (Math.random() - 0.5);
-							this.requestThread = window.setTimeout(fn, Math.round(Math.pow(2, retryCount) * jitter * 1000));
+							success(response);
 						}
-						else if (error != null)
+					}
+					catch (e)
+					{
+						if (error != null)
 						{
-							this.writingFile = false;
-							error(err);
+							error(e);
 						}
 					}
+		    	}
+			}));
+			// Workaround for IE8/9 support with catch function
+			promise['catch'](mxUtils.bind(this, function(err)
+			{
+		    	window.clearTimeout(timeoutThread);
+			    
+		    	if (acceptResponse)
+				{
+		    		// LATER: Check error codes where a retry makes sense
+					if (retryCount < this.maxRetries)
+					{
+						retryCount++;
+						var jitter = 1 + 0.1 * (Math.random() - 0.5);
+						this.requestThread = window.setTimeout(fn, Math.round(Math.pow(2, retryCount) * jitter * 1000));
+					}
 					else
 					{
 						this.writingFile = false;
 						
-						if (success != null)
+						if (error != null)
 						{
-							success(stat);
+							error(err);
 						}
 					}
-		    	}
+				}
 			}));
 		});
 		
 		fn();
 	}
-	else if (error != null)
-	{
-		error({code: App.ERROR_BUSY});
-	}
 };
 
 /**
@@ -520,9 +626,9 @@ DropboxClient.prototype.pickLibrary = function(fn)
 					{		
 						var rel = decodeURIComponent(files[0].link.substring(tmp + this.appPath.length - 1));
 						
-						this.client.readFile(rel, null, mxUtils.bind(this, function(err, data, stat)
+						this.readFile({path: rel}, mxUtils.bind(this, function(data, stat)
 						{
-							if (stat != null && parseInt(files[0].bytes) === parseInt(stat.size) && rel === stat.path)
+							if (stat != null && parseInt(files[0].bytes) === parseInt(stat.size) && rel === stat.path_display)
 							{
 								// No need to load file a second time
 								try
@@ -539,7 +645,7 @@ DropboxClient.prototype.pickLibrary = function(fn)
 							{
 								this.createLibrary(files[0], fn, error);
 							}
-						}));
+						}), error);
 					}));
 				}
 				else
@@ -651,9 +757,9 @@ DropboxClient.prototype.pickFile = function(fn, readOnly)
 								{		
 									var rel = decodeURIComponent(files[0].link.substring(tmp + this.appPath.length - 1));
 									
-									this.client.readFile(rel, null, mxUtils.bind(this, function(err, data, stat)
+									this.readFile({path: rel}, mxUtils.bind(this, function(data, stat)
 									{
-										if (stat != null && parseInt(files[0].bytes) === parseInt(stat.size) && rel === stat.path)
+										if (stat != null && parseInt(files[0].bytes) === parseInt(stat.size) && rel === stat.path_display)
 										{
 											this.ui.spinner.stop();
 											

+ 1 - 1
war/js/diagramly/DropboxFile.js

@@ -20,7 +20,7 @@ mxUtils.extend(DropboxFile, DrawioFile);
  */
 DropboxFile.prototype.getHash = function()
 {
-	return 'D' + encodeURIComponent(this.stat.path.substring(1));
+	return 'D' + encodeURIComponent(this.stat.path_display.substring(1));
 };
 
 /**

+ 81 - 56
war/js/diagramly/EditorUi.js

@@ -73,7 +73,12 @@
 	 * Holds the current file.
 	 */
 	EditorUi.prototype.currentFile = null;
-
+	
+	/**
+	 * Switch to disable logging for mode and search terms.
+	 */
+	EditorUi.prototype.enableLogging = true;
+	
 	/**
 	 * Capability check for canvas export
 	 */
@@ -396,7 +401,7 @@
 		try
 		{
 			var doc = mxUtils.parseXml(data);
-			var node = this.editor.extractGraphModel(doc.documentElement);
+			var node = this.editor.extractGraphModel(doc.documentElement, true);
 			
 			return node != null && node.getElementsByTagName('parsererror').length == 0;
 		}
@@ -436,7 +441,7 @@
 		    	{
 		    		// Gets compressed data from mxgraph element in HTML document
 					var doc = mxUtils.parseXml(data);
-					var node = this.editor.extractGraphModel(doc.documentElement);
+					var node = this.editor.extractGraphModel(doc.documentElement, this.pages != null);
 					result = (node != null) ? mxUtils.getXml(node) : '';
 		    	}
 			}
@@ -1153,7 +1158,7 @@
        			}
        		}
        	}
-
+       	
 		return new mxXmlRequest(EXPORT_URL, 'format=' + format + range +
 			'&base64=' + base64 + '&embedXml=' + embed + '&xml=' +
 			encodeURIComponent(data) + ((filename != null) ?
@@ -1195,12 +1200,7 @@
 	
 		var noFile = mxUtils.bind(this, function()
 		{
-			this.diagramContainer.style.visibility = 'hidden';
-			this.formatContainer.style.visibility = 'hidden';
-			this.hsplit.style.display = 'none';
-			this.sidebarContainer.style.display = 'none';
-			this.sidebarFooterContainer.style.display = 'none';
-			this.editor.graph.setEnabled(false);
+			this.setGraphEnabled(false);
 			
 			// Keeps initial title if no file existed before
 			if (oldFile != null)
@@ -1241,11 +1241,7 @@
 				this.descriptorChanged();
 				file.open();
 				
-				this.diagramContainer.style.visibility = '';
-				this.formatContainer.style.visibility = '';
-				this.hsplit.style.display = '';
-				this.sidebarContainer.style.display = '';
-				this.sidebarFooterContainer.style.display = '';
+				this.setGraphEnabled(true);
 				this.setMode(file.getMode());
 				this.editor.undoManager.clear();
 				this.updateUi();
@@ -1288,23 +1284,20 @@
 				this.editor.fireEvent(new mxEventObject('fileLoaded'));
 				result = true;
 				
-	//			if (this.enableLogging)
-	//			{
-	//	        	try
-	//	        	{
-	//		        	if (!this.isOffline())
-	//		        	{
-	//	        			var img = new Image();
-	//						var logDomain = window.DRAWIO_LOG_URL != null ? window.DRAWIO_LOG_URL : '';
-	//	        			img.src = logDomain + '/log?msg=storageMode:' + encodeURIComponent(file.getMode()) +
-	//        				'&v=' + encodeURIComponent(EditorUi.VERSION);
-	//		        	}
-	//	        	}
-	//	        	catch (e)
-	//	        	{
-	//	        		// ignore
-	//	        	}
-	//			}
+				if (this.enableLogging && !this.isOffline())
+				{
+		        	try
+		        	{
+	        			var img = new Image();
+						var logDomain = window.DRAWIO_LOG_URL != null ? window.DRAWIO_LOG_URL : '';
+	        			img.src = logDomain + '/log?msg=storageMode:' + encodeURIComponent(file.getMode()) +
+        				'&v=' + encodeURIComponent(EditorUi.VERSION);
+		        	}
+		        	catch (e)
+		        	{
+		        		// ignore
+		        	}
+				}
 				
 				if (this.mode == file.getMode() && file.getMode() != App.MODE_DEVICE && file.getMode() != null)
 				{
@@ -2774,8 +2767,8 @@
 		}), mxUtils.bind(this, function()
 		{
 			this.hideDialog();
-		}), mxResources.get('saveAs'), mxResources.get('download'), false, false, allowTab);
-		this.showDialog(dlg.container, 380, (this.getServiceCount(false) - 1 < 4) ? 270 : 390, true, true);
+		}), mxResources.get('saveAs'), mxResources.get('download'), false, false, allowTab, null, null, 4);
+		this.showDialog(dlg.container, 380, (this.getServiceCount(false) - 1 < 5) ? 270 : 390, true, true);
 		dlg.init();
 	};
 		
@@ -2918,7 +2911,7 @@
 	/**
 	 * 
 	 */
-	EditorUi.prototype.addLinkSection = function(div)
+	EditorUi.prototype.addLinkSection = function(div, showFrameOption)
 	{
 		mxUtils.write(div, mxResources.get('links') + ':');
 
@@ -2942,6 +2935,16 @@
 		selfOption.setAttribute('value', 'self');
 		mxUtils.write(selfOption, mxResources.get('openInThisWindow'));
 		linkSelect.appendChild(selfOption);
+
+		if (showFrameOption)
+		{
+			var frameOption = document.createElement('option');
+			frameOption.setAttribute('value', 'frame');
+			mxUtils.write(frameOption, mxResources.get('openInThisWindow') +
+				' (' + mxResources.get('iframe') + ')');
+			linkSelect.appendChild(frameOption);
+		}
+		
 		div.appendChild(linkSelect);
 		
 		mxUtils.write(div, mxResources.get('borderColor') + ':');
@@ -3005,7 +3008,6 @@
 		
 		if (lightbox)
 		{
-			params.push('chrome=0');
 			params.push('lightbox=1');
 
 			if (linkTarget != 'auto')
@@ -3297,7 +3299,7 @@
 	/**
 	 * 
 	 */
-	EditorUi.prototype.showPublishLinkDialog = function(title, hideShare, width, height, fn)
+	EditorUi.prototype.showPublishLinkDialog = function(title, hideShare, width, height, fn, showFrameOption)
 	{
 		var div = document.createElement('div');
 		div.style.whiteSpace = 'nowrap';
@@ -3390,7 +3392,7 @@
 			mxUtils.br(div);
 		}
 		
-		var linkSection = this.addLinkSection(div);
+		var linkSection = this.addLinkSection(div, showFrameOption);
 		var hasPages = this.pages != null && this.pages.length > 1;
 		var allPages = null;
 		
@@ -3752,7 +3754,7 @@
 			{
 				// KNOWN: Message passing does not seem to work in IE11
 				onclick = " onclick=\"(function(img){if(img.wnd!=null&&!img.wnd.closed){img.wnd.focus();}else{var r=function(evt){if(evt.data=='ready'&&evt.source==img.wnd){img.wnd.postMessage(decodeURIComponent(" +
-					"img.getAttribute('src')),'*');window.removeEventListener('message',r);}};window.addEventListener('message',r);img.wnd=window.open('https://www.draw.io/?client=1&lightbox=1&chrome=0" +
+					"img.getAttribute('src')),'*');window.removeEventListener('message',r);}};window.addEventListener('message',r);img.wnd=window.open('https://www.draw.io/?client=1&lightbox=1" +
 					((edit) ? "&edit=_blank" : "") +
 					((layers) ? '&layers=1' : '') + "');}})(this);\"";
 				css += 'cursor:pointer;';
@@ -3875,7 +3877,7 @@
 			{
 				// KNOWN: Message passing does not seem to work in IE11
 				onclick = "onclick=\"(function(img){if(img.wnd!=null&&!img.wnd.closed){img.wnd.focus();}else{var r=function(evt){if(evt.data=='ready'&&evt.source==img.wnd){img.wnd.postMessage(decodeURIComponent(" +
-					"img.getAttribute('src')),'*');window.removeEventListener('message',r);}};window.addEventListener('message',r);img.wnd=window.open('https://www.draw.io/?client=1&lightbox=1&chrome=0" +
+					"img.getAttribute('src')),'*');window.removeEventListener('message',r);}};window.addEventListener('message',r);img.wnd=window.open('https://www.draw.io/?client=1&lightbox=1" +
 					((edit) ? "&edit=_blank" : "") + ((layers) ? '&layers=1' : '') + "');}})(this);\"";
 				css += 'cursor:pointer;';
 			}
@@ -3910,7 +3912,7 @@
 					"svg.getAttribute('content')),'*');window.removeEventListener('message',r);}};" +
 					"window.addEventListener('message',r);" +
 					// Opens lightbox window
-					"svg.wnd=window.open('https://www.draw.io/?client=1&lightbox=1&chrome=0" +
+					"svg.wnd=window.open('https://www.draw.io/?client=1&lightbox=1" +
 					((edit) ? "&edit=_blank" : "") + ((layers) ? '&layers=1' : '') + "');}}})(this);";
 				svgRoot.setAttribute('onclick', js);
 				css += 'cursor:pointer;';
@@ -4080,7 +4082,7 @@
 
 		if (xml != null)
 		{
-			svgRoot.setAttribute('content', encodeURIComponent(xml));
+			svgRoot.setAttribute('content', xml);
 		}
 		
 		if (url != null)
@@ -6120,7 +6122,7 @@
 				    {
 				    	var uri = (mxUtils.indexOf(evt.dataTransfer.types, 'text/uri-list') >= 0) ?
 				    		evt.dataTransfer.getData('text/uri-list') : null;
-				    	var data = this.extractGraphModelFromEvent(evt);
+				    	var data = this.extractGraphModelFromEvent(evt, this.pages != null);
 				    	
 				    	if (data != null)
 				    	{
@@ -6817,14 +6819,25 @@
 		}
 	};
 	
+	/**
+	 * Shows the layers dialog if the graph has more than one layer.
+	 */
+	EditorUi.prototype.setGraphEnabled = function(enabled)
+	{
+		this.diagramContainer.style.visibility = (enabled) ? '' : 'hidden';
+		this.formatContainer.style.visibility = (enabled) ? '' : 'hidden';
+		this.sidebarFooterContainer.style.display = (enabled) ? '' : 'none';
+		this.sidebarContainer.style.display = (enabled) ? '' : 'none';
+		this.hsplit.style.display = (enabled) ? '' : 'none';
+		this.editor.graph.setEnabled(enabled);
+	};
+	
 	/**
 	 * Shows the layers dialog if the graph has more than one layer.
 	 */
 	EditorUi.prototype.initializeEmbedMode = function()
 	{
-		this.diagramContainer.style.visibility = 'hidden';
-		this.formatContainer.style.visibility = 'hidden';
-		this.editor.graph.setEnabled(false);
+		this.setGraphEnabled(false);
 		var parent = window.opener || window.parent;
 
 		if (parent != window)
@@ -6835,9 +6848,7 @@
 				{
 					this.spinner.stop();
 					this.addEmbedButtons();
-					this.diagramContainer.style.visibility = '';
-					this.formatContainer.style.visibility = '';
-					this.editor.graph.setEnabled(true);
+					this.setGraphEnabled(true);
 					
 					if (xml != null && xml.length > 0)
 					{
@@ -8123,7 +8134,7 @@
 			serviceCount++
 		}
 		
-		if (allowBrowser && isLocalStorage && urlParams['browser'] == '1')
+		if (allowBrowser && isLocalStorage && (urlParams['browser'] == '1' || mxClient.IS_IOS))
 		{
 			serviceCount++
 		}
@@ -8146,18 +8157,31 @@
 		
 		// Action states that only need update for new files
 		var file = this.getCurrentFile();
-		var active = file != null || urlParams['embed'] == '1';
+		var active = file != null || (urlParams['embed'] == '1' &&
+			this.editor.graph.isEnabled());
 		this.menus.get('viewPanels').setEnabled(active);
 		this.menus.get('viewZoom').setEnabled(active);
 		
-		var restricted = urlParams['embed'] != '1' && (file == null || file.isRestricted());
+		var restricted = (urlParams['embed'] != '1' ||
+			!this.editor.graph.isEnabled()) &&
+			(file == null || file.isRestricted());
 		this.actions.get('makeCopy').setEnabled(!restricted);
 		this.actions.get('print').setEnabled(!restricted);
 		this.menus.get('exportAs').setEnabled(!restricted);
 		this.menus.get('embed').setEnabled(!restricted);
 		
+		// Disables libraries and extras menu in embed mode
+		// while waiting for file data
+		var libsEnabled = urlParams['embed'] != '1' ||
+				this.editor.graph.isEnabled();
+		this.menus.get('openLibraryFrom').setEnabled(libsEnabled);
+		this.menus.get('newLibrary').setEnabled(libsEnabled);
+		this.menus.get('extras').setEnabled(libsEnabled);
+		
 		// Disables actions in the toolbar
-		var editable = (urlParams['embed'] == '1') || (file != null && file.isEditable());
+		var editable = (urlParams['embed'] == '1' &&
+			this.editor.graph.isEnabled()) ||
+			(file != null && file.isEditable());
 		this.actions.get('image').setEnabled(active);
 		this.actions.get('zoomIn').setEnabled(active);
 		this.actions.get('zoomOut').setEnabled(active);
@@ -8250,7 +8274,8 @@
 
 		var graph = this.editor.graph;
 		var file = this.getCurrentFile();
-		var active = (file != null && file.isEditable()) || urlParams['embed'] == '1';
+		var active = (file != null && file.isEditable()) || 
+			(urlParams['embed'] == '1'  && this.editor.graph.isEnabled());
 		this.actions.get('pageSetup').setEnabled(active);
 		this.actions.get('autosave').setEnabled(file != null && file.isEditable() && file.isAutosaveOptional());
 		this.actions.get('guides').setEnabled(active);
@@ -8264,8 +8289,8 @@
 		this.actions.get('createRevision').setEnabled(active);
 		this.actions.get('moveToFolder').setEnabled(file != null);
 		this.actions.get('makeCopy').setEnabled(file != null && !file.isRestricted());
-		this.actions.get('editDiagram').setEnabled(urlParams['embed'] == '1' ||
-				(file != null && !file.isRestricted()));
+		this.actions.get('editDiagram').setEnabled((urlParams['embed'] == '1'  &&
+			this.editor.graph.isEnabled()) || (file != null && !file.isRestricted()));
 		this.actions.get('publishLink').setEnabled(file != null && !file.isRestricted());
 		this.menus.get('publish').setEnabled(file != null && !file.isRestricted());
 		

+ 19 - 12
war/js/diagramly/ElectronApp.js

@@ -62,15 +62,6 @@ FeedbackDialog.feedbackUrl = 'https://log.draw.io/email';
 		var editorUi = this;
 		var graph = this.editor.graph;
 
-		global.__emt_isModified = e => {
-			if (this.getCurrentFile())
-				return this.getCurrentFile().isModified()
-			return false
-		}
-		// global.__emt_getCurrentFile = e => {
-		// 	return this.getCurrentFile()
-		// }
-
 		// Adds support for libraries
 		this.actions.addAction('newLibrary...', mxUtils.bind(this, function()
 		{
@@ -176,10 +167,24 @@ FeedbackDialog.feedbackUrl = 'https://log.draw.io/email';
 			{
 				oldNew();
 			}
-			else {
-				const ipc = require('electron').ipcRenderer
-				ipc.sendSync('winman', {action: 'newfile', opt: {width: 1600}})
+			else
+			{
+				const electron = require('electron');
+				const remote = electron.remote;
+				const BrowserWindow = remote.BrowserWindow;
+				mainWindow = new BrowserWindow({width: 1600, height: 1200, "web-security" : false});
 
+				// and load the index.html of the app.
+				mainWindow.loadURL(`file://${__dirname}/index.html?dev=1&test=1&db=0&gapi=0&od=0&analytics=0&picker=0&mode=device&browser=0&p=electron`);
+
+				// Emitted when the window is closed.
+				mainWindow.on('closed', function()
+				{
+				    // Dereference the window object, usually you would store windows
+				    // in an array if your app supports multi windows, this is the time
+				    // when you should delete the corresponding element.
+				    mainWindow = null;
+				});
 			}
 		}), null, null, 'Ctrl+N');
 		
@@ -605,4 +610,6 @@ FeedbackDialog.feedbackUrl = 'https://log.draw.io/email';
         	}));
 		}
 	};
+
+	EditorUi.prototype.addBeforeUnloadListener = function() {};
 })();

+ 140 - 202
war/js/diagramly/GitHubClient.js

@@ -4,17 +4,11 @@
  */
 GitHubClient = function(editorUi)
 {
-	mxEventSource.call(this);
-	
-	/**
-	 * Holds a reference to the UI. Needed for the sharing client.
-	 */
-	this.ui = editorUi;
-	this.token = this.getPersistentToken();
+	DrawioClient.call(this, editorUi, 'ghauth');
 };
 
-// Extends mxEventSource
-mxUtils.extend(GitHubClient, mxEventSource);
+// Extends DrawioClient
+mxUtils.extend(GitHubClient, DrawioClient);
 
 /**
  * Specifies if thumbnails should be enabled. Default is true.
@@ -39,91 +33,9 @@ GitHubClient.prototype.extension = '.xml';
 GitHubClient.prototype.baseUrl = 'https://api.github.com';
 
 /**
- * Token for the current user.
- */
-GitHubClient.prototype.token = null;
-
-/**
- * Authorizes the client, gets the userId and calls <open>.
+ * Maximum file size of the GitHub REST API.
  */
-GitHubClient.prototype.setUser = function(user)
-{
-	this.user = user;
-	this.fireEvent(new mxEventObject('userChanged'));
-};
-
-/**
- * Authorizes the client, gets the userId and calls <open>.
- */
-GitHubClient.prototype.getUser = function()
-{
-	return this.user;
-};
-
-/**
- * 
- */
-GitHubClient.prototype.clearPersistentToken = function()
-{
-	var expiration = new Date();
-	expiration.setYear(expiration.getFullYear() - 1);
-	document.cookie = 'ghauth=; expires=' + expiration.toUTCString();	
-};
-
-/**
- * Authorizes the client, gets the userId and calls <open>.
- */
-GitHubClient.prototype.getPersistentToken = function()
-{
-	var cookies = document.cookie;
-	var name = 'ghauth=';
-	var start = cookies.indexOf(name);
-
-	if (start >= 0)
-	{
-		start += name.length;
-		var end = cookies.indexOf(';', start);
-	    
-		if (end < 0)
-		{
-			end = cookies.length;
-		}
-		else
-		{
-			postCookie = cookies.substring(end);
-	    }
-
-		var value = cookies.substring(start, end);
-		
-		return (value.length > 0) ? value : null;
-	}
-
-	return null;
-};
-
-/**
- * Authorizes the client, gets the userId and calls <open>.
- */
-GitHubClient.prototype.setPersistentToken = function(token)
-{
-	if (token != null)
-	{
-		var expiration = new Date();
-		expiration.setYear(expiration.getFullYear() + 10);
-		var cookie = 'ghauth=' + token +'; path=/; expires=' + expiration.toUTCString();
-
-		if (document.location.protocol.toLowerCase() == 'https')
-		{
-			cookie = cookie + ';secure';
-		}
-
-		document.cookie = cookie;
-	}
-	else
-	{
-		this.clearPersistentToken();
-	}
-};
+GitHubClient.prototype.maxFileSize = 1000000 /*1MB*/;
 
 /**
  * Authorizes the client, gets the userId and calls <open>.
@@ -150,6 +62,10 @@ GitHubClient.prototype.updateUser = function(success, error)
 				{
 					this.authorizeRequest(fn, error);
 				}
+				else if (userReq.getStatus() < 200 || userReq.getStatus() >= 300)
+				{
+					error({message: mxResources.get('accessDenied')});
+				}
 				else
 				{
 					var userInfo = JSON.parse(userReq.getText());
@@ -170,23 +86,14 @@ GitHubClient.prototype.authorizeRequest = function(success, error)
 {
 	this.ui.showAuthDialog(this, true, mxUtils.bind(this, function(remember, authSuccess)
 	{
-		if (authSuccess != null)
-		{
-			authSuccess();
-		}
-		
 		// Initializes oauth flow
-		window.open('https://github.com/login/oauth/authorize?client_id=' + this.clientId + '&scope=' + this.scope);
+		window.open('https://github.com/login/oauth/authorize?client_id=' +
+			this.clientId + '&scope=' + this.scope, 'oauth');
 		
 		window.onGitHubCallback = mxUtils.bind(this, function(code, authWindow)
 		{
 			window.onGitHubCallback = null;
 
-			if (authWindow != null)
-			{
-				authWindow.close();
-			}
-			
 			// Gets token for code via servlet
 			var fn = mxUtils.bind(this, function()
 			{
@@ -204,15 +111,39 @@ GitHubClient.prototype.authorizeRequest = function(success, error)
 					
 					if (acceptResponse)
 					{
-						var res = authReq.getText();
-						this.token = res.substring(res.indexOf('=') + 1, res.indexOf('&'));
-						
-						if (remember)
+						try
 						{
-							this.setPersistentToken(this.token);
+							if (authWindow != null)
+							{
+								authWindow.close();
+							}
+	
+							if (authReq.getStatus() < 200 || authReq.getStatus() >= 300)
+							{
+								error({message: mxResources.get('cannotLogin')});
+							}
+							else
+							{
+								if (authSuccess != null)
+								{
+									authSuccess();
+								}
+								
+								var res = authReq.getText();
+								this.token = res.substring(res.indexOf('=') + 1, res.indexOf('&'));
+								
+								if (remember)
+								{
+									this.setPersistentToken(this.token);
+								}
+								
+								success();
+							}
+						}
+						catch (e)
+						{
+							error(e);
 						}
-						
-						success();
 					}
 				}));
 			});
@@ -225,7 +156,7 @@ GitHubClient.prototype.authorizeRequest = function(success, error)
 /**
  * Authorizes the client, gets the userId and calls <open>.
  */
-GitHubClient.prototype.executeRequest = function(req, success, error, refresh, overwrite)
+GitHubClient.prototype.executeRequest = function(req, success, error)
 {
 	var doExecute = mxUtils.bind(this, function()
 	{
@@ -258,6 +189,26 @@ GitHubClient.prototype.executeRequest = function(req, success, error, refresh, o
 				{
 					this.authorizeRequest(fn, error);
 				}
+				else if (req.getStatus() === 403)
+				{
+					var tooLarge = false;
+					
+					try
+					{
+						var temp = JSON.parse(req.getText());
+						
+						if (temp != null && temp.errors != null && temp.errors.length > 0)
+						{
+							tooLarge = temp.errors[0].code == 'too_large';
+						}
+					}
+					catch (e)
+					{
+						// ignore
+					}
+					
+					error({message: mxResources.get((tooLarge) ? 'drawingTooLarge' : 'forbidden')});
+				}
 				else if (req.getStatus() === 404)
 				{
 					error({message: mxResources.get('fileNotFound')});
@@ -346,7 +297,7 @@ GitHubClient.prototype.getFile = function(path, success, error, asLibrary)
 		{
 			try
 			{
-				success(this.createGitHubFile(org, repo, ref, req, asLibrary));
+				success(this.createGitHubFile(org, repo, ref, JSON.parse(req.getText()), asLibrary));
 			}
 			catch (e)
 			{
@@ -365,9 +316,8 @@ GitHubClient.prototype.getFile = function(path, success, error, asLibrary)
  * @param {number} dx X-coordinate of the translation.
  * @param {number} dy Y-coordinate of the translation.
  */
-GitHubClient.prototype.createGitHubFile = function(org, repo, ref, req, asLibrary)
+GitHubClient.prototype.createGitHubFile = function(org, repo, ref, data, asLibrary)
 {
-	var data = JSON.parse(req.getText());
 	var meta = {'org': org, 'repo': repo, 'ref': ref, 'name': data.name,
 		'path': data.path, 'sha': data.sha, 'html_url': data.html_url,
 		'download_url': data.download_url};
@@ -375,8 +325,7 @@ GitHubClient.prototype.createGitHubFile = function(org, repo, ref, req, asLibrar
 	
 	if (data.encoding === 'base64')
 	{
-		// Workaround for character encoding issues in IE10/11
-		content = (window.atob && !mxClient.IS_IE && !mxClient.IS_IE11) ? atob(content) : Base64.decode(content);
+		content = Base64.decode(content);
 	}
 	
 	return (asLibrary) ? new GitHubLibrary(this.ui, content, meta) : new GitHubFile(this.ui, content, meta);
@@ -430,14 +379,25 @@ GitHubClient.prototype.insertFile = function(filename, data, success, error, asL
 			{
 				if (!base64Encoded)
 				{
-					data = (window.btoa) ? btoa(data) : Base64.encode(data);
+					data = Base64.encode(data);
 				}
 				
 				this.showCommitDialog(filename, true, mxUtils.bind(this, function(message)
 				{
 					this.writeFile(org, repo, ref, path, message, data, sha, mxUtils.bind(this, function(req)
 					{
-						this.getFile(org + '/' + repo + '/' + ref + '/' + path, success, error, asLibrary);
+						try
+						{
+							var msg = JSON.parse(req.getText());
+							success(this.createGitHubFile(org, repo, ref, msg.content, asLibrary));
+						}
+						catch (e)
+						{
+							if (error != null)
+							{
+								error(e);
+							}
+						}
 					}), error);
 				}), mxUtils.bind(this, function()
 				{
@@ -481,26 +441,37 @@ GitHubClient.prototype.showCommitDialog = function(filename, isNew, success, can
  */
 GitHubClient.prototype.writeFile = function(org, repo, ref, path, message, data, sha, success, error)
 {
-	var entity =
+	if (data.length >= this.maxFileSize)
 	{
-		path: path,
-		message: message,
-		content: data
-	};
-	
-	if (sha != null)
-	{
-		entity.sha = sha;
+		if (error != null)
+		{
+			error({message: mxResources.get('drawingTooLarge') + ' (' +
+				this.ui.formatFileSize(data.length) + ' / 1 MB)'});
+		}
 	}
-	
-	var req = new mxXmlRequest(this.baseUrl + '/repos/' + org + '/' + repo +
-		'/contents/' + path + '?ref=' + encodeURIComponent(ref),
-		JSON.stringify(entity), 'PUT');
-	
-	this.executeRequest(req, mxUtils.bind(this, function(req)
+	else
 	{
-		success(req);
-	}), error);
+		var entity =
+		{
+			path: path,
+			message: message,
+			content: data
+		};
+		
+		if (sha != null)
+		{
+			entity.sha = sha;
+		}
+		
+		var req = new mxXmlRequest(this.baseUrl + '/repos/' + org + '/' + repo +
+			'/contents/' + path + '?ref=' + encodeURIComponent(ref),
+			JSON.stringify(entity), 'PUT');
+		
+		this.executeRequest(req, mxUtils.bind(this, function(req)
+		{
+			success(req);
+		}), error);
+	}
 };
 
 /**
@@ -513,7 +484,7 @@ GitHubClient.prototype.checkExists = function(path, askReplace, fn)
 {
 	this.getFile(path, mxUtils.bind(this, function(file)
 	{
-		if (askReplace)
+		if (askReplace && file.meta != null)
 		{
 			var resume = this.ui.spinner.pause();
 			
@@ -557,7 +528,7 @@ GitHubClient.prototype.saveFile = function(file, success, error)
 	
 	this.showCommitDialog(file.meta.name, file.meta.sha == null || file.meta.isNew, mxUtils.bind(this, function(message)
 	{
-		var data = (window.btoa) ? btoa(file.getData()) : Base64.encode(file.getData());
+		var data = Base64.encode(file.getData());
 		
 		var fn = mxUtils.bind(this, function(sha)
 		{
@@ -669,36 +640,35 @@ GitHubClient.prototype.showGitHubDialog = function(showFiles, fn)
 		dlg.okButton.parentNode.removeChild(dlg.okButton);
 	}
 	
+	var createLink = mxUtils.bind(this, function(label, fn)
+	{
+		var link = document.createElement('a');
+		link.setAttribute('href', 'javascript:void(0);');
+		mxUtils.write(link,  label);
+		mxEvent.addListener(link, 'click', fn);
+		
+		return link;
+	});
+	
 	var updatePathInfo = mxUtils.bind(this, function(hideRef)
 	{
 		var pathInfo = document.createElement('div');
 		pathInfo.style.marginBottom = '8px';
 		
-		var link = document.createElement('a');
-		link.setAttribute('href', 'javascript:void(0);');
-		mxUtils.write(link,  org + '/' + repo);
-		pathInfo.appendChild(link);
-		
-		mxEvent.addListener(link, 'click', mxUtils.bind(this, function()
+		pathInfo.appendChild(createLink(org + '/' + repo, mxUtils.bind(this, function()
 		{
 			path = null;
 			selectRepo();
-		}));
+		})));
 		
 		if (!hideRef)
 		{
 			mxUtils.write(pathInfo, ' / ');
-			
-			var link = document.createElement('a');
-			link.setAttribute('href', 'javascript:void(0);');
-			mxUtils.write(link,  ref);
-			pathInfo.appendChild(link);
-			
-			mxEvent.addListener(link, 'click', mxUtils.bind(this, function()
+			pathInfo.appendChild(createLink(ref, mxUtils.bind(this, function()
 			{
 				path = null;
 				selectRef();
-			}));
+			})));
 		}
 		
 		if (path != null && path.length > 0)
@@ -710,17 +680,11 @@ GitHubClient.prototype.showGitHubDialog = function(showFiles, fn)
 				(function(index)
 				{
 					mxUtils.write(pathInfo, ' / ');
-	
-					var link = document.createElement('a');
-					link.setAttribute('href', 'javascript:void(0);');
-					mxUtils.write(link, tokens[index]);
-					pathInfo.appendChild(link);
-					
-					mxEvent.addListener(link, 'click', mxUtils.bind(this, function()
+					pathInfo.appendChild(createLink(tokens[index], mxUtils.bind(this, function()
 					{
 						path = tokens.slice(0, index + 1).join('/');
 						selectFile();
-					}));
+					})));
 				})(i);
 			}
 		}
@@ -741,14 +705,7 @@ GitHubClient.prototype.showGitHubDialog = function(showFiles, fn)
 			updatePathInfo();
 			this.ui.spinner.stop();
 			var files = JSON.parse(req.getText());
-			
-			var link = document.createElement('a');
-			link.setAttribute('href', 'javascript:void(0);');
-			mxUtils.write(link, '../ [Up]');
-			div.appendChild(link);
-			mxUtils.br(div);
-			
-			mxEvent.addListener(link, 'click', mxUtils.bind(this, function()
+			div.appendChild(createLink('../ [Up]', mxUtils.bind(this, function()
 			{
 				if (path == '')
 				{
@@ -761,7 +718,8 @@ GitHubClient.prototype.showGitHubDialog = function(showFiles, fn)
 					path = tokens.slice(0, tokens.length - 1).join('/');
 					selectFile();
 				}
-			}));
+			})));
+			mxUtils.br(div);
 
 			if (files == null || files.length == 0)
 			{
@@ -777,13 +735,7 @@ GitHubClient.prototype.showGitHubDialog = function(showFiles, fn)
 						{
 							if (showFolders == (file.type == 'dir'))
 							{
-								var link = document.createElement('a');
-								link.setAttribute('href', 'javascript:void(0);');
-								mxUtils.write(link, file.name + ((file.type == 'dir') ? '/' : ''));
-								div.appendChild(link);
-								mxUtils.br(div);
-								
-								mxEvent.addListener(link, 'click', mxUtils.bind(this, function()
+								div.appendChild(createLink(file.name + ((file.type == 'dir') ? '/' : ''), mxUtils.bind(this, function()
 								{
 									if (file.type == 'dir')
 									{
@@ -795,7 +747,8 @@ GitHubClient.prototype.showGitHubDialog = function(showFiles, fn)
 										this.ui.hideDialog();
 										fn(org + '/' + repo + '/' + ref + '/' + file.path);
 									}
-								}));
+								})));
+								mxUtils.br(div);
 							}
 						}))(files[i]);
 					}
@@ -829,17 +782,12 @@ GitHubClient.prototype.showGitHubDialog = function(showFiles, fn)
 			updatePathInfo(true);
 			var branches = JSON.parse(req.getText());
 			
-			var link = document.createElement('a');
-			link.setAttribute('href', 'javascript:void(0);');
-			mxUtils.write(link, '../ [Up]');
-			div.appendChild(link);
-			mxUtils.br(div);
-			
-			mxEvent.addListener(link, 'click', mxUtils.bind(this, function()
+			div.appendChild(createLink('../ [Up]', mxUtils.bind(this, function()
 			{
 				path = null;
 				selectRepo();
-			}));
+			})));
+			mxUtils.br(div);
 			
 			if (branches == null || branches.length == 0)
 			{
@@ -851,18 +799,13 @@ GitHubClient.prototype.showGitHubDialog = function(showFiles, fn)
 				{
 					(mxUtils.bind(this, function(branch)
 					{
-						var link = document.createElement('a');
-						link.setAttribute('href', 'javascript:void(0);');
-						mxUtils.write(link, branch.name);
-						div.appendChild(link);
-						mxUtils.br(div);
-						
-						mxEvent.addListener(link, 'click', mxUtils.bind(this, function()
+						div.appendChild(createLink(branch.name, mxUtils.bind(this, function()
 						{
 							ref = branch.name;
 							path = '';
 							selectFile();
-						}));
+						})));
+						mxUtils.br(div);
 					}))(branches[i]);
 				}
 			}
@@ -896,13 +839,7 @@ GitHubClient.prototype.showGitHubDialog = function(showFiles, fn)
 				{
 					(mxUtils.bind(this, function(repository)
 					{
-						var link = document.createElement('a');
-						link.setAttribute('href', 'javascript:void(0);');
-						mxUtils.write(link, repository.full_name);
-						div.appendChild(link);
-						mxUtils.br(div);
-						
-						mxEvent.addListener(link, 'click', mxUtils.bind(this, function()
+						div.appendChild(createLink(repository.full_name, mxUtils.bind(this, function()
 						{
 							org = repository.owner.login;
 							repo = repository.name;
@@ -910,7 +847,8 @@ GitHubClient.prototype.showGitHubDialog = function(showFiles, fn)
 							path = '';
 	
 							selectFile();
-						}));
+						})));
+						mxUtils.br(div);
 					}))(repos[i]);
 				}
 			}

+ 2 - 2
war/js/diagramly/GraphViewer.js

@@ -1144,13 +1144,13 @@ GraphViewer.prototype.showLightbox = function()
 			});
 			
 			mxEvent.addListener(window, 'message', receive);
-			wnd = window.open('https://www.draw.io/?client=1&chrome=0&lightbox=1&close=1&edit=_blank' + p);
+			wnd = window.open('https://www.draw.io/?client=1&lightbox=1&close=1&edit=_blank' + p);
 		}
 		else
 		{
 			// Data is pulled from global variable after tab loads
 			window.drawdata = this.xml;
-			window.open('https://www.draw.io/?client=1&chrome=0&lightbox=1&edit=_blank' + p);
+			window.open('https://www.draw.io/?client=1&lightbox=1&edit=_blank' + p);
 		}
 	}
 	else

+ 6 - 0
war/js/diagramly/Init.js

@@ -240,6 +240,12 @@ if (urlParams['offline'] == '1' || urlParams['local'] == '1')
 	urlParams['math'] = '0';
 }
 
+// Lightbox enabled chromeless mode
+if (urlParams['lightbox'] == '1')
+{
+	urlParams['chrome'] = '0';
+}
+
 // Adds hard-coded logging domain for draw.io domains
 var host = window.location.host;
 var searchString = 'draw.io';

+ 30 - 29
war/js/diagramly/Menus.js

@@ -139,46 +139,43 @@
 				if (file.constructor == DropboxFile)
 				{
 					// Limit is maximum number of entries to return
-					editorUi.dropbox.client.revisions(file.stat.path, {limit: 100}, function(err, stats)
+					var promise = editorUi.dropbox.client.filesListRevisions({path: file.stat.path_lower, limit: 100});
+					promise.then(mxUtils.bind(this, function(response)
 					{
 						editorUi.spinner.stop();
 						
-						if (err == null)
+						try
 						{
 							var revs = [];
 							
-							for (var i = stats.length - 1; i >= 0; i--)
+							for (var i = response.entries.length - 1; i >= 0; i--)
 							{
 								(function(stat)
 								{
-									revs.push({modifiedDate: stat.clientModifiedAt, fileSize: stat.size, getXml: function(success, error)
+									revs.push({modifiedDate: stat.client_modified, fileSize: stat.size, getXml: function(success, error)
 									{
-										editorUi.dropbox.client.readFile('/' + file.stat.path, {versionTag: stat.versionTag}, mxUtils.bind(this, function(err2, data)
-										{
-											if (err2 == null)
-											{
-												success(data);
-											}
-											else
-											{
-												error(err2);
-											}
-										}));
+										editorUi.dropbox.readFile({path: file.stat.path_lower, rev: stat.rev}, success, error);
 									}, getUrl: function()
 									{
-										return editorUi.getUrl(window.location.pathname + '?rev=' + stat.versionTag + '&chrome=0&edit=_blank') + window.location.hash;
+										return editorUi.getUrl(window.location.pathname + '?rev=' + stat.rev + '&chrome=0&edit=_blank') + window.location.hash;
 									}});
-								})(stats[i]);
+								})(response.entries[i]);
 							}
 							
 							var dlg = new RevisionDialog(editorUi, revs);
 							editorUi.showDialog(dlg.container, 640, 480, true, true);
 							dlg.init();
 						}
-						else
+						catch (e)
 						{
-							editorUi.handleError(err);
+							editorUi.handleError(e);
 						}
+					}));
+					// Workaround for IE8/9 support with catch function
+					promise['catch'](function(err)
+					{
+						editorUi.spinner.stop();
+						editorUi.handleError(err);
 					});
 				}
 				// Google Drive File
@@ -433,7 +430,7 @@
 			}
 			else
 			{
-				window.open('https://www.draw.io/?chrome=0&lightbox=1#Uhttps%3A%2F%2Fwww.draw.io%2Fshortcuts.svg');
+				window.open('https://www.draw.io/?lightbox=1#Uhttps%3A%2F%2Fwww.draw.io%2Fshortcuts.svg');
 			}
 		});
 
@@ -614,9 +611,16 @@
 						// Logs search terms for improving search results
 						if (editorUi.enableLogging)
 						{
-							var img = new Image();
-							var logDomain = window.DRAWIO_LOG_URL != null ? window.DRAWIO_LOG_URL : '';
-							img.src = logDomain + '/log?severity=CONFIG&msg=helpsearch:' + encodeURIComponent(input.value) + '&v=' + encodeURIComponent(EditorUi.VERSION);
+				        	try
+				        	{
+								var img = new Image();
+								var logDomain = window.DRAWIO_LOG_URL != null ? window.DRAWIO_LOG_URL : '';
+								img.src = logDomain + '/log?severity=CONFIG&msg=helpsearch:' + encodeURIComponent(input.value) + '&v=' + encodeURIComponent(EditorUi.VERSION);
+				        	}
+				        	catch (e)
+				        	{
+				        		// ignore
+				        	}
 						}
 						
 						// Workaround for blocked submit on iOS/IE11
@@ -1019,7 +1023,7 @@
 						dlg.init();
 					});
 				}
-			});
+			}, true);
 		}));
 		
 		editorUi.actions.put('publishLink', new Action(mxResources.get('link') + '...', function()
@@ -1907,16 +1911,13 @@
 		this.put('openRecent', new Menu(function(menu, parent)
 		{
 			var recent = editorUi.getRecent();
-			var count = 0;
-			
+
 			if (recent != null)
 			{
 				for (var i = 0; i < recent.length; i++)
 				{
 					(function(entry)
-					{	
-						count++;
-						
+					{
 						var modeKey = entry.mode;
 						
 						// Google and oneDrive use different keys

+ 10 - 10
war/js/diagramly/OneDriveClient.js

@@ -115,7 +115,7 @@ OneDriveClient.prototype.execute = function(fn, userEvent)
 	}
 	else
 	{
-		var next = mxUtils.bind(this, function(newToken)
+		var next = mxUtils.bind(this, function(newToken, cb)
 		{
 			if (newToken != null && newToken.length > 0)
 			{
@@ -129,6 +129,11 @@ OneDriveClient.prototype.execute = function(fn, userEvent)
 					{
 						if (req.getStatus() >= 200 && req.getStatus() <= 299)
 						{
+							if (cb != null)
+							{
+								cb();
+							}
+							
 							var data = JSON.parse(req.getText());
 							this.setUser(new DrawioUser(data.owner.user.id, null, data.owner.user.displayName));
 							fn(newToken);
@@ -154,11 +159,11 @@ OneDriveClient.prototype.execute = function(fn, userEvent)
 		
 		if (token != null && token.length > 0)
 		{
-			next(token);
+			next(token, null);
 		}
 		else
 		{
-			var auth = mxUtils.bind(this, function()
+			var auth = mxUtils.bind(this, function(cb)
 			{
 				var url = 'https://login.live.com/oauth20_authorize.srf?client_id=' + this.clientId +
 					'&scope=' + encodeURIComponent(this.scopes) + '&response_type=token' +
@@ -193,7 +198,7 @@ OneDriveClient.prototype.execute = function(fn, userEvent)
 							authWindow.close();
 						}
 	
-						next(newToken);
+						next(newToken, cb);
 					});
 	
 					popup.focus();
@@ -209,12 +214,7 @@ OneDriveClient.prototype.execute = function(fn, userEvent)
 				// Requires a user event to about popups being blocked
 				this.ui.showAuthDialog(this, false, mxUtils.bind(this, function(remember, success)
 				{
-					if (success != null)
-					{
-						success();
-					}
-					
-					auth();
+					auth(success);
 				}));
 			}
 		}

+ 10 - 3
war/js/diagramly/sidebar/Sidebar.js

@@ -1006,9 +1006,16 @@
 		// Logs search terms for improving search results
 		if (this.editorUi.enableLogging && !this.editorUi.isOffline() && page == 0)
 		{
-			var img = new Image();
-			var logDomain = window.DRAWIO_LOG_URL != null ? window.DRAWIO_LOG_URL : '';
-			img.src = logDomain + '/log?severity=CONFIG&msg=shapesearch:' + encodeURIComponent(searchTerms) + '&v=' + encodeURIComponent(EditorUi.VERSION);
+			try
+			{
+				var img = new Image();
+				var logDomain = window.DRAWIO_LOG_URL != null ? window.DRAWIO_LOG_URL : '';
+				img.src = logDomain + '/log?severity=CONFIG&msg=shapesearch:' + encodeURIComponent(searchTerms) + '&v=' + encodeURIComponent(EditorUi.VERSION);
+		 	}
+	    	catch (e)
+	    	{
+	    		// ignore
+	    	}
 		}
 		
 		success = mxUtils.bind(this, function(results, len, more, terms)

Разница между файлами не показана из-за своего большого размера
+ 0 - 4
war/js/dropbox/dropbox.min.js


+ 0 - 0
war/js/dropbox/dropbox.min.map


Разница между файлами не показана из-за своего большого размера
+ 916 - 918
war/js/embed-static.min.js


Разница между файлами не показана из-за своего большого размера
+ 20 - 20
war/js/embed.min.js


Разница между файлами не показана из-за своего большого размера
+ 164 - 176
war/js/extensions.min.js


+ 5 - 0
war/js/mxgraph/EditorUi.js

@@ -3293,6 +3293,11 @@ EditorUi.prototype.executeLayout = function(exec, animate, post)
 			else
 			{
 				graph.getModel().endUpdate();
+				
+				if (post != null)
+				{
+					post();
+				}
 			}
 		}
 	}

+ 30 - 8
war/js/mxgraph/Graph.js

@@ -870,15 +870,15 @@ Graph.prototype.minFitScale = null;
 Graph.prototype.maxFitScale = null;
 
 /**
- * Sets the policy for links. Possible values are self to keep all links within
- * the same window, blank to open all links in a new window and auto (default).
+ * Sets the policy for links. Possible values are "self" to replace any framesets,
+ * "blank" to load the URL in <linkTarget> and "auto" (default).
  */
-Graph.prototype.linkPolicy = urlParams['target'] || 'auto';
+Graph.prototype.linkPolicy = (urlParams['target'] == 'frame') ? 'blank' : (urlParams['target'] || 'auto');
 
 /**
  * Target for links that open in a new window. Default is _blank.
  */
-Graph.prototype.linkTarget = '_blank';
+Graph.prototype.linkTarget = (urlParams['target'] == 'frame') ? '_self' : '_blank';
 
 /**
  * Scrollbars are enabled on non-touch devices (not including Firefox because touch events
@@ -980,9 +980,20 @@ Graph.prototype.init = function(container)
 					
 					if (href != null)
 					{
-						window.open(state.view.graph.getAbsoluteUrl(href),
-							(state.view.graph.isBlankLink(href)) ?
-							state.view.graph.linkTarget : '_top');
+						var target = state.view.graph.isBlankLink(href) ?
+							state.view.graph.linkTarget : '_top';
+						href = state.view.graph.getAbsoluteUrl(href);
+
+						// Workaround for blocking in same iframe
+						if (target == '_self' && window != window.top)
+						{
+							window.location.href = href;
+						}
+						else
+						{
+							window.open(href, target);
+						}
+						
 						mxEvent.consume(evt);
 					}
 	
@@ -4551,7 +4562,18 @@ if (typeof mxVertexHandler != 'undefined')
 			    				beforeClick(me.getEvent());
 				    		}
 				    		
-				    		window.open(this.currentLink, (blank) ? graph.linkTarget : '_top');
+				    		var target = (blank) ? graph.linkTarget : '_top';
+				    		
+				    		// Workaround for blocking in same iframe
+							if (target == '_self' && window != window.top)
+							{
+								window.location.href = this.currentLink;
+							}
+							else
+							{
+								window.open(this.currentLink, target);
+							}
+				    		
 				    		me.consume();
 				    	}
 				    	else if (onClick != null && !me.isConsumed() &&

+ 0 - 1
war/js/mxgraph/Shapes.js

@@ -2677,7 +2677,6 @@
 			'rectangle': createArcHandleFunction(),
 			'triangle': createArcHandleFunction(),
 			'rhombus': createArcHandleFunction(),
-			'hexagon': createArcHandleFunction(),
 			'umlLifeline': function(state)
 			{
 				return [createHandle(state, ['size'], function(bounds)

Разница между файлами не показана из-за своего большого размера
+ 896 - 898
war/js/reader.min.js


Разница между файлами не показана из-за своего большого размера
+ 1415 - 1441
war/js/shapes.min.js


Разница между файлами не показана из-за своего большого размера
+ 1451 - 1459
war/js/viewer.min.js


+ 1 - 1
war/onedrive.html

@@ -76,7 +76,7 @@
 			// Continues execution of main program flow
 			try
 			{
-				if (window.opener != null)
+				if (window.opener != null && window.opener.onAuthenticated != null)
 				{
 					window.opener.onAuthenticated(token, window);
 				}

+ 0 - 1
war/shapes/mxArchiMate.js

@@ -378,7 +378,6 @@ mxArchiMateApplication.prototype.cst = {
 		APPLICATION : 'mxgraph.archimate.application',
 		TYPE : 'appType',
 		COMPONENT : 'comp',
-		COLLABORATION : 'collab',
 		INTERFACE : 'interface',
 		INTERFACE2 : 'interface2',
 		FUNCTION : 'function',

+ 0 - 2
war/shapes/mxArchiMate3.js

@@ -38,8 +38,6 @@ mxArchiMate3Application.prototype.cst = {
 		NODE : 'node',
 		NETWORK : 'netw',
 		COMM_PATH : 'commPath',
-		SYS_SW : 'sysSw',
-		ARTIFACT : 'artifact',
 		ACTOR : 'actor',
 		ASSESSMENT : 'assess',
 		GOAL : 'goal',

+ 0 - 2
war/shapes/pid2/mxPidValves.js

@@ -46,13 +46,11 @@ mxShapePidValve.prototype.cst = {
 		DIGITAL : 'digital',
 		WEIGHT : 'weight',
 		KEY : 'key',
-		ANGLE_BLOWDOWN : 'angleBlowdown', 
 		ELECTRO_HYDRAULIC : 'elHyd',
 		//types
 		VALVE_TYPE : 'valveType',
 		BUTTERFLY : 'butterfly',
 		CHECK : 'check',
-		PLUG : 'plug',
 		GATE : 'gate',
 		GLOBE : 'globe',
 		NEEDLE : 'needle',