Gaudenz Alder 6 лет назад
Родитель
Сommit
8d6790795d

+ 6 - 0
ChangeLog

@@ -1,3 +1,9 @@
+20-JUL-2019: 11.0.1
+
+- Fixes math typesetting in legacy embed script
+- Fixes encoding issues for GitHub and GitLab
+- Fixes file handling issues for GitLab
+
 19-JUL-2019: 11.0.0
 
 - Adds Show more option in splash screen

+ 1 - 1
VERSION

@@ -1 +1 @@
-11.0.0
+11.0.1

+ 257 - 0
src/main/java/com/mxgraph/online/ConverterServlet.java

@@ -0,0 +1,257 @@
+package com.mxgraph.online;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileUploadException;
+import org.apache.commons.fileupload.disk.DiskFileItemFactory;
+import org.apache.commons.fileupload.servlet.ServletFileUpload;
+import org.apache.commons.io.FilenameUtils;
+
+//This servlet is an interface between draw.io and CloudConverter.
+//For EMF files, it detect its size and resize the huge images such that max dimension is MAX_DIM
+public class ConverterServlet  extends HttpServlet 
+{
+	private static final long serialVersionUID = -5084595244442555865L;
+
+	private static final int MAX_DIM = 5000;
+	private static final double EMF_10thMM2PXL = 26.458;
+	private static final String API_KEY_FILE_PATH = "/WEB-INF/cloud_convert_api_key";
+	private static final String CONVERT_SERVICE_URL = "https://api.cloudconvert.com/convert";
+	private static final String CRLF = "\r\n";
+	private static final String TWO_HYPHENS = "--";
+	private static final String BOUNDARY =  "----WebKitFormBoundary6XTanBMjO0kFwa3p"; //FIXME The boundary should not occur inside the file, it is very unlikely but still a possibility
+	
+	private static String API_KEY = null;
+	
+	private void readApiKey() 
+	{
+		if (API_KEY == null)
+		{
+			try 
+			{
+				API_KEY = Utils
+							.readInputStream(getServletContext()
+							.getResourceAsStream(API_KEY_FILE_PATH))
+							.replaceAll("\n", "");
+			} 
+			catch (IOException e) 
+			{
+				throw new RuntimeException("Invalid API key file/path");
+			}
+		}
+	}
+	
+	//Little Indian
+	private int fromByteArray(byte[] bytes, int start) 
+	{
+		return ((bytes[start + 3] & 0xFF) << 24) | 
+	           ((bytes[start + 2] & 0xFF) << 16) | 
+	           ((bytes[start + 1] & 0xFF) << 8 ) | 
+	           ((bytes[start] & 0xFF) << 0 );
+	}
+	/**
+	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
+	 */
+	protected void doPost(HttpServletRequest request,
+			HttpServletResponse response) throws ServletException, IOException
+	{
+		readApiKey();
+		
+		String inputformat = null, outputformat = null, fileName = null;
+		InputStream fileContent = null;
+		
+		try 
+		{
+	        List<FileItem> items = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(request);
+	        
+	        for (FileItem item : items) 
+	        {
+	            if (item.isFormField()) 
+	            {
+	                String fieldName = item.getFieldName();
+	                
+	                if ("inputformat".equals(fieldName))
+	                {
+	                	inputformat = item.getString();
+	                }
+	                else if ("outputformat".equals(fieldName))
+	                {
+	                	outputformat = item.getString();
+	                }
+	            }
+	            else
+	            {
+	            	//We expect only one file
+	                fileName = FilenameUtils.getName(item.getName());
+	                fileContent = item.getInputStream();
+	            }
+	        }
+	    } 
+		catch (FileUploadException e)
+		{
+	        throw new ServletException("Cannot parse multipart request.", e);
+	    }
+
+		if (inputformat == null || outputformat == null || fileContent == null)
+		{
+			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+		}
+		else
+		{
+		    HttpURLConnection con = null;
+			
+			try
+			{
+				URL obj = new URL(CONVERT_SERVICE_URL);
+				con = (HttpURLConnection) obj.openConnection();
+				con.setUseCaches(false);
+				con.setDoOutput(true);
+				
+				con.setRequestMethod("POST");
+				con.setRequestProperty("Connection", "Keep-Alive");
+				con.setRequestProperty("Cache-Control", "no-cache");
+				con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
+				
+				DataOutputStream postRequest = new DataOutputStream(con.getOutputStream());
+
+				byte[] data      = new byte[10240]; //10 KB buffer
+				int    bytesRead = fileContent.read(data);
+				int w = 0, h = 0, dpi = 96;
+				
+				if (inputformat.equals("emf") && bytesRead >= 40)
+				{
+					//Read Frame from EMF header (the rectangular inclusive-inclusive dimensions, in .01 millimeter units, 
+					//		of a rectangle that surrounds the image stored in the metafile.)
+					int x0 = fromByteArray(data, 24);
+					int y0 = fromByteArray(data, 28);
+					int x1 = fromByteArray(data, 32);
+					int y1 = fromByteArray(data, 36);
+					
+					//Approximate dimensions of the image
+					w = (int) ((x1 - x0) / EMF_10thMM2PXL);
+					h = (int) ((y1 - y0) / EMF_10thMM2PXL);
+				}
+				
+				if (w > MAX_DIM || h > MAX_DIM)
+				{
+					dpi = (int) (dpi * Math.min(MAX_DIM / (double) w, MAX_DIM / (double) h));
+					
+					if (dpi == 0)
+					{
+						dpi = 1;
+					}
+				}
+				
+				addParameter("apikey", API_KEY, postRequest);
+				addParameter("inputformat", inputformat, postRequest);
+				addParameter("outputformat", outputformat, postRequest);
+				addParameter("input", "upload", postRequest);
+				addParameter("wait", "true", postRequest);
+				addParameter("download", "true", postRequest);
+				
+				if (dpi != 96)
+				{
+					addParameter("converteroptions[density]", Integer.toString(dpi), postRequest);
+				}
+
+				addParameterHeader("file", fileName, postRequest);
+				
+				while(bytesRead != -1) 
+				{
+					postRequest.write(data, 0, bytesRead);
+					bytesRead = fileContent.read(data);
+				}
+				
+				postRequest.writeBytes(CRLF + TWO_HYPHENS + BOUNDARY + TWO_HYPHENS + CRLF);
+				
+				postRequest.flush();
+				postRequest.close();
+	
+				InputStream in = con.getInputStream();
+				
+				response.setStatus(con.getResponseCode());
+				
+				String contentType = "application/octet-stream";
+				
+				if ("png".equals(outputformat))
+				{
+					contentType = "image/png";
+				}
+				else if ("jpg".equals(outputformat))
+				{
+					contentType = "image/jpeg";
+				}
+				
+				response.setHeader("Content-Type", contentType);
+				
+				OutputStream out = response.getOutputStream();
+
+				bytesRead = in.read(data);
+				
+				while(bytesRead != -1) 
+				{
+					out.write(data, 0, bytesRead);
+					bytesRead = in.read(data);
+				}
+				
+				in.close();
+				out.flush();
+				out.close();
+			}
+			catch(Exception e)
+			{
+				e.printStackTrace();
+
+				if (con != null)
+				{
+					try 
+					{
+						BufferedReader in = new BufferedReader(
+								new InputStreamReader(con.getErrorStream()));
+						
+						String inputLine;
+	
+						while ((inputLine = in.readLine()) != null)
+						{
+							System.err.println(inputLine);
+						}
+						in.close();
+					}
+					catch (Exception e2) 
+					{
+						// Ignore
+					}
+				}
+				
+				response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+			}
+		}
+	}
+
+	private void addParameter(String name, String val, DataOutputStream postRequest) throws IOException {
+		addParameterHeader(name, null, postRequest);
+		postRequest.writeBytes(val);
+		postRequest.writeBytes(CRLF);
+	}
+	
+	private void addParameterHeader(String name, String fileName, DataOutputStream postRequest) throws IOException {
+		postRequest.writeBytes(TWO_HYPHENS + BOUNDARY + CRLF);
+		postRequest.writeBytes("Content-Disposition: form-data; name=\"" + name + "\"" + 
+					(fileName != null? "; filename=\"" + fileName + "\"" + CRLF + "Content-Type: application/octet-stream" : "") + CRLF);
+		postRequest.writeBytes(CRLF);
+	}
+}

+ 1 - 0
src/main/webapp/WEB-INF/cloud_convert_api_key

@@ -0,0 +1 @@
+Replace_with_your_own_cloud_convert_api_key

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

@@ -1,7 +1,7 @@
 CACHE MANIFEST
 
 # THIS FILE WAS GENERATED. DO NOT MODIFY!
-# 07/19/2019 10:46 AM
+# 07/20/2019 02:32 PM
 
 app.html
 index.html?offline=1

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
src/main/webapp/images/gitlab-logo-white.svg


Разница между файлами не показана из-за своего большого размера
+ 539 - 537
src/main/webapp/js/app.min.js


+ 5 - 3
src/main/webapp/js/diagramly/DrawioFile.js

@@ -1639,7 +1639,8 @@ DrawioFile.prototype.handleConflictError = function(err, manual)
 		{
 			this.ui.editor.setStatus('');
 			var isRepoFile = (this.constructor == GitHubFile) || (this.constructor == GitLabFile);
-			this.save(true, success, error, null, true, (isRepoFile && err != null) ? err.commitMessage : null)
+			this.save(true, success, error, null, true, (isRepoFile &&
+				err != null) ? err.commitMessage : null);
 		}
 	});
 
@@ -1653,8 +1654,9 @@ DrawioFile.prototype.handleConflictError = function(err, manual)
 				
 				if (this.ui.spinner.spin(document.body, mxResources.get('saving')))
 				{
-					this.save(true, success, error, null, null, (this.constructor ==
-						GitHubFile && err != null) ? err.commitMessage : null)
+					var isRepoFile = (this.constructor == GitHubFile) || (this.constructor == GitLabFile);
+					this.save(true, success, error, null, null, (isRepoFile &&
+						err != null) ? err.commitMessage : null);
 				}
 			}), error);
 		}

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

@@ -8511,6 +8511,10 @@
 
 			// Focused but invisible textarea during control or meta key events
 			var textInput = document.createElement('div');
+			textInput.setAttribute('autocomplete', 'off');
+			textInput.setAttribute('autocorrect', 'off');
+			textInput.setAttribute('autocapitalize', 'off');
+			textInput.setAttribute('spellcheck', 'false');
 			textInput.style.position = 'absolute';
 			textInput.style.whiteSpace = 'nowrap';
 			textInput.style.overflow = 'hidden';

