瀏覽代碼

20.8.14 release

David Benson 2 年之前
父節點
當前提交
23d6fa1021

+ 9 - 0
ChangeLog

@@ -1,3 +1,12 @@
+31-JAN-2023: 20.8.14
+
+- Updates in-place editor after scroll and UI change
+- Fixes CSS specificity and global styles [DID-7294]
+- Limit convert labels to SVG option to online draw.io only
+- [conf cloud] Adds config option to disable the automatic generation of the preview images from the page view ["disableEmbedAutoImgGen": true] [DID-7251]
+- Ignore events on selection border and parent shape
+- Fixes library save, improves error handling [3323]
+
 26-JAN-2023: 20.8.13
 
 - Fixes NPE when changing colors from toolbar [3315]

+ 1 - 1
VERSION

@@ -1 +1 @@
-20.8.13
+20.8.14

+ 9 - 1
src/main/webapp/index.html

@@ -138,7 +138,7 @@
 		/**
 		 * Synchronously adds scripts to the page.
 		 */
-		function mxscript(src, onLoad, id, dataAppKey, noWrite)
+		function mxscript(src, onLoad, id, dataAppKey, noWrite, onError)
 		{
 			var defer = onLoad == null && !noWrite;
 			
@@ -173,6 +173,14 @@
 						}
 				  	};
 				}
+
+				if (onError != null)
+				{
+					s.onerror = function(e)
+					{
+						onError('Failed to load ' + src, e);
+					};
+				}
 			  	
 			  	var t = document.getElementsByTagName('script')[0];
 			  	

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


+ 12 - 3
src/main/webapp/js/diagramly/App.js

