Selaa lähdekoodia

13.1.13 release

Gaudenz Alder 5 vuotta sitten
vanhempi
commit
461a1ff1d1

+ 24 - 1
ChangeLog

@@ -1,4 +1,27 @@
-29-MAY-2020: 13.1.9
+02-JUN-2020: 13.1.13
+
+- Fixes caching for HTML embed code
+- Fixes copy paste from other apps
+- Fixes math client-side printing
+- Adds math-output URL parameter
+- Adds custom table properties
+- Uses mxGraph 4.2.0 beta 7
+
+01-JUN-2020: 13.1.12
+
+- Uses mxGraph 4.2.0 beta 6
+- Adds tables to Misc sidebar
+- Fixes OS clipboard in Firefox
+
+31-MAY-2020: 13.1.11
+
+- Stability improvements and bug fixes
+
+31-MAY-2020: 13.1.10
+
+- Fixes minor bugs for tables
+
+30-MAY-2020: 13.1.9
 
 - Fixes insert tables on touch devices
 - Adds Feature-Policy header

+ 1 - 1
VERSION

@@ -1 +1 @@
-13.1.9
+13.1.13

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 3 - 3
etc/mxgraph/mxClient.js


+ 190 - 169
src/main/java/com/mxgraph/online/AbsAuthServlet.java

@@ -9,19 +9,27 @@ import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.math.BigInteger;
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.security.SecureRandom;
-import java.util.Date;
 import java.util.HashMap;
+import java.util.Map;
 
+import javax.cache.Cache;
+import javax.cache.CacheException;
+import javax.cache.CacheFactory;
+import javax.cache.CacheManager;
 import javax.servlet.ServletException;
 import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import com.google.appengine.api.memcache.MemcacheService;
+import com.google.appengine.api.memcache.stdimpl.GCacheFactory;
+
 @SuppressWarnings("serial")
 abstract public class AbsAuthServlet extends HttpServlet
 {
@@ -33,8 +41,25 @@ abstract public class AbsAuthServlet extends HttpServlet
 	private static final int COOKIE_AGE = 600;
 	
 	public static final SecureRandom random = new SecureRandom();
-	public static BigInteger prime1 = null, prime2 = null;
-	public static long lastPrimeChange = 0;
+	protected static Cache tokens;
+	
+	static
+	{
+		try
+		{
+			CacheFactory cacheFactory = CacheManager.getInstance()
+					.getCacheFactory();
+			Map<Object, Object> properties = new HashMap<>();
+			properties.put(MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT,
+					true);
+			properties.put(GCacheFactory.EXPIRATION_DELTA, COOKIE_AGE); //Cache servlet set it to 300 (5 min), all cache instances are the same so 5 will be enforced
+			tokens = cacheFactory.createCache(properties);
+		}
+		catch (CacheException e)
+		{
+			e.printStackTrace();
+		}
+	}
 	
 	protected int postType = X_WWW_FORM_URLENCODED; 
 	
@@ -96,29 +121,17 @@ abstract public class AbsAuthServlet extends HttpServlet
 	protected void doGet(HttpServletRequest request,
 			HttpServletResponse response) throws ServletException, IOException
 	{
-		//Prime secrets changes every 10 minutes
-		if (new Date().getTime() - lastPrimeChange > COOKIE_AGE * 1000)
-		{
-			synchronized (AbsAuthServlet.class)
-			{
-				//Recheck after acquiring the lock
-				if (new Date().getTime() - lastPrimeChange > COOKIE_AGE * 1000)
-				{
-					prime2 = prime1;
-					prime1 = BigInteger.probablePrime(256, random);
-					lastPrimeChange = new Date().getTime();
-				}
-			}
-		}
-		
 		String stateOnly = request.getParameter("getState");
 		
 		if ("1".equals(stateOnly))
 		{
-			String state = new BigInteger(256, random).multiply(prime1).toString(32);
+			String state = new BigInteger(256, random).toString(32);
+			String key = new BigInteger(256, random).toString(32);
+			String casheKey = request.getRemoteAddr() + ":" + key;
+			tokens.put(casheKey, state);
 			response.setStatus(HttpServletResponse.SC_OK);
 			//Chrome blocks this cookie when draw.io is running in an iframe. The cookie is added to parent frame. TODO FIXME
-			response.setHeader("Set-Cookie", STATE_COOKIE + "=" + state + "; Max-Age=" + COOKIE_AGE + "; Secure; HttpOnly; SameSite=none"); //10 min to finish auth
+			response.setHeader("Set-Cookie", STATE_COOKIE + "=" + key + "; Max-Age=" + COOKIE_AGE + "; Secure; HttpOnly; SameSite=none"); //10 min to finish auth
 			response.setHeader("Content-Type", "text/plain");
 			OutputStream out = response.getOutputStream();
 			out.write(state.getBytes());
@@ -161,18 +174,8 @@ abstract public class AbsAuthServlet extends HttpServlet
 				{
 					if (STATE_COOKIE.equals(cookie.getName()))
 					{
-						//Ensure cookie value is divisible with prime1 or prime2
-						String val = cookie.getValue();
-						BigInteger iVal = new BigInteger(val, 32);
-						
-						if (iVal.mod(prime1).equals(BigInteger.ZERO))
-						{
-							cookieToken = val;
-						}
-						else if (prime2 != null && iVal.mod(prime2).equals(BigInteger.ZERO))
-						{
-							cookieToken = val;
-						}
+						//Get the cached state based on the cookie key 
+						cookieToken = (String) tokens.get(request.getRemoteAddr() + ":" + cookie.getValue());
 						break;
 					}
 				}
@@ -213,158 +216,176 @@ abstract public class AbsAuthServlet extends HttpServlet
 			}
 			else
 			{
-				HttpURLConnection con = null;
+				Response authResp = contactOAuthServer(CONFIG.AUTH_SERVICE_URL, code, refreshToken, secret, client, redirectUri, 1);
+				response.setStatus(authResp.status);
 				
-				try
+				if (authResp.content != null)
 				{
-					String url = CONFIG.AUTH_SERVICE_URL;
-					URL obj = new URL(url);
-					con = (HttpURLConnection) obj.openConnection();
-		
-					con.setRequestMethod("POST");
-					
-					boolean jsonResponse = false;
-					StringBuilder urlParameters = new StringBuilder();
+					OutputStream out = response.getOutputStream();
+					PrintWriter writer = new PrintWriter(out);
+					writer.println(authResp.content);
+					writer.flush();
+					writer.close();
+				}
+			}
+		}
+		catch (Exception e) 
+		{
+			response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+		}
+	}
 
-					if (postType == X_WWW_FORM_URLENCODED)
-					{
-						con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
-		
-						urlParameters.append("client_id=");
-						urlParameters.append(client);
-						urlParameters.append("&redirect_uri=");
-						urlParameters.append(redirectUri);
-						urlParameters.append("&client_secret=");
-						urlParameters.append(secret);
-					
-						if (code != null)
-						{
-							urlParameters.append("&code=");
-							urlParameters.append(code);
-							urlParameters.append("&grant_type=authorization_code");
-						}
-						else
-						{
-							urlParameters.append("&refresh_token=");
-							urlParameters.append(refreshToken);
-							urlParameters.append("&grant_type=refresh_token");
-							jsonResponse = true;
-						}
-					}
-					else if (postType == JSON)
-					{
-						con.setRequestProperty("Content-Type", "application/json");
-						
-						urlParameters.append("{");
-						urlParameters.append("\"client_id\": \"");
-						urlParameters.append(client);
-						urlParameters.append("\", \"redirect_uri\": \"");
-						urlParameters.append(redirectUri);
-						urlParameters.append("\", \"client_secret\": \"");
-						urlParameters.append(secret);
-					
-						if (code != null)
-						{
-							urlParameters.append("\", \"code\": \"");
-							urlParameters.append(code);
-							urlParameters.append("\", \"grant_type\": \"authorization_code\"}");
-						}
-						else
-						{
-							urlParameters.append("\", \"refresh_token\": \"");
-							urlParameters.append(refreshToken);
-							urlParameters.append("\", \"grant_type\": \"refresh_token\"}");
-							jsonResponse = true;
-						}
-					}
-					
-					// Send post request
-					con.setDoOutput(true);
-					DataOutputStream wr = new DataOutputStream(con.getOutputStream());
-					wr.writeBytes(urlParameters.toString());
-					wr.flush();
-					wr.close();
+	class Response
+	{
+		public int status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+		public String content = null;
+	}
+	
+	private Response contactOAuthServer(String authSrvUrl, String code, String refreshToken, String secret,
+			String client, String redirectUri, int retryCount)
+	{
+		HttpURLConnection con = null;
+		Response response = new Response();
 		
+		try
+		{
+			URL obj = new URL(authSrvUrl);
+			con = (HttpURLConnection) obj.openConnection();
+
+			con.setRequestMethod("POST");
+			
+			boolean jsonResponse = false;
+			StringBuilder urlParameters = new StringBuilder();
+
+			if (postType == X_WWW_FORM_URLENCODED)
+			{
+				con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+
+				urlParameters.append("client_id=");
+				urlParameters.append(client);
+				urlParameters.append("&redirect_uri=");
+				urlParameters.append(redirectUri);
+				urlParameters.append("&client_secret=");
+				urlParameters.append(secret);
+			
+				if (code != null)
+				{
+					urlParameters.append("&code=");
+					urlParameters.append(code);
+					urlParameters.append("&grant_type=authorization_code");
+				}
+				else
+				{
+					urlParameters.append("&refresh_token=");
+					urlParameters.append(refreshToken);
+					urlParameters.append("&grant_type=refresh_token");
+					jsonResponse = true;
+				}
+			}
+			else if (postType == JSON)
+			{
+				con.setRequestProperty("Content-Type", "application/json");
+				
+				urlParameters.append("{");
+				urlParameters.append("\"client_id\": \"");
+				urlParameters.append(client);
+				urlParameters.append("\", \"redirect_uri\": \"");
+				urlParameters.append(redirectUri);
+				urlParameters.append("\", \"client_secret\": \"");
+				urlParameters.append(secret);
+			
+				if (code != null)
+				{
+					urlParameters.append("\", \"code\": \"");
+					urlParameters.append(code);
+					urlParameters.append("\", \"grant_type\": \"authorization_code\"}");
+				}
+				else
+				{
+					urlParameters.append("\", \"refresh_token\": \"");
+					urlParameters.append(refreshToken);
+					urlParameters.append("\", \"grant_type\": \"refresh_token\"}");
+					jsonResponse = true;
+				}
+			}
+			
+			// Send post request
+			con.setDoOutput(true);
+			DataOutputStream wr = new DataOutputStream(con.getOutputStream());
+			wr.writeBytes(urlParameters.toString());
+			wr.flush();
+			wr.close();
+
+			BufferedReader in = new BufferedReader(
+					new InputStreamReader(con.getInputStream()));
+			String inputLine;
+			StringBuffer authRes = new StringBuffer();
+
+			while ((inputLine = in.readLine()) != null)
+			{
+				authRes.append(inputLine);
+			}
+			in.close();
+
+			response.status = con.getResponseCode();
+			// Writes JavaScript code
+			response.content = processAuthResponse(authRes.toString(), jsonResponse);
+		}
+		catch(IOException e)
+		{
+			StringBuilder details = new StringBuilder("");
+			
+			if (con != null)
+			{
+				try 
+				{
 					BufferedReader in = new BufferedReader(
-							new InputStreamReader(con.getInputStream()));
+							new InputStreamReader(con.getErrorStream()));
+					
 					String inputLine;
-					StringBuffer authRes = new StringBuffer();
-		
+
 					while ((inputLine = in.readLine()) != null)
 					{
-						authRes.append(inputLine);
+						System.err.println(inputLine);
+						details.append(inputLine);
+						details.append("\n");
 					}
 					in.close();
-	
-					response.setStatus(con.getResponseCode());
-					
-					OutputStream out = response.getOutputStream();
-		
-					PrintWriter writer = new PrintWriter(out);
-	
-					// Writes JavaScript code
-					writer.println(processAuthResponse(authRes.toString(), jsonResponse));
-		
-					writer.flush();
-					writer.close();
 				}
-				catch(IOException e)
+				catch (Exception e2) 
 				{
-					StringBuilder details = new StringBuilder("");
-					
-					if (con != null)
-					{
-						try 
-						{
-							BufferedReader in = new BufferedReader(
-									new InputStreamReader(con.getErrorStream()));
-							
-							String inputLine;
-		
-							while ((inputLine = in.readLine()) != null)
-							{
-								System.err.println(inputLine);
-								details.append(inputLine);
-								details.append("\n");
-							}
-							in.close();
-						}
-						catch (Exception e2) 
-						{
-							// Ignore
-						}
-					}
-					
-					if (e.getMessage() != null && e.getMessage().contains("401"))
-					{
-						response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
-					}
-					else
-					{
-						response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-						e.printStackTrace();
-						System.err.println(details);
-					}
-					
-					if (DEBUG)
-					{
-						OutputStream out = response.getOutputStream();
-						
-						PrintWriter writer = new PrintWriter(out);
-		
-						e.printStackTrace(writer);
-						writer.println(details.toString());
-			
-						writer.flush();
-						writer.close();
-					}
+					// Ignore
 				}
 			}
+			
+			if (e.getMessage() != null && e.getMessage().contains("401"))
+			{
+				response.status = HttpServletResponse.SC_UNAUTHORIZED;
+			}
+			else if (retryCount > 0 && e.getMessage() != null && e.getMessage().contains("Connection timed out"))
+		    {
+				return contactOAuthServer(authSrvUrl, code, refreshToken, secret,
+						client, redirectUri, --retryCount);
+		    }
+			else
+			{
+				response.status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+				e.printStackTrace();
+				System.err.println(details);
+			}
+			
+			if (DEBUG)
+			{
+				StringWriter sw = new StringWriter();
+				PrintWriter pw = new PrintWriter(sw);
+				e.printStackTrace(pw);
+				pw.println(details.toString());
+				pw.flush();
+				response.content = sw.toString();
+			}
 		}