+ 1 - 1
src/main/webapp/js/diagramly/Embed.js

@@ -73,7 +73,7 @@
 
 			var script = document.createElement('script');
 			script.type = 'text/javascript';
-			script.src = 'https://math.draw.io/current/MathJax.js?config=TeX-MML-AM_HTMLorMML';
+			script.src = 'https://www.draw.io/math/MathJax.js?config=TeX-MML-AM_HTMLorMML';
 			document.getElementsByTagName('head')[0].appendChild(script);
 		}
 	};

+ 69 - 43
src/main/webapp/js/diagramly/GitHubClient.js

@@ -361,6 +361,29 @@ GitHubClient.prototype.getLibrary = function(path, success, error)
 	this.getFile(path, success, error, true);
 };
 
+/**
+ * Checks if the client is authorized and calls the next step.
+ */
+GitHubClient.prototype.getSha = function(org, repo, path, ref, success, error)
+{
+	// Adds random parameter to bypass cache
+	var rnd = '&t=' + new Date().getTime();
+	var req = new mxXmlRequest(this.baseUrl + '/repos/' + org + '/' + repo +
+		'/contents/' + path + '?ref=' + ref + rnd, null, 'HEAD');
+	
+	this.executeRequest(req, mxUtils.bind(this, function(req)
+	{
+		try
+		{
+			success(req.request.getResponseHeader('Etag').match(/"([^"]+)"/)[1]);
+		}
+		catch (e)
+		{
+			error(e);
+		}
+	}), error);
+};
+
 /**
  * Checks if the client is authorized and calls the next step.
  */