@@ -358,18 +358,27 @@ App.publicPlugin = [
  * Loads all given scripts and invokes onload after
  * all scripts have finished loading.
  */
-App.loadScripts = function(scripts, onload)
+App.loadScripts = function(scripts, onload, onerror)
 {
 	var n = scripts.length;
+	var failed = false;
 	
 	for (var i = 0; i < scripts.length; i++)
 	{
 		mxscript(scripts[i], function()
 		{
-			if (--n == 0 && onload != null)
+			if (--n == 0 && !failed && onload != null)
 			{
 				onload();
 			}
+		}, null, null, null, function(e)
+		{
+			failed = true;
+
+			if (onerror != null)
+			{
+				onerror(e);
+			}
 		});
 	}
 };
@@ -693,7 +702,7 @@ App.main = function(callback, createUi)
 				{
 					var content = mxUtils.getTextContent(scripts[0]);
 					
-					if (CryptoJS.MD5(content).toString() != '1f536e2400baaa30261b8c3976d6fe06')
+					if (CryptoJS.MD5(content).toString() != '94ebd7472449efab95e00746ea00db60')
 					{
 						console.log('Change bootstrap script MD5 in the previous line:', CryptoJS.MD5(content).toString());
 						alert('[Dev] Bootstrap script change requires update of CSP');

+ 2 - 0
src/main/webapp/js/diagramly/Devel.js

@@ -17,6 +17,8 @@ if (!mxIsElectron && location.protocol !== 'http:')
 			//----------------------------------------------------------//
 			//------------- Bootstrap script in index.html -------------//
 			//----------------------------------------------------------//
+			// Version 20.8.14
+			'\'sha256-vrEVJkYyBW9H4tt1lYZtK5fDowIeRwUgYZfFTT36YpE=\' ' +
 			// Version 20.8.12
 			'\'sha256-6g514VrT/cZFZltSaKxIVNFF46+MFaTSDTPB8WfYK+c=\' ' +
 			// Version 16.4.4

+ 18 - 15
src/main/webapp/js/diagramly/Dialogs.js

@@ -3250,7 +3250,7 @@ var NewDialog = function(editorUi, compact, showName, callback, createOnly, canc
 		// Adds diagram type options
 		var typeSelect = document.createElement('select');
 		typeSelect.className = 'geBtn';
-		typeSelect.style.maxWidth = '150px';
+		typeSelect.style.maxWidth = '160px';
 		typeSelect.style.marginLeft = '10px';
 
 		var option = document.createElement('option');
@@ -3341,6 +3341,8 @@ var NewDialog = function(editorUi, compact, showName, callback, createOnly, canc
 			);
 		});
 
+		button.className = 'geBtn gePrimaryBtn';
+
 		mxEvent.addListener(description, 'keydown', function(e)
 		{
 			if (e.keyCode == 13)
@@ -3349,12 +3351,13 @@ var NewDialog = function(editorUi, compact, showName, callback, createOnly, canc
 			}
 		});
 
-		button.className = 'geBtn gePrimaryBtn';
-		button.style.marginTop = '4px';
-		button.style.marginBottom = '4px';
-
-		content.appendChild(button);
-		content.appendChild(typeSelect);
+		var buttons = document.createElement('div');
+		buttons.style.height = '40px';
+		buttons.style.marginTop = '4px';
+		buttons.style.marginBottom = '4px';
+		buttons.style.whiteSpace = 'nowrap';
+		buttons.style.overflowX = 'auto';
+		buttons.style.overflowY = 'hidden';
 
 		var keyButton = mxUtils.button('Get Key', function()
 		{
@@ -3362,23 +3365,22 @@ var NewDialog = function(editorUi, compact, showName, callback, createOnly, canc
 		});
 
 		keyButton.className = 'geBtn';
-		keyButton.style.marginTop = '4px';
-		keyButton.style.marginBottom = '4px';
 		keyButton.style.marginLeft = '10px';
 
-		content.appendChild(keyButton);
-
 		var helpButton = mxUtils.button(mxResources.get('help'), function()
 		{
 			editorUi.openLink('https://github.com/jgraph/drawio/discussions/3313');
 		});
 
 		helpButton.className = 'geBtn';
-		helpButton.style.marginTop = '4px';
-		helpButton.style.marginBottom = '4px';
 		helpButton.style.marginLeft = '10px';
 
-		content.appendChild(helpButton);
+		buttons.appendChild(button);
+		buttons.appendChild(typeSelect);
+		buttons.appendChild(keyButton);
+		buttons.appendChild(helpButton);
+
+		content.appendChild(buttons);
 		content.appendChild(preview);
 
 		return content;
@@ -8449,7 +8451,8 @@ var MoreShapesDialog = function(editorUi, expanded, entries)
 			}
 
 			// Redirects scratchpad and search entries
-			if ((Editor.currentTheme == 'sketch' || Editor.currentTheme == 'simple') &&
+			if ((Editor.currentTheme == 'sketch' ||
+				Editor.currentTheme == 'min') &&
 				Editor.isSettingsEnabled())
 			{
 				var idx = mxUtils.indexOf(libs, '.scratchpad');

+ 395 - 276
src/main/webapp/js/diagramly/EditorUi.js

@@ -6353,8 +6353,7 @@
 		lblToSvgOption.setAttribute('value', 'lblToSvg');
 		mxUtils.write(lblToSvgOption, mxResources.get('lblToSvg'));
 		
-
-		if (!this.isOffline() && !EditorUi.isElectronApp)
+		if (this.getServiceName() == 'draw.io' && !this.isOffline() && !EditorUi.isElectronApp)
 		{
 			txtSettingsSelect.appendChild(lblToSvgOption);
 		}
@@ -7463,212 +7462,255 @@
 	/**
 	 * Imports the given Visio file
 	 */
-	EditorUi.prototype.importVisio = function(file, done, onerror, filename, customParam)
+	EditorUi.prototype.importVisio = function(file, done, error, filename, customParam)
 	{
-		//A reduced version of this code is used in conf/jira plugins, review that code whenever this function is changed
-		filename = (filename != null) ? filename : file.name; 
-
-		onerror = (onerror != null) ? onerror : mxUtils.bind(this, function(e)
+		var onerror = mxUtils.bind(this, function(e)
 		{
-			this.handleError(e);
+			this.loadingExtensions = false;
+
+			if (error != null)
+			{
+				error(e);
+			}
+			else
+			{
+				this.handleError(e);
+			}
 		});
-		
-		var delayed = mxUtils.bind(this, function()
+
+		//A reduced version of this code is used in conf/jira plugins, review that code whenever this function is changed
+		this.createTimeout(null, mxUtils.bind(this, function(timeout)
 		{
-			this.loadingExtensions = false;
-			
-			if (this.doImportVisio)
+			filename = (filename != null) ? filename : file.name;
+
+			var handleError = mxUtils.bind(this, function(e)
 			{
-				var remote = this.isRemoteVisioFormat(filename);
-				
-				try
+				if (timeout.clear())
+				{
+					onerror(e);
+				}
+			});
+
+			var delayed = mxUtils.bind(this, function()
+			{
+				this.loadingExtensions = false;
+
+				if (this.doImportVisio)
 				{
-					var ext = 'UNKNOWN-VISIO';
-					var dot = filename.lastIndexOf('.');
+					var remote = this.isRemoteVisioFormat(filename);
 					
-					if (dot >= 0 && dot < filename.length)
-					{
-						ext = filename.substring(dot + 1).toUpperCase();
-					}
-					else
+					try
 					{
-						var slash = filename.lastIndexOf('/');
+						var ext = 'UNKNOWN-VISIO';
+						var dot = filename.lastIndexOf('.');
 						
-						if (slash >= 0 && slash < filename.length)
+						if (dot >= 0 && dot < filename.length)
 						{
-							filename = filename.substring(slash + 1);
+							ext = filename.substring(dot + 1).toUpperCase();
 						}
-					}
-					
-					EditorUi.logEvent({category: ext + '-MS-IMPORT-FILE',
-						action: 'filename_' + filename,
-						label: (remote) ? 'remote' : 'local'});
-				}
-				catch (e)
-				{
-					// ignore
-				}
-				
-				if (remote) 
-				{
-					if (VSD_CONVERT_URL != null && !this.isOffline())
-					{
-						var formData = new FormData();
-						formData.append('file1', file, filename);
-	
-						var xhr = new XMLHttpRequest();
-						xhr.open('POST', VSD_CONVERT_URL + (/(\.vss|\.vsx)$/.test(filename)? '?stencil=1' : ''));
-						xhr.responseType = 'blob';
-						this.addRemoteServiceSecurityCheck(xhr);
-						
-						if (customParam != null)
+						else
 						{
-							xhr.setRequestHeader('x-convert-custom', customParam);
+							var slash = filename.lastIndexOf('/');
+							
+							if (slash >= 0 && slash < filename.length)
+							{
+								filename = filename.substring(slash + 1);
+							}
 						}
 						
-						xhr.onreadystatechange = mxUtils.bind(this, function()
+						EditorUi.logEvent({category: ext + '-MS-IMPORT-FILE',
+							action: 'filename_' + filename,
+							label: (remote) ? 'remote' : 'local'});
+					}
+					catch (e)
+					{
+						// ignore
+					}
+					
+					if (remote) 
+					{
+						if (VSD_CONVERT_URL != null && !this.isOffline())
 						{
-							if (xhr.readyState == 4)
-							{	
-								if (xhr.status >= 200 && xhr.status <= 299)
+							var formData = new FormData();
+							formData.append('file1', file, filename);
+		
+							var xhr = new XMLHttpRequest();
+							xhr.open('POST', VSD_CONVERT_URL + (/(\.vss|\.vsx)$/.test(filename)? '?stencil=1' : ''));
+							xhr.responseType = 'blob';
+							this.addRemoteServiceSecurityCheck(xhr);
+							
+							if (customParam != null)
+							{
+								xhr.setRequestHeader('x-convert-custom', customParam);
+							}
+							
+							xhr.onreadystatechange = mxUtils.bind(this, function()
+							{
+								if (xhr.readyState == 4 && timeout.clear())
 								{
-									try
+									if (xhr.status >= 200 && xhr.status <= 299)
 									{
-										var resp = xhr.response;
-
-										if (resp.type == 'text/xml')
+										try
 										{
-											var reader = new FileReader();
-											
-											reader.onload = mxUtils.bind(this, function(e)
+											var resp = xhr.response;
+
+											if (resp.type == 'text/xml')
 											{
-												try
-												{
-													done(e.target.result);
-												}
-												catch (e)
+												var reader = new FileReader();
+												
+												reader.onload = mxUtils.bind(this, function(e)
 												{
-													onerror({message: mxResources.get('errorLoadingFile')});
-												}
-											});
-					
-											reader.readAsText(resp);
+													try
+													{
+														done(e.target.result);
+													}
+													catch (e)
+													{
+														handleError({message: mxResources.get('errorLoadingFile')});
+													}
+												});
+						
+												reader.readAsText(resp);
+											}
+											else
+											{
+												this.doImportVisio(resp, done, handleError, filename);
+											}
 										}
-										else
+										catch (e)
 										{
-											this.doImportVisio(resp, done, onerror, filename);
+											handleError(e);
 										}
 									}
-									catch (e)
-									{
-										onerror(e);
-									}
-								}
-								else
-								{
-									try
+									else
 									{
-										if (xhr.responseType == '' || xhr.responseType == 'text')
-										{
-											onerror({message: xhr.responseText});
-										}
-										else
+										try
 										{
-											var reader = new FileReader();
-											reader.onload = function() 
+											if (xhr.responseType == '' || xhr.responseType == 'text')
+											{
+												handleError({message: xhr.responseText});
+											}
+											else
 											{
-												onerror({message: JSON.parse(reader.result).Message});
+												var reader = new FileReader();
+
+												reader.onload = function() 
+												{
+													handleError({message: JSON.parse(reader.result).Message});
+												}
+
+												reader.readAsText(xhr.response);
 											}
-											reader.readAsText(xhr.response);
 										}
-									}
-									catch(e)
-									{
-										onerror({});
+										catch(e)
+										{
+											handleError({});
+										}
 									}
 								}
-							}
-						});
-						
-						xhr.send(formData);
+							});
+							
+							xhr.send(formData);
+						}
+						else
+						{
+							handleError({message: this.getServiceName() != 'draw.io'? mxResources.get('vsdNoConfig') :
+								mxResources.get('serviceUnavailableOrBlocked')});
+						}
 					}
-					else
+					else if (timeout.clear())
 					{
-						onerror({message: this.getServiceName() != 'draw.io'? mxResources.get('vsdNoConfig') : mxResources.get('serviceUnavailableOrBlocked')});
+						try
+						{
+							this.doImportVisio(file, done, handleError, filename);
+						}
+						catch (e)
+						{
+							handleError(e);
+						}
 					}
 				}
 				else
 				{
-					try
-					{
-						this.doImportVisio(file, done, onerror, filename);
-					}
-					catch (e)
-					{
-						onerror(e);
-					}
+					handleError({message: mxResources.get('serviceUnavailableOrBlocked')});
 				}
+			});
+			
+			if (!this.doImportVisio && !this.loadingExtensions && !this.isOffline(true))
+			{
+				this.loadingExtensions = true;
+				mxscript('js/extensions.min.js', delayed, null, null, null, handleError);
 			}
 			else
 			{
-				this.spinner.stop();
-				this.handleError({message: mxResources.get('serviceUnavailableOrBlocked')});
+				delayed();
 			}
-		});
-		
-		if (!this.doImportVisio && !this.loadingExtensions && !this.isOffline(true))
-		{
-			this.loadingExtensions = true;
-			mxscript('js/extensions.min.js', delayed);
-		}
-		else
-		{
-			delayed();
-		}
+		}), onerror);
 	};
 
 	/**
 	 * Imports the given GraphML (yEd) file
 	 */
-	EditorUi.prototype.importGraphML = function(xmlData, done, onerror)
+	EditorUi.prototype.importGraphML = function(xmlData, done, error)
 	{
-		onerror = (onerror != null) ? onerror : mxUtils.bind(this, function(e)
+		var onerror = mxUtils.bind(this, function(e)
 		{
-			this.handleError(e);
+			this.loadingExtensions = false;
+
+			if (error != null)
+			{
+				error(e);
+			}
+			else
+			{
+				this.handleError(e);
+			}
 		});
-		
-		var delayed = mxUtils.bind(this, function()
+
+		this.createTimeout(null, mxUtils.bind(this, function(timeout)
 		{
-			this.loadingExtensions = false;
-			
-			if (this.doImportGraphML)
+			var handleError = mxUtils.bind(this, function(e)
 			{
-				
-				try
+				if (timeout.clear())
 				{
-					this.doImportGraphML(xmlData, done, onerror);
+					onerror(e);
 				}
-				catch (e)
+			});
+
+			var delayed = mxUtils.bind(this, function()
+			{
+				this.loadingExtensions = false;
+
+				if (timeout.clear())
 				{
-					onerror(e);
+					if (this.doImportGraphML)
+					{
+						try
+						{
+							this.doImportGraphML(xmlData, done, onerror);
+						}
+						catch (e)
+						{
+							handleError(e);
+						}
+					}
+					else
+					{
+						handleError({message: mxResources.get('serviceUnavailableOrBlocked')});
+					}
 				}
+			});
+			
+			if (!this.doImportGraphML && !this.loadingExtensions && !this.isOffline(true))
+			{
+				this.loadingExtensions = true;
+				mxscript('js/extensions.min.js', delayed, null, null, null, handleError);
 			}
 			else
 			{
-				this.spinner.stop();
-				this.handleError({message: mxResources.get('serviceUnavailableOrBlocked')});
+				delayed();
 			}
-		});
-		
-		if (!this.doImportGraphML && !this.loadingExtensions && !this.isOffline(true))
-		{
-			this.loadingExtensions = true;
-			mxscript('js/extensions.min.js', delayed);
-		}
-		else
-		{
-			delayed();
-		}
+		}), onerror);
 	};	
 	
 	/**
@@ -7676,41 +7718,65 @@
 	 */
 	EditorUi.prototype.exportVisio = function(currentPage)
 	{
-		var delayed = mxUtils.bind(this, function()
+		if (this.spinner.spin(document.body, mxResources.get('loading')))
 		{
-			this.loadingExtensions = false;
-			
-			if (typeof VsdxExport  !== 'undefined')
+			var onerror = mxUtils.bind(this, function(e)
 			{
-				try
+				this.loadingExtensions = false;
+				this.handleError(e);
+			});
+
+			this.createTimeout(null, mxUtils.bind(this, function(timeout)
+			{
+				var handleError = mxUtils.bind(this, function(e)
 				{
-					var expSuccess = new VsdxExport(this).exportCurrentDiagrams(currentPage);
-					
-					if (!expSuccess)
+					if (timeout.clear())
+					{
+						onerror(e);
+					}
+				});
+
+				var delayed = mxUtils.bind(this, function()
+				{
+					this.loadingExtensions = false;
+
+					if (timeout.clear())
 					{
-						this.handleError({message: mxResources.get('unknownError')});
+						if (typeof VsdxExport  !== 'undefined')
+						{
+							try
+							{
+								this.spinner.stop();
+								var expSuccess = new VsdxExport(this).exportCurrentDiagrams(currentPage);
+								
+								if (!expSuccess)
+								{
+									handleError({message: mxResources.get('unknownError')});
+								}
+							}
+							catch (e)
+							{
+								handleError(e);
+							}
+						}
+						else
+						{
+							handleError({message: mxResources.get('serviceUnavailableOrBlocked')});
+						}
 					}
+				});
+				
+				if (typeof VsdxExport === 'undefined' && !this.loadingExtensions && !this.isOffline(true))
+				{
+					this.loadingExtensions = true;
+					mxscript('js/extensions.min.js', delayed, null, null, null, handleError);
 				}
-				catch (e)
+				else
 				{
-					this.handleError(e);
+					// Async needed for showing spinner for longer exports
+					window.setTimeout(delayed, 0);
 				}
-			}
-			else
-			{
-				this.spinner.stop();
-				this.handleError({message: mxResources.get('serviceUnavailableOrBlocked')});
-			}
-		});
-		
-		if (typeof VsdxExport === 'undefined' && !this.loadingExtensions && !this.isOffline(true))
-		{
-			this.loadingExtensions = true;
-			mxscript('js/extensions.min.js', delayed);
-		}
-		else
-		{
-			delayed();
+			}), onerror);
 		}
 	};
 	