-		catch (Exception e) 
-		{
-			response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-		}
+		
+		return response;
 	}
 
 }

+ 5 - 0
src/main/java/com/mxgraph/online/EmbedServlet2.java

@@ -227,6 +227,11 @@ public class EmbedServlet2 extends HttpServlet
 		response.setCharacterEncoding("UTF-8");
 		response.setContentType("application/javascript; charset=UTF-8");
 		response.setHeader("Last-Modified", lastModified);
+		
+		if (request.getParameter("fetch") != null)
+		{
+			response.setHeader("Cache-Control", "no-store");
+		}
 
 		OutputStream out = response.getOutputStream();
 

+ 0 - 1
src/main/webapp/WEB-INF/appengine-web.xml

@@ -13,7 +13,6 @@
   <!-- Path patterns not supported in production -->
   <static-files>
     <include path="/**">
-	  <http-header name="Feature-Policy" value="accelerometer 'none'; ambient-light-sensor 'none'; battery 'none'; camera 'none'; display-capture 'none'; document-domain 'none; encrypted-media 'none'; fullscreen 'none'; geolocation 'none'; gyroscope 'none'; magnetometer 'none'; midi 'none'; navigation-override 'none'; payment 'none'; picture-in-picture 'none'; publickey-credentials-get 'none'; usb 'none'; wake-lock 'none'; xr-spatial-tracking 'none';"/>	
       <http-header name="Referrer-Policy" value="strict-origin" />
 	  <http-header name="Access-Control-Allow-Origin" value="*"/>
 	</include>

+ 9 - 2
src/main/webapp/connect/confluence/admin.html

@@ -12,7 +12,9 @@
 			<h4 style="padding-top:20px;" data-i18n="gliffyImport">Gliffy Import</h4>
 			<div><br><span data-i18n="gliffyImportInst1">Click the "Start Import" button to import all Gliffy diagrams to draw.io.</span><br>
 			<span data-i18n="gliffyImportInst2">Please note that the import procedure will take some time and the browser window must remain open until the import is completed.</span></div>
-			<div style="padding-bottom:4px;"><br><button id="importBtn" disabled="disabled" class="aui-button aui-button-primary" data-i18n="startImport">Start Import</button></div>
+			<div style="padding-bottom:4px;"><br><button id="importBtn" disabled="disabled" class="aui-button aui-button-primary" data-i18n="startImport">Start Import</button>
+				<img id="busyIcon" src="/images/spin.gif" style="display: none">
+			</div>
 			<div id="operationLog"><br></div>
 		</div>
 		<script type="text/javascript">
@@ -48,7 +50,12 @@
 					
 					importBtn.click(function()
 					{
-						GliffyMassImporter(logDiv);
+						$('#busyIcon').show();
+						
+						GliffyMassImporter(logDiv, function()
+						{
+							$('#busyIcon').hide();
+						});
 					});
 				});
 			};

+ 18 - 13
src/main/webapp/connect/confluence/admin.js

@@ -409,7 +409,7 @@ var MassDiagramsProcessor = function(macroName, readableName, attParams, process
 	});
 };
 
-var GliffyMassImporter = function(logDiv) 
+var GliffyMassImporter = function(logDiv, doneFn) 
 {
 	var link = document.createElement('a');
 	link.href = location.href;
@@ -513,7 +513,7 @@ var GliffyMassImporter = function(logDiv)
 	
 	logDiv.html("<br>");
 	
-	MassDiagramsProcessor('gliffy', 'Gliffy', ['name'], importGliffyAtt, logDiv);
+	MassDiagramsProcessor('gliffy', 'Gliffy', ['name'], importGliffyAtt, logDiv, doneFn);
 };
 
 function cleanBrokenCustomContents(logDiv, callback, error)
@@ -547,7 +547,7 @@ function cleanBrokenCustomContents(logDiv, callback, error)
 			obj[diagramName] = {id: contentId, ver: contentVer};
 			pageCustomContents[pageId] = obj;
 		}
-		else if (pageCustomContents[pageId][diagramName])
+		else if (pageCustomContents[pageId][diagramName] && pageCustomContents[pageId][diagramName].id != contentId) //Sometimes, search returns duplicate entries!
 		{
 			customContent2Del.push({id: contentId, name: diagramName, duplicate: true});
 			return false;
@@ -601,11 +601,13 @@ function cleanBrokenCustomContents(logDiv, callback, error)
 		}
 	};
 	
-    function collectAtts(pageId, callback, error, url, atts)
+    function collectAtts(pageId, callback, error, start, atts)
     {
     	//first call
-    	if (url == null)
+    	if (start == null)
     	{
+    		start = 0;
+
     		if (typeof(pendingCallbacks[pageId]) === 'undefined')
 			{
     			atts = {};
@@ -620,7 +622,7 @@ function cleanBrokenCustomContents(logDiv, callback, error)
     	
     	AP.request({
             type: 'GET',
-			url: url != null? url : '/rest/api/content/' + pageId + '/child/attachment?limit=100&expand=version',
+			url: '/rest/api/content/' + pageId + '/child/attachment?limit=100&expand=version&start=' + start,
             contentType: 'application/json;charset=UTF-8',
             success: function (resp) 
             {
@@ -635,7 +637,8 @@ function cleanBrokenCustomContents(logDiv, callback, error)
                	//Support pageing
 				if (resp._links && resp._links.next) 
 				{
-					collectAtts(pageId, callback, error, resp._links.next, atts);
+					start += resp.limit; //Sometimes the limit is changed by the server
+					collectAtts(pageId, callback, error, start, atts);
 				}
 				else
 				{
@@ -672,10 +675,10 @@ function cleanBrokenCustomContents(logDiv, callback, error)
 		});
     };
     
-	function processChunk(nextUrl)
+	function processChunk(start)
 	{
 		AP.request({
-			url: nextUrl != null? nextUrl : '/rest/api/content/search?cql=' + encodeURIComponent('type="ac:com.mxgraph.confluence.plugins.diagramly:drawio-diagram"') + '&limit=50&expand=body.storage,version',  
+			url: '/rest/api/content/search?cql=' + encodeURIComponent('type="ac:com.mxgraph.confluence.plugins.diagramly:drawio-diagram"') + '&limit=50&expand=body.storage,version&start=' + start,  
 			success: function(resp) 
 			{
 				resp = JSON.parse(resp);
@@ -748,6 +751,7 @@ function cleanBrokenCustomContents(logDiv, callback, error)
 							//ignore, this should not happen! But, if it happens, it means a corrupted custom content. Just delete it
 							console.log(e);
 							customContent2Del.push({id: list[i].id, name: list[i].title});
+							processedItems++;
 							checkDone();
 						}
 					}
@@ -758,7 +762,8 @@ function cleanBrokenCustomContents(logDiv, callback, error)
 				//Support pageing
 				if (resp._links && resp._links.next) 
 				{
-					processChunk(resp._links.next);
+					start += resp.limit; //Sometimes the limit is changed by the server
+					processChunk(start);
 				}
 				else
 				{
@@ -770,10 +775,10 @@ function cleanBrokenCustomContents(logDiv, callback, error)
 		});
 	};
 	
-	processChunk();
+	processChunk(0);
 };
 
-var DrawIoDiagramsIndexer = function(logDiv)
+var DrawIoDiagramsIndexer = function(logDiv, doneFn)
 {
 	var pageCustomContents = {}, customContentsMap = {};
 	
@@ -890,7 +895,7 @@ var DrawIoDiagramsIndexer = function(logDiv)
 		
 		MassDiagramsProcessor('drawio', 'draw.io', 
 				drawioMacroParams,
-				fixDrawIoCustomContent, logDiv);
+				fixDrawIoCustomContent, logDiv, doneFn);
 	});
 };
 

+ 3 - 1
src/main/webapp/connect/confluence/config.html

@@ -161,7 +161,9 @@
 			    <h4 data-i18n="refreshDrawIndex">Refresh draw.io Diagrams Index</h4>
 				<div><br><span data-i18n="reindexInst1">Click the "Start Indexing" button to refresh draw.io diagrams index.</span><br>
 				<span data-i18n="reindexInst2">Please note that the indexing procedure will take some time and the browser window must remain open until the indexing is completed.</span></div>