@@ -372,7 +395,7 @@ GitHubClient.prototype.getFile = function(path, success, error, asLibrary, check
 	var org = tokens[0];
 	var repo = tokens[1];
 	var ref = tokens[2];
-	var path = tokens.slice(3, tokens.length).join('/');
+	path = tokens.slice(3, tokens.length).join('/');
 	var binary = /\.png$/i.test(path);
 	
 	// Handles .vsdx, Gliffy and PNG+XML files by creating a temporary file
@@ -384,7 +407,7 @@ GitHubClient.prototype.getFile = function(path, success, error, asLibrary, check
 		{
 			var url = this.baseUrl + '/repos/' + org + '/' + repo + '/contents/' +
 				path + '?ref=' + ref + '&token=' + this.token;
-			var tokens = path.split('/');
+			tokens = path.split('/');
 			var name = (tokens.length > 0) ? tokens[tokens.length - 1] : path;
 	
 			this.ui.convertFile(url, name, null, this.extension, success, error);
@@ -430,12 +453,7 @@ GitHubClient.prototype.createGitHubFile = function(org, repo, ref, data, asLibra
 	
 	if (data.encoding === 'base64')
 	{
-		// Checks for base64 encoded mxfile
-		if (content.substring(0, 10) == 'PG14ZmlsZS')
-		{
-			content = (window.atob && !mxClient.IS_SF) ? atob(content) : Base64.decode(content);
-		}
-		else if (/\.jpe?g$/i.test(data.name))
+		if (/\.jpe?g$/i.test(data.name))
 		{
 			content = 'data:image/jpeg;base64,' + content;
 		}
@@ -608,16 +626,22 @@ GitHubClient.prototype.writeFile = function(org, repo, ref, path, message, data,
  */
 GitHubClient.prototype.checkExists = function(path, askReplace, fn)
 {
-	this.getFile(path, mxUtils.bind(this, function(file)
+	var tokens = path.split('/');
+	var org = tokens[0];
+	var repo = tokens[1];
+	var ref = tokens[2];
+	path = tokens.slice(3, tokens.length).join('/');
+	
+	this.getSha(org, repo, path, ref, mxUtils.bind(this, function(sha)
 	{
-		if (askReplace && file.meta != null)
+		if (askReplace)
 		{
 			var resume = this.ui.spinner.pause();
 			
 			this.ui.confirm(mxResources.get('replaceIt', [path]), function()
 			{
 				resume();
-				fn(true, file.meta.sha);
+				fn(true, sha);
 			}, function()
 			{
 				resume();
@@ -658,7 +682,7 @@ GitHubClient.prototype.saveFile = function(file, success, error, overwrite, mess
 			mxUtils.bind(this, function(req)
 		{
 			delete file.meta.isNew;
-			success(JSON.parse(req.getText()));
+			success(JSON.parse(req.getText()).content.sha);
 		}), mxUtils.bind(this, function(err)
 		{
 			error(err);
@@ -680,15 +704,13 @@ GitHubClient.prototype.saveFile = function(file, success, error, overwrite, mess
 		}
 	});
 	
-	// TODO: Get only sha not content for overwrite
 	if (overwrite)
 	{
-		this.getFile(org + '/' + repo + '/' + encodeURIComponent(ref) + '/' + path,
-			mxUtils.bind(this, function(tempFile)
+		this.getSha(org, repo, path, ref, mxUtils.bind(this, function(sha)
 		{
-			file.meta.sha = tempFile.meta.sha;
+			file.meta.sha = sha;
 			fn2();
-		}), error);	
+		}), error);
 	}
 	else
 	{
@@ -754,7 +776,14 @@ GitHubClient.prototype.showGitHubDialog = function(showFiles, fn)
 	div.style.lineHeight = '1.2em';
 	div.style.height = '194px';
 	content.appendChild(div);
-
+	
+	var listItem = document.createElement('div');
+	listItem.style.textOverflow = 'ellipsis';
+	listItem.style.boxSizing = 'border-box';
+	listItem.style.overflow = 'hidden';
+	listItem.style.padding = '4px';
+	listItem.style.width = '100%';
+	
 	var dlg = new CustomDialog(this.ui, content, mxUtils.bind(this, function()
 	{
 		fn(org + '/' + repo + '/' + encodeURIComponent(ref) + '/' + path);
@@ -766,13 +795,21 @@ GitHubClient.prototype.showGitHubDialog = function(showFiles, fn)
 		dlg.okButton.parentNode.removeChild(dlg.okButton);
 	}
 	
-	var createLink = mxUtils.bind(this, function(label, fn)
+	var createLink = mxUtils.bind(this, function(label, fn, padding)
 	{
 		var link = document.createElement('a');
 		link.setAttribute('href', 'javascript:void(0);');
-		link.setAttribute('title', label);
 		mxUtils.write(link,  label);
 		mxEvent.addListener(link, 'click', fn);
+
+		if (padding != null)
+		{
+			var temp = listItem.cloneNode();
+			temp.style.padding = padding;
+			temp.appendChild(link);
+			
+			link = temp;
+		}
 		
 		return link;
 	});
@@ -867,8 +904,7 @@ GitHubClient.prototype.showGitHubDialog = function(showFiles, fn)
 					path = tokens.slice(0, tokens.length - 1).join('/');
 					selectFile();
 				}
-			})));
-			mxUtils.br(div);
+			}), '4px'));
 
 			if (files == null || files.length == 0)
 			{
@@ -886,13 +922,7 @@ GitHubClient.prototype.showGitHubDialog = function(showFiles, fn)
 						{
 							if (showFolders == (file.type == 'dir'))
 							{
-								var temp = document.createElement('div');
-								temp.style.textOverflow = 'ellipsis';
-								temp.style.boxSizing = 'border-box';
-								temp.style.overflow = 'hidden';
-								temp.style.padding = '4px';
-								temp.style.width = '100%';
-								
+								var temp = listItem.cloneNode();
 								temp.style.backgroundColor = (gray) ? '#eeeeee' : '';
 								gray = !gray;
 
@@ -982,9 +1012,7 @@ GitHubClient.prototype.showGitHubDialog = function(showFiles, fn)
 				{
 					path = null;
 					selectRepo();
-				})));
-				
-				mxUtils.br(div);
+				}), '4px'));
 			}
 
 			var branches = JSON.parse(req.getText());
@@ -997,16 +1025,20 @@ GitHubClient.prototype.showGitHubDialog = function(showFiles, fn)
 			{
 				for (var i = 0; i < branches.length; i++)
 				{
-					(mxUtils.bind(this, function(branch)
+					(mxUtils.bind(this, function(branch, idx)
 					{
-						div.appendChild(createLink(branch.name, mxUtils.bind(this, function()
+						var temp = listItem.cloneNode();
+						temp.style.backgroundColor = (idx % 2 == 0) ? '#eeeeee' : '';
+						
+						temp.appendChild(createLink(branch.name, mxUtils.bind(this, function()
 						{
 							ref = branch.name;
 							path = '';
 							selectFile();
 						})));
-						mxUtils.br(div);
-					}))(branches[i]);
+						
+						div.appendChild(temp);
+					}))(branches[i], i);
 				}
 				
 				if (branches.length == pageSize)
@@ -1132,13 +1164,7 @@ GitHubClient.prototype.showGitHubDialog = function(showFiles, fn)
 				{
 					(mxUtils.bind(this, function(repository, idx)
 					{
-						var temp = document.createElement('div');
-						temp.style.textOverflow = 'ellipsis';
-						temp.style.boxSizing = 'border-box';
-						temp.style.overflow = 'hidden';
-						temp.style.padding = '4px';
-						temp.style.width = '100%';
-						
+						var temp = listItem.cloneNode();
 						temp.style.backgroundColor = (idx % 2 == 0) ? '#eeeeee' : '';
 						
 						temp.appendChild(createLink(repository.full_name, mxUtils.bind(this, function()

+ 10 - 11
src/main/webapp/js/diagramly/GitHubFile.js

@@ -7,6 +7,7 @@ GitHubFile = function(ui, data, meta)
 	DrawioFile.call(this, ui, data);
 	
 	this.meta = meta;
+	this.peer = this.ui.gitHub;
 };
 
 //Extends mxEventSource
@@ -113,7 +114,7 @@ GitHubFile.prototype.isRenamable = function()
  */
 GitHubFile.prototype.getLatestVersion = function(success, error)
 {
-	this.ui.gitHub.getFile(this.getId(), success, error);
+	this.peer.getFile(this.getId(), success, error);
 };
 
 /**
@@ -236,15 +237,12 @@ GitHubFile.prototype.saveFile = function(title, revision, success, error, unload
 					var savedData = this.data;
 					prepare();
 
-					this.ui.gitHub.saveFile(this, mxUtils.bind(this, function(commit)
+					this.peer.saveFile(this, mxUtils.bind(this, function(etag)
 					{
-						this.isModified = prevModified;
 						this.savingFile = false;
+						this.isModified = prevModified;
+						this.setDescriptorEtag(this.meta, etag);
 						
-						this.meta.sha = commit.content.sha;
-						this.meta.html_url = commit.content.html_url;
-						this.meta.download_url = commit.content.download_url;
-	
 						this.fileSaved(savedData, savedEtag, mxUtils.bind(this, function()
 						{
 							this.contentChanged();
@@ -319,9 +317,9 @@ GitHubFile.prototype.saveFile = function(title, revision, success, error, unload
 				this.savingFile = true;
 				this.savingFileTime = new Date();
 				
-				this.ui.pickFolder(App.MODE_GITHUB, mxUtils.bind(this, function(folderId)
+				this.ui.pickFolder(this.getMode(), mxUtils.bind(this, function(folderId)
 				{
-					this.ui.gitHub.insertFile(title, this.getData(), mxUtils.bind(this, function(file)
+					this.peer.insertFile(title, this.getData(), mxUtils.bind(this, function(file)
 					{
 						this.savingFile = false;
 						
@@ -350,8 +348,9 @@ GitHubFile.prototype.saveFile = function(title, revision, success, error, unload
 		}
 		else
 		{
-			this.ui.gitHub.showCommitDialog(this.meta.name, this.meta.sha == null || this.meta.isNew,
-				mxUtils.bind(this, function(message)
+			this.peer.showCommitDialog(this.meta.name,
+				this.getDescriptorEtag(this.meta) == null ||
+				this.meta.isNew, mxUtils.bind(this, function(message)
 			{
 				doSave(message);	
 			}), error);

+ 303 - 158
src/main/webapp/js/diagramly/GitLabClient.js

@@ -227,69 +227,131 @@ GitLabClient.prototype.executeRequest = function(req, success, error, ignoreNotF
 };
 
 /**
- * Checks if the client is authorized and calls the next step.
+ * Finds index of ref in given token list. This is required to support groups and subgroups.
  */
-GitLabClient.prototype.getFile = function(path, success, error, asLibrary, checkExists)
+GitLabClient.prototype.getRefIndex = function(tokens, isFolder, success, error, knownRefPos)
 {
-	asLibrary = (asLibrary != null) ? asLibrary : false;
-	
-	var tokens = decodeURIComponent(path).split('/');
-	var refPos = tokens.indexOf('master');
-	var repoPos = Math.max(refPos-1, 0);
-	var org = tokens.slice(0, repoPos).join('/');
-	var repo = tokens[repoPos];
-	var ref = tokens[refPos];
-	var path = tokens.slice(refPos + 1, tokens.length).join('/');
-	var binary = /\.png$/i.test(path);
-	
-	// Handles .vsdx, Gliffy and PNG+XML files by creating a temporary file
-	if (!checkExists && (/\.v(dx|sdx?)$/i.test(path) || /\.gliffy$/i.test(path) ||
-		(!this.ui.useCanvasForExport && binary)))
+	if (knownRefPos != null)
 	{
-		// Should never be null
-		if (this.token != null)
-		{
-
-			var url = this.baseUrl + '/projects/' + encodeURIComponent(org + '/' + repo) + '/repository/files/' + encodeURIComponent(ref);
-			var tokens = path.split('/');
-			var name = (tokens.length > 0) ? tokens[tokens.length - 1] : path;
-	
-			this.ui.convertFile(url, name, null, this.extension, success, error);
-		}
-		else
-		{
-			error({message: mxResources.get('accessDenied')});
-		}
+		success(tokens, knownRefPos);
 	}
 	else
 	{
-		// Adds random parameter to bypass cache
-		var rnd = '&t=' + new Date().getTime();
-		url = this.baseUrl + '/projects/' + encodeURIComponent(org + '/' + repo) +
-			'/repository/files/' + encodeURIComponent(path) + '?ref=' + encodeURIComponent(ref);
-		var req = new mxXmlRequest(url + rnd, null, 'GET');
+		var refPos = tokens.length - 2;
 		
-		this.executeRequest(req, mxUtils.bind(this, function(req)
+		// Finds ref in token list by checking which URL works
+		var checkUrl = mxUtils.bind(this, function()
 		{
-			try
+			if (refPos < 2)
 			{
-				success(this.createGitLabFile(org, repo, ref, JSON.parse(req.getText()), asLibrary, url));
+				error({message: mxResources.get('fileNotFound')});
 			}
-			catch (e)
+			else
 			{
-				error(e);
+				var repoPos = Math.max(refPos - 1, 0);
+				var org = tokens.slice(0, repoPos).join('/');
+				var repo = tokens[repoPos];
+				var ref = tokens[refPos];
+				var path = tokens.slice(refPos + 1, tokens.length).join('/');
+				var url = this.baseUrl + '/projects/' + encodeURIComponent(org + '/' + repo) + '/repository/' +
+					(!isFolder ? 'files/' + encodeURIComponent(path) + '?ref=' + ref :
+					'tree?path=' + path + '&ref=' + ref);
+				
+				var req = new mxXmlRequest(url, null, 'HEAD');
+				
+				this.executeRequest(req, mxUtils.bind(this, function()
+				{
+					if (req.getStatus() == 200)
+					{
+						success(tokens, refPos);
+					}
+					else
+					{
+						error({message: mxResources.get('fileNotFound')});
+					}
+				}), mxUtils.bind(this, function()
+				{
+					if (req.getStatus() == 404)
+					{
+						refPos--;
+						checkUrl();
+					}
+					else
+					{
+						error({message: mxResources.get('fileNotFound')});
+					}
+				}));
 			}
-		}), error);
+		});
+		
+		checkUrl();
 	}
 };
 
+/**
+ * Checks if the client is authorized and calls the next step.
+ */
+GitLabClient.prototype.getFile = function(path, success, error, asLibrary, checkExists, knownRefPos)
+{
+	asLibrary = (asLibrary != null) ? asLibrary : false;
+
+	this.getRefIndex(path.split('/'), false, mxUtils.bind(this, function(tokens, refPos)
+	{
+		var repoPos = Math.max(refPos - 1, 0);
+		var org = tokens.slice(0, repoPos).join('/');
+		var repo = tokens[repoPos];
+		var ref = tokens[refPos];
+		path = tokens.slice(refPos + 1, tokens.length).join('/');
+		var binary = /\.png$/i.test(path);
+		
+		// Handles .vsdx, Gliffy and PNG+XML files by creating a temporary file
+		if (!checkExists && (/\.v(dx|sdx?)$/i.test(path) || /\.gliffy$/i.test(path) ||
+			(!this.ui.useCanvasForExport && binary)))
+		{
+			// Should never be null
+			if (this.token != null)
+			{
+				var url = this.baseUrl + '/projects/' + encodeURIComponent(org + '/' + repo) + '/repository/files/' + encodeURIComponent(ref);
+				var tokens = path.split('/');
+				var name = (tokens.length > 0) ? tokens[tokens.length - 1] : path;
+		
+				this.ui.convertFile(url, name, null, this.extension, success, error);
+			}
+			else
+			{
+				error({message: mxResources.get('accessDenied')});
+			}
+		}
+		else
+		{
+			// Adds random parameter to bypass cache
+			var rnd = '&t=' + new Date().getTime();
+			url = this.baseUrl + '/projects/' + encodeURIComponent(org + '/' + repo) +
+				'/repository/files/' + encodeURIComponent(path) + '?ref=' + ref;
+			var req = new mxXmlRequest(url + rnd, null, 'GET');
+			
+			this.executeRequest(req, mxUtils.bind(this, function(req)
+			{
+				try
+				{
+					success(this.createGitLabFile(org, repo, ref, JSON.parse(req.getText()), asLibrary, refPos));
+				}
+				catch (e)
+				{
+					error(e);
+				}
+			}), error);
+		}
+	}), error, knownRefPos);
+};
+
 /**
  * Translates this point by the given vector.
  * 
  * @param {number} dx X-coordinate of the translation.
  * @param {number} dy Y-coordinate of the translation.
  */
-GitLabClient.prototype.createGitLabFile = function(org, repo, ref, data, asLibrary)
+GitLabClient.prototype.createGitLabFile = function(org, repo, ref, data, asLibrary, refPos)
 {
 	var gitLabUrl = 'https://gitlab.com/';
 	var htmlUrl = gitLabUrl + org + '/' + repo + '/blob/' + ref + '/' + data.file_path;
@@ -297,18 +359,13 @@ GitLabClient.prototype.createGitLabFile = function(org, repo, ref, data, asLibra
 	var fileName = data.file_name;
 
 	var meta = {'org': org, 'repo': repo, 'ref': ref, 'name': fileName,
-		'path': data.file_path, 'sha': data.content_sha256, 'html_url': htmlUrl,
-		'download_url': downloadUrl, 'last_commit_id': data.last_commit_id};
+		'path': data.file_path, 'html_url': htmlUrl, 'download_url': downloadUrl,
+		'last_commit_id': data.last_commit_id, 'refPos': refPos};
 	var content = data.content;
 	
 	if (data.encoding === 'base64')
 	{
-		// Checks for base64 encoded mxfile
-		if (content.substring(0, 10) == 'PG14ZmlsZS')
-		{
-			content = (window.atob && !mxClient.IS_SF) ? atob(content) : Base64.decode(content);
-		}
-		else if (/\.jpe?g$/i.test(fileName))
+		if (/\.jpe?g$/i.test(fileName))
 		{
 			content = 'data:image/jpeg;base64,' + content;
 		}
@@ -350,62 +407,109 @@ GitLabClient.prototype.createGitLabFile = function(org, repo, ref, data, asLibra
 GitLabClient.prototype.insertFile = function(filename, data, success, error, asLibrary, folderId, base64Encoded)
 {
 	asLibrary = (asLibrary != null) ? asLibrary : false;
-
-	var tokens = decodeURIComponent(folderId).split('/');
-	var refPos = tokens.indexOf('master');
-	var repoPos = Math.max(refPos-1, 0);
-	var org = tokens.slice(0, repoPos).join('/');
-	var repo = tokens[repoPos];
-	var ref = tokens[refPos];
-	var path = tokens.slice(refPos + 1, tokens.length).join('/');
-
-	if (path.length > 0)
-	{
-		path = path + '/';
-	}
 	
-	path = path + filename;
-
-	this.checkExists(org + '/' + repo + '/' + ref + '/' + path, true, mxUtils.bind(this, function(checked, sha)
+	console.log('folderId', folderId);
+	
+	this.getRefIndex(folderId.split('/'), true, mxUtils.bind(this, function(tokens, refPos)
 	{
-		if (checked)
+		var repoPos = Math.max(refPos - 1, 0);
+		var org = tokens.slice(0, repoPos).join('/');
+		var repo = tokens[repoPos];
+		var ref = tokens[refPos];
+		path = tokens.slice(refPos + 1, tokens.length).join('/');
+	
+		if (path.length > 0)
 		{
-			// Does not insert file here as there is another writeFile implicit via fileCreated
-			if (!asLibrary)
-			{
-				success(new GitLabFile(this.ui, data, {'org': org, 'repo': repo, 'ref': ref,
-					'name': filename, 'path': path, 'sha': sha, isNew: true}));
-			}
-			else
+			path = path + '/';
+		}
+		
+		path = path + filename;
+	
+		this.checkExists(org + '/' + repo + '/' + ref + '/' + path, true, mxUtils.bind(this, function(checked, last_commit_id)
+		{
+			if (checked)
 			{
-				if (!base64Encoded)
+				// Does not insert file here as there is another writeFile implicit via fileCreated
+				if (!asLibrary)
 				{
-					data = Base64.encode(data);
+					var gitLabUrl = 'https://gitlab.com/';
+					var htmlUrl = gitLabUrl + org + '/' + repo + '/blob/' + ref + '/' + path;
+					var downloadUrl = gitLabUrl + org + '/' + repo + '/raw/' + ref + '/' + path + '?inline=false';
+					
+					success(new GitLabFile(this.ui, data, {'org': org, 'repo': repo, 'ref': ref, 'name': filename,
+						'path': path, 'html_url': htmlUrl, 'download_url': downloadUrl, 'refPos': refPos,
+						'last_commit_id': last_commit_id, isNew: true}));
 				}
-				
-				this.showCommitDialog(filename, true, mxUtils.bind(this, function(message)
+				else
 				{
-					this.writeFile(org, repo, ref, path, message, data, sha, mxUtils.bind(this, function(req)
+					if (!base64Encoded)
 					{
-						try
-						{
-							var msg = JSON.parse(req.getText());
-							success(this.createGitLabFile(org, repo, ref, msg.content, asLibrary));
-						}
-						catch (e)
+						data = Base64.encode(data);
+					}
+					
+					this.showCommitDialog(filename, true, mxUtils.bind(this, function(message)
+					{
+						this.writeFile(org, repo, ref, path, message, data, last_commit_id, mxUtils.bind(this, function(req)
 						{
-							error(e);
-						}
+							try
+							{
+								var msg = JSON.parse(req.getText());
+								success(this.createGitLabFile(org, repo, ref, msg.content, asLibrary, refPos));
+							}
+							catch (e)
+							{
+								error(e);
+							}
+						}), error);
 					}), error);
-				}), error);
+				}
+			}
+			else
+			{
+				// create if it does not exists
+				error();
 			}
+		}))
+	}), error);
+};
+
+/**
+ * Translates this point by the given vector.
+ * 
+ * @param {number} dx X-coordinate of the translation.
+ * @param {number} dy Y-coordinate of the translation.
+ */
+GitLabClient.prototype.checkExists = function(path, askReplace, fn)
+{
+	this.getFile(path, mxUtils.bind(this, function(file)
+	{
+		if (askReplace)
+		{
+			var resume = this.ui.spinner.pause();
+			
+			this.ui.confirm(mxResources.get('replaceIt', [path]), function()
+			{
+				resume();
+				fn(true, file.getCurrentEtag());
+			}, function()
+			{
+				resume();
+				fn(false);
+			});
 		}
 		else
 		{
-			// create if it does not exists
-			error();
+			this.ui.spinner.stop();
+			
+			this.ui.showError(mxResources.get('error'), mxResources.get('fileExists'), mxResources.get('ok'), function()
+			{
+				fn(false);						
+			});
 		}
-	}))
+	}), mxUtils.bind(this, function(err)
+	{
+		fn(true);
+	}), null, true);
 };
 
 /**
@@ -420,8 +524,9 @@ GitLabClient.prototype.writeFile = function(org, repo, ref, path, message, data,
 	}
 	else
 	{
-		var entity =
-		{
+		var method = 'POST';
+		
+		var entity = {
 			path: encodeURIComponent(path),
 			branch: decodeURIComponent(ref),
 			commit_message: message,
@@ -432,14 +537,13 @@ GitLabClient.prototype.writeFile = function(org, repo, ref, path, message, data,
 		if (last_commit_id != null)
 		{
 			entity.last_commit_id = last_commit_id;
+			method = 'PUT';
 		}
-
-		var method = !this.ui.currentFile ? 'POST' : 'PUT'
-
-		// {"branch": "master", "author_email": "author@example.com", "author_name": "Firstname Lastname", "content": "some content", "commit_message": "update file"}
-		// https://docs.gitlab.com/ee/api/repository_files.html#update-existing-file-in-repository
+		
+		// See https://docs.gitlab.com/ee/api/repository_files.html#update-existing-file-in-repository
 		var url = this.baseUrl + '/projects/' + encodeURIComponent(org + '/' + repo) + '/repository/files/' + encodeURIComponent(path);
 		var req = new mxXmlRequest(url, JSON.stringify(entity), method);
+		
 		this.executeRequest(req, mxUtils.bind(this, function(req)
 		{
 			success(req);
@@ -462,17 +566,24 @@ GitLabClient.prototype.saveFile = function(file, success, error, overwrite, mess
 
 	var fn = mxUtils.bind(this, function(last_commit_id, data)
 	{
-		this.writeFile(org, repo, ref, path, message, data, last_commit_id,
-			mxUtils.bind(this, function(req)
+		this.writeFile(org, repo, ref, path, message, data, last_commit_id, mxUtils.bind(this, function(req)
 		{
 			delete file.meta.isNew;
-			success({
-				content: file.meta
-			});
-		}), mxUtils.bind(this, function(err)
-		{
-			error(err);
-		}));
+			
+			// Response does not return last_commit_id so we have to get the file
+			// to to update last_commit_id and compare data to avoid lost commit
+			this.getFile(org + '/' + repo + '/' + ref + '/' + path, mxUtils.bind(this, function(tempFile)
+			{
+				if (tempFile.getData() == file.getData())
+				{
+					success(tempFile.getCurrentEtag());
+				}
+				else
+				{
+					success({content: file.getCurrentEtag()});
+				}
+			}), error, null, null, file.meta.refPos);
+		}), error);
 	});
 	
 	var fn2 = mxUtils.bind(this, function()
@@ -490,11 +601,11 @@ GitLabClient.prototype.saveFile = function(file, success, error, overwrite, mess
 		}
 	});
 	
-	// TODO: Get only sha not content for overwrite
+	// LATER: Get last_commit_id is currently not possible since HEAD does
+	// not have Access-Control-Expose-Headers for X-Gitlab-Last-Commit-Id
 	if (overwrite)
 	{
-		this.getFile(org + '/' + repo + '/' + encodeURIComponent(ref) + '/' + path,
-			mxUtils.bind(this, function(tempFile)
+		this.getFile(org + '/' + repo + '/' + encodeURIComponent(ref) + '/' + path, mxUtils.bind(this, function(tempFile)
 		{
 			file.meta.last_commit_id = tempFile.meta.last_commit_id;
 			fn2();
@@ -557,6 +668,13 @@ GitLabClient.prototype.showGitLabDialog = function(showFiles, fn)
 	div.style.height = '194px';
 	content.appendChild(div);
 
+	var listItem = document.createElement('div');
+	listItem.style.textOverflow = 'ellipsis';
+	listItem.style.boxSizing = 'border-box';
+	listItem.style.overflow = 'hidden';
+	listItem.style.padding = '4px';
+	listItem.style.width = '100%';
+	
 	var dlg = new CustomDialog(this.ui, content, mxUtils.bind(this, function()
 	{
 		fn(org + '/' + repo + '/' + encodeURIComponent(ref) + '/' + path);
@@ -568,12 +686,21 @@ GitLabClient.prototype.showGitLabDialog = function(showFiles, fn)
 		dlg.okButton.parentNode.removeChild(dlg.okButton);
 	}
 	
-	var createLink = mxUtils.bind(this, function(label, fn)
+	var createLink = mxUtils.bind(this, function(label, fn, padding)
 	{
 		var link = document.createElement('a');
 		link.setAttribute('href', 'javascript:void(0);');
 		mxUtils.write(link,  label);
 		mxEvent.addListener(link, 'click', fn);
+
+		if (padding != null)
+		{
+			var temp = listItem.cloneNode();
+			temp.style.padding = padding;
+			temp.appendChild(link);
+			
+			link = temp;
+		}
 		
 		return link;
 	});
@@ -645,7 +772,7 @@ GitLabClient.prototype.showGitLabDialog = function(showFiles, fn)
 	var selectFile = mxUtils.bind(this, function()
 	{
 		var req = new mxXmlRequest(this.baseUrl + '/projects/' + encodeURIComponent(org + '/' + repo) +
-			'/repository/tree?path=' + path, null, 'GET');
+			'/repository/tree?path=' + path + '&ref=' + ref, null, 'GET');
 		dlg.okButton.removeAttribute('disabled');
 		div.innerHTML = '';
 		this.ui.spinner.spin(div, mxResources.get('loading'));
@@ -655,6 +782,7 @@ GitLabClient.prototype.showGitLabDialog = function(showFiles, fn)
 			updatePathInfo(!ref);
 			this.ui.spinner.stop();
 			var files = JSON.parse(req.getText());
+			
 			div.appendChild(createLink('../ [Up]', mxUtils.bind(this, function()
 			{
 				if (path == '')
@@ -668,8 +796,7 @@ GitLabClient.prototype.showGitLabDialog = function(showFiles, fn)
 					path = tokens.slice(0, tokens.length - 1).join('/');
 					selectFile();
 				}
-			})));
-			mxUtils.br(div);
+			}), '4px'));
 
 			if (files == null || files.length == 0)
 			{
@@ -687,13 +814,7 @@ GitLabClient.prototype.showGitLabDialog = function(showFiles, fn)
 						{
 							if (showFolders == (file.type == 'tree'))
 							{
-								var temp = document.createElement('div');
-								temp.style.textOverflow = 'ellipsis';
-								temp.style.boxSizing = 'border-box';
-								temp.style.overflow = 'hidden';
-								temp.style.padding = '4px';
-								temp.style.width = '100%';
-								
+								var temp = listItem.cloneNode();
 								temp.style.backgroundColor = (gray) ? '#eeeeee' : '';
 								gray = !gray;
 
@@ -715,7 +836,7 @@ GitLabClient.prototype.showGitLabDialog = function(showFiles, fn)
 									else if (showFiles && file.type == 'blob')
 									{
 										this.ui.hideDialog();
-										fn(org + '/' + repo + '/' + encodeURIComponent(ref) + '/' + file.path);
+										fn(org + '/' + repo + '/' + ref + '/' + file.path);
 									}
 								})));
 								
@@ -783,9 +904,7 @@ GitLabClient.prototype.showGitLabDialog = function(showFiles, fn)
 				{
 					path = null;
 					selectRepo();
-				})));
-				
-				mxUtils.br(div);
+				}), '4px'));
 			}
 
 			var branches = JSON.parse(req.getText());
@@ -798,16 +917,20 @@ GitLabClient.prototype.showGitLabDialog = function(showFiles, fn)
 			{
 				for (var i = 0; i < branches.length; i++)
 				{
-					(mxUtils.bind(this, function(branch)
+					(mxUtils.bind(this, function(branch, idx)
 					{
-						div.appendChild(createLink(branch.name, mxUtils.bind(this, function()
+						var temp = listItem.cloneNode();
+						temp.style.backgroundColor = (idx % 2 == 0) ? '#eeeeee' : '';
+						
+						temp.appendChild(createLink(branch.name, mxUtils.bind(this, function()
 						{
-							ref = branch.name;
+							ref = encodeURIComponent(branch.name);
 							path = '';
 							selectFile();
 						})));
-						mxUtils.br(div);
-					}))(branches[i]);
+						
+						div.appendChild(temp);
+					}))(branches[i], i);
 				}
 				
 				if (branches.length == pageSize)
@@ -828,17 +951,19 @@ GitLabClient.prototype.showGitLabDialog = function(showFiles, fn)
 		}), error);
 	});
 
+	dlg.okButton.setAttribute('disabled', 'disabled');
+	this.ui.spinner.spin(div, mxResources.get('loading'));
+	
 	var selectRepo = mxUtils.bind(this, function(page)
 	{
+		this.ui.spinner.stop();
+		
 		if (page == null)
 		{
 			div.innerHTML = '';
 			page = 1;
 		}
 
-		dlg.okButton.setAttribute('disabled', 'disabled');
-		this.ui.spinner.spin(div, mxResources.get('loading'));
-		
 		if (nextPageDiv != null && nextPageDiv.parentNode != null)
 		{
 			nextPageDiv.parentNode.removeChild(nextPageDiv);
@@ -859,33 +984,39 @@ GitLabClient.prototype.showGitLabDialog = function(showFiles, fn)
 
 		var listGroups = mxUtils.bind(this, function(callback)
 		{
+			this.ui.spinner.spin(div, mxResources.get('loading'));
 			var req = new mxXmlRequest(this.baseUrl + '/groups?per_page=100', null, 'GET');
-			this.executeRequest(req, function(req)
+			
+			this.executeRequest(req, mxUtils.bind(this, function(req)
 			{
+				this.ui.spinner.stop();
 				callback(JSON.parse(req.getText()));
-			}, error);
+			}), error);
 		});
 
 		var listProjects = mxUtils.bind(this, function(group, callback)
 		{
+			this.ui.spinner.spin(div, mxResources.get('loading'));
 			var req = new mxXmlRequest(this.baseUrl + '/groups/' + group.id + '/projects?per_page=100', null, 'GET');
-			this.executeRequest(req, function(req)
+			
+			this.executeRequest(req, mxUtils.bind(this, function(req)
 			{
+				this.ui.spinner.stop();
 				callback(group, JSON.parse(req.getText()));
-			}, error);
+			}), error);
 		});
-
 		
-		listGroups(mxUtils.bind(this, function(groups) {
-			this.ui.spinner.stop();
-      var req = new mxXmlRequest(this.baseUrl + '/users/' + this.user.id + '/projects?per_page=' +
-        pageSize + '&page=' + page, null, 'GET');
+		listGroups(mxUtils.bind(this, function(groups)
+		{
+			var req = new mxXmlRequest(this.baseUrl + '/users/' + this.user.id + '/projects?per_page=' +
+				pageSize + '&page=' + page, null, 'GET');
+			this.ui.spinner.spin(div, mxResources.get('loading'));
+			
 			this.executeRequest(req, mxUtils.bind(this, function(req)
 			{
 				this.ui.spinner.stop();
 				var repos = JSON.parse(req.getText());
 
-				
 				if ((repos == null || repos.length == 0) && (groups == null || groups.length == 0))
 				{
 					mxUtils.write(div, mxResources.get('noFiles'));
@@ -908,10 +1039,12 @@ GitLabClient.prototype.showGitLabDialog = function(showFiles, fn)
 										repo = tokens[1];
 										ref = 'master';
 										path = null;
+										
 										if (tokens.length > 2)
 										{
 											path = encodeURIComponent(tokens.slice(2, tokens.length).join('/'));
 										}
+										
 										selectFile();
 									}
 									else
@@ -929,18 +1062,15 @@ GitLabClient.prototype.showGitLabDialog = function(showFiles, fn)
 						mxUtils.br(div);
 					}
 					
+					var gray = true;
+					
 					for (var i = 0; i < repos.length; i++)
 					{
 						(mxUtils.bind(this, function(repository, idx)
 						{
-							var temp = document.createElement('div');
-							temp.style.textOverflow = 'ellipsis';
-							temp.style.boxSizing = 'border-box';
-							temp.style.overflow = 'hidden';
-							temp.style.padding = '4px';
-							temp.style.width = '100%';
-							
-							temp.style.backgroundColor = (idx % 2 == 0) ? '#eeeeee' : '';
+							var temp = listItem.cloneNode();
+							temp.style.backgroundColor = (gray) ? '#eeeeee' : '';
+							gray = !gray;
 							
 							temp.appendChild(createLink(repository.name_with_namespace, mxUtils.bind(this, function()
 							{
@@ -960,9 +1090,15 @@ GitLabClient.prototype.showGitLabDialog = function(showFiles, fn)
 					{
 						listProjects(groups[i], (mxUtils.bind(this, function(group, projects)
 						{
-							for (var j = 0; j < projects.length; j++) {
+							for (var j = 0; j < projects.length; j++)
+							{
+								var temp = listItem.cloneNode();
+								temp.style.backgroundColor = (gray) ? '#eeeeee' : '';
+								gray = !gray;
+								
 								var project = projects[j];
-								div.appendChild(createLink(project.name_with_namespace, mxUtils.bind(this, function()
+								
+								temp.appendChild(createLink(project.name_with_namespace, mxUtils.bind(this, function()
 								{
 									org = group.full_path;
 									repo = project.path;
@@ -971,7 +1107,8 @@ GitLabClient.prototype.showGitLabDialog = function(showFiles, fn)
 			
 									selectFile();
 								})));
-								mxUtils.br(div);
+
+								div.appendChild(temp);
 							}
 						})));
 					}
@@ -995,14 +1132,22 @@ GitLabClient.prototype.showGitLabDialog = function(showFiles, fn)
 		}));
 	});
 
-	if (!this.token) {
+	if (!this.token)
+	{
 		this.authenticate(mxUtils.bind(this, function()
 		{
 			this.updateUser(function() { selectRepo(); }, error, true);
 		}), error);
-	} else if (!this.user) {
-		this.updateUser(function() { selectRepo(); }, error, true);
-	} else {
+	}
+	else if (!this.user)
+	{
+		this.updateUser(function()
+		{
+			selectRepo();
+		}, error, true);
+	}
+	else
+	{
 		selectRepo();
 	}
 };

+ 3 - 208
src/main/webapp/js/diagramly/GitLabFile.js

@@ -4,9 +4,9 @@
  */
 GitLabFile = function(ui, data, meta)
 {
-	DrawioFile.call(this, ui, data);
+	GitHubFile.call(this, ui, data, meta);
 	
-	this.meta = meta;
+	this.peer = this.ui.gitLab;
 };
 
 //Extends mxEventSource
@@ -20,7 +20,7 @@ mxUtils.extend(GitLabFile, GitHubFile);
  */
 GitLabFile.prototype.getId = function()
 {
-	return encodeURIComponent(this.meta.org) + '/' +
+	return this.meta.org + '/' +
 		((this.meta.repo != null) ? encodeURIComponent(this.meta.repo) + '/' +
 		((this.meta.ref != null) ? this.meta.ref +
 		((this.meta.path != null) ? '/' + this.meta.path : '') : '') : '');
@@ -37,28 +37,6 @@ GitLabFile.prototype.getHash = function()
 	return encodeURIComponent('A' + this.getId());
 };
 
-/**
- * Returns true if copy, export and print are not allowed for this file.
- */
-GitLabFile.prototype.getPublicUrl = function(fn)
-{
-	// LATER: Check if download_url is always null for private repos
-	if (this.meta.download_url != null)
-	{
-		mxUtils.get(this.meta.download_url, mxUtils.bind(this, function(req)
-		{
-			fn((req.getStatus() >= 200 && req.getStatus() <= 299) ? this.meta.download_url : null);
-		}), mxUtils.bind(this, function()
-		{
-			fn(null);
-		}));
-	}
-	else
-	{
-		fn(null);
-	}
-};
-
 /**
  * Adds the listener for automatically saving the diagram for local changes.
  */
@@ -78,14 +56,6 @@ GitLabFile.prototype.getMode = function()
 	return App.MODE_GITLAB;
 };
 
-/**
- * Adds the listener for automatically saving the diagram for local changes.
- */
-GitLabFile.prototype.getLatestVersion = function(success, error)
-{
-	this.ui.gitLab.getFile(this.getId(), success, error);
-};
-
 /**
  * Adds all listeners.
  */
@@ -101,178 +71,3 @@ GitLabFile.prototype.setDescriptorEtag = function(desc, etag)
 {
 	desc.last_commit_id = etag;
 };
-
-/**
- * Translates this point by the given vector.
- * 
- * @param {number} dx X-coordinate of the translation.
- * @param {number} dy Y-coordinate of the translation.
- */
-GitLabFile.prototype.saveFile = function(title, revision, success, error, unloading, overwrite, message)
-{
-	if (!this.isEditable())
-	{
-		if (success != null)
-		{
-			success();
-		}
-	}
-	else if (!this.savingFile)
-	{
-		var doSave = mxUtils.bind(this, function(message)
-		{
-			if (this.getTitle() == title)
-			{
-				var prevModified = null;
-				var modified = null;
-				
-				try
-				{
-					// Makes sure no changes get lost while the file is saved
-					prevModified = this.isModified;
-					modified = this.isModified();
-					this.savingFile = true;
-					this.savingFileTime = new Date();
-						
-					// Makes sure no changes get lost while the file is saved
-					var prepare = mxUtils.bind(this, function()
-					{
-						this.setModified(false);
-						
-						this.isModified = function()
-						{
-							return modified;
-						};
-					});
-					
-					var savedEtag = this.getCurrentEtag();
-					var savedData = this.data;
-					prepare();
-					
-					this.ui.gitLab.saveFile(this, mxUtils.bind(this, function(commit)
-					{
-						this.isModified = prevModified;
-						this.savingFile = false;
-						
-						this.meta.sha = commit.content.sha;
-						this.meta.html_url = commit.content.html_url;
-						this.meta.download_url = commit.content.download_url;
-						this.meta.last_commit_id = commit.content.last_commit_id;
-						
-						this.fileSaved(savedData, savedEtag, mxUtils.bind(this, function()
-						{
-							this.contentChanged();
-							
-							if (success != null)
-							{
-								success();
-							}
-						}), error);
-					}),
-					mxUtils.bind(this, function(err)
-					{
-						this.savingFile = false;
-						this.isModified = prevModified;
-						this.setModified(modified || this.isModified());
-	
-						if (this.isConflict(err))
-						{
-							this.inConflictState = true;
-							
-							if (error != null)
-							{
-								// Passes current commit message to avoid
-								// multiple dialogs after synchronize
-								error({commitMessage: message});
-							}
-						}
-						else if (error != null)
-						{
-							// Handles modified state for retries
-							if (err != null && err.retry != null)
-							{
-								var retry = err.retry;
-								
-								err.retry = function()
-								{
-									prepare();
-									retry();
-								};
-							}
-							
-							error(err);
-						}
-					}), overwrite, message);
-				}
-				catch (e)
-				{
-					this.savingFile = false;
-					
-					if (prevModified != null)
-					{
-						this.isModified = prevModified;
-					}
-					
-					if (modified != null)
-					{
-						this.setModified(modified || this.isModified());
-					}
-					
-					if (error != null)
-					{
-						error(e);
-					}
-					else
-					{
-						throw e;
-					}
-				}
-			}
-			else
-			{
-				this.savingFile = true;
-				
-				this.ui.pickFolder(App.MODE_GITLAB, mxUtils.bind(this, function(folderId)
-				{
-					this.ui.gitLab.insertFile(title, this.getData(), mxUtils.bind(this, function(file)
-					{
-						this.savingFile = false;
-						
-						if (success != null)
-						{
-							success();
-						}
-						
-						this.ui.fileLoaded(file);
-					}), mxUtils.bind(this, function()
-					{
-						this.savingFile = false;
-						
-						if (error != null)
-						{
-							error();
-						}
-					}), false, folderId, message);
-				}));
-			}
-		});
-		
-		if (message != null)
-		{
-			doSave(message);
-		}
-		else
-		{
-			this.ui.gitLab.showCommitDialog(this.meta.name,
-				this.meta.last_commit_id == null || this.meta.isNew,
-				mxUtils.bind(this, function(message)
-			{
-				doSave(message);	
-			}), error);
-		}
-	}
-	else if (error != null)
-	{
-		error({code: App.ERROR_BUSY});
-	}
-};

+ 8 - 1
src/main/webapp/js/diagramly/mxFreehand.js

@@ -19,6 +19,7 @@ function mxFreehand(graph)
 	var autoClose = true;
 	var buffer = []; // Contains the last positions of the mouse cursor
 	var enabled = false;
+	var stopClickEnabled = false;
 
 	this.setClosedPath = function(isClosed)//TODO add closed settings
 	{
@@ -29,6 +30,11 @@ function mxFreehand(graph)
 	{
 		autoClose = isAutoClose;
 	};
+
+	this.setStopClickEnabled = function(enabled)
+	{
+		stopClickEnabled = enabled;
+	};
 	
 	this.setSmoothing = function(smoothing)//TODO add smoothing settings
 	{
@@ -60,7 +66,8 @@ function mxFreehand(graph)
 	    if (path) 
 	    {
 	    	// Click stops drawing
-	    	var doStop = drawPoints.length > 0 && lastPart != null && lastPart.length < 2;
+	    	var doStop = stopClickEnabled && drawPoints.length > 0 &&
+	    		lastPart != null && lastPart.length < 2;
 	    	
 			if (!doStop)
 			{

+ 7 - 3
src/main/webapp/js/diagramly/vsdx/importer.js

@@ -327,7 +327,9 @@ var com;
 	                            				//send to emf conversion service
 	                        					var formData = new FormData();
 	                        					formData.append('img', emfBlob, name);
-
+	                        					formData.append('inputformat', 'emf');
+	                        					formData.append('outputformat', 'png');
+	                        					
 	                        					var xhr = new XMLHttpRequest();
 	                        					xhr.open('POST', EMF_CONVERT_URL);
 	                        					xhr.responseType = 'blob';
@@ -342,8 +344,10 @@ var com;
 	                        								{
 	                        									var reader = new FileReader();
 	                        									reader.readAsDataURL(xhr.response); 
-	                        									reader.onloadend = function() {
-	                        									    mediaData[filename] = reader.result.substr(22); //data:image/png;base64, is 23 character
+	                        									reader.onloadend = function() 
+	                        									{
+	                        										var dataPos = reader.result.indexOf(',') + 1;
+	                        									    mediaData[filename] = reader.result.substr(dataPos);
 		                        									emfDone();
 	                        									}
 	                        								}

+ 1 - 1
src/main/webapp/js/embed.dev.js

@@ -73,7 +73,7 @@
 
 			var script = document.createElement('script');
 			script.type = 'text/javascript';
-			script.src = 'https://math.draw.io/current/MathJax.js?config=TeX-MML-AM_HTMLorMML';
+			script.src = 'https://www.draw.io/math/MathJax.js?config=TeX-MML-AM_HTMLorMML';
 			document.getElementsByTagName('head')[0].appendChild(script);
 		}
 	};

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


Разница между файлами не показана из-за своего большого размера
+ 295 - 294
src/main/webapp/js/viewer.min.js


+ 2 - 2
src/main/webapp/resources/dia_es.txt

@@ -916,9 +916,9 @@ confAIndexDiagFailed=Indexing diagram "{1}" failed.
 confASkipDiagOtherPage=Skipped "{1}" as it belongs to another page!
 confADiagUptoDate=Diagram "{1}" is up to date.
 confACheckPagesWDraw=Checking pages having draw.io diagrams.
-confAErrOccured=An error occured!
+confAErrOccured= 
 savedSucc=Saved successfully
-confASaveFailedErr=Saving Failed (Unexpected Error)
+confASaveFailedErr= 
 character=Character
 confAConfPageDesc=This page contains draw.io configuration file (configuration.json) as attachment
 confALibPageDesc=This page contains draw.io custom libraries as attachments

+ 103 - 103
src/main/webapp/resources/dia_et.txt

@@ -159,7 +159,7 @@ device=Seade
 diagram=Diagramm
 diagramContent=Diagrammi sisu
 diagramLocked=Diagramm on lukustatud edasise andmekadu vältimiseks
-diagramLockedBySince=The diagram is locked by {1} since {2} ago
+diagramLockedBySince=Diagrammi lukustas {1} {2} tagasi
 diagramName=Diagrammi nimi
 diagramIsPublic=Diagramm on avalik
 diagramIsNotPublic=Diagramm pole avalik
@@ -390,9 +390,9 @@ insertRowAfter=Sisesta rida allapoole
 insertText=Sisesta tekst
 inserting=Sisestamine
 invalidFilename=Diagrammi nimed ei tohi sisaldada järgnevaid tähemärke:  \ / | : ; { } < > & + ? = "
-invalidLicenseSeeThisPage=Sinu litsents on kehtete, palun vaata seda <a target="_blank" href="https://support.draw.io/display/DFCS/Licensing+your+draw.io+plugin">lehte</a>.
-invalidInput=Invalid input
-invalidName=Kehtetu nimi
+invalidLicenseSeeThisPage=Sinu litsents on kehtetu, palun vaata seda <a target="_blank" href="https://support.draw.io/display/DFCS/Licensing+your+draw.io+plugin">lehte</a>.
+invalidInput=Viga sisestamisel
+invalidName=Viga nimes
 invalidOrMissingFile=Kehtetu või puuduv fail
 invalidPublicUrl=Kehtetu avalik URL
 isometric=Isomeetriline
@@ -409,7 +409,7 @@ lessThanAMinute=vähem kui minut
 licensingError=Litsentsiviga
 licenseHasExpired=Litsents {1} jaoks aegub {2}. Vajuta siia.
 licenseWillExpire=Litsents {1} jaoks aegub {2}. Vajuta siia.
-lineJumps=Line jumps
+lineJumps=Joone punktid
 linkAccountRequired=Kui diagramm pole avalik, siis läheb lingi vaatamiseks Google'i kasutajakontot vaja.
 linkText=Lingi tekst
 list=Nimekiri
@@ -470,7 +470,7 @@ new=Uus
 newLibrary=Uus raamatukogu
 nextPage=Järgmine lehekülg
 no=Ei
-noPickFolder=No, pick folder
+noPickFolder=Ei, vali kaust
 noAttachments=Manuseid ei leitud
 noColor=Ilma värvita
 noFiles=Ilma failideta
@@ -535,7 +535,7 @@ paperSize=Paberi suurus
 pattern=Muster
 paste=Kleebi
 pasteHere=Kleebi siia
-pasteSize=Paste Size
+pasteSize=Kleebi mõõdud
 pasteStyle=Kleebi stiil
 perimeter=Perimeeter
 permissionAnyone=Igaüks saab toimetada
@@ -575,8 +575,8 @@ realtimeTimeout=Tundub, et oled teinud mõned muudatused offline'is. Vabandame,
 redo=Tee uuesti
 refresh=Värskenda
 regularExpression=Tavaline väljendus
-relative=Relative
-relativeUrlNotAllowed=Relative URL not allowed
+relative=Suhteline
+relativeUrlNotAllowed=Suhteline URL pole lubatud
 rememberMe=Jäta mind meelde
 rememberThisSetting=Jäta see seade meelde
 removeFormat=Tühjenda formaat
@@ -605,7 +605,7 @@ retryingLogin=Logimine võttis liiga kaua aega. Proovin uuesti...
 reverse=Pööra ümber
 revision=Redigeerimine
 revisionHistory=Muudatuste ajalugu
-rhombus=Rhombus
+rhombus=Romb
 right=Parem
 rightAlign=Joonda paremale
 rightToLeft=Paremalt vasakule
@@ -634,7 +634,7 @@ selectFile=Vali fail
 selectFolder=Vali kaust
 selectFont=Vali font
 selectNone=Ära vali midagi
-selectTemplate=Select Template
+selectTemplate=Vali mall
 selectVertices=Vali punktid
 sendMessage=Saada
 sendYourFeedbackToDrawIo=Saada oma tagasiside draw.io'le
@@ -655,7 +655,7 @@ signs=Märgid
 signOut=Logi välja
 simple=Lihtne
 simpleArrow=Lihtne nool
-simpleViewer=Simple Viewer
+simpleViewer=Lihtne vaade
 size=Suurus
 solid=Katkematu
 sourceSpacing=Allika vahekaugus
@@ -665,11 +665,11 @@ space=Väli
 spacing=Vahekaugus
 specialLink=Erilink
 standard=Standard
-startDrawing=Start drawing
-stopDrawing=Stop drawing
+startDrawing=Alusta joonistamist
+stopDrawing=Lõpeta joonistamine
 starting=Algab
 straight=Sirge
-strikethrough=Strikethrough
+strikethrough=Läbikriipsutatud
 strokeColor=Joone värv
 style=Stiil
 subscript=Alaindeks
@@ -693,7 +693,7 @@ title=Pealkiri
 to=kuni
 toBack=Taha
 toFront=Ette
-toolbar=Toolbar
+toolbar=Tööriistariba
 tooltips=Kohtspikker
 top=Üles
 topAlign=Ülesse joondatud
@@ -725,13 +725,13 @@ updatingSelection=Uuendan valikut. Palun oota...
 upload=Lae üles
 url=URL
 useOffline=Kasuta võrguta režiimis
-useRootFolder=Use root folder?
+useRootFolder=Kasuta juurkataloogi?
 userManual=Kasutusjuhend
 vertical=Vertikaalne
 verticalFlow=Vertikaalne voog
 verticalTree=Vertikaalne puu
 view=Vaade
-viewerSettings=Viewer Settings
+viewerSettings=Vaate seaded
 viewUrl=Link vaatamiseks: {1}
 voiceAssistant=Kõneassistent (beta)
 warning=Hoiatus
@@ -762,47 +762,47 @@ venndiagrams=Venni diagrammid
 webEmailOrOther=Veeb, e-post või muu internetiaadress
 webLink=Veebilink
 wireframes=Veebimaketid
-property=Property
-value=Value
-showMore=Show More
-showLess=Show Less
-myDiagrams=My Diagrams
-allDiagrams=All Diagrams
-recentlyUsed=Recently used
-listView=List view
+property=Parameeter
+value=Väärtus
+showMore=Näita rohkem
+showLess=Näita vähem
+myDiagrams=Minu diagrammid
+allDiagrams=Kõik diagrammid
+recentlyUsed=Viimati kasutatud
+listView=Nimekiri
 gridView=Grid view
-resultsFor=Results for '{1}'
-oneDriveCharsNotAllowed=The following characters are not allowed: ~ " # %  * : < > ? / \ { | }
-oneDriveInvalidDeviceName=The specified device name is invalid
-officeNotLoggedOD=You are not logged in to OneDrive. Please open draw.io task pane and login first.
-officeSelectSingleDiag=Please select a single draw.io diagram only without other contents.
-officeSelectDiag=Please select a draw.io diagram.
-officeCannotFindDiagram=Cannot find a draw.io diagram in the selection
-noDiagrams=No diagrams found
-authFailed=Authentication failed
-officeFailedAuthMsg=Unable to successfully authenticate user or authorize application.
-convertingDiagramFailed=Converting diagram failed
-officeCopyImgErrMsg=Due to some limitations in the host application, the image could not be inserted. Please manually copy the image then paste it to the document.
-insertingImageFailed=Inserting image failed
+resultsFor=Tulemused {1} jaoks
+oneDriveCharsNotAllowed=Ebasobivad sümbolid:  ~ " # %  * : < > ? / \ { | }
+oneDriveInvalidDeviceName=Antud seadme nimi on vigane.
+officeNotLoggedOD=Sa pole OneDrive sisse logitud. Palun ava draw.io töölaud ja logi sisse.
+officeSelectSingleDiag=Palun vali ainult üks draw.io diagramm.
+officeSelectDiag=Palun vali draw.io diagramm.
+officeCannotFindDiagram=Ei leia valikutest draw.io diagrammi.
+noDiagrams=Ühtegi draw.io diagrammi ei valitud.
+authFailed=Autentimine ebaõnnestus.
+officeFailedAuthMsg=Ei õnnestu kasutaja autentimine või rakenduse autoriseerimine.
+convertingDiagramFailed=Diagrammi teisendamine ei õnnestunud.
+officeCopyImgErrMsg=Pildi lisamine "Lisa" funktsiooniga ei õnnestunud vastava programmi piirangute tõttu.  Palun kopeeri ja kleebi pilt käsitsi.
+insertingImageFailed=Pildi lisamine ei õnnestunud
 officeCopyImgInst=Instructions: Right-click the image below. Select "Copy image" from the context menu. Then, in the document, right-click and select "Paste" from the context menu.
-folderEmpty=Folder is empty
-recent=Recent
-sharedWithMe=Shared With Me
-sharepointSites=Sharepoint Sites
-errorFetchingFolder=Error fetching folder items
-errorAuthOD=Error authenticating to OneDrive
-officeMainHeader=Adds draw.io diagrams to your document.
-officeStepsHeader=This add-in performs the following steps:
-officeStep1=Connects to Microsoft OneDrive, Google Drive or your device.
-officeStep2=Select a draw.io diagram.
-officeStep3=Insert the diagram into the document.
-officeAuthPopupInfo=Please complete the authentication in the pop-up window.
-officeSelDiag=Select draw.io Diagram:
-files=Files
-shared=Shared
+folderEmpty=Kaust on tühi
+recent=Hiljutised
+sharedWithMe=Minuga jagatud
+sharepointSites=Sharepoint saidid
+errorFetchingFolder=Kaustade lugemine ebaõnnestus
+errorAuthOD=Viga OneDrive autentimisel
+officeMainHeader=Lisab draw.io diagrammi su dokumenti.
+officeStepsHeader=See lisamoodul teeb järgnevad sammud:
+officeStep1=Ühendub Mirosoft OneDrive, Google Drive või su seadmega.
+officeStep2=Vali draw.io diagramm
+officeStep3=Sisesta draw.io diagramm dokumenti.
+officeAuthPopupInfo=Palun lõpeta autentimine eraldi hüpikaknas.
+officeSelDiag=Vali draw.io diagramm:
+files=Failid
+shared=Jagatud
 sharepoint=Sharepoint
-officeManualUpdateInst=Instructions: Copy draw.io diagram from the document. Then, in the box below, right-click and select "Paste" from the context menu.
-officeClickToEdit=Click icon to start editing:
+officeManualUpdateInst=Juhised: Kopeeri draw.io diagramm dokumendis.  Seejärel vali paremklikiga alltoodud kastis ja vali "Kleebi" kontekstimenüüs.
+officeClickToEdit=Muutmiseks kliki ikoonil:
 pasteDiagram=Paste draw.io diagram here
 connectOD=Connect to OneDrive
 selectChildren=Select Children
@@ -813,56 +813,56 @@ lastSaved=Last saved {1} ago
 resolve=Resolve
 reopen=Re-open
 showResolved=Show Resolved
-reply=Reply
-objectNotFound=Object not found
+reply=Vasta
+objectNotFound=Objekti ei leitud
 reOpened=Re-opened
 markedAsResolved=Marked as resolved
-noCommentsFound=No comments found
-comments=Comments
-timeAgo={1} ago
+noCommentsFound=Kommentaare pole
+comments=Kommentaarid
+timeAgo={1} tagasi
 confluenceCloud=Confluence Cloud
 libraries=Libraries
-confAnchor=Confluence Page Anchor
-confTimeout=The connection has timed out
-confSrvTakeTooLong=The server at {1} is taking too long to respond.
+confAnchor=Confluence lehe ankur
+confTimeout=Ühendus aegus
+confSrvTakeTooLong=Serveri {1} vastamien võtab liiga kaua aega. 
 confCannotInsertNew=Cannot insert draw.io diagram to a new Confluence page
-confSaveTry=Please save the page and try again.
+confSaveTry=Palun salvesta leht ja proovi uuesti.
 confCannotGetID=Unable to determine page ID
-confContactAdmin=Please contact your Confluence administrator.
-readErr=Read Error
-editingErr=Editing Error
-confExtEditNotPossible=This diagram cannot be edited externally. Please try editing it while editing the page
+confContactAdmin=Palun võta ühendust enda Confluence administraatoriga.
+readErr=Viga lugemisel
+editingErr=Viga muutmisel.
+confExtEditNotPossible=Seda diagrammi ei saa väliselt muuta. Palun proovi seda lehel muuta.
 confEditedExt=Diagram/Page edited externally
-diagNotFound=Diagram Not Found
+diagNotFound=Diagrammi ei leitud
 confEditedExtRefresh=Diagram/Page is edited externally. Please refresh the page.
 confCannotEditDraftDelOrExt=Cannot edit diagrams in a draft page, diagram is deleted from the page, or diagram is edited externally. Please, check the page.
-retBack=Return back
+retBack=Mine tagasi
 confDiagNotPublished=The diagram does not belong to a published page
 createdByDraw=Created by draw.io
-filenameShort=Filename too short
-invalidChars=Invalid characters
-alreadyExst={1} already exists
+filenameShort=Liiga lühike failinimi
+invalidChars=Sobimatud sümbolid
+alreadyExst={1} on juba olemas
 draftReadErr=Draft Read Error
-diagCantLoad=Diagram cannot be loaded
-draftWriteErr=Draft Write Error
+diagCantLoad=Diagrammi laadimine ei õnnestunud
+draftWriteErr=Diagrammi kirjutamise viga
 draftCantCreate=Draft could not be created
 confDuplName=Duplicate diagram name detected. Please pick another name.
 confSessionExpired=Looks like your session expired. Log in again to keep working.
-login=Login
-drawPrev=draw.io preview
-drwaDiag=draw.io diagram
-unknownErr=Unkown Error
+login=Sisselogimine
+drawPrev=draw.io eelvaade
+drwaDiag=draw.io diagramm
+unknownErr=Tundmatu viga
 invalidCallFnNotFound=Invalid Call: {1} not found
 invalidCallErrOccured=Invalid Call: An error occured, {1}
-anonymous=Anonymous
+anonymous=Anonüümne
 confGotoPage=Go to containing page
-showComments=Show Comments
-confError=Error: {1}
+showComments=Näita kommentaare
+confError=Viga: {1}
 gliffyImport=Gliffy Import
 gliffyImportInst1=Click the "Start Import" button to import all Gliffy diagrams to draw.io.
 gliffyImportInst2=Please note that the import procedure will take some time and the browser window must remain open until the import is completed.
-startImport=Start Import
-drawConfig=draw.io Configuration
+startImport=Alusta importi
+drawConfig=draw.io Seaded
 customLib=Custom Libraries
 customTemp=Custom Templates
 pageIdsExp=Page IDs Export
@@ -884,7 +884,7 @@ customTempInst2=For more details, please refer to
 tempsPage=Templates page
 pageIdsExpInst1=Click the "Start Export" button to export all pages IDs.
 pageIdsExpInst2=Please note that the export procedure will take some time and the browser window must remain open until the export is completed.
-startExp=Start Export
+startExp=Alusta eksporti
 refreshDrawIndex=Refresh draw.io Diagrams Index
 reindexInst1=Click the "Start Indexing" button to refresh draw.io diagrams index.
 reindexInst2=Please note that the indexing procedure will take some time and the browser window must remain open until the indexing is completed.
@@ -910,32 +910,32 @@ confADelDiagLinkOf=Deleting diagram link of "{1}"
 confADupLnk=(duplicate link)
 confADelDiagLnkFailed=Deleting diagram link of "{1}" failed.
 confAUnexpErrProcessPage=Unexpected error during processing the page with id: {1}
-confADiagFoundIndex=Diagram "{1}" found. Indexing
-confADiagIndexSucc=Diagram "{1}" indexed successfully.
-confAIndexDiagFailed=Indexing diagram "{1}" failed.
+confADiagFoundIndex=Diagramm {1} leiti. Indekseeritakse
+confADiagIndexSucc=Diagramm {1} on indekseeritud.
+confAIndexDiagFailed=Diagrammi {1} ebaõnnestus.
 confASkipDiagOtherPage=Skipped "{1}" as it belongs to another page!
-confADiagUptoDate=Diagram "{1}" is up to date.
+confADiagUptoDate=Diagramm {1} on ajakohane.
 confACheckPagesWDraw=Checking pages having draw.io diagrams.
-confAErrOccured=An error occured!
-savedSucc=Saved successfully
-confASaveFailedErr=Saving Failed (Unexpected Error)
-character=Character
+confAErrOccured=Viga!
+savedSucc=Salvestatud
+confASaveFailedErr=Salvestamine ei õnnestunud (Ootamatu viga)
+character=Sümbol
 confAConfPageDesc=This page contains draw.io configuration file (configuration.json) as attachment
 confALibPageDesc=This page contains draw.io custom libraries as attachments
 confATempPageDesc=This page contains draw.io custom templates as attachments
 working=Working
 confAConfSpaceDesc=This space is used to store draw.io configuration files and custom libraries/templates
 confANoCustLib=No Custom Libraries
-delFailed=Delete failed!
-showID=Show ID
+delFailed=Kustutamine ei õnnestunud!
+showID=Näita ID
 confAIncorrectLibFileType=Incorrect file type. Libraries should be XML files.
-uploading=Uploading
+uploading=Üleslaadimine
 confALibExist=This library already exists
-confAUploadSucc=Uploaded successfully
-confAUploadFailErr=Upload Failed (Unexpected Error)
+confAUploadSucc=Üleslaadimine õnnestus
+confAUploadFailErr=Üleslaadimine ei õnnestunud (Ootamatu viga)
 hiResPreview=High Res Preview
 officeNotLoggedGD=You are not logged in to Google Drive. Please open draw.io task pane and login first.
-officePopupInfo=Please complete the process in the pop-up window.
-pickODFile=Pick OneDrive File
-pickGDriveFile=Pick Google Drive File
-pickDeviceFile=Pick Device File
+officePopupInfo=Palun lõpeta protsess hüpikaknas.
+pickODFile=Vali OneDrive fail
+pickGDriveFile=Vali Google Drive fail
+pickDeviceFile=Vali fail seadmelt

+ 22 - 4
src/main/webapp/templates/index.xml

@@ -24,6 +24,26 @@
   <add>Uhttps://jgraph.github.io/drawio-libs/libs/integration/users_and_roles.xml</add>
   <add>Uhttps://jgraph.github.io/drawio-libs/libs/integration/deprecated.xml</add>
 </clibs>
+<clibs name="fortinet">
+  <add>Uhttps://jgraph.github.io/drawio-libs/libs/fortinet/Fortinet_Buildings.xml</add>
+  <add>Uhttps://jgraph.github.io/drawio-libs/libs/fortinet/Fortinet_Cloud.xml</add>
+  <add>Uhttps://jgraph.github.io/drawio-libs/libs/fortinet/Fortinet_Connector_DevOps_API.xml</add>
+  <add>Uhttps://jgraph.github.io/drawio-libs/libs/fortinet/Fortinet_Devices.xml</add>
+  <add>Uhttps://jgraph.github.io/drawio-libs/libs/fortinet/Fortinet_Features.xml</add>
+  <add>Uhttps://jgraph.github.io/drawio-libs/libs/fortinet/Fortinet_Generic_Devices.xml</add>
+  <add>Uhttps://jgraph.github.io/drawio-libs/libs/fortinet/Fortinet_Generic_Products.xml</add>
+  <add>Uhttps://jgraph.github.io/drawio-libs/libs/fortinet/Fortinet_Generic_Technology.xml</add>
+  <add>Uhttps://jgraph.github.io/drawio-libs/libs/fortinet/Fortinet_OT_and_IoT.xml</add>
+  <add>Uhttps://jgraph.github.io/drawio-libs/libs/fortinet/Fortinet_People_and_NOC_SOC.xml</add>
+  <add>Uhttps://jgraph.github.io/drawio-libs/libs/fortinet/Fortinet_People_and_Red_Blue_Team.xml</add>
+  <add>Uhttps://jgraph.github.io/drawio-libs/libs/fortinet/Fortinet_Platform_Core_Elements.xml</add>
+  <add>Uhttps://jgraph.github.io/drawio-libs/libs/fortinet/Fortinet_Products.xml</add>
+  <add>Uhttps://jgraph.github.io/drawio-libs/libs/fortinet/Fortinet_SaaS_Family_of_Offerings.xml</add>
+  <add>Uhttps://jgraph.github.io/drawio-libs/libs/fortinet/Fortinet_Solutions_and_Deployment_Scenarios.xml</add>
+  <add>Uhttps://jgraph.github.io/drawio-libs/libs/fortinet/Fortinet_Threats_and_Threat_Services.xml</add>
+  <add>Uhttps://jgraph.github.io/drawio-libs/libs/fortinet/Fortinet_Vertical_Related.xml</add>
+  <add>Uhttps://jgraph.github.io/drawio-libs/libs/fortinet/Fortinet_VM_Components.xml</add>
+</clibs>
 <template url="business/accd.xml" libs="general"/>
 <template url="business/accd.xml" libs="general"/>
 <template url="business/archimate.xml" libs="general;archimate3"/>
@@ -44,7 +64,6 @@
 <template url="charts/org_chart_1.xml" libs="general"/>
 <template url="charts/org_chart_2.xml" libs="general"/>
 <template url="charts/work_breakdown_structure.xml" libs="general"/>
-
 <template url="cloud/aws_1.xml" libs="general;aws4"/>
 <template url="cloud/aws_2.xml" libs="general;aws4"/>
 <template url="cloud/aws_3.xml" libs="general;aws4"/>
@@ -86,7 +105,6 @@
 <template url="cloud/ibm_private_cloud.xml" libs="general;ibm"/>
 <template url="cloud/ibm_vcenter_server_platform.xml" libs="general;ibm"/>
 <template url="cloud/ibm_vpc_architecture.xml" libs="general;ibm"/>
-
 <template url="engineering/cabinet.xml" libs="general;cabinets"/>
 <template url="engineering/electrical_1.xml" libs="general;electrical"/>
 <template url="engineering/electrical_2.xml" libs="general;electrical"/>
@@ -99,8 +117,6 @@
 <template url="flowcharts/flowchart_1.xml" libs="general;flowchart"/>
 <template url="flowcharts/flowchart_2.xml" libs="general;flowchart"/>
 <template url="flowcharts/workflow_1.xml" libs="general;flowchart"/>
-
-
 <template url="layout/blog_wireframe.xml" libs="general;mockups"/>
 <template url="layout/bootstrap.xml" libs="general;bootstrap"/>
 <template url="layout/wireframe_1.xml" libs="general;mockups"/>
@@ -116,6 +132,7 @@
 <template url="network/cisco_2.xml" libs="general;cisco"/>
 <template url="network/citrix.xml" libs="general;citrix"/>
 <template url="network/enterprise_1.xml" libs="general;citrix"/>
+<template url="network/fortinet.xml" libs="general" clibs="fortinet"/>
 <template url="network/internet.xml" libs="general;network"/>
 <template url="network/lan.xml" libs="general;network"/>
 <template url="network/telecomm.xml" libs="general;network"/>
@@ -124,6 +141,7 @@
 <template url="other/block.xml" libs="general;"/>
 <template url="other/cycle_1.xml" libs="general;"/>
 <template url="other/decision_tree.xml" libs="general;"/>
+<template url="other/delivery_diagram.xml" libs="general;" clibs="Uhttps%3A%2F%2Fjgraph.github.io%2Fdrawio-libs%2Flibs%2F%2Fdelivery-icons.xml"/>
 <template url="other/educational.xml" libs="general;"/>
 <template url="other/floor_plan.xml" libs="general;floorplan"/>
 <template url="other/infographic_1.xml" libs="general;mockups;signs"/>

BIN
src/main/webapp/templates/network/fortinet.png


Разница между файлами не показана из-за своего большого размера
+ 1 - 0
src/main/webapp/templates/network/fortinet.xml


BIN
src/main/webapp/templates/other/delivery_diagram.png


Разница между файлами не показана из-за своего большого размера
+ 1 - 0
src/main/webapp/templates/other/delivery_diagram.xml