@@ -7719,101 +7785,131 @@
 	 */
 	EditorUi.prototype.convertLucidChart = function(data, success, error)
 	{
-		var delayed = mxUtils.bind(this, function()
+		var onerror = mxUtils.bind(this, function(e)
 		{
 			this.loadingExtensions = false;
-			
-			// Checks for signature method
-			if (typeof window.LucidImporter !== 'undefined')
+
+			if (error != null)
 			{
-				try
+				error(e);
+			}
+			else
+			{
+				this.handleError(e);
+			}
+		});
+
+		this.createTimeout(null, mxUtils.bind(this, function(timeout)
+		{
+			var handleError = mxUtils.bind(this, function(e)
+			{
+				if (timeout.clear())
 				{
-					var obj = JSON.parse(data);
-					success(LucidImporter.importState(obj));
+					onerror(e);
+				}
+			});
 
-					try
+			var delayed = mxUtils.bind(this, function()
+			{
+				this.loadingExtensions = false;
+				
+				if (timeout.clear())
+				{
+					// Checks for signature method
+					if (typeof window.LucidImporter !== 'undefined')
 					{
-						EditorUi.logEvent({category: 'LUCIDCHART-IMPORT-FILE',
-							action: 'size_' + data.length});
+						try
+						{
+							var obj = JSON.parse(data);
+							success(LucidImporter.importState(obj));
 
-							if (window.console != null && urlParams['test'] == '1')
+							try
 							{
-								var args = [new Date().toISOString(), 'convertLucidChart', obj];
+								EditorUi.logEvent({category: 'LUCIDCHART-IMPORT-FILE',
+									action: 'size_' + data.length});
 
-								if (obj.state != null)
-								{
-									args.push(JSON.parse(obj.state));
-								}
-		
-								if (obj.svgThumbs != null)
-								{
-									for (var i = 0; i < obj.svgThumbs.length; i++)
+									if (window.console != null && urlParams['test'] == '1')
 									{
-										args.push(Editor.createSvgDataUri(obj.svgThumbs[i]));
-									}
-								}
+										var args = [new Date().toISOString(), 'convertLucidChart', obj];
 
-								if (obj.thumb != null)
-								{
-									args.push(obj.thumb);
-								}
+										if (obj.state != null)
+										{
+											args.push(JSON.parse(obj.state));
+										}
+				
+										if (obj.svgThumbs != null)
+										{
+											for (var i = 0; i < obj.svgThumbs.length; i++)
+											{
+												args.push(Editor.createSvgDataUri(obj.svgThumbs[i]));
+											}
+										}
+
+										if (obj.thumb != null)
+										{
+											args.push(obj.thumb);
+										}
 
-								console.log.apply(console, args);
+										console.log.apply(console, args);
+									}
 							}
+							catch (e)
+							{
+								// ignore
+							}
+						}
+						catch (e)
+						{
+							if (window.console != null)
+							{
+								console.error(e);
+							}
+							
+							handleError(e);
+						}
 					}
-					catch (e)
-					{
-						// ignore
-					}
-				}
-				catch (e)
-				{
-					if (window.console != null)
+					else
 					{
-						console.error(e);
+						handleError({message: mxResources.get('serviceUnavailableOrBlocked')});
 					}
-					
-					error(e);
 				}
-			}
-			else
-			{
-				error({message: mxResources.get('serviceUnavailableOrBlocked')});
-			}
-		});
-		
-		if (typeof window.LucidImporter === 'undefined' &&
-			!this.loadingExtensions && !this.isOffline(true))
-		{
-			this.loadingExtensions = true;
+			});
 			
-			if (urlParams['dev'] == '1')
+			if (typeof window.LucidImporter === 'undefined' &&
+				!this.loadingExtensions && !this.isOffline(true))
 			{
-				//Lucid org chart requires orgChart layout, in production, it is part of the extemsions.min.js
-				mxscript('js/diagramly/Extensions.js', function()
+				this.loadingExtensions = true;
+				
+				if (urlParams['dev'] == '1')
 				{
-					mxscript('js/orgchart/bridge.min.js', function()
+					//Lucid org chart requires orgChart layout, in production, it is part of the extemsions.min.js
+					mxscript('js/diagramly/Extensions.js', function()
 					{
-						mxscript('js/orgchart/bridge.collections.min.js', function()
+						mxscript('js/orgchart/bridge.min.js', function()
 						{
-							mxscript('js/orgchart/OrgChart.Layout.min.js', function()
+							mxscript('js/orgchart/bridge.collections.min.js', function()
 							{
-								mxscript('js/orgchart/mxOrgChartLayout.js', delayed);											
-							});		
-						});	
-					});
-				});
+								mxscript('js/orgchart/OrgChart.Layout.min.js', function()
+								{
+									mxscript('js/orgchart/mxOrgChartLayout.js',
+										delayed, null, null, null, handleError);											
+								}, null, null, null, handleError);		
+							}, null, null, null, handleError);	
+						}, null, null, null, handleError);
+					}, null, null, null, handleError);
+				}
+				else
+				{
+					mxscript('js/extensions.min.js', delayed,
+						null, null, null, handleError);
+				}
 			}
 			else
 			{
-				mxscript('js/extensions.min.js', delayed);
+				// Async needed for selection
+				window.setTimeout(delayed, 0);
 			}