-				<div><br><button id="indexBtn" disabled="disabled" class="aui-button aui-button-primary" data-i18n="startIndexing">Start Indexing</button></div>
+				<div><br><button id="indexBtn" disabled="disabled" class="aui-button aui-button-primary" data-i18n="startIndexing">Start Indexing</button>
+					<img id="DRIbusyIcon" src="/images/spin.gif" style="display: none">
+				</div>
 				<div id="operationLog"><br></div>
 			</div>
 		</div>

+ 5 - 1
src/main/webapp/connect/confluence/config.js

@@ -1622,7 +1622,11 @@ script.onload = function()
 		
 		indexBtn.click(function()
 		{
-			DrawIoDiagramsIndexer($('#operationLog'));
+			$('#DRIbusyIcon').show();
+			DrawIoDiagramsIndexer($('#operationLog'), function()
+			{
+				$('#DRIbusyIcon').hide();
+			});
 		});
 		
 		var exportBtn = $('#exportBtn');

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

@@ -789,10 +789,10 @@ AC.getPageAttachments = function(pageId, success, error)
 {
 	var attachments = [];
 
-	function getAttsChunk(nextUrl)
+	function getAttsChunk(start)
 	{
 		AP.request({
-			url: nextUrl != null? nextUrl : '/rest/api/content/' + pageId + '/child/attachment?limit=100',
+			url: '/rest/api/content/' + pageId + '/child/attachment?limit=100&start=' + start,
 			type: 'GET',
 			contentType: 'application/json;charset=UTF-8',
 			success: function(resp) 
@@ -803,7 +803,8 @@ AC.getPageAttachments = function(pageId, success, error)
 				//Support paging
 				if (resp._links && resp._links.next) 
 				{
-					getAttsChunk(resp._links.next);
+					start += resp.limit; //Sometimes the limit is changed by the server
+					getAttsChunk(start);
 				}
 				else
 				{
@@ -814,7 +815,7 @@ AC.getPageAttachments = function(pageId, success, error)
 		});
 	};
 	
-	getAttsChunk();	
+	getAttsChunk(0);	
 };
 
 AC.searchDiagrams = function(searchStr, success, error)

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 946 - 939
src/main/webapp/js/app.min.js


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

@@ -1458,8 +1458,8 @@ App.prototype.init = function()
 		}
 		
 		if (!mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp && !this.isOffline() &&
-			!mxClient.IS_ANDROID && !mxClient.IS_IOS &&
-			urlParams['open'] == null && (!this.editor.chromeless || this.editor.editable))
+			!mxClient.IS_ANDROID && !mxClient.IS_IOS && urlParams['open'] == null &&
+			(!this.editor.chromeless || this.editor.editable))
 		{
 			this.editor.addListener('fileLoaded', mxUtils.bind(this, function()
 			{
@@ -2727,7 +2727,7 @@ App.prototype.start = function()
 				}
 
 				if (!mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp && !this.isOfflineApp() &&
-					/.*\.draw\.io$/.test(window.location.hostname) &&
+					urlParams['open'] == null && /.*\.draw\.io$/.test(window.location.hostname) &&
 					(!this.editor.chromeless || this.editor.editable))
 				{
 					this.showNameChangeBanner();
@@ -3036,6 +3036,14 @@ App.prototype.start = function()
 				}
 				else
 				{
+					// Removes open URL parameter. Hash is also updated in Init to load client.
+					if (urlParams['open'] != null && window.history && window.history.replaceState)
+					{
+						window.history.replaceState(null, null, window.location.pathname +
+							this.getSearch(['open']));
+						window.location.hash = urlParams['open'];
+					}
+					
 					done();
 				}
 			}

+ 7 - 4
src/main/webapp/js/diagramly/Devel.js

@@ -11,19 +11,22 @@ if (!mxIsElectron)
 	(function()
 	{
 		var csp = 'default-src \'self\'; ' +
+			// storage.googleapis.com is needed for workbox-service-worker
 			'script-src %dev-script-src% \'self\' https://storage.googleapis.com ' +
 				'https://apis.google.com https://*.pusher.com https://code.jquery.com '+
 				'https://www.dropbox.com https://api.trello.com ' +
 				// Scripts in index.html (not checked here)
 				'\'sha256-JqdgAC+ydIDMtmQclZEqgbw94J4IeABIfXAxwEJGDJs=\' ' +
-				'\'sha256-4Dg3/NrB8tLC7TUSCbrtUDWD/J6bSLka01GHn+qtNZ0=\';' +
+				'\'sha256-4Dg3/NrB8tLC7TUSCbrtUDWD/J6bSLka01GHn+qtNZ0=\'; ' +
 			'connect-src \'self\' https://*.draw.io https://*.googleapis.com wss://*.pusher.com ' +
 				'https://api.github.com https://raw.githubusercontent.com https://gitlab.com ' +
 				'https://graph.microsoft.com https://*.sharepoint.com  https://*.1drv.com ' +
 				'https://*.dropboxapi.com https://api.trello.com https://*.google.com ' +
-				'https://fonts.gstatic.com https://fonts.googleapis.com;' +
-			'img-src * data:; media-src * data:; font-src *; ' +
-			'frame-src \'self\' https://*.google.com; ' +
+				'https://fonts.gstatic.com https://fonts.googleapis.com; ' +
+			// font-src about: is required for MathJax HTML-CSS output with STIX
+			'img-src * data:; media-src * data:; font-src * about:; ' +
+			// www.draw.io required for browser data migration to app.diagrams.net
+			'frame-src \'self\' https://www.draw.io https://*.google.com; ' +
 			'style-src %dev-style-src% \'self\' \'unsafe-inline\' https://fonts.googleapis.com;';
 		var devCsp = csp.
 			// Loads common.css from mxgraph

+ 89 - 60
src/main/webapp/js/diagramly/Editor.js

@@ -4,6 +4,15 @@
  */
 (function()
 {
+	/**
+	 * Enables paste from Lucidchart
+	 */
+	if (typeof html4 !== 'undefined')
+	{
+		html4.ATTRIBS["span::data-lucid-content"] = 0;
+		html4.ATTRIBS["span::data-lucid-type"] = 0;
+	}
+
 	/**
 	 * Specifies the app name. Default is document.title.
 	 */
@@ -207,6 +216,34 @@
 	 */
 	Editor.commonVertexProperties = [
         {type: 'separator'},
+        {name: 'resizeLastRow', dispName: 'Resize Last Row', type: 'bool', getDefaultValue: function(state, format)
+        {
+        	var cell = (state.vertices.length == 1 && state.edges.length == 0) ? state.vertices[0] : null;
+        	var graph = format.editorUi.editor.graph;
+        	var style = graph.getCellStyle(cell);
+        	
+        	return mxUtils.getValue(style, 'resizeLastRow', '0') == '1';
+        }, isVisible: function(state, format)
+        {
+        	var graph = format.editorUi.editor.graph;
+        	
+    		return state.vertices.length == 1 && state.edges.length == 0 &&
+    			graph.isTable(state.vertices[0]);
+        }},
+        {name: 'resizeLast', dispName: 'Resize Last Column', type: 'bool', getDefaultValue: function(state, format)
+        {
+        	var cell = (state.vertices.length == 1 && state.edges.length == 0) ? state.vertices[0] : null;
+        	var graph = format.editorUi.editor.graph;
+        	var style = graph.getCellStyle(cell);
+        	
+        	return mxUtils.getValue(style, 'resizeLast', '0') == '1';
+        }, isVisible: function(state, format)
+        {
+        	var graph = format.editorUi.editor.graph;
+        	
+    		return state.vertices.length == 1 && state.edges.length == 0 &&
+    			graph.isTable(state.vertices[0]);
+        }},
         {name: 'fillOpacity', dispName: 'Fill Opacity', type: 'int', min: 0, max: 100, defVal: 100},
         {name: 'strokeOpacity', dispName: 'Stroke Opacity', type: 'int', min: 0, max: 100, defVal: 100},
         {name: 'overflow', dispName: 'Text Overflow', defVal: 'visible', type: 'enum',
@@ -221,7 +258,16 @@
         	enumList: [{val: 'none', dispName: 'None'}, {val: 'north', dispName: 'North'}, {val: 'east', dispName: 'East'}, {val: 'south', dispName: 'South'}, {val: 'west', dispName: 'West'}]
         },
         {name: 'portConstraintRotation', dispName: 'Rotate Constraint', type: 'bool', defVal: false},
-        {name: 'connectable', dispName: 'Connectable', type: 'bool', defVal: true},
+        {name: 'connectable', dispName: 'Connectable', type: 'bool', getDefaultValue: function(state, format)
+        {
+        	var cell = (state.vertices.length == 1 && state.edges.length == 0) ? state.vertices[0] : null;
+        	var graph = format.editorUi.editor.graph;
+        	
+        	return graph.isCellConnectable(cell);
+        }, isVisible: function(state, format)
+        {
+    		return state.vertices.length == 1 && state.edges.length == 0;
+        }},
         {name: 'allowArrows', dispName: 'Allow Arrows', type: 'bool', defVal: true},
         {name: 'snapToPoint', dispName: 'Snap to Point', type: 'bool', defVal: false},
         {name: 'perimeter', dispName: 'Perimeter', defVal: 'none', type: 'enum',
@@ -1368,6 +1414,37 @@
 				}
 			}, 0);
 		};
+		
+		var font = (urlParams['math-font'] != null) ?
+			decodeURIComponent(urlParams['math-font']) :
+			'TeX';
+		
+		config = (config != null) ? config :
+		{
+			jax: ['input/TeX', 'input/MathML', 'input/AsciiMath'].concat(
+				[(urlParams['math-output'] == 'html') ?
+					'output/HTML-CSS' : 'output/SVG']),
+			extensions: ['tex2jax.js', 'mml2jax.js', 'asciimath2jax.js'],
+			TeX: {
+				extensions: ['AMSmath.js', 'AMSsymbols.js', 'noErrors.js', 'noUndefined.js']
+			},
+			'HTML-CSS': {
+				availableFonts: [font],
+				imageFont: null
+			},
+			SVG: {
+				font: font,
+				// Needed for client-side export to work
+				useFontCache: false
+			},
+			// Ignores math in in-place editor
+			tex2jax: {
+				ignoreClass: 'mxCellEditor'
+		  	},
+		  	asciimath2jax: {
+				ignoreClass: 'mxCellEditor'
+		  	}
+		};
 
 		// Disables global typesetting and messages on startup, adds queue for
 		// asynchronous rendering while MathJax is loading
@@ -1378,36 +1455,7 @@
 			messageStyle: 'none',
 			AuthorInit: function ()
 			{
-				// Specification recommends using SVG over HTML-CSS if browser is known
-				// Check if too inconsistent with image export and print output
-				MathJax.Hub.Config(config || {
-					jax: ['input/TeX', 'input/MathML', 'input/AsciiMath', 'output/SVG'],
-					extensions: ['tex2jax.js', 'mml2jax.js', 'asciimath2jax.js'],
-					TeX: {
-						extensions: ['AMSmath.js', 'AMSsymbols.js', 'noErrors.js', 'noUndefined.js']
-					},
-					// Needed for client-side export to work
-					SVG: {
-						font: (urlParams['math-font'] != null) ?
-							decodeURIComponent(urlParams['math-font']) :
-							'TeX',
-						useFontCache: false
-					},
-					// Ignores math in in-place editor
-					tex2jax: {
-						ignoreClass: 'mxCellEditor'
-				  	},
-				  	asciimath2jax: {
-						ignoreClass: 'mxCellEditor'
-				  	}
-				});
-				MathJax.Hub.Register.StartupHook('Begin', function()
-				{
-					for (var i = 0; i < Editor.mathJaxQueue.length; i++)
-					{
-						Editor.doMathJaxRender(Editor.mathJaxQueue[i]);
-					}
-				});
+				MathJax.Hub.Config(config);
 		    }
 		};
 
@@ -6040,39 +6088,20 @@
 				if (pv.mathEnabled)
 				{
 					var doc = pv.wnd.document;
-			
-					doc.writeln('<script type="text/x-mathjax-config">');
-					doc.writeln('MathJax.Hub.Config({');
-					doc.writeln('showMathMenu: false,');
-					doc.writeln('messageStyle: "none",');
-					doc.writeln('jax: ["input/TeX", "input/MathML", "input/AsciiMath", "output/SVG"],');
-					doc.writeln('extensions: ["tex2jax.js", "mml2jax.js", "asciimath2jax.js"],');
-					doc.writeln('TeX: {');
-					doc.writeln('extensions: ["AMSmath.js", "AMSsymbols.js", "noErrors.js", "noUndefined.js"]');
-					doc.writeln('},');
-					doc.writeln('SVG: {');
-					doc.writeln('font: "' + ((urlParams['math-font'] != null) ?
-						decodeURIComponent(urlParams['math-font']) :
-						'TeX') + '"');
-					doc.writeln('},');
-					doc.writeln('tex2jax: {');
-					doc.writeln('ignoreClass: "geDisableMathJax"');
-				  	doc.writeln('},');
-				  	doc.writeln('asciimath2jax: {');
-					doc.writeln('ignoreClass: "geDisableMathJax"');
-				  	doc.writeln('}');
-					doc.writeln('});');
 					
-					// Adds asynchronous printing when MathJax finished rendering
+					// Adds asynchronous printing when MathJax finishes rendering
 					if (print)
 					{
-						doc.writeln('MathJax.Hub.Queue(function () {');
-						doc.writeln('window.print();');
-						doc.writeln('});');
+						// Injects variable to execute print via Queue without unsafe-inline
+						pv.wnd.IMMEDIATE_PRINT = true;
+//						doc.writeln('<script type="text/x-mathjax-config">');
+//						doc.writeln('MathJax.Hub.Queue(function () {');
+//						doc.writeln('window.print();');
+//						doc.writeln('});');
+//						doc.writeln('</script>');
 					}
-					
-					doc.writeln('</script>');
-					doc.writeln('<script type="text/javascript" src="' + DRAW_MATH_URL + '/MathJax.js"></script>');
+
+					doc.writeln('<script type="text/javascript" src="' + DRAWIO_BASE_URL + '/js/math-print.js"></script>');
 				}
 				
 				pv.closeDocument();

+ 12 - 5
src/main/webapp/js/diagramly/EditorUi.js

@@ -9092,7 +9092,7 @@
 					    {
 				    		var html = evt.dataTransfer.getData('text/html');
 				    		var div = document.createElement('div');
-				    		div.innerHTML = html;
+				    		div.innerHTML = graph.sanitizeHtml(html);
 				    		
 				    		// The default is based on the extension
 				    		var asImage = null;
@@ -9104,6 +9104,11 @@
 				    		{
 				    			html = imgs[0].getAttribute('src');
 				    			
+				    			if (html == null)
+				    			{
+				    				html = imgs[0].getAttribute('srcset');
+				    			}
+				    			
 				    			// Handles special case where the src attribute has no valid extension
 				    			// in which case the text would be inserted as text with a link
 				    			if (!(/\.(gif|jpg|jpeg|tiff|png|svg)$/i).test(html))
@@ -9139,7 +9144,7 @@
 				    			graph.setSelectionCells(this.insertTextAt(html, x, y, true, asImage, null, resizeImages));
 				    		});
 				    		
-				    		if (asImage && html.length > this.resampleThreshold)
+				    		if (asImage && html != null && html.length > this.resampleThreshold)
 				    		{
 				    			this.confirmImageResize(function(doResize)
 		    					{
@@ -9712,8 +9717,10 @@
 				
 				if (data != null && data.length > 0)
 				{
+					var hasMeta = data.substring(0, 6) == '<meta ';
 					elt = document.createElement('div');
-					elt.innerHTML = data;
+					elt.innerHTML = ((hasMeta) ? '<meta charset="utf-8">' : '') +
+						this.editor.graph.sanitizeHtml(data);
 					asHtml = true;
 					
 					// Workaround for innerText not ignoring style elements in Chrome
@@ -10006,7 +10013,7 @@
 								    	
 								    	if (mxUtils.indexOf(provider.types, 'text/uri-list') >= 0)
 								    	{
-								    		var data = evt.dataTransfer.getData('text/uri-list');
+								    		data = evt.dataTransfer.getData('text/uri-list');
 								    	}
 								    	else
 								    	{
@@ -10016,7 +10023,7 @@
 										if (data != null && data.length > 0)
 										{
 											var div = document.createElement('div');
-								    		div.innerHTML = data;
+								    		div.innerHTML = this.editor.graph.sanitizeHtml(data);
 		
 								    		// Extracts single image
 								    		var imgs = div.getElementsByTagName('img');

+ 5 - 1
src/main/webapp/js/diagramly/Menus.js

@@ -1314,8 +1314,12 @@
 			{
 				if (!graph.isSelectionEmpty())
 				{
+					var cells = graph.cloneCells(graph.getSelectionCells());
+					var bbox = graph.getBoundingBoxFromGeometry(cells);
+					cells = graph.moveCells(cells, -bbox.x, -bbox.y);
+					
 					editorUi.showTextDialog('Create Sidebar Entry', 'sb.createVertexTemplateFromData(\'' +
-						Graph.compress(mxUtils.getXml(graph.encodeCells(graph.getSelectionCells()))) +
+						Graph.compress(mxUtils.getXml(graph.encodeCells(cells))) +
 						'\', width, height, \'Title\');');
 				}
 			}));

+ 63 - 0
src/main/webapp/js/math-print.js

@@ -0,0 +1,63 @@
+(function() {
+	
+	var font = (window.opener.urlParams['math-font'] != null) ?
+		decodeURIComponent(window.opener.urlParams['math-font']) : 'TeX';
+
+	window.MathJax = {
+		showMathMenu: false,
+		messageStyle: 'none',
+		AuthorInit: function () {
+			MathJax.Hub.Config({
+				jax: ['input/TeX', 'input/MathML', 'input/AsciiMath'].concat(
+				[(window.opener.urlParams['math-output'] == 'html') ?
+					'output/HTML-CSS' : 'output/SVG']),
+				extensions: ['tex2jax.js', 'mml2jax.js', 'asciimath2jax.js'],
+				TeX: {
+					extensions:['AMSmath.js', 'AMSsymbols.js', 'noErrors.js', 'noUndefined.js']
+				},
+				'HTML-CSS': {
+					availableFonts: [font],
+					imageFont: null
+				},
+				SVG: {
+					font: font,
+					useFontCache: false
+				},
+				tex2jax: {
+					ignoreClass: 'geDisableMathJax'
+				},
+				asciimath2jax: {
+					ignoreClass: 'geDisableMathJax'
+				}
+			});
+		}
+	};
+	
+	var s = document.createElement('script');
+	s.setAttribute('type', 'text/javascript');
+	s.setAttribute('src', window.opener.DRAW_MATH_URL + '/MathJax.js');
+	
+	// Waits for the Math to be rendered and then calls window.print
+	if (window.IMMEDIATE_PRINT)
+    {
+		var r = false;
+		
+		s.onload = s.onreadystatechange = function()
+		{
+			if (!r && (!this.readyState || this.readyState == 'complete'))
+			{
+	      		MathJax.Hub.Queue(function ()
+	      		{
+	      			window.print();
+	      		});
+			}
+		};
+    }
+	
+	var t = document.getElementsByTagName('script')[0];
+			  	
+  	if (t != null)
+  	{
+  		t.parentNode.insertBefore(s, t);
+  	}
+})();

+ 459 - 245
src/main/webapp/js/mxgraph/Graph.js

@@ -254,11 +254,13 @@ Graph = function(container, model, renderHint, stylesheet, themes, standalone)
 		    		{
 			   			var handler = this.selectionCellsHandler.getHandler(state.cell);
 
-			   			// Cell custom handles have precedence over row and col resize
+			   			// Cell handles have precedence over row and col resize
 		    			if (handler == null || handler.getHandleForEvent(me) == null)
 		    			{
 				    		var box = new mxRectangle(me.getGraphX(), me.getGraphY());
-			    			box.grow(mxShape.prototype.svgStrokeTolerance - 1);
+			    			box.grow(mxEvent.isTouchEvent(me.getEvent()) ?
+			    				mxShape.prototype.svgStrokeTolerance - 1 :
+			    				mxShape.prototype.svgStrokeTolerance / 2);
 			    			
 			    			if (this.isTableCell(state.cell))
 			    			{
@@ -519,7 +521,7 @@ Graph = function(container, model, renderHint, stylesheet, themes, standalone)
 				    		else
 				    		{
 				    			var box = new mxRectangle(me.getGraphX(), me.getGraphY());
-			    				box.grow(mxShape.prototype.svgStrokeTolerance - 1);
+			    				box.grow(mxShape.prototype.svgStrokeTolerance / 2);
 	
 					    		if (this.isTableCell(state.cell))
 					    		{
@@ -2055,7 +2057,12 @@ Graph.prototype.initLayoutManager = function()
 	
 	this.layoutManager.getLayout = function(cell, eventName)
 	{
-		if (eventName != mxEvent.BEGIN_UPDATE)
+		var parent = this.graph.model.getParent(cell);
+		
+		// Executes layouts from top to bottom except for nested layouts where
+		// child layouts are executed before and after the parent layout runs
+		// in case the layout changes the size of the child cell
+		if (eventName != mxEvent.BEGIN_UPDATE || this.hasLayout(parent, eventName))
 		{
 			var style = this.graph.getCellStyle(cell);
 	
@@ -3000,7 +3007,8 @@ Graph.prototype.getLinkForCell = function(cell)
 };
 
 /**
- * Overrides label orientation for collapsed swimlanes inside stack.
+ * Overrides label orientation for collapsed swimlanes inside stack and
+ * for partial rectangles inside tables.
  */
 Graph.prototype.getCellStyle = function(cell)
 {
@@ -3222,26 +3230,6 @@ Graph.prototype.isContainer = function(cell)
 	}
 };
 
-/**
- * Adds a expand style.
- */
-Graph.prototype.isExtendParent = function(cell)
-{
-	var parent = this.model.getParent(cell);
-	
-	if (parent != null)
-	{
-		var style = this.getCurrentCellStyle(parent);
-		
-		if (style['expand'] != null)
-		{
-			return style['expand'] != '0';
-		}
-	}
-	
-	return mxGraph.prototype.isExtendParent.apply(this, arguments);
-};
-
 /**
  * Adds a connectable style.
  */
@@ -4471,13 +4459,44 @@ Graph.prototype.createTable = function(rowCount, colCount, w, h, title, startSiz
 		((title != null) ? 'swimlane;startSize=' + startSize + ';' : '') +
 		'html=1;whiteSpace=wrap;container=1;collapsible=0;childLayout=tableLayout;'),
 		this.createParent(this.createVertex(null, null, '', 0, 0, colCount * w, h,
-    		'html=1;whiteSpace=wrap;collapsible=0;dropTarget=0;pointerEvents=0;fillColor=none;' +
-    		'strokeColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;'),
+    		'shape=partialRectangle;html=1;whiteSpace=wrap;collapsible=0;dropTarget=0;pointerEvents=0;' +
+    		'fillColor=none;strokeColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;'),
 			this.createVertex(null, null, '', 0, 0, w, h,
 				'shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;fillColor=none;'),
 				colCount, w, 0), rowCount, 0, h);
 };
 
+/**
+ * Sets the values for the cells and rows in the given table and returns the table.
+ */
+Graph.prototype.setTableValues = function(table, values, rowValues)
+{
+	var rows = this.model.getChildCells(table, true);
+	
+	for (var i = 0; i < rows.length; i++)
+	{
+		if (rowValues != null)
+		{
+			rows[i].value = rowValues[i];
+		}	
+		
+		if (values != null)
+		{
+			var cells = this.model.getChildCells(rows[i], true);
+			
+			for (var j = 0; j < cells.length; j++)
+			{
+				if (values[i][j] != null)
+				{
+					cells[j].value = values[i][j];
+				}
+			}
+		}
+	}
+	
+	return table;
+};
+
 /**
  * 
  */
@@ -4545,8 +4564,9 @@ Graph.prototype.isTable = function(cell)
 /**
  * Updates the row and table heights.
  */
-Graph.prototype.setTableRowHeight = function(row, dy)
+Graph.prototype.setTableRowHeight = function(row, dy, extend)
 {
+	extend = (extend != null) ? extend : true;
 	var model = this.getModel();
 	
 	model.beginUpdate();
@@ -4561,15 +4581,47 @@ Graph.prototype.setTableRowHeight = function(row, dy)
 			rgeo.height += dy;
 			model.setGeometry(row, rgeo);
 			
-			// Updates height of table
 			var table = model.getParent(row);
+			var rows = model.getChildCells(table, true);
+			
+			// Shifts and resizes neighbor row
+			if (!extend)
+			{
+				var index = mxUtils.indexOf(rows, row);
+	
+				if (index < rows.length - 1)
+				{
+					var nextRow = rows[index + 1];
+					var geo = this.getCellGeometry(nextRow);
+				
+					if (geo != null)
+					{
+						geo = geo.clone();
+						geo.y += dy;
+						geo.height -= dy;
+						
+						model.setGeometry(nextRow, geo);
+					}
+				}
+			}
+			
+			// Updates height of table
 			var tgeo = this.getCellGeometry(table);
 			
 			if (tgeo != null)
 			{
-				tgeo = tgeo.clone();
-				tgeo.height += dy;
-				model.setGeometry(table, tgeo);
+				// Always extends for last row
+				if (!extend)
+				{
+					extend = row == rows[rows.length - 1];
+				}
+				
+				if (extend)
+				{
+					tgeo = tgeo.clone();
+					tgeo.height += dy;
+					model.setGeometry(table, tgeo);
+				}
 			}
 		}
 	}
@@ -4582,8 +4634,10 @@ Graph.prototype.setTableRowHeight = function(row, dy)
 /**
  * Updates column width and row height.
  */
-Graph.prototype.setTableColumnWidth = function(col, dx)
+Graph.prototype.setTableColumnWidth = function(col, dx, extend)
 {
+	extend = (extend != null) ? extend : false;
+	
 	var model = this.getModel();
 	var row = model.getParent(col);
 	var table = model.getParent(row);
@@ -4621,13 +4675,18 @@ Graph.prototype.setTableColumnWidth = function(col, dx)
 				{
 					geo = geo.clone();
 					geo.x += dx;
-					geo.width -= dx;
+					
+					if (!extend)
+					{
+						geo.width -= dx;
+					}
+					
 					model.setGeometry(cell, geo);
 				}
 			}
 		}
 		
-		if (lastColumn)
+		if (lastColumn || extend)
 		{
 			// Updates width of table
 			var tgeo = this.getCellGeometry(table);
@@ -4749,8 +4808,8 @@ TableLayout.prototype.layoutRow = function(row, positions, height)
 		{
 			cell = cell.clone();
 			
-			cell.y = 0;
-			cell.height = height;
+			cell.y = off.y;
+			cell.height = height - off.y - off.height;
 			
 			if (positions != null)
 			{
@@ -6013,6 +6072,28 @@ if (typeof mxVertexHandler != 'undefined')
 			return codec.encode(model);
 		};
 		
+		/**
+		 * Overridden to add expand style.
+		 */
+		var graphIsExtendParent = Graph.prototype.isExtendParent;
+		Graph.prototype.isExtendParent = function(cell)
+		{
+			var parent = this.model.getParent(cell);
+			
+			if (parent != null)
+			{
+				var style = this.getCurrentCellStyle(parent);
+				
+				if (style['expand'] != null)
+				{
+					return style['expand'] != '0';
+				}
+			}
+			
+			return graphIsExtendParent.apply(this, arguments) &&
+				(parent == null || !this.isTable(parent));
+		};
+		
 		/**
 		 * Overridden to use table cell instead of table as parent.
 		 */
@@ -6100,6 +6181,44 @@ if (typeof mxVertexHandler != 'undefined')
 		Graph.prototype.moveCells = function(cells, dx, dy, clone, target, evt, mapping)
 		{
 			mapping = (mapping != null) ? mapping : new Object();
+			
+			// Updates source and target table heights for moving rows
+			for (var i = 0; i < cells.length; i++)
+			{
+				if (target != null && this.isTableRow(cells[i]))
+				{
+					var parent = this.model.getParent(cells[i]);
+					var row = this.getCellGeometry(cells[i]);
+					
+					if (parent != null && row != null &&
+						this.isTable(parent) &&
+						this.isTable(target) &&
+						(clone || parent != target))
+					{
+						if (!clone)
+						{
+							var table = this.getCellGeometry(parent);
+					
+							if (table != null)
+							{
+								table = table.clone();
+								table.height -= row.height;
+								this.model.setGeometry(parent, table);
+							}
+						}
+
+						var table = this.getCellGeometry(target);
+				
+						if (table != null)
+						{
+							table = table.clone();
+							table.height += row.height;
+							this.model.setGeometry(target, table);
+						}
+					}
+				}
+			}
+			
 			var result = graphMoveCells.apply(this, arguments);
 			
 			if (clone)
@@ -6127,10 +6246,52 @@ if (typeof mxVertexHandler != 'undefined')
 				{
 					if (this.isTableCell(cells[i]))
 					{
-						this.labelChanged(cells[i], '');
+						var row = this.model.getParent(cells[i]);
+						var table = this.model.getParent(row);
+						
+						// Removes table if one cell in one row left
+						if (this.model.getChildCount(row) == 1 &&
+							this.model.getChildCount(table) == 1)
+						{
+							if (mxUtils.indexOf(cells, table) < 0 &&
+								mxUtils.indexOf(result, table) < 0)
+							{
+								result.push(table);
+							}
+						}
+						else
+						{
+							this.labelChanged(cells[i], '');
+						}
 					}
 					else
 					{
+						// Deletes table if all rows are removed
+						if (this.isTableRow(cells[i]))
+						{
+							var table = this.model.getParent(cells[i]);
+							
+							if (mxUtils.indexOf(cells, table) < 0 &&
+								mxUtils.indexOf(result, table) < 0)
+							{
+								var rows = this.model.getChildCells(table, true);
+								var deleteCount = 0;
+								
+								for (var j = 0; j < rows.length; j++)
+								{
+									if (mxUtils.indexOf(cells, rows[j]) >= 0)
+									{
+										deleteCount++;
+									}
+								}
+								
+								if (deleteCount == rows.length)
+								{
+									result.push(table);
+								}
+							}
+						}
+						
 						result.push(cells[i]);
 					}
 				}
@@ -6422,13 +6583,17 @@ if (typeof mxVertexHandler != 'undefined')
 						if (geo != null)
 						{
 							// Rotates the size and position in the geometry
-							geo = geo.clone();
-							geo.x += geo.width / 2 - geo.height / 2;
-							geo.y += geo.height / 2 - geo.width / 2;
-							var tmp = geo.width;
-							geo.width = geo.height;
-							geo.height = tmp;
-							model.setGeometry(cell, geo);
+							if (!this.isTable(cell) && !this.isTableRow(cell) &&
+								!this.isTableCell(cell))
+							{
+								geo = geo.clone();
+								geo.x += geo.width / 2 - geo.height / 2;
+								geo.y += geo.height / 2 - geo.width / 2;
+								var tmp = geo.width;
+								geo.width = geo.height;
+								geo.height = tmp;
+								model.setGeometry(cell, geo);
+							}
 							
 							// Reads the current direction and advances by 90 degrees
 							var state = this.view.getState(cell);
@@ -6775,16 +6940,6 @@ if (typeof mxVertexHandler != 'undefined')
 			this.model.setValue(cell, value);
 		};
 		
-		
-		/**
-		 * Overridden to stop extending tables.
-		 */
-		var graphIsExtendParent = Graph.prototype.isExtendParent; 
-		Graph.prototype.isExtendParent = function(cell)
-		{
-			return graphIsExtendParent.apply(this, arguments) && !this.isTable(cell);
-		};
-		
 		/**
 		 * Overridden to stop moving edge labels between cells.
 		 */
@@ -6936,18 +7091,18 @@ if (typeof mxVertexHandler != 'undefined')
 			if (mxUtils.hasScrollbars(this.container))
 			{
 				return new mxPoint(
-					this.snap((this.container.scrollLeft + this.container.clientWidth / 2) / this.view.scale -
-						this.view.translate.x - bbox.width / 2),
-					this.snap((this.container.scrollTop + this.container.clientHeight / 2) / this.view.scale -
-						this.view.translate.y - bbox.height / 2));
+					this.snap(Math.round((this.container.scrollLeft + this.container.clientWidth / 2) /
+						this.view.scale - this.view.translate.x - bbox.width / 2)),
+					this.snap(Math.round((this.container.scrollTop + this.container.clientHeight / 2) /
+						this.view.scale - this.view.translate.y - bbox.height / 2)));
 			}
 			else
 			{
 				return new mxPoint(
-					this.snap(this.container.clientWidth / 2 / this.view.scale -
-						this.view.translate.x - bbox.width / 2),
-					this.snap(this.container.clientHeight / 2 / this.view.scale -
-						this.view.translate.y - bbox.height / 2));
+					this.snap(Math.round(this.container.clientWidth / 2 / this.view.scale -
+						this.view.translate.x - bbox.width / 2)),
+					this.snap(Math.round(this.container.clientHeight / 2 / this.view.scale -
+						this.view.translate.y - bbox.height / 2)));
 			}
 		};
 		
@@ -8188,50 +8343,71 @@ if (typeof mxVertexHandler != 'undefined')
 			try
 			{
 				var table = cell;
-				var rows = null;
-				var index = 0;
+				var row = cell;
 				
 				if (this.isTableCell(cell))
 				{
-					var row = model.getParent(cell);
-					table = model.getParent(row);
-					index = mxUtils.indexOf(model.getChildCells(row, true), cell);
-				}
-				else if (this.isTableRow(cell))
-				{
-					table = model.getParent(cell);
-					index = model.getChildCells(cell, true).length - 1;
+					row = model.getParent(cell);
 				}
-				else if (this.isTable(cell))
+				
+				if (this.isTableRow(row))
 				{
-					rows = model.getChildCells(cell, true);
-					index = model.getChildCells(rows[0], true).length - 1;
+					table = model.getParent(row);
 				}
 				
-				var width = 0;
-				rows = (rows != null) ? rows : model.getChildCells(table, true);
+				var rows = model.getChildCells(table, true);
 				
-				for (var i = 0; i < rows.length; i++)
+				if (rows.length == 0)
 				{
-					var child = model.getChildCells(rows[i], true)[index];
-					model.remove(child);
+					model.remove(table);
+				}
+				else
+				{
+					if (!this.isTableRow(row))
+					{
+						row = rows[0];
+					}
 					
-					var geo = this.getCellGeometry(child);
+					var cells = model.getChildCells(row, true);
 					
-					if (geo != null)
+					if (cells.length <= 1)
 					{
-						width = Math.max(width, geo.width);
+						model.remove(table);
+					}
+					else
+					{
+						var index = cells.length - 1;
+						
+						if (this.isTableCell(cell))
+						{
+							index = mxUtils.indexOf(cells, cell);
+						}
+
+						var width = 0;
+		
+						for (var i = 0; i < rows.length; i++)
+						{
+							var child = model.getChildCells(rows[i], true)[index];
+							model.remove(child);
+							
+							var geo = this.getCellGeometry(child);
+							
+							if (geo != null)
+							{
+								width = Math.max(width, geo.width);
+							}
+						}
+						
+						var tableGeo = this.getCellGeometry(table);
+						
+						if (tableGeo != null)
+						{
+							tableGeo = tableGeo.clone();
+							tableGeo.width -= width;
+							
+							model.setGeometry(table, tableGeo);
+						}
 					}
-				}
-				
-				var tableGeo = this.getCellGeometry(table);
-				
-				if (tableGeo != null)
-				{
-					tableGeo = tableGeo.clone();
-					tableGeo.width -= width;
-					
-					model.setGeometry(table, tableGeo);
 				}
 			}
 			finally
@@ -8250,37 +8426,52 @@ if (typeof mxVertexHandler != 'undefined')
 			
 			try
 			{
+				var table = cell;
 				var row = cell;
 				
 				if (this.isTableCell(cell))
 				{
 					row = model.getParent(cell);
+					cell = row;
 				}
-				else if (this.isTable(cell))
+				
+				if (this.isTableRow(cell))
 				{
-					var rows = model.getChildCells(cell, true);
-					row = rows[rows.length - 1];
+					table = model.getParent(row);
 				}
 				
-				var table = model.getParent(row);
-				model.remove(row);
-				var height = 0;
-				
-				var geo = this.getCellGeometry(row);
+				var rows = model.getChildCells(table, true);
 				
-				if (geo != null)
+				if (rows.length <= 1)
 				{
-					height = geo.height;
+					model.remove(table);
 				}
-				
-				var tableGeo = this.getCellGeometry(table);
-				
-				if (tableGeo != null)
+				else
 				{
-					tableGeo = tableGeo.clone();
-					tableGeo.height -= height;
+					if (!this.isTableRow(row))
+					{
+						row = rows[rows.length - 1];
+					}	
 					
-					model.setGeometry(table, tableGeo);
+					model.remove(row);
+					var height = 0;
+					
+					var geo = this.getCellGeometry(row);
+					
+					if (geo != null)
+					{
+						height = geo.height;
+					}
+					
+					var tableGeo = this.getCellGeometry(table);
+					
+					if (tableGeo != null)
+					{
+						tableGeo = tableGeo.clone();
+						tableGeo.height -= height;
+						
+						model.setGeometry(table, tableGeo);
+					}
 				}
 			}
 			finally
@@ -9316,7 +9507,46 @@ if (typeof mxVertexHandler != 'undefined')
 				this.hint = null;
 			}
 		};
-		
+								
+		/**
+		 * Overridden to allow for shrinking pools when lanes are resized.
+		 */
+		var stackLayoutResizeCell = mxStackLayout.prototype.resizeCell;
+		mxStackLayout.prototype.resizeCell = function(cell, bounds)
+		{
+			stackLayoutResizeCell.apply(this, arguments);
+			var style = this.graph.getCellStyle(cell);
+				
+			if (style['childLayout'] == null)
+			{
+				var parent = this.graph.model.getParent(cell);
+				var geo = (parent != null) ? this.graph.getCellGeometry(parent) : null;
+			
+				if (geo != null)
+				{
+					style = this.graph.getCellStyle(parent);
+					
+					if (style['childLayout'] == 'stackLayout')
+					{
+						var horizontal = mxUtils.getValue(style, 'horizontalStack', '1') == '1';
+						geo = geo.clone();
+						
+						if (horizontal)
+						{
+							geo.height = bounds.height;
+						}
+						else
+						{
+							geo.width = bounds.width;
+							
+						}
+			
+						this.graph.model.setGeometry(parent, geo);			
+					}
+				}
+			}
+		};
+
 		/**
 		 * Shows handle for table instead of rows and cells.
 		 */
@@ -9530,153 +9760,137 @@ if (typeof mxVertexHandler != 'undefined')
 					handles = [];
 				}
 				
-				// Finds rows
-				var rows = [];
+				// Adds handles for rows and columns
+				var rows = graph.view.getCellStates(model.getChildCells(this.state.cell, true));
 				
-				for (var i = 0; i < model.getChildCount(this.state.cell); i++)
+				if (rows.length > 0)
 				{
-					var row = this.graph.view.getState(model.getChildAt(this.state.cell, i));
-					
-					if (row != null && model.isVertex(row.cell))
-					{
-						rows.push(row); 
-					}
-				}
-				
-				// Finds columns in row
-				var cols = [];
-				
-				for (var i = 0; i < model.getChildCount(rows[0].cell); i++)
-				{
-					var col = this.graph.view.getState(model.getChildAt(rows[0].cell, i));
-					
-					if (col != null && model.isVertex(col.cell))
-					{
-						cols.push(col); 
-					}
-				}
-				
-				// Adds col width handles
-				for (var i = 0; i < cols.length; i++)
-				{
-					(mxUtils.bind(this, function(index)
+					var cols = graph.view.getCellStates(model.getChildCells(rows[0].cell, true));
+	
+					// Adds column width handles
+					for (var i = 0; i < cols.length; i++)
 					{
-						var colState = cols[index];
-						var nextCol = (index < cols.length - 1) ? cols[index + 1] : null;
-						
-						// Adds handle to change column width
-						var shape = new mxLine(new mxRectangle(), mxConstants.NONE, 1, true);
-						shape.isDashed = sel.isDashed;
-						var handle = new mxHandle(colState, 'col-resize', null, shape);
-						handle.tableHandle = true;
-						var dx = 0;
-						
-						handle.shape.node.parentNode.insertBefore(handle.shape.node,
-							handle.shape.node.parentNode.firstChild);
-
-						handle.redraw = function()
+						(mxUtils.bind(this, function(index)
 						{
-							if (this.shape != null && this.state.shape != null)
+							var colState = cols[index];
+							var nextCol = (index < cols.length - 1) ? cols[index + 1] : null;
+							
+							var shape = new mxLine(new mxRectangle(), mxConstants.NONE, 1, true);
+							shape.isDashed = sel.isDashed;
+							
+							var handle = new mxHandle(colState, 'col-resize', null, shape);
+							handle.tableHandle = true;
+							var dx = 0;
+							
+							handle.shape.node.parentNode.insertBefore(handle.shape.node,
+								handle.shape.node.parentNode.firstChild);
+	
+							handle.redraw = function()
 							{
-								var start = graph.getActualStartSize(tableState.cell);
-								this.shape.stroke = (dx == 0) ? mxConstants.NONE : sel.stroke;
-								this.shape.bounds.x = this.state.x + this.state.width +
-									dx * this.graph.view.scale;
-								this.shape.bounds.width = 1;
-								this.shape.bounds.y = tableState.y + ((index == cols.length - 1) ?
-									0 : start.y * this.graph.view.scale);
-								this.shape.bounds.height = tableState.height -
-									((index == cols.length - 1) ? 0 :
-									(start.height + start.y) * this.graph.view.scale);
-								this.shape.redraw();
-							}
-						};
-						
-						handle.setPosition = function(bounds, pt, me)
-						{
-							dx = Math.max(Graph.minTableColumnWidth - bounds.width,
-								pt.x - bounds.x - bounds.width);
+								if (this.shape != null && this.state.shape != null)
+								{
+									var start = graph.getActualStartSize(tableState.cell);
+									this.shape.stroke = (dx == 0) ? mxConstants.NONE : sel.stroke;
+									this.shape.bounds.x = this.state.x + this.state.width +
+										dx * this.graph.view.scale;
+									this.shape.bounds.width = 1;
+									this.shape.bounds.y = tableState.y + ((index == cols.length - 1) ?
+										0 : start.y * this.graph.view.scale);
+									this.shape.bounds.height = tableState.height -
+										((index == cols.length - 1) ? 0 :
+										(start.height + start.y) * this.graph.view.scale);
+									this.shape.redraw();
+								}
+							};
 							
-							if (nextCol != null)
+							handle.setPosition = function(bounds, pt, me)
 							{
-								dx = Math.min(nextCol.x + nextCol.width - colState.x -
-									colState.width - Graph.minTableColumnWidth, dx);
-							}
-						};
-						
-						handle.execute = function()
-						{
-							if (dx != 0)
+								dx = Math.max(Graph.minTableColumnWidth - bounds.width,
+									pt.x - bounds.x - bounds.width);
+								
+								if (nextCol != null)
+								{
+									dx = Math.min(nextCol.x + nextCol.width - colState.x -
+										colState.width - Graph.minTableColumnWidth, dx);
+								}
+							};
+							
+							handle.execute = function(me)
 							{
-								graph.setTableColumnWidth(this.state.cell, dx);
-							}
+								if (dx != 0)
+								{
+									graph.setTableColumnWidth(this.state.cell, dx,
+										mxEvent.isShiftDown(me.getEvent()));
+								}
+								
+								dx = 0;
+							};
 							
-							dx = 0;
-						};
-						
-						handle.reset = function()
-						{
-							dx = 0;
-						};
-						
-						handles.push(handle);
-					}))(i);
-				}
-				
-				// Adds row height handles
-				for (var i = 0; i < rows.length; i++)
-				{
-					(mxUtils.bind(this, function(index)
+							handle.reset = function()
+							{
+								dx = 0;
+							};
+							
+							handles.push(handle);
+						}))(i);
+					}
+					
+					// Adds row height handles
+					for (var i = 0; i < rows.length; i++)
 					{
-						var rowState = rows[index];
-
-						// Adds handle to change row height
-						var shape = new mxLine(new mxRectangle(), mxConstants.NONE, 1);
-						shape.isDashed = sel.isDashed;
-						var handle = new mxHandle(rowState, 'row-resize', null, shape);
-						handle.tableHandle = true;
-						var dy = 0;
-
-						handle.shape.node.parentNode.insertBefore(handle.shape.node,
-							handle.shape.node.parentNode.firstChild);
-						
-						handle.redraw = function()
+						(mxUtils.bind(this, function(index)
 						{
-							if (this.shape != null && this.state.shape != null)
+							var rowState = rows[index];
+	
+							var shape = new mxLine(new mxRectangle(), mxConstants.NONE, 1);
+							shape.isDashed = sel.isDashed;
+							
+							var handle = new mxHandle(rowState, 'row-resize', null, shape);
+							handle.tableHandle = true;
+							var dy = 0;
+	
+							handle.shape.node.parentNode.insertBefore(handle.shape.node,
+								handle.shape.node.parentNode.firstChild);
+							
+							handle.redraw = function()
 							{
-								this.shape.stroke = (dy == 0) ? mxConstants.NONE : sel.stroke;;
-								this.shape.bounds.x = this.state.x;
-								this.shape.bounds.width = this.state.width;
-								this.shape.bounds.y = this.state.y + this.state.height +
-									dy * this.graph.view.scale;
-								this.shape.bounds.height = 1;
-								this.shape.redraw();
-							}
-						};
-						
-						handle.setPosition = function(bounds, pt, me)
-						{
-							dy = Math.max(Graph.minTableRowHeight - bounds.height,
-								pt.y - bounds.y - bounds.height);
-						};
-						
-						handle.execute = function()
-						{
-							if (dy != 0)
+								if (this.shape != null && this.state.shape != null)
+								{
+									this.shape.stroke = (dy == 0) ? mxConstants.NONE : sel.stroke;;
+									this.shape.bounds.x = this.state.x;
+									this.shape.bounds.width = this.state.width;
+									this.shape.bounds.y = this.state.y + this.state.height +
+										dy * this.graph.view.scale;
+									this.shape.bounds.height = 1;
+									this.shape.redraw();
+								}
+							};
+							
+							handle.setPosition = function(bounds, pt, me)
 							{
-								graph.setTableRowHeight(this.state.cell, dy);
-							}
+								dy = Math.max(Graph.minTableRowHeight - bounds.height,
+									pt.y - bounds.y - bounds.height);
+							};
 							
-							dy = 0;
-						};
-						
-						handle.reset = function()
-						{
-							dy = 0;
-						};
-						
-						handles.push(handle);
-					}))(i);
+							handle.execute = function(me)
+							{
+								if (dy != 0)
+								{
+									graph.setTableRowHeight(this.state.cell, dy,
+										!mxEvent.isShiftDown(me.getEvent()));
+								}
+								
+								dy = 0;
+							};
+							
+							handle.reset = function()
+							{
+								dy = 0;
+							};
+							
+							handles.push(handle);
+						}))(i);
+					}
 				}
 			}
 			

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 25 - 5
src/main/webapp/js/mxgraph/Sidebar.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 548 - 545
src/main/webapp/js/viewer.min.js


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