-		}
-		else
-		{
-			// Async needed for selection
-			window.setTimeout(delayed, 0);
-		}
+		}), onerror);
 	};
 
 	/**
@@ -8054,11 +8150,13 @@
 			
 			if (urlParams['dev'] == '1')
 			{
-				mxscript('js/mermaid/mermaid.min.js', delayed);
+				mxscript('js/mermaid/mermaid.min.js',
+					delayed, null, null, null, error);
 			}
 			else
 			{
-				mxscript('js/extensions.min.js', delayed);
+				mxscript('js/extensions.min.js',
+					delayed, null, null, null, error);
 			}
 		}
 		else
@@ -8751,7 +8849,8 @@
 		if (typeof JSZip === 'undefined' && !this.loadingExtensions && !this.isOffline(true))
 		{
 			this.loadingExtensions = true;
-			mxscript('js/extensions.min.js', delayed);
+			mxscript('js/extensions.min.js', delayed,
+				null, null, null, onerror);
 		}
 		else
 		{
@@ -11314,6 +11413,7 @@
 
 			window.setTimeout(mxUtils.bind(this, function()
 			{
+				this.editor.graph.stopEditing(false);
 				this.container.style.opacity = '0';
 
 				window.setTimeout(mxUtils.bind(this, function()
@@ -12122,7 +12222,7 @@
 							mxUtils.bind(this, function(cells)
 						{
 							return graph.getCenterInsertPoint(graph.getBoundingBoxFromGeometry(cells, true));
-						}), value == 'simple');
+						}), value == 'simple', false);
 					}
 
 					mxEvent.consume(evt);
@@ -12207,7 +12307,7 @@
 								var textElt = this.sidebar.createVertexTemplate('text;strokeColor=none;fillColor=none;html=1;' +
 									'align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;', 60, 30, 'Text',
 									mxResources.get('text') + ' (A)', true, false, null, value != 'simple', null,
-									tw + 10, th + 10, value == 'simple' ? Editor.thinTextImage : null);
+									tw + 10, th + 10, value == 'simple' ? Editor.thinTextImage : null, true);
 
 								if (value == 'simple')
 								{
@@ -16328,43 +16428,62 @@
 	 */
 	EditorUi.prototype.loadOrgChartLayouts = function(fn)
 	{
-		var onload = mxUtils.bind(this, function()
+		this.createTimeout(null, mxUtils.bind(this, function(timeout)
 		{
-			Graph.layoutNames.push('mxOrgChartLayout');
-			this.loadingOrgChart = false;
-			this.spinner.stop();
-			fn();
-		});
+			var onload = mxUtils.bind(this, function()
+			{
+				this.loadingOrgChart = false;
 
-		if (typeof mxOrgChartLayout === 'undefined' && !this.loadingOrgChart && !this.isOffline(true))
-		{
-			if (this.spinner.spin(document.body, mxResources.get('loading')))
+				if (timeout.clear())
+				{
+					Graph.layoutNames.push('mxOrgChartLayout');
+					this.spinner.stop();
+					fn();
+				}
+			});
+
+			var onerror = mxUtils.bind(this, function(e)
 			{
-				this.loadingOrgChart = true;
-				
-				if (urlParams['dev'] == '1')
+				this.loadingOrgChart = false;
+
+				if (timeout.clear())
 				{
-					mxscript('js/orgchart/bridge.min.js', function()
+					this.handleError(e);
+				}
+			});
+
+			if (typeof mxOrgChartLayout === 'undefined' && !this.loadingOrgChart && !this.isOffline(true))
+			{
+				if (this.spinner.spin(document.body, mxResources.get('loading')))
+				{
+					this.loadingOrgChart = true;
+
+					if (urlParams['dev'] == '1')
 					{
-						mxscript('js/orgchart/bridge.collections.min.js', function()
+						mxscript('js/orgchart/bridge.min.js', function()
 						{
-							mxscript('js/orgchart/OrgChart.Layout.min.js', function()
+							mxscript('js/orgchart/bridge.collections.min.js', function()
 							{
-								mxscript('js/orgchart/mxOrgChartLayout.js', onload);											
-							});		
-						});	
-					});
-				}
-				else
-				{
-					mxscript(DRAWIO_BASE_URL + '/js/orgchart.min.js', onload);
+								mxscript('js/orgchart/OrgChart.Layout.min.js', function()
+								{
+									mxscript('js/orgchart/mxOrgChartLayout.js',
+										onload, null, null, null, onerror);											
+								}, null, null, null, onerror);		
+							}, null, null, null, onerror);	
+						}, null, null, null, onerror);
+					}
+					else
+					{
+						mxscript(DRAWIO_BASE_URL + '/js/orgchart.min.js',
+							onload, null, null, null, onerror);
+					}
 				}
 			}
-		}
-		else
-		{
-			onload();
-		}
+			else
+			{
+				onload();
+			}
+		}), onerror);
 	};
 	
 	/**

+ 93 - 73
src/main/webapp/js/diagramly/ElectronApp.js

@@ -1049,6 +1049,21 @@ mxStencilRegistry.allowEval = false;
         	this.spinner.stop();
         }
 	};
+	
+	EditorUi.prototype.normalizeFilename = function(title, defaultExtension)
+	{
+		var tokens = title.split('.');
+		var ext = (tokens.length > 1) ? tokens[tokens.length - 1] : '';
+		defaultExtension = (defaultExtension != null) ? defaultExtension : 'drawio';
+
+		if (tokens.length == 1 || mxUtils.indexOf(['xml',
+			'html', 'drawio', 'png', 'svg'], ext) < 0)
+		{
+			tokens.push(defaultExtension);
+		}
+
+		return tokens.join('.');
+	};
 
 	//In order not to repeat the logic for opening a file, we collect files information here and use them in openLocalFile
 	var origOpenFiles = EditorUi.prototype.openFiles;
@@ -1398,23 +1413,7 @@ mxStencilRegistry.allowEval = false;
 	{
 		this.editable = editable;
 	};
-
-
-	LocalFile.prototype.normalizeFilename = function(title)
-	{
-		var tokens = title.split('.');
-		var ext = (tokens.length > 1) ? tokens[tokens.length - 1] : '';
-
-		if (tokens.length == 1 || mxUtils.indexOf(['xml',
-			'html', 'drawio', 'png', 'svg'], ext) < 0)
-		{
-			tokens.push('drawio');
-		}
-
-		return tokens.join('.');
-	};
 	
-
 	LocalFile.prototype.saveFile = async function(revision, success, error, unloading, overwrite)
 	{
 		//Safeguard in case saveFile is called from online code in the future
@@ -1511,47 +1510,55 @@ mxStencilRegistry.allowEval = false;
 				}
 			});
 			
-			if (this.fileObject == null)
+			try
 			{
-				var lastDir = localStorage.getItem('.lastSaveDir');
-				var name = this.normalizeFilename(this.getTitle());
-				var ext = null;
-				
-				if (name != null)
+				if (this.fileObject == null)
 				{
-					var idx = name.lastIndexOf('.');
+					var lastDir = localStorage.getItem('.lastSaveDir');
+					var name = this.ui.normalizeFilename(this.getTitle(),
+						this.constructor == LocalLibrary ? 'xml' : null);
+					var ext = null;
 					
-					if (idx > 0)
+					if (name != null)
 					{
-						ext = name.substring(idx + 1);
+						var idx = name.lastIndexOf('.');
+						
+						if (idx > 0)
+						{
+							ext = name.substring(idx + 1);
+						}
 					}
-				}
-				
-				var path = await requestSync({
-					action: 'showSaveDialog',
-					defaultPath: (lastDir || (await requestSync('getDocumentsFolder'))) + '/' + name,
-					filters: this.ui.createFileSystemFilters(ext)
-				});
+					
+					var path = await requestSync({
+						action: 'showSaveDialog',
+						defaultPath: (lastDir || (await requestSync('getDocumentsFolder'))) + '/' + name,
+						filters: this.ui.createFileSystemFilters(ext)
+					});
 
-		        if (path != null)
-		        {
-		        	localStorage.setItem('.lastSaveDir', await requestSync({action: 'dirname', path: path}));
-					this.fileObject = new Object();
-					this.fileObject.path = path;
-					this.fileObject.name = path.replace(/^.*[\\\/]/, '');
-					this.fileObject.type = 'utf-8';
-					this.title = this.fileObject.name;
-					this.addToRecent();
+					if (path != null)
+					{
+						localStorage.setItem('.lastSaveDir', await requestSync({action: 'dirname', path: path}));
+						this.fileObject = new Object();
+						this.fileObject.path = path;
+						this.fileObject.name = path.replace(/^.*[\\\/]/, '');
+						this.fileObject.type = 'utf-8';
+						this.title = this.fileObject.name;
+						this.addToRecent();
+						fn();
+					}
+					else
+					{
+						this.ui.spinner.stop();
+					}
+				}
+				else
+				{
 					fn();
 				}
-		        else
-		        {
-	            	this.ui.spinner.stop();
-		        }
 			}
-			else
+			catch (e)
 			{
-				fn();
+				error(e);
 			}
 		}
 	};
@@ -1572,37 +1579,45 @@ mxStencilRegistry.allowEval = false;
 
 	LocalFile.prototype.saveAs = async function(title, success, error)
 	{
-		var lastDir = localStorage.getItem('.lastSaveDir');
-		var name = this.normalizeFilename(this.getTitle());
-		var ext = null;
-		
-		if (name != null)
+		try
 		{
-			var idx = name.lastIndexOf('.');
+			var lastDir = localStorage.getItem('.lastSaveDir');
+			var name = this.ui.normalizeFilename(this.getTitle(),
+				this.constructor == LocalLibrary ? 'xml' : null);
+			var ext = null;
 			
-			if (idx > 0)
+			if (name != null)
 			{
-				ext = name.substring(idx + 1);
+				var idx = name.lastIndexOf('.');
+				
+				if (idx > 0)
+				{
+					ext = name.substring(idx + 1);
+				}
 			}
-		}
-		
-		var path = await requestSync({
-			action: 'showSaveDialog',
-			defaultPath: (lastDir || (await requestSync('getDocumentsFolder'))) + '/' + name,
-			filters: this.ui.createFileSystemFilters(ext)
-		});
+			
+			var path = await requestSync({
+				action: 'showSaveDialog',
+				defaultPath: (lastDir || (await requestSync('getDocumentsFolder'))) + '/' + name,
+				filters: this.ui.createFileSystemFilters(ext)
+			});
 
-        if (path != null)
-        {
-        	localStorage.setItem('.lastSaveDir', await requestSync({action: 'dirname', path: path}));
-			this.fileObject = new Object();
-			this.fileObject.path = path;
-			this.fileObject.name = path.replace(/^.*[\\\/]/, '');
-			this.fileObject.type = 'utf-8';
-			this.title = this.fileObject.name;
-			this.addToRecent();
-			this.setEditable(true); //In case original file is read only
-			this.save(false, success, error, null, true);
+			if (path != null)
+			{
+				localStorage.setItem('.lastSaveDir', await requestSync({action: 'dirname', path: path}));
+				this.fileObject = new Object();
+				this.fileObject.path = path;
+				this.fileObject.name = path.replace(/^.*[\\\/]/, '');
+				this.fileObject.type = 'utf-8';
+				this.title = this.fileObject.name;
+				this.addToRecent();
+				this.setEditable(true); //In case original file is read only
+				this.save(false, success, error, null, true);
+			}
+		}
+		catch (e)
+		{
+			error(e);
 		}
 	};
 	
@@ -1649,6 +1664,11 @@ mxStencilRegistry.allowEval = false;
 		}
 	};
 
+	LocalLibrary.prototype.addToRecent = function()
+	{
+		// do nothing
+	};
+	
 	/**
 	 * Loads the given file handle as a local file.
 	 */

+ 15 - 3
src/main/webapp/js/diagramly/Menus.js

@@ -4000,7 +4000,7 @@
 			}
 		}));
 		
-		// Extends toolbar dropdown to add comments
+		// Extends toolbar dropdown
 		var viewPanelsMenu = this.get('viewPanels');
 		
 		viewPanelsMenu.funct = function(menu, parent)
@@ -4035,9 +4035,21 @@
 					editorUi.menus.addMenuItems(menu, ['pageTabs'], parent);
 				}
 
-				editorUi.menus.addMenuItems(menu, ['ruler', '-', 'findReplace',
-					'layers', 'tags', 'outline', '-'], parent);
+				editorUi.menus.addMenuItems(menu, ['ruler', '-', 'search'], parent);
 
+				if (isLocalStorage || mxClient.IS_CHROMEAPP)
+				{
+					var item = editorUi.menus.addMenuItem(menu, 'scratchpad', parent);
+					
+					if (!editorUi.isOffline() || mxClient.IS_CHROMEAPP || EditorUi.isElectronApp)
+					{
+						editorUi.menus.addLinkToItem(item, 'https://www.diagrams.net/doc/faq/scratchpad');
+					}
+				}
+				
+				editorUi.menus.addMenuItems(menu, ['-', 'findReplace',
+					'layers', 'tags', 'outline', '-'], parent);
+				
 				if (editorUi.commentsSupported())
 				{
 					editorUi.menus.addMenuItems(menu, ['comments'], parent);

+ 0 - 1
src/main/webapp/js/diagramly/sidebar/Sidebar.js

@@ -529,7 +529,6 @@
 			{title: mxResources.get('flowchart'), id: 'flowchart', image: IMAGE_PATH + '/sidebar-flowchart.png'}];
 		
 		if (Editor.currentTheme == 'sketch' ||
-			Editor.currentTheme == 'simple' ||
 			Editor.currentTheme == 'min')
 		{
 			stdEntries = [{title: mxResources.get('searchShapes'), id: 'search'},

+ 10 - 4
src/main/webapp/js/grapheditor/EditorUi.js

@@ -1863,7 +1863,8 @@ EditorUi.prototype.centerShapePicker = function(div, rect, x, y, dir)
 /**
  * Creates a temporary graph instance for rendering off-screen content.
  */
-EditorUi.prototype.showShapePicker = function(x, y, source, callback, direction, hovering, getInsertLocationFn, showEdges)
+EditorUi.prototype.showShapePicker = function(x, y, source, callback, direction, hovering,
+	getInsertLocationFn, showEdges, startEditing)
 {
 	showEdges = showEdges || source == null;
 
@@ -1871,7 +1872,7 @@ EditorUi.prototype.showShapePicker = function(x, y, source, callback, direction,
 	{	
 		this.hideShapePicker();
 	}), this.getCellsForShapePicker(source, hovering, showEdges), hovering,
-		getInsertLocationFn, showEdges);
+		getInsertLocationFn, showEdges, startEditing);
 	
 	if (div != null)
 	{
@@ -1897,8 +1898,9 @@ EditorUi.prototype.showShapePicker = function(x, y, source, callback, direction,
  * Creates a temporary graph instance for rendering off-screen content.
  */
 EditorUi.prototype.createShapePicker = function(x, y, source, callback, direction,
-	afterClick, cells, hovering, getInsertLocationFn, showEdges)
+	afterClick, cells, hovering, getInsertLocationFn, showEdges, startEditing)
 {
+	startEditing = (startEditing != null) ? startEditing : true;
 	var graph = this.editor.graph;
 	var div = null;
 
@@ -2038,7 +2040,11 @@ EditorUi.prototype.createShapePicker = function(x, y, source, callback, directio
 							
 							graph.setSelectionCell(clone);
 							graph.scrollCellToVisible(clone);
-							graph.startEditingAtCell(clone);
+							
+							if (startEditing)
+							{
+								graph.startEditing(clone);
+							}
 							
 							if (ui.hoverIcons != null)
 							{

+ 22 - 17
src/main/webapp/js/grapheditor/Sidebar.js

@@ -2194,7 +2194,7 @@ Sidebar.prototype.createSection = function(title)
  * Creates and returns a new palette item for the given image.
  */
 Sidebar.prototype.createItem = function(cells, title, showLabel, showTitle, width, height,
-	allowCellsInserted, showTooltip, clickFn, thumbWidth, thumbHeight, icon)
+	allowCellsInserted, showTooltip, clickFn, thumbWidth, thumbHeight, icon, startEditing)
 {
 	showTooltip = (showTooltip != null) ? showTooltip : true;
 	thumbWidth = (thumbWidth != null) ? thumbWidth : this.thumbWidth;
@@ -2240,8 +2240,8 @@ Sidebar.prototype.createItem = function(cells, title, showLabel, showTitle, widt
 	if (cells.length > 1 || cells[0].vertex)
 	{
 		var ds = this.createDragSource(elt, this.createDropHandler(cells, true, allowCellsInserted,
-			bounds), this.createDragPreview(width, height), cells, bounds);
-		this.addClickHandler(elt, ds, cells, clickFn);
+			bounds, startEditing), this.createDragPreview(width, height), cells, bounds, startEditing);
+		this.addClickHandler(elt, ds, cells, clickFn, startEditing);
 	
 		// Uses guides for vertices only if enabled in graph
 		ds.isGuidesEnabled = mxUtils.bind(this, function()
@@ -2252,7 +2252,7 @@ Sidebar.prototype.createItem = function(cells, title, showLabel, showTitle, widt
 	else if (cells[0] != null && cells[0].edge)
 	{
 		var ds = this.createDragSource(elt, this.createDropHandler(cells, false, allowCellsInserted,
-			bounds), this.createDragPreview(width, height), cells, bounds);
+			bounds, startEditing), this.createDragPreview(width, height), cells, bounds, startEditing);
 		this.addClickHandler(elt, ds, cells, clickFn);
 	}
 	
@@ -2274,7 +2274,7 @@ Sidebar.prototype.createItem = function(cells, title, showLabel, showTitle, widt
 /**
  * Creates a drop handler for inserting the given cells.
  */
-Sidebar.prototype.createDropHandler = function(cells, allowSplit, allowCellsInserted, bounds)
+Sidebar.prototype.createDropHandler = function(cells, allowSplit, allowCellsInserted, bounds, startEditing)
 {
 	allowCellsInserted = (allowCellsInserted != null) ? allowCellsInserted : true;
 	
@@ -2383,8 +2383,9 @@ Sidebar.prototype.createDropHandler = function(cells, allowSplit, allowCellsInse
 						graph.setSelectionCells(select);
 					}
 
-					if (graph.editAfterInsert && evt != null && mxEvent.isMouseEvent(evt) &&
-						select != null && select.length == 1)
+					if (startEditing || (graph.editAfterInsert && evt != null &&
+						mxEvent.isMouseEvent(evt) && select != null &&
+						select.length == 1))
 					{
 						window.setTimeout(function()
 						{
@@ -2812,7 +2813,7 @@ Sidebar.prototype.disablePointerEvents = function(node)
 /**
  * Creates a drag source for the given element.
  */
-Sidebar.prototype.createDragSource = function(elt, dropHandler, preview, cells, bounds)
+Sidebar.prototype.createDragSource = function(elt, dropHandler, preview, cells, bounds, startEditing)
 {
 	// Checks if the cells contain any vertices
 	var ui = this.editorUi;
@@ -3696,19 +3697,20 @@ Sidebar.prototype.createVertexTemplateEntry = function(style, width, height, val
  * Creates a drop handler for inserting the given cells.
  */
 Sidebar.prototype.createVertexTemplate = function(style, width, height, value, title, showLabel, showTitle,
-	allowCellsInserted, showTooltip, clickFn, thumbWidth, thumbHeight, icon)
+	allowCellsInserted, showTooltip, clickFn, thumbWidth, thumbHeight, icon, startEditing)
 {
 	var cells = [new mxCell((value != null) ? value : '', new mxGeometry(0, 0, width, height), style)];
 	cells[0].vertex = true;
 
 	return this.createVertexTemplateFromCells(cells, width, height, title, showLabel, showTitle,
-		allowCellsInserted, showTooltip, clickFn, thumbWidth, thumbHeight, icon);
+		allowCellsInserted, showTooltip, clickFn, thumbWidth, thumbHeight, icon, startEditing);
 };
 
 /**
  * Creates a drop handler for inserting the given cells.
  */
-Sidebar.prototype.createVertexTemplateFromData = function(data, width, height, title, showLabel, showTitle, allowCellsInserted, showTooltip)
+Sidebar.prototype.createVertexTemplateFromData = function(data, width, height, title, showLabel,
+	showTitle, allowCellsInserted, showTooltip)
 {
 	var doc = mxUtils.parseXml(Graph.decompress(data));
 	var codec = new mxCodec(doc);
@@ -3718,25 +3720,27 @@ Sidebar.prototype.createVertexTemplateFromData = function(data, width, height, t
 	
 	var cells = this.graph.cloneCells(model.root.getChildAt(0).children);
 
-	return this.createVertexTemplateFromCells(cells, width, height, title, showLabel, showTitle, allowCellsInserted, showTooltip);
+	return this.createVertexTemplateFromCells(cells, width, height, title, showLabel, showTitle,
+		allowCellsInserted, showTooltip);
 };
 
 /**
  * Creates a drop handler for inserting the given cells.
  */
-Sidebar.prototype.createVertexTemplateFromCells = function(cells, width, height, title, showLabel, showTitle, allowCellsInserted,
-	showTooltip, clickFn, thumbWidth, thumbHeight, icon)
+Sidebar.prototype.createVertexTemplateFromCells = function(cells, width, height, title, showLabel,
+	showTitle, allowCellsInserted, showTooltip, clickFn, thumbWidth, thumbHeight, icon, startEditing)
 {
 	// Use this line to convert calls to this function with lots of boilerplate code for creating cells
 	//console.trace('xml', Graph.compress(mxUtils.getXml(this.graph.encodeCells(cells))), cells);
 	return this.createItem(cells, title, showLabel, showTitle, width, height, allowCellsInserted,
-		showTooltip, clickFn, thumbWidth, thumbHeight, icon);
+		showTooltip, clickFn, thumbWidth, thumbHeight, icon, startEditing);
 };
 
 /**
  * 
  */
-Sidebar.prototype.createEdgeTemplateEntry = function(style, width, height, value, title, showLabel, tags, allowCellsInserted, showTooltip)
+Sidebar.prototype.createEdgeTemplateEntry = function(style, width, height, value, title, showLabel,
+	tags, allowCellsInserted, showTooltip)
 {
 	tags = (tags != null && tags.length > 0) ? tags : title.toLowerCase();
 	
@@ -3749,7 +3753,8 @@ Sidebar.prototype.createEdgeTemplateEntry = function(style, width, height, value
 /**
  * Creates a drop handler for inserting the given cells.
  */
-Sidebar.prototype.createEdgeTemplate = function(style, width, height, value, title, showLabel, allowCellsInserted, showTooltip)
+Sidebar.prototype.createEdgeTemplate = function(style, width, height, value, title, showLabel,
+	allowCellsInserted, showTooltip)
 {
 	var cell = new mxCell((value != null) ? value : '', new mxGeometry(0, 0, width, height), style);
 	cell.geometry.setTerminalPoint(new mxPoint(0, height), true);

File diff suppressed because it is too large
+ 1926 - 1923
src/main/webapp/js/integrate.min.js


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


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


File diff suppressed because it is too large
+ 15 - 14
src/main/webapp/mxgraph/mxClient.js


+ 67 - 67
src/main/webapp/resources/dia_zh-tw.txt

@@ -3,11 +3,11 @@
 about=關於
 aboutDrawio=關於draw.io
 accessDenied=拒絕存取
-accounts=Accounts
+accounts=帳戶
 action=動作
 actualSize=實際尺寸
 add=新增
-addAccount=新增帳
+addAccount=新增帳
 addedFile=已新增{1}
 addImages=新增圖片
 addImageUrl=新增圖片URL
@@ -38,7 +38,7 @@ allChangesSavedInDrive=所有修改已儲存至雲端硬碟
 allowPopups=允許彈出式視窗以阻止此對話框。
 allowRelativeUrl=允許相對網址
 alreadyConnected=節點已連接
-appearance=Appearance
+appearance=外觀
 apply=套用
 archiMate21=ArchiMate 2.1
 arrange=調整
@@ -93,7 +93,7 @@ cannotOpenFile=無法開啟檔案
 change=變更
 changeOrientation=變更方向
 changeUser=變更使用者
-changeStorage=Change storage
+changeStorage=變更儲存格式
 changesNotSaved=變更尚未儲存
 classDiagram=Class Diagram
 userJoined={1}已加入
@@ -107,7 +107,7 @@ compressed=已壓縮
 commitMessage=提交訊息
 configLinkWarn=This link configures draw.io. Only click OK if you trust whoever gave you it!
 configLinkConfirm=Click OK to configure and restart draw.io.
-container=Container
+container=容器
 csv=CSV
 dark=暗色
 diagramXmlDesc=XML 檔案
@@ -130,7 +130,7 @@ clearDefaultStyle=清除預設樣式
 clearWaypoints=清除航點
 clipart=剪貼畫
 close=關閉
-closingFile=Closing file
+closingFile=關閉檔案中
 realtimeCollaboration=即時協作
 collaborate=Collaborate
 collaborator=協作者
@@ -143,7 +143,7 @@ comic=手繪
 comment=評論
 commentsNotes=評論/備註
 compress=Compress
-configuration=Configuration
+configuration=組態
 connect=連線
 connecting=連線中
 connectWithDrive=連結至Google雲端硬碟
@@ -154,13 +154,13 @@ constrainProportions=限制寬高比
 containsValidationErrors=含有驗證錯誤
 copiedToClipboard=已拷貝至剪貼簿
 copy=拷貝
-copyConnect=連接時拷貝
+copyConnect=連接時複製
 copyCreated=A copy of the file was created.
 copyData=複製資料
 copyOf={1}副本
 copyOfDrawing=圖紙副本
-copySize=Copy Size
-copyStyle=拷貝樣式
+copySize=複製大小
+copyStyle=複製樣式
 create=新增
 createNewDiagram=新增圖表
 createRevision=新增修訂版本
@@ -201,7 +201,7 @@ discardChanges=取消修改
 disconnected=連線中斷
 distribute=等距分佈
 done=完成
-doNotShowAgain=Do not show again
+doNotShowAgain=不再顯示
 dotted=點線的
 doubleClickOrientation=雙擊改變方向
 doubleClickTooltip=雙撃插入文字
@@ -240,7 +240,7 @@ editStyle=編輯樣式
 editText=編輯文字
 editTooltip=編輯提示
 glass=玻璃
-googleImages=Google圖片
+googleImages=Google 圖片
 imageSearch=圖片搜尋
 eip=EIP
 embed=嵌入
@@ -255,7 +255,7 @@ enterName=輸入名稱
 enterPropertyName=輸入屬性名
 enterValue=輸入值
 entityRelation=實體關係
-entityRelationshipDiagram=Entity Relationship Diagram
+entityRelationshipDiagram=E-R 圖
 error=錯誤
 errorDeletingFile=刪除檔案時發生錯誤
 errorLoadingFile=載入檔案時發生錯誤
@@ -280,7 +280,7 @@ exporting=匯出中
 exportAs=匯出為
 exportOptionsDisabled=已停用匯出
 exportOptionsDisabledDetails=擁有者已禁止留言者和瀏覽者下載,列印或複製此檔案。
-externalChanges=External Changes
+externalChanges=外部變更
 extras=其他
 facebook=Facebook
 failedToSaveTryReconnect=存檔失敗,正嘗試重新連線
@@ -296,7 +296,7 @@ overwrite=覆蓋
 synchronize=同步
 filename=檔案名稱
 fileExists=檔案已存在
-fileMovedToTrash=File was moved to trash
+fileMovedToTrash=檔案已移到資源回收桶
 fileNearlyFullSeeFaq=檔案容量即將達到上限,請參閱常見問題集
 fileNotFound=找不到檔案
 repositoryNotFound=未找到儲存庫
@@ -386,7 +386,7 @@ height=高
 help=說明
 helpTranslate=協助我們翻譯此應用程式
 hide=隱藏
-hideIt=隱藏{1}
+hideIt=隱藏 {1}
 hidden=已隱藏
 home=最上層的組
 horizontal=水平
@@ -460,7 +460,7 @@ years=年
 restartForChangeRequired=修改將在頁面重新載入後生效。
 laneColor=泳道底色
 lastModified=最近修改
-layout=布局
+layout=配置
 left=左
 leftAlign=向左對齊
 leftToRight=向右對齊
@@ -639,8 +639,8 @@ replaceIt={1}已經存在。是否要取代它?
 replaceExistingDrawing=取代現有圖紙
 required=必填
 requirementDiagram=Requirement Diagram
-reset=重
-resetView=重置視圖
+reset=重
+resetView=重設檢視
 resize=調整大小
 resizeLargeImages=您是否要縮小大圖尺寸以使程式執行地更快?
 retina=Retina
@@ -694,19 +694,19 @@ serviceUnavailableOrBlocked=服務無法使用或已被阻擋
 sessionExpired=工作階段已過期。請重新載入瀏覽器畫面。
 sessionTimeoutOnSave=您的工作階段已逾時,且與 Google Drive 斷線。按下確定以登入並儲存。
 setAsDefaultStyle=設為預設樣式
-settings=Settings
+settings=設定
 shadow=陰影
 shape=圖形
 shapes=圖形
 share=共用
-shareCursor=Share Mouse Cursor
+shareCursor=共用滑鼠游標
 shareLink=分享編輯連結
 sharingAvailable=Google Drive 及 OneDrive 檔案可以共用。
 saveItToGoogleDriveToCollaborate=You'll need to save "{1}" to Google Drive before you can collaborate.
-saveToGoogleDrive=Save to Google Drive
+saveToGoogleDrive=儲存到 Google Drive
 sharp=銳角
 show=顯示
-showRemoteCursors=Show Remote Mouse Cursors
+showRemoteCursors=顯示遠端滑鼠游標
 showStartScreen=顯示開始畫面
 sidebarTooltip=點擊展開。將圖形拖曳至圖表中。Shift+單擊以更改選擇。Alt+單擊以插入和連接。
 signs=標誌
@@ -774,7 +774,7 @@ uml=UML
 underline=底線
 undo=還原
 ungroup=取消組合
-unmerge=Unmerge
+unmerge=取消合併
 unsavedChanges=未儲存的修改
 unsavedChangesClickHereToSave=修改未儲存。點此以儲存。
 untitled=未命名
@@ -795,7 +795,7 @@ vertical=垂直
 verticalFlow=垂直流線
 verticalTree=垂直樹狀
 view=檢視
-viewerSettings=Viewer Settings
+viewerSettings=檢視器設定
 viewUrl=用於檢視的連結:{1}
 voiceAssistant=語音助理(測試版)
 warning=警告
@@ -879,14 +879,14 @@ resolve=Resolve
 reopen=重新開啟
 showResolved=Show Resolved
 reply=回覆
-objectNotFound=Object not found
+objectNotFound=找不到物件
 reOpened=Re-opened
 markedAsResolved=標示為已解決
 noCommentsFound=找不到留言
 comments=留言
 timeAgo={1} 前
 confluenceCloud=Confluence Cloud
-libraries=Libraries
+libraries=圖庫
 confAnchor=Confluence Page Anchor
 confTimeout=連線逾時
 confSrvTakeTooLong={1} 伺服器太久無回應
@@ -907,10 +907,10 @@ createdByDraw=使用 draw.io 製作
 filenameShort=檔名太短
 invalidChars=無效字元
 alreadyExst={1} 已存在
-draftReadErr=Draft Read Error
+draftReadErr=草稿讀取錯誤
 diagCantLoad=無法載入圖表
-draftWriteErr=Draft Write Error
-draftCantCreate=Draft could not be created
+draftWriteErr=草稿寫入錯誤
+draftCantCreate=無法建立草稿
 confDuplName=偵測到重複的圖表名稱。請取其它名稱。
 confSessionExpired=工作階段似乎過期了。重新登入以繼續工作。
 login=登入
@@ -927,22 +927,22 @@ gliffyImportInst1=Click the "Start Import" button to import all Gliffy diagrams
 gliffyImportInst2=Please note that the import procedure will take some time and the browser window must remain open until the import is completed.
 startImport=開始匯入
 drawConfig=draw.io 組態
-customLib=Custom Libraries
-customTemp=Custom Templates
-pageIdsExp=Page IDs Export
+customLib=自訂圖庫
+customTemp=自訂樣板
+pageIdsExp=匯出頁面 ID
 drawReindex=draw.io re-indexing (beta)
 working=Working
-drawConfigNotFoundInst=draw.io Configuration Space (DRAWIOCONFIG) does not exist. This space is needed to store draw.io configuration files and custom libraries/templates.
-createConfSp=Create Config Space
+drawConfigNotFoundInst=draw.io 組態空間 (DRAWIOCONFIG) 不存在。儲存 draw.io 組態檔和自訂圖庫/樣板時需要該空間。
+createConfSp=建立組態空間
 unexpErrRefresh=Unexpected error, please refresh the page and try again.
 configJSONInst=Write draw.io JSON configuration in the editor below then click save. If you need help, please refer to
 thisPage=this page
 curCustLib=Current Custom Libraries
-libName=Library Name
-action=Action
-drawConfID=draw.io Config ID
+libName=圖庫名稱
+action=動作
+drawConfID=draw.io 組態 ID
 addLibInst=Click the "Add Library" button to upload a new library.
-addLib=Add Library
+addLib=新增圖庫
 customTempInst1=Custom templates are draw.io diagrams saved in children pages of
 customTempInst2=For more details, please refer to
 tempsPage=Templates page
@@ -994,7 +994,7 @@ delFailed=刪除失敗!
 showID=顯示 ID
 confAIncorrectLibFileType=Incorrect file type. Libraries should be XML files.
 uploading=上傳中
-confALibExist=This library already exists
+confALibExist=圖庫已存在
 confAUploadSucc=上傳成功
 confAUploadFailErr=上傳失敗(意外錯誤)
 hiResPreview=高解析度預覽
@@ -1038,7 +1038,7 @@ confAFixingMacro=Fixing macro of diagram "{1}"
 confAErrReadingExpFile=讀取匯出的檔案時發生錯誤
 confAPrcsDiagInPageDone=Processing draw.io diagrams in page "{1}" finished
 confAFixingMacroSkipped=Fixing macro of diagram "{1}" failed. Cannot find its new page ID. Maybe it points to a page that is not imported.
-pageIdsExpTrg=Export target
+pageIdsExpTrg=匯出目標
 confALucidDiagImgImported={2} diagram "{1}" image extracted successfully
 confASavingLucidDiagImgFailed=Extracting {2} diagram "{1}" image failed
 confGetInfoFailed=Fetching file info from {1} failed.
@@ -1093,9 +1093,9 @@ csvImport=匯入 CSV
 chooseFile=選擇檔案…
 choose=選擇
 gdriveFname=Google Drive 檔案名稱
-widthOfViewer=Width of the viewer (px)
-heightOfViewer=Height of the viewer (px)
-autoSetViewerSize=Automatically set the size of the viewer
+widthOfViewer=檢視器寬度 (px)
+heightOfViewer=檢視器高度 (px)
+autoSetViewerSize=自動設定檢視器的大小
 thumbnail=縮圖
 prevInDraw=在 draw.io 中預覽
 onedriveFname=Onedrive 檔案名稱
@@ -1117,8 +1117,8 @@ plsTypeStr=Please type a search string.
 unsupportedFileChckUrl=不支援的檔案,請檢查網址。
 diagNotFoundChckUrl=圖表不存在或無法被存取,請檢查網址。
 csvNotFoundChckUrl=CSV 檔不存在或無法被存取,請檢查網址。
-cantReadUpload=Cannot read the uploaded diagram
-select=Select
+cantReadUpload=無法讀取上傳的圖表
+select=選擇
 errCantGetIdType=Unexpected Error: Cannot get content id or type.
 errGAuthWinBlocked=錯誤:Google Authentication 視窗被封鎖
 authDrawAccess=驗證 {1} 以賦予 draw.io 存取權限
@@ -1129,7 +1129,7 @@ mustBgtZ={1} 須為大於 0 的值
 cantLoadPrev=無法載入檔案預覽
 errAccessFile=錯誤:存取被拒。您沒有存取「{1}」的權限。
 noPrevAvail=預覽尚不可用。
-personalAccNotSup=Personal accounts are not supported.
+personalAccNotSup=不支援個人帳戶。
 errSavingTryLater=儲存時發生錯誤,請稍後再試
 plsEnterFld=請輸入 {1}
 invalidDiagUrl=無效的圖表網址
@@ -1153,12 +1153,12 @@ someImagesFailed={1} out of {2} failed due to the following errors
 importingNoUsedDiagrams=Importing {1} Diagrams not used in pages
 importingDrafts=Importing {1} Diagrams in drafts
 processingDrafts=Processing drafts
-updatingDrafts=Updating drafts
-updateDrafts=Update drafts
+updatingDrafts=更新草稿中
+updateDrafts=更新草稿
 notifications=通知
-drawioImp=draw.io Import
-confALibsImp=Importing draw.io Libraries
-confALibsImpFailed=Importing {1} library failed
+drawioImp=draw.io 匯入
+confALibsImp=匯入 draw.io 圖庫
+confALibsImpFailed=匯入 {1} 圖庫失敗
 contributors=貢獻者
 drawDiagrams=draw.io 圖表
 errFileNotFoundOrNoPer=錯誤:存取被拒。{2} 上的「{1}」不存在或缺少存取權限。
@@ -1176,7 +1176,7 @@ linkToDiagram=Link to Diagram
 changedBy=Changed By
 lastModifiedOn=最後修改於
 searchResults=搜尋結果
-showAllTemps=Show all templates
+showAllTemps=顯示所有樣板
 notionToken=Notion Token
 selectDB=選擇 Database
 noDBs=無 Database
@@ -1185,10 +1185,10 @@ confDraftPermissionErr=Draft cannot be written. Do you have attachment write/rea
 confDraftTooBigErr=Draft size is too large. Pease check "Attachment Maximum Size" of "Attachment Settings" in Confluence Configuration?
 owner=擁有者
 repository=儲存庫
-branch=Branch
+branch=分支
 meters=Meters
 teamsNoEditingMsg=Editor functionality is only available in Desktop environment (in MS Teams App or a web browser)
-contactOwner=聯擁有者
+contactOwner=聯擁有者
 viewerOnlyMsg=You cannot edit the diagrams in the mobile platform, please use the desktop client or a web browser.
 website=網站
 check4Updates=檢查更新
@@ -1197,13 +1197,13 @@ confPartialPageList=We couldn't fetch all pages due to an error in Confluence. C
 spellCheck=拼寫檢查
 noChange=No Change
 lblToSvg=Convert labels to SVG
-txtSettings=Text Settings
+txtSettings=文字設定
 LinksLost=Links will be lost
 arcSize=Arc Size
-editConnectionPoints=Edit Connection Points
+editConnectionPoints=編輯連接點
 notInOffline=離線時不支援
 notInDesktop=桌面應用程式中不支援
-confConfigSpaceArchived=draw.io Configuration space (DRAWIOCONFIG) is archived. Please restore it first.
+confConfigSpaceArchived=draw.io 組態空間 (DRAWIOCONFIG) 已封存。請先還原。
 confACleanOldVerStarted=Cleaning old diagram draft versions started
 confACleanOldVerDone=Cleaning old diagram draft versions finished
 confACleaningFile=Cleaning diagram draft "{1}" old versions
@@ -1211,14 +1211,14 @@ confAFileCleaned=Cleaning diagram draft "{1}" done
 confAFileCleanFailed=Cleaning diagram draft "{1}" failed
 confACleanOnly=Clean Diagram Drafts Only
 brush=筆刷
-openDevTools=Open Developer Tools
-autoBkp=Automatic Backup
+openDevTools=開啟開發人員工具
+autoBkp=自動備份
 confAIgnoreCollectErr=Ignore collecting current pages errors
-drafts=Drafts
+drafts=草稿
 draftSaveInt=Draft save interval [sec] (0 to disable)
 pluginsDisabled=External plugins disabled.
 extExpNotConfigured=External image service is not configured
-pathFilename=Path/Filename
+pathFilename=路徑/檔名
 confAHugeInstances=Very Large Instances
 confAHugeInstancesDesc=If this instance includes 100,000+ pages, it is faster to request the current instance pages list from Atlassian. Please contact our support for more details.
 choosePageIDsFile=Choose current page IDs csv file
@@ -1230,18 +1230,18 @@ xyzTeam={1} Team
 addTeamTitle=Adding a new draw.io Team
 addTeamInst1=To create a new draw.io Team, you need to create a new Atlassian group with "drawio-" postfix (e.g, a group named "drawio-marketing").
 addTeamInst2=Then, configure which team member can edit/add configuration, templates, and libraries from this page.
-drawioTeams=draw.io Teams
-members=Members
+drawioTeams=draw.io 團隊
+members=成員
 adminEditors=Admins/Editors
 allowAll=Allow all
-noTeams=No teams found
+noTeams=找不到團隊
 errorLoadingTeams=Error Loading Teams
-noTeamMembers=No team members found
+noTeamMembers=找不到團隊成員
 errLoadTMembers=Error loading team members
 errCreateTeamPage=Error creating team "{1}" page in "draw.io Configuration" space, please check you have the required permissions.
 gotoConfigPage=Please create the space from draw.io "Configuration" page.
 noAdminsSelected=No admins/editors selected
 errCreateConfigFile=Error creating "configuration.json" file, please check you have the required permissions.
 errSetPageRestr=Error setting page restrictions
-notAdmin4Team=You are not an admin for this team
+notAdmin4Team=你不是該團隊的管理員
 configUpdated=Configuration updated, restart the editor if you want to work with last configuration.

File diff suppressed because it is too large
+ 1 - 1
src/main/webapp/service-worker.js


File diff suppressed because it is too large
+ 1 - 1
src/main/webapp/service-worker.js.map


+ 3 - 3
src/main/webapp/styles/dark.css

@@ -110,9 +110,9 @@ html body.geEditor select:hover:not([disabled]) {
 	background:var(--dark-color);
 	border: 1px solid var(--border-color);
 }
-html body .geSidebar, html body .geSidebarContainer .geTitle, html body input, html body textarea,
-html body .geColorBtn, html body .geBaseButton, html body .geSidebarTooltip, html body .geBaseButton,
-html body .geSidebarContainer .geDropTarget, html body .geToolbarContainer, html body select {
+html body.geEditor .geSidebar, html body.geEditor .geSidebarContainer .geTitle, html body.geEditor input, html body.geEditor textarea,
+html body.geEditor .geColorBtn, html body.geEditor .geBaseButton, html body.geEditor .geSidebarTooltip, html body.geEditor .geBaseButton,
+html body.geEditor .geSidebarContainer .geDropTarget, html body.geEditor .geToolbarContainer, html body.geEditor select {
 	background:var(--panel-color);
 	border-color:var(--dark-color);
 	box-shadow:none;

+ 10 - 11
src/main/webapp/styles/grapheditor.css

@@ -28,7 +28,8 @@ div td.mxWindowTitle {
 .geEditor input[type=text]::-ms-clear {
 	display: none;
 }
-.geEditor input, select, textarea, button {
+.geEditor input, .geEditor select,
+.geEditor textarea, .geEditor button {
     font-size: inherit;
 }
 .geMenubarContainer, .geToolbarContainer, .geHsplit,
@@ -224,10 +225,17 @@ a.geStatus .geStatusMessage {
 	-moz-box-shadow: 2px 2px 3px 0px #ddd;
 	box-shadow: 2px 2px 3px 0px #ddd;
 }
-input {
+.geEditor input, .geEditor button, .geEditor select {
 	border: 1px solid #d8d8d8;
 	border-radius: 4px;
 }
+.geEditor button, .geEditor select {
+	background:#eee;
+}
+.geEditor button:hover:not([disabled], .geBigButton, .geShareBtn),
+.geEditor select:hover:not([disabled]) {
+	background:#e5e5e5;
+}
 .geColorDropper {
 	cursor:pointer;
 	opacity:0.7;
@@ -235,15 +243,6 @@ input {
 .geColorDropper:hover {
 	opacity:1;
 }
-button, select {
-	border: 1px solid #d8d8d8;
-	background:#eee;
-	border-radius: 4px;
-}
-button:hover:not([disabled], .geBigButton, .geShareBtn),
-select:hover:not([disabled]) {
-	background:#e5e5e5;
-}
 .geBtn, .mxWindow .geBtn {
 	font-size: 13px;
 	font-weight: 500;

+ 9 - 1
src/main/webapp/teams.html

@@ -138,7 +138,7 @@
 		/**
 		 * Synchronously adds scripts to the page.
 		 */
-		function mxscript(src, onLoad, id, dataAppKey, noWrite)
+		function mxscript(src, onLoad, id, dataAppKey, noWrite, onError)
 		{
 			var defer = onLoad == null && !noWrite;
 			
@@ -173,6 +173,14 @@
 						}
 				  	};
 				}
+
+				if (onError != null)
+				{
+					s.onerror = function(e)
+					{
+						onError('Failed to load ' + src, e);
+					};
+				}
 			  	
 			  	var t = document.getElementsByTagName('script')[0];