@@ -3,472 +3,7 @@ importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox
 if (workbox)
 {
 	// This is replaced in build process
-	workbox.precaching.precacheAndRoute([
-  {
-    "url": "js/app.min.js",
-    "revision": "cff100af7cbd76d60113c1c8269bcda7"
-  },
-  {
-    "url": "js/extensions.min.js",
-    "revision": "ecd53ce33feef17a0ac6be648bfd056f"
-  },
-  {
-    "url": "js/stencils.min.js",
-    "revision": "be66334d69e1fc4bead3535f87dfa611"
-  },
-  {
-    "url": "js/shapes.min.js",
-    "revision": "90d8f1edfecfe931438f4cd995bed5b0"
-  },
-  {
-    "url": "index.html",
-    "revision": "c356c0a671c0e709c0b49d17dbf99cf9"
-  },
-  {
-    "url": "open.html",
-    "revision": "f7de43d2d8d04cdc6f0d923942fad4d4"
-  },
-  {
-    "url": "app.html",
-    "revision": "e28e1516e715420680ab6f3bc3fe2288"
-  },
-  {
-    "url": "styles/grapheditor.css",
-    "revision": "4b2c4b76bae7c1a7c7df74fff3cb75af"
-  },
-  {
-    "url": "styles/atlas.css",
-    "revision": "40f54334c7a62821dbf1f7c7d8ad62cc"
-  },
-  {
-    "url": "styles/dark.css",
-    "revision": "bd0e851bff9047866cfa0683fe89ac38"
-  },
-  {
-    "url": "js/croppie/croppie.min.css",
-    "revision": "fc297c9002c79c15a132f13ee3ec427e"
-  },
-  {
-    "url": "js/dropbox/Dropbox-sdk.min.js",
-    "revision": "4b9842892aa37b156db0a8364b7a83b0"
-  },
-  {
-    "url": "js/onedrive/OneDrive.js",
-    "revision": "d82b9c14d7a069efabef719a8a5f3975"
-  },
-  {
-    "url": "math/MathJax.js",
-    "revision": "c32a502b8b4a6bd6bad8ddb1b59e9e72"
-  },
-  {
-    "url": "resources/dia.txt",
-    "revision": "566c40fd69752783a093b32b28eb98cf"
-  },
-  {
-    "url": "resources/dia_cs.txt",
-    "revision": "2a181c4fd68559bab01d3db5011ed050"
-  },
-  {
-    "url": "resources/dia_my.txt",
-    "revision": "566c40fd69752783a093b32b28eb98cf"
-  },
-  {
-    "url": "resources/dia_am.txt",
-    "revision": "138b6875d12860b14ff1aaaa6bb80da8"
-  },
-  {
-    "url": "resources/dia_ml.txt",
-    "revision": "52a9e39c1e6bb367d3b764e15903c2b6"
-  },
-  {
-    "url": "resources/dia_uk.txt",
-    "revision": "52c9ee7f8fe5ded1b412454ca9b76dd5"
-  },
-  {
-    "url": "resources/dia_bg.txt",
-    "revision": "519967d576645a4e77385e5c874d7a73"
-  },
-  {
-    "url": "resources/dia_ca.txt",
-    "revision": "17025b72fd74c4c894a7909970a069e8"
-  },
-  {
-    "url": "resources/dia_th.txt",
-    "revision": "f95aab8985ae3612fc9d64e62f035098"
-  },
-  {
-    "url": "resources/dia_bs.txt",
-    "revision": "8c5cc8ed52dd99d4c4d8a2c6deedc430"
-  },
-  {
-    "url": "resources/dia_id.txt",
-    "revision": "09b0be439257a5e648c9f4b3d1ee2706"
-  },
-  {
-    "url": "resources/dia_sk.txt",
-    "revision": "e34a5eb4cab6000eccd82b7b92a2d793"
-  },
-  {
-    "url": "resources/dia_ro.txt",
-    "revision": "978ebac9f57d036b5e4d20a505f209fd"
-  },
-  {
-    "url": "resources/dia_gl.txt",
-    "revision": "097d79496f590ef346093fe117da183d"
-  },
-  {
-    "url": "resources/dia_es.txt",
-    "revision": "94be775c840c8f010f21727e37a91c3e"
-  },
-  {
-    "url": "resources/dia_eu.txt",
-    "revision": "467b2121c8e2313421f81335d8bcd646"
-  },
-  {
-    "url": "resources/dia_ko.txt",
-    "revision": "588d651ae4680fd01613b0dc4b7a009f"
-  },
-  {
-    "url": "resources/dia_si.txt",
-    "revision": "566c40fd69752783a093b32b28eb98cf"
-  },
-  {
-    "url": "resources/dia_kn.txt",
-    "revision": "51a5e373e5b17bef75c29ae2d133f2ef"
-  },
-  {
-    "url": "resources/dia_hu.txt",
-    "revision": "b4b5fc9d2202888b3b241540921180e9"
-  },
-  {
-    "url": "resources/dia_fi.txt",
-    "revision": "ff7ad12f24bcbf83feabd80d31481aa5"
-  },
-  {
-    "url": "resources/dia_da.txt",
-    "revision": "64b4d600c995997e4a6b865bf2f9297c"
-  },
-  {
-    "url": "resources/dia_de.txt",
-    "revision": "9999834cf05ea01cfa66ceb9c3132015"
-  },
-  {
-    "url": "resources/dia_sl.txt",
-    "revision": "bdedc03d6a161f0785cd3c86de9812c9"
-  },
-  {
-    "url": "resources/dia_it.txt",
-    "revision": "63a4b879a08a807d2b1f97fc7e9081e5"
-  },
-  {
-    "url": "resources/dia_hr.txt",
-    "revision": "39f9fd7bc75d0dfe797de20cdafa321a"
-  },
-  {
-    "url": "resources/dia_he.txt",
-    "revision": "a8211c153af5628ad8abb2b3c891e806"
-  },
-  {
-    "url": "resources/dia_pt.txt",
-    "revision": "e7a16acf016d9c44df55b046f646e592"
-  },
-  {
-    "url": "resources/dia_zh-tw.txt",
-    "revision": "9dcb5f9edd1ef7f3fe114d146d15e4de"
-  },
-  {
-    "url": "resources/dia_et.txt",
-    "revision": "c983ffd38089ef14f0fb4b79fba3a270"
-  },
-  {
-    "url": "resources/dia_ja.txt",
-    "revision": "deeeb9ee6b528f5c97623daf1c41abde"
-  },
-  {
-    "url": "resources/dia_hi.txt",
-    "revision": "9a0eea69b84323f98950b5d290c8649a"
-  },
-  {
-    "url": "resources/dia_eo.txt",
-    "revision": "590289f937014f78a3c119ece175d41b"
-  },
-  {
-    "url": "resources/dia_fa.txt",
-    "revision": "462f15e995c8b8f8039fbfdc68c4e7a3"
-  },
-  {
-    "url": "resources/dia_sw.txt",
-    "revision": "c2473d4373a1706a3adbd24974435ced"
-  },
-  {
-    "url": "resources/dia_pl.txt",
-    "revision": "55c8ad18b1b85174968131e6722c53d4"
-  },
-  {
-    "url": "resources/dia_pt-br.txt",
-    "revision": "a961725bf74365dbf085deaa52101cfc"
-  },
-  {
-    "url": "resources/dia_sv.txt",
-    "revision": "192d03ba5387a08af56596c8d9dcf2b1"
-  },
-  {
-    "url": "resources/dia_el.txt",
-    "revision": "a0a748122aba16a095daff7607fdd4a1"
-  },
-  {
-    "url": "resources/dia_sr.txt",
-    "revision": "1de85bc0c9a08d0c72e875db8830a85a"
-  },
-  {
-    "url": "resources/dia_fr.txt",
-    "revision": "43084b576112d2f748215c870b341c48"
-  },
-  {
-    "url": "resources/dia_ru.txt",
-    "revision": "a2118b3326b618ce00faf147d82e8ba4"
-  },
-  {
-    "url": "resources/dia_gu.txt",
-    "revision": "fba35ba1d8f467a293d77f75a816e12f"
-  },
-  {
-    "url": "resources/dia_ar.txt",
-    "revision": "2ebfed9bd7456fb9190e545c2911708f"
-  },
-  {
-    "url": "resources/dia_tr.txt",
-    "revision": "5d4bfd15d0034d048d216cbad237c7ad"
-  },
-  {
-    "url": "resources/dia_te.txt",
-    "revision": "ef46829fd7b623514cd282c1c8e7c771"
-  },
-  {
-    "url": "resources/dia_lt.txt",
-    "revision": "25ec3135ad03308bf4bdfa501756ed28"
-  },
-  {
-    "url": "resources/dia_lv.txt",
-    "revision": "3124c0bd6ccabc9abcf793275df7efeb"
-  },
-  {
-    "url": "resources/dia_mr.txt",
-    "revision": "b366684f0d5931339ee0223d0ed4a2aa"
-  },
-  {
-    "url": "resources/dia_ms.txt",
-    "revision": "857a51c45995cd311dbd706b63cb7b6b"
-  },
-  {
-    "url": "resources/dia_nl.txt",
-    "revision": "c64e6e231ae284cc926e518c880531a4"
-  },
-  {
-    "url": "resources/dia_fil.txt",
-    "revision": "98048d4062bfaea79f891b60f83629a4"
-  },
-  {
-    "url": "resources/dia_zh.txt",
-    "revision": "d868e4fd1f5acc16ad6d67d26d360653"
-  },
-  {
-    "url": "resources/dia_bn.txt",
-    "revision": "6c9a6a3e9f9ce56be1702d7423abf275"
-  },
-  {
-    "url": "resources/dia_no.txt",
-    "revision": "f7e447e358e0b19af1434d7e08accd89"
-  },
-  {
-    "url": "resources/dia_vi.txt",
-    "revision": "2341b6b28875fb87af91a8f180dbff25"
-  },
-  {
-    "url": "resources/dia_ta.txt",
-    "revision": "f3e42d2330c443134a0ae22feee81d53"
-  },
-  {
-    "url": "favicon.ico",
-    "revision": "fab2d88b37c72d83607527573de45281"
-  },
-  {
-    "url": "images/manifest.json",
-    "revision": "078a1ee03d5b64100c5b760d8e73dc88"
-  },
-  {
-    "url": "images/logo.png",
-    "revision": "89630b64b911ebe0daa3dfe442087cfa"
-  },
-  {
-    "url": "images/drawlogo.svg",
-    "revision": "4bf4d14ebcf072d8bd4c5a1c89e88fc6"
-  },
-  {
-    "url": "images/drawlogo-gray.svg",
-    "revision": "0aabacbc0873816e1e09e4736ae44c7d"
-  },
-  {
-    "url": "images/apple-touch-icon.png",
-    "revision": "73da7989a23ce9a4be565ec65658a239"
-  },
-  {
-    "url": "images/favicon-16x16.png",
-    "revision": "1a79d5461a5d2bf21f6652e0ac20d6e5"
-  },
-  {
-    "url": "images/favicon-32x32.png",
-    "revision": "e3b92da2febe70bad5372f6f3474b034"
-  },
-  {
-    "url": "images/android-chrome-192x192.png",
-    "revision": "a886234330ea25d4ba1c47f678b00ef2"
-  },
-  {
-    "url": "images/android-chrome-512x512.png",
-    "revision": "959b5fac2453963ff6d60fb85e4b73fd"
-  },
-  {
-    "url": "images/delete.png",
-    "revision": "5f2350f2fd20f1a229637aed32ed8f29"
-  },
-  {
-    "url": "images/droptarget.png",
-    "revision": "bbf7f563fb6784de1ce96f329519b043"
-  },
-  {
-    "url": "images/help.png",
-    "revision": "9266c6c3915bd33c243d80037d37bf61"
-  },
-  {
-    "url": "images/download.png",
-    "revision": "35418dd7bd48d87502c71b578cc6c37f"
-  },
-  {
-    "url": "images/logo-flat.png",
-    "revision": "038070ab43aee6e54a791211859fc67b"
-  },
-  {
-    "url": "images/google-drive-logo.svg",
-    "revision": "5d9f2f5bbc7dcc252730a0072bb23059"
-  },
-  {
-    "url": "images/onedrive-logo.svg",
-    "revision": "3645b344ec0634c1290dd58d7dc87b97"
-  },
-  {
-    "url": "images/dropbox-logo.svg",
-    "revision": "e6be408c77cf9c82d41ac64fa854280a"
-  },
-  {
-    "url": "images/github-logo.svg",
-    "revision": "a1a999b69a275eac0cb918360ac05ae1"
-  },
-  {
-    "url": "images/gitlab-logo.svg",
-    "revision": "0faea8c818899e58533e153c44b10517"
-  },
-  {
-    "url": "images/trello-logo.svg",
-    "revision": "006fd0d7d70d7e95dc691674cb12e044"
-  },
-  {
-    "url": "images/osa_drive-harddisk.png",
-    "revision": "b954e1ae772087c5b4c6ae797e1f9649"
-  },
-  {
-    "url": "images/osa_database.png",
-    "revision": "c350d9d9b95f37b6cfe798b40ede5fb0"
-  },
-  {
-    "url": "images/google-drive-logo-white.svg",
-    "revision": "f329d8b1be7778515a85b93fc35d9f26"
-  },
-  {
-    "url": "images/dropbox-logo-white.svg",
-    "revision": "4ea8299ac3bc31a16f199ee3aec223bf"
-  },
-  {
-    "url": "images/onedrive-logo-white.svg",
-    "revision": "b3602fa0fc947009cff3f33a581cff4d"
-  },
-  {
-    "url": "images/github-logo-white.svg",
-    "revision": "537b1127b3ca0f95b45782d1304fb77a"
-  },
-  {
-    "url": "images/gitlab-logo-white.svg",
-    "revision": "5fede9ac2f394c716b8c23e3fddc3910"
-  },
-  {
-    "url": "images/trello-logo-white-orange.svg",
-    "revision": "e2a0a52ba3766682f138138d10a75eb5"
-  },
-  {
-    "url": "images/logo-confluence.png",
-    "revision": "ed1e55d44ae5eba8f999aba2c93e8331"
-  },
-  {
-    "url": "images/logo-jira.png",
-    "revision": "f8d460555a0d1f87cfd901e940666629"
-  },
-  {
-    "url": "images/clear.gif",
-    "revision": "db13c778e4382e0b55258d0f811d5d70"
-  },
-  {
-    "url": "images/spin.gif",
-    "revision": "487cbb40b9ced439aa1ad914e816d773"
-  },
-  {
-    "url": "images/checkmark.gif",
-    "revision": "ba764ce62f2bf952df5bbc2bb4d381c5"
-  },
-  {
-    "url": "images/hs.png",
-    "revision": "fefa1a03d92ebad25c88dca94a0b63db"
-  },
-  {
-    "url": "mxgraph/css/common.css",
-    "revision": "b5b7280ec98671bb6c3847a36bc7ea12"
-  },
-  {
-    "url": "mxgraph/images/maximize.gif",
-    "revision": "5cd13d6925493ab51e876694cc1c2ec2"
-  },
-  {
-    "url": "mxgraph/images/minimize.gif",
-    "revision": "8957741b9b0f86af9438775f2aadbb54"
-  },
-  {
-    "url": "mxgraph/images/close.gif",
-    "revision": "8b84669812ac7382984fca35de8da48b"
-  },
-  {
-    "url": "mxgraph/images/resize.gif",
-    "revision": "a6477612b3567a34033f9cac6184eed3"
-  },
-  {
-    "url": "mxgraph/images/separator.gif",
-    "revision": "7819742ff106c97da7a801c2372bbbe5"
-  },
-  {
-    "url": "mxgraph/images/window.gif",
-    "revision": "fd9a21dd4181f98052a202a0a01f18ab"
-  },
-  {
-    "url": "mxgraph/images/window-title.gif",
-    "revision": "3fb1d6c43246cdf991a11dfe826dfe99"
-  },
-  {
-    "url": "mxgraph/images/button.gif",
-    "revision": "00759bdc3ad218fa739f584369541809"
-  },
-  {
-    "url": "mxgraph/images/point.gif",
-    "revision": "83a43717b284902442620f61bc4e9fa6"
-  }
-],
+	workbox.precaching.precacheAndRoute([],
 	{
 		// Ignore all URL parameters
 		// FIXME: Using this /open redirects to /open.html so

+ 11 - 5
src/main/webapp/styles/dark.css

@@ -6,6 +6,10 @@ html body.geEditor div.mxPopupMenu {
 	background:#2a2a2a;
 	box-shadow:none;
 }
+html .geSidebarContainer button {
+	border: 1px solid #505759;
+	border-radius: 3px;
+}
 html body.geEditor div.mxPopupMenu hr {
 	background-color:#505759;
 }
@@ -13,7 +17,7 @@ html body .geTabContainer {
 	border-top:1px solid #505759;
 	background-color:#2a2a2a;
 }
-html body .geMenubarContainer .geItem:active {
+html body .geMenubarContainer .geItem:active, html .geSidebarContainer button:active {
 	opacity: 0.7;
 }
 html body, html body .geFooterContainer, html body #geFooterItem1, html body textarea,
@@ -27,14 +31,15 @@ html body a {
 	color:#337ab7;
 }
 html body div.mxRubberband {
-	border:1px dashed #ffffff !important; background:#505759 !important;
+	border:1px dashed #ffffff !important;
+	background:#505759 !important;
 }
 html body .geTemplate {
 	color:#000000;
 }
 html body .geToolbarContainer, html body .geSidebar, html body .geSidebarContainer .geTitle, html body input,
 html body textarea, html body button, html body .geColorBtn, html body .geBaseButton, html body .geSidebarTooltip,
-html body .geBaseButton {
+html body .geBaseButton, html .geSidebarContainer button {
 	background:#2a2a2a;
 	border-color:#505759;
 	box-shadow:none;
@@ -108,7 +113,8 @@ html body tr.mxPopupMenuItemHover {
 	color:#cccccc;
 }
 html body .geSidebarContainer .geTitle:hover, html body .geSidebarContainer .geItem:hover,
-html body .geMenubarContainer .geItem:hover, html body .geBaseButton:hover {
+html body .geMenubarContainer .geItem:hover, html body .geBaseButton:hover,
+html .geSidebarContainer button:hover {
 	background:#000000;
 }
 html body .geToolbarContainer .geSeparator {
@@ -120,7 +126,7 @@ html body .geHsplit, html body .geVsplit, html body table.mxPopupMenu hr {
 }
 html body .geToolbarContainer .geButton:hover, html body .geToolbarContainer .geButton:active,
 html body .geToolbarContainer .geLabel:hover, html body .geToolbarContainer .geLabel:active,
-html body .geHsplit:hover, html body .geVsplit:hover {
+html body .geHsplit:hover, html body .geVsplit:hover, html .geSidebarContainer button:active {
 	background-color:#000;
 }
 html body .geToolbar {