David Benson před 7 roky
rodič
revize
52575c1aa0

+ 5 - 0
ChangeLog

@@ -1,3 +1,8 @@
+04-MAR-2018: 8.5.3
+
+- Gliffy import improvement
+- Fix for anchor download issue in Chrome 65
+
 31-MAR-2018: 8.5.2
 
 - Add mass gliffy import in Confluence Cloud

+ 1 - 1
VERSION

@@ -1 +1 @@
-8.5.2
+8.5.3

+ 606 - 0
etc/slidesaddon/Code.gs

@@ -0,0 +1,606 @@
+/**
+ * Draw.io Diagrams Docs add-on v1.9
+ * Copyright (c) 2018, JGraph Ltd
+ */
+var EXPORT_URL = "https://exp.draw.io/ImageExport4/export";
+var DRAW_URL = "https://www.draw.io/";
+var SCALING_VALUE = 0.8; // Google Docs seem to be downscaling all images by this amount
+
+/**
+ * Creates a menu entry in the Google Docs UI when the document is opened.
+ */
+function onOpen()
+{
+  SlidesApp.getUi().createAddonMenu()
+      .addItem("Insert Diagrams", "insertDiagrams")
+      .addItem("Update Selected", "updateSelected")
+      .addItem("Update All", "updateAll")
+      .addToUi();
+}
+
+/**
+ * Runs when the add-on is installed.
+ */
+function onInstall()
+{
+  onOpen();
+}
+
+/**
+ * Gets the user's OAuth 2.0 access token so that it can be passed to Picker.
+ * This technique keeps Picker from needing to show its own authorization
+ * dialog, but is only possible if the OAuth scope that Picker needs is
+ * available in Apps Script. In this case, the function includes an unused call
+ * to a DriveApp method to ensure that Apps Script requests access to all files
+ * in the user's Drive.
+ *
+ * @return {string} The user's OAuth 2.0 access token.
+ */
+function getOAuthToken() {
+  DriveApp.getRootFolder();
+  return ScriptApp.getOAuthToken();
+}
+
+/**
+ * Shows a picker and lets the user select multiple diagrams to insert.
+ */
+function insertDiagrams()
+{
+  var html = HtmlService.createHtmlOutputFromFile('Picker.html')
+      .setWidth(620).setHeight(440)
+      .setSandboxMode(HtmlService.SandboxMode.IFRAME);
+  SlidesApp.getUi().showModalDialog(html, 'Select draw.io Diagrams');
+}
+
+/**
+ * Inserts an image for the given diagram.
+ */
+function pickerHandler(items)
+{
+  var app = UiApp.getActiveApplication();
+  
+  // Delay after closing the picker used to show spinner on client-side
+  app.close();
+  
+  if (items != null && items.length > 0)
+  {
+      var inserted = 0;
+      var errors = [];
+    
+      // if there are selected items in the slides, assume they are going to be replaced
+      // by the newly inserted images
+      var selectionCoordinates = getSelectionCoordinates();
+      var offsetX = selectionCoordinates[0];
+      var offsetY = selectionCoordinates[1];
+    
+      deleteSelectedElements();
+    
+      var step = 10;
+    
+      for (var i = 0; i < items.length; i++)
+      {
+        try
+        {
+          if (insertDiagram(items[i].id, items[i].page, offsetX, offsetY) != null)
+          { 
+            inserted++;
+            offsetX = offsetX + step;
+            offsetY = offsetY + step;
+          }
+	      else
+	      {
+	    	errors.push("- " + items[i].name + ": Unknown error");
+	      }
+        }
+        catch (e)
+        {
+          errors.push("- " + items[i].name + ": " + e.message);
+        }
+      }
+    
+      // Shows message only in case of errors
+      if (errors.length > 0)
+      {
+        var msg = "";
+
+        if (errors.length > 0)
+        {
+          msg += errors.length + " insert" + ((errors.length > 1) ? "s" : "") + " failed:\n";
+        }
+        
+        msg += errors.join("\n");
+        SlidesApp.getUi().alert(msg);
+      }
+  }
+}
+
+/**
+  Finds left-most and top-most coordinates of selected page elements; (0,0) by default
+  @return left-most and top-most coordinates in an array
+**/
+function getSelectionCoordinates() 
+{
+  var selection = SlidesApp.getActivePresentation().getSelection();
+  switch (selection.getSelectionType()) 
+  {
+    case SlidesApp.SelectionType.PAGE_ELEMENT:
+    {
+      // only interested if selection is containing page elements
+      var elements = selection.getPageElementRange();
+      var top = 1000000;
+      var left = 1000000;
+      if (elements) 
+      {
+        // find the left-most, top-most coordinate of selected elements
+        var pageElements = elements.getPageElements();
+        for (var i = 0; i < pageElements.length; i++) 
+        {
+          var element = pageElements[i];
+          var elementTop = element.getTop();
+          var elementLeft = element.getLeft();
+          if (top > elementTop)
+            top = elementTop;
+          if (left > elementLeft)
+            left = elementLeft;
+        }
+        return [left, top];
+      }
+    }
+  }
+  return [0, 0];
+}
+
+/**
+  Deletes selected elements
+**/
+function deleteSelectedElements() 
+{
+  var selection = SlidesApp.getActivePresentation().getSelection();
+  switch (selection.getSelectionType()) 
+  {
+    case SlidesApp.SelectionType.PAGE_ELEMENT: 
+      {
+      // only interested if selection is containing page elements
+      var elements = selection.getPageElementRange();
+      if (elements) {
+        var pageElements = elements.getPageElements();
+        // find the left-most, top-most coordinate of selected elements
+        for (var i = 0; i < pageElements.length; i++) 
+        {
+          // delete all selected page elements
+          var element = pageElements[i];
+          element.remove();
+        }
+      }
+    } 
+  }
+}
+
+/**
+ * Inserts the given diagram at the given position.
+ */
+function insertDiagram(id, page, offsetX, offsetY)
+{
+  var scale = 2;
+  var result = fetchImage(id, page, scale);
+  
+  var blob = result[0];
+  var w = result[1] * SCALING_VALUE;
+  var h = result[2] * SCALING_VALUE;
+  var img = null;
+  
+  if (blob != null)
+  {
+      var slide = SlidesApp.getActivePresentation().getSelection().getCurrentPage().asSlide();
+      var img = slide.insertImage(blob);
+      img.setLeft(offsetX);
+      img.setTop(offsetY);
+      var wmax = SlidesApp.getActivePresentation().getPageWidth();
+      var hmax = SlidesApp.getActivePresentation().getPageHeight();
+    
+      var link = createLink(id, page, scale);
+	  img.setLinkUrl(link);
+	  
+	  // Scales to document width if not placeholder
+      if (w == 0 && h == 0) {
+	      var w = img.getWidth();
+          var h = img.getHeight();
+      }
+    
+      var nw = w;
+      var nh = h;
+    
+      // adjust scale (retina/HiDPI display support)
+      w /= scale;
+      h /= scale;
+	  
+	  if (wmax > 0 && w > wmax)
+	  {
+	    var aspect = w / h;
+	    
+	    // Keeps width and aspect
+	    nw = wmax;
+	    nh = wmax / aspect;
+	  }
+      else if (hmax > 0 && h > hmax) {
+        var aspect = h / w;
+
+        // Keeps height and aspect
+	    nh = hmax;
+	    nw = hmax / aspect;
+      }
+      img.setWidth(w);
+      img.setHeight(h);
+  }
+  else
+  {
+    throw new Error("Invalid image " + id);
+  }
+  
+  return img;
+}
+
+/**
+ * Updates the selected diagrams in-place.
+ */
+function updateSelected()
+{
+  var selection = SlidesApp.getActivePresentation().getSelection();
+    
+  if (selection)
+  {
+    switch (selection.getSelectionType()) 
+    {
+      case SlidesApp.SelectionType.PAGE_ELEMENT:
+      {
+        var selected = selection.getPageElementRange();
+        if (!selected)
+          return;
+        
+        selected = selected.getPageElements();
+        
+        var elts = [];
+        
+        // Unwraps selection elements
+        for (var i = 0; i < selected.length; i++)
+        {
+          var pageElement = selected[i];
+          switch (pageElement.getPageElementType())
+          {
+            case SlidesApp.PageElementType.IMAGE:
+            {
+              elts.push(selected[i].asImage());
+            }
+          }
+        }
+        updateElements(elts);
+      }
+    }
+  }
+  else
+  {
+    SlidesApp.getUi().alert("No selection");
+  }
+}
+
+/**
+ * Updates all diagrams in the document.
+ */
+function updateAll()
+{
+  // collect all slides
+  var slides = SlidesApp.getActivePresentation().getSlides();
+  var images = [];
+  for (var i = 0; i < slides.length; i++)
+  {
+    // collect all images on all slides
+    var slide = slides[i];
+    var slideImages = slide.getImages();
+    images = images.concat(slideImages);
+  }
+  updateElements(images);
+}
+
+/**
+ * Updates all diagrams in the document.
+ */
+function updateElements(elts)
+{
+  if (elts != null)
+  {
+    var updated = 0;
+    var errors = [];
+    
+    for (var i = 0; i < elts.length; i++)
+    {
+      try
+      {
+        if (updateElement(elts[i]) != null)
+        {
+          updated++;
+        }
+      }
+      catch (e)
+      {
+        errors.push("- " + e.message);
+      }
+    }
+    
+    // Shows status in case of errors or multiple updates
+    if (errors.length > 0 || updated > 1)
+    {
+      var msg = "";
+      
+      if (updated > 0)
+      {
+        msg += updated + " diagram" + ((updated > 1) ? "s" : "") + " updated\n";
+      }
+      
+      if (errors.length > 0)
+      {
+        msg += errors.length + " update" + ((errors.length > 1) ? "s" : "") + " failed:\n";
+      }
+      
+      msg += errors.join("\n");
+      SlidesApp.getUi().alert(msg);
+    }
+  }
+}
+
+/**
+ * Returns true if the given URL points to draw.io
+ */
+function createLink(id, page, scale)
+{
+  var params = [];
+  
+  if (page != null && page != "0")
+  {
+    params.push('page=' + page);
+  }
+  
+  // This URL parameter is ignored and is used internally to
+  // store the retina flag since there is no other storage
+  if (scale != null && scale != 1)
+  {
+    params.push('scale=' + scale);
+  }
+  
+  return DRAW_URL + ((params.length > 0) ? "?" + params.join("&") : "") + "#G" + id;
+}
+
+/**
+ * Returns true if the given URL points to draw.io
+ */
+function isValidLink(url)
+{
+  return url != null && (url.substring(0, DRAW_URL.length) == DRAW_URL || url.substring(0, 22) == "https://drive.draw.io/");
+}
+
+/**
+ * Returns the diagram ID for the given URL.
+ */
+function getDiagramId(url)
+{
+  return url.substring(url.lastIndexOf("#G") + 2);
+}
+
+/**
+ * Returns the diagram ID for the given URL.
+ */
+function getUrlParams(url)
+{
+  var result = {};
+  var idx = url.indexOf("?");
+  
+  if (idx > 0)
+  {
+    var idx2 = url.indexOf("#", idx + 1);
+    
+    if (idx2 < 0)
+    {
+      idx2 = url.length;
+    }
+    
+    if (idx2 > idx)
+    {
+      var search = url.substring(idx + 1, idx2);
+      var tokens = search.split("&");
+      
+      for (var i = 0; i < tokens.length; i++)
+      {
+         var idx3 = tokens[i].indexOf('=');
+        
+         if (idx3 > 0)
+         {
+           result[tokens[i].substring(0, idx3)] = tokens[i].substring(idx3 + 1);
+         }
+      }
+    }
+  }
+  
+  return result;
+}
+
+/**
+ * Updates the diagram in the given inline image and returns the new inline image.
+ */
+function updateElement(elt)
+{
+  var result = null;
+  
+  if (elt.getPageElementType() == SlidesApp.PageElementType.IMAGE)
+  {
+    var url = elt.getLink();
+    if (url != null)
+      url = url.getUrl();
+    
+    if (url == null)
+    {
+      // commenting this out - missing link is most of the time an indicator image is not coming from draw.io
+      // we probably don't want to have this popping out as an error
+      // throw new Error("Missing link")
+    }
+    else if (isValidLink(url))
+    {
+      var id = getDiagramId(url);
+      var params = getUrlParams(url);
+      
+      if (id != null && id.length > 0)
+      {
+        result = updateDiagram(id, params["page"], parseFloat(params["scale"] || 1), elt);
+      }
+      else
+      {
+        // commenting this out as well - invalid link might indicate image is not coming from draw.io
+        // throw new Error("Invalid link " + url);
+      }
+    }
+  }
+  
+  return result;
+}
+
+/**
+ * Updates the diagram in the given inline image and returns the new inline image.
+ */
+function updateDiagram(id, page, scale, elt)
+{
+  var img = null;
+  var result = fetchImage(id, page, scale);
+  
+  var isOK = false;
+  
+  if (result != null) 
+  {
+    var blob = result[0];
+    var w = result[1] / scale;
+    var h = result[2] / scale;
+    
+    if (blob != null)
+    {
+      isOK = true;
+      // There doesn't seem to be a natural way to replace images in SlidesApp
+      // Slides API seems to only provide means to get a page and a group associated with the image
+      // Groups only allow removal of elements though, not insertions (seems like a half-baked API)
+      
+      // This code just adds a new image to page to the same position as the old image and removes the old image
+      // TODO: No group information will be preserved right now
+      // TODO: A question about it was posted here:
+      // TODO: https://plus.google.com/111221846870143003075/posts/EPAM8HrZhe2
+      // TODO: if there is a satisfying answer, it will be implemented
+      
+      var page = elt.getParentPage();
+      var left = elt.getLeft();
+      var top = elt.getTop();
+      var width = elt.getWidth();
+      var height = elt.getHeight();
+      
+      // This part addresses the possibility of updated diagrams having completely different shapes,
+      // causing diagrams no longer fitting into page
+      
+      // Keep top-left corner, adjust width x height
+      // keep either original width or height together with new aspect ratio
+      
+      if (w == 0 && h == 0)
+      {
+        w = width;
+        h = height;
+      }
+      
+      // make sure dimensions are reasonable
+      if (w == 0 || h == 0)
+        throw new Error("One or both image dimensions are zero!");
+      
+      var nw = w;
+      var nh = h;
+
+      // if aspect w/h < 1, use original width, otherwise use original height
+      // i.e. the smaller dimension keeps preserved (seems more natural while testing than preserving the larger dimension; can be changed anyway)
+      var aspect = w / h;
+      
+      if (aspect < 1)
+      {
+        nw = width;
+        nh = width / aspect;
+      }
+      else
+      {
+        nw = height * aspect;
+        nh = height;
+      }
+      
+      // now check if the image is still not too large
+      var wmax = SlidesApp.getActivePresentation().getPageWidth();
+      var hmax = SlidesApp.getActivePresentation().getPageHeight();
+
+	  if (wmax > 0 && w > wmax)
+	  {
+	    aspect = w / h;
+	    
+	    // Keeps width and aspect
+	    nw = wmax;
+	    nh = wmax / aspect;
+	  }
+      else if (hmax > 0 && h > hmax) {
+        aspect = h / w;
+
+        // Keeps height and aspect
+	    nh = hmax;
+	    nw = hmax / aspect;
+      }
+      
+      // make sure image is at least 1 pixel wide/high
+      nw = Math.max(1, nw);
+      nh = Math.max(1, nh);
+      
+      // replace image with the same link
+      var img = page.insertImage(blob, left, top, nw, nh);
+      var link = createLink(id, page, scale);
+      img.setLinkUrl(link);
+      
+      elt.remove();
+    }
+  }
+  if (!isOK)
+  {
+    throw new Error("Invalid image " + id);
+  }
+  
+  return img;
+}
+
+/**
+ * Fetches the diagram for the given document ID.
+ */
+function fetchImage(id, page, scale)
+{
+    var file = DriveApp.getFileById(id);
+
+    if (file != null && file.getSize() > 0)
+    {
+	    var response = UrlFetchApp.fetch(EXPORT_URL,
+	    {
+		  "method": "post",
+		  "payload":
+		  {
+		    "format": "png",
+            "scale": scale || "1",
+//            "border": (scale == 2) ? "1" : "0", // not sure why it was set to 1, it looks better with 0 now, so I commented it out
+            "border": "0",
+            "from": page || "0",
+		    "xml": encodeURIComponent(file.getBlob().getDataAsString())
+		  }
+		});
+      
+      var headers = response.getHeaders();
+
+      return [response.getBlob(), headers["content-ex-width"] || 0, headers["content-ex-height"] || 0];
+    }
+    else
+    {
+    	// Returns an empty, transparent 1x1 PNG image as a placeholder
+    	return Utilities.newBlob(Utilities.base64Decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNg+M9QDwADgQF/e5IkGQAAAABJRU5ErkJggg=="), "image/png");
+    }
+}
+

+ 252 - 0
etc/slidesaddon/Picker.html

@@ -0,0 +1,252 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
+<script type="text/javascript">
+  var DIALOG_DIMENSIONS = {width: 600, height: 420};
+  var DEVELOPER_KEY = 'AIzaSyB4sU8tc25bR_87qNb7eUVQN72_vv8mpbU';
+
+  /**
+   * Loads the Google Picker API.
+   */
+  function onApiLoad()
+  {
+    gapi.load('picker', {'callback': function()
+    {
+      getOAuthToken();
+    }});
+  }
+
+  /**
+   * Gets the user's OAuth 2.0 access token from the server-side script so that
+   * it can be passed to Picker. This technique keeps Picker from needing to
+   * show its own authorization dialog, but is only possible if the OAuth scope
+   * that Picker needs is available in Apps Script. Otherwise, your Picker code
+   * will need to declare its own OAuth scopes.
+   */
+  function getOAuthToken()
+  {
+	try
+	{
+    	google.script.run.withSuccessHandler(createPicker)
+        	.withFailureHandler(showError).getOAuthToken();
+	}
+	catch (e)
+	{
+		showError(e.message);
+	}
+  }
+
+  /**
+   * Creates a Picker that can access the user's spreadsheets. This function
+   * uses advanced options to hide the Picker's left navigation panel and
+   * default title bar.
+   *
+   * @param {string} token An OAuth 2.0 access token that lets Picker access the
+   *     file type specified in the addView call.
+   */
+  function createPicker(token)
+  {
+    if (token)
+    {
+		var view1 = new google.picker.DocsView(google.picker.ViewId.FOLDERS)
+		    	.setParent('root')
+		    	.setIncludeFolders(true)
+			.setMimeTypes('*/*');
+		
+		var view2 = new google.picker.DocsView()
+			.setIncludeFolders(true);
+		
+		var view3 = new google.picker.DocsView()
+			.setEnableTeamDrives(true)
+			.setIncludeFolders(true);
+
+		var view4 = new google.picker.DocsUploadView()
+			.setIncludeFolders(true);
+
+		var picker = new google.picker.PickerBuilder()
+		    .addView(view1)
+		    .addView(view2)
+		    .addView(view3)
+		    	.addView(view4)
+		    	.addView(google.picker.ViewId.RECENTLY_PICKED)
+		    .enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
+		    .enableFeature(google.picker.Feature.SUPPORT_TEAM_DRIVES)
+		    .hideTitleBar()
+		    .setOAuthToken(token)
+		    .setDeveloperKey(DEVELOPER_KEY)
+		    .setCallback(pickerCallback)
+		    .setOrigin(google.script.host.origin)
+		    .setSize(DIALOG_DIMENSIONS.width - 2, DIALOG_DIMENSIONS.height - 2)
+		    .build();
+		picker.setVisible(true);
+    }
+    else
+    {
+      	showError('Unable to load the file picker.');
+    }
+  }
+
+  /**
+   * A callback function that extracts the chosen document's metadata from the
+   * response object. For details on the response object, see
+   * https://developers.google.com/picker/docs/result
+   *
+   * @param {object} data The response object.
+   */
+  function pickerCallback(data)
+  {
+    var action = data[google.picker.Response.ACTION];
+    
+    if (action == google.picker.Action.PICKED)
+    {
+      var items = [];
+      var docs = data[google.picker.Response.DOCUMENTS];
+      
+      for (var i = 0; i < docs.length; i++)
+      {
+        items.push({name: docs[i][google.picker.Document.NAME], id: docs[i][google.picker.Document.ID]}); 
+      }
+      
+      if (items.length > 0)
+      {
+        selectPages(items, function(execute)
+        {
+          if (execute)
+          {
+            document.getElementById('status').innerHTML = (items.length > 1) ?
+              'Inserting ' + items.length + ' Diagrams...' : "Inserting Diagram...";
+            google.script.run.withSuccessHandler(closeWindow).pickerHandler(items);
+          }
+          else
+          {
+            google.script.host.close();
+          }
+        });
+      }
+      else
+      {
+        google.script.host.close();
+      }
+    }
+    else if (action == google.picker.Action.CANCEL)
+    {
+      google.script.host.close();
+    }
+  }
+    
+  /**
+   * Closes the window after all diagrams have been inserted.
+   */
+  function selectPages(items, handler)
+  {
+    document.getElementById('spinner').style.display = 'none';
+    
+    var pageInputs = [];
+    var table = document.createElement('table');
+    table.setAttribute('cellpadding', '4');
+    table.style.width = '100%';
+    var tbody = document.createElement('tbody');
+    
+    var title = document.createElement('td');
+    title.setAttribute('colspan', '2');
+    title.innerHTML = '<font size="3">Select ' + ((items.length > 1) ? 'Pages' : 'Page') +
+      ' and Click Insert</font>';
+    
+    var row = document.createElement('tr');
+    row.appendChild(title);
+    tbody.appendChild(row);
+    
+    for (var i = 0; i < items.length; i++)
+    {
+      var row = document.createElement('tr');
+      
+      var td1 = document.createElement('td');
+      td1.appendChild(document.createTextNode(items[i].name));
+      td1.setAttribute('title', 'ID ' + items[i].id);
+      row.appendChild(td1);
+      
+      var td2 = document.createElement('td');
+      td2.style.paddingLeft = '10px';
+      td2.setAttribute('align', 'right');
+      td2.innerHTML = 'Page: ';
+      var input = document.createElement('input');
+      input.setAttribute('type', 'number');
+      input.setAttribute('min', '1');
+      input.setAttribute('value', '1');
+      input.style.width = '60px';
+      td2.appendChild(input);
+      pageInputs.push(input);
+      row.appendChild(td2);
+      
+      tbody.appendChild(row);
+    }
+    
+    var buttons = document.createElement('td');
+    buttons.setAttribute('colspan', '2');
+    buttons.setAttribute('align', 'right');
+
+    var cancelButton = document.createElement('button');
+    cancelButton.innerHTML = 'Cancel';
+    buttons.appendChild(cancelButton);
+
+    cancelButton.addEventListener('click', function()
+    {
+      handler(false);
+    });
+
+    var insertButton = document.createElement('button');
+    insertButton.innerHTML = 'Insert';
+    insertButton.className = 'blue';
+    buttons.appendChild(insertButton);
+
+    insertButton.addEventListener('click', function()
+    {
+      table.parentNode.removeChild(table);
+      document.getElementById('spinner').style.display = '';
+      
+      for (var i = 0; i < items.length; i++)
+      {
+        items[i].page = (parseInt(pageInputs[i].value) || 1) - 1;
+      }
+      
+      handler(true);
+    });
+    
+    var row = document.createElement('tr');
+    row.appendChild(buttons);
+    tbody.appendChild(row);
+    
+    table.appendChild(tbody);
+    document.body.appendChild(table);
+  }
+
+  /**
+   * Closes the window after all diagrams have been inserted.
+   */
+  function closeWindow()
+  {
+    google.script.host.close();
+  }
+
+  /**
+   * Displays an error message within the #result element.
+   *
+   * @param {string} message The error message to display.
+   */
+  function showError(message)
+  {
+	document.getElementById('icon').setAttribute('src', 'https://www.draw.io/images/stop-flat-icon-80.png');
+    document.getElementById('status').innerHTML = 'Error: ' + message;
+  }
+</script>
+</head>
+<body>
+<div id="spinner" style="text-align:center;padding-top:100px;">
+<img id="icon" src="https://www.draw.io/images/ajax-loader.gif"/>
+<h3 id="status" style="margin-top:6px;">Loading...</h3>
+</div>
+<script src="https://apis.google.com/js/api.js?onload=onApiLoad"></script>
+</body>
+</html>
+

+ 15 - 0
etc/slidesaddon/README

@@ -0,0 +1,15 @@
+For Testing:
+
+As david.benson@appsscripttesting.com use (see Tools, Script Editor):
+- TBA -
+
+For Deployment:
+
+As david@jgraph.com use (see Tools, Script Editor):
+- TBA -
+
+Open the web app script in the script editor.
+Make the changes you wanted. Test the code to ensure it functions as intended and is bug-free.
+Click File > Manage Versions. Enter a new version description and click Save New Version. Click OK to close the dialog.
+Click Publish > Deploy as Docs add-on. Update the Project version to the new version you just created.
+Click Update.

+ 2 - 3
src/main/java/com/mxgraph/io/gliffy/importer/gliffyTranslation.properties

@@ -1121,8 +1121,7 @@ com.gliffy.shape.network.network_v3.business.comm_link=mxgraph.networks.comm_lin
 com.gliffy.shape.network.network_v3.business.user=image;image=img/lib/clip_art/people/Tech_Man_128x128.png
 com.gliffy.shape.network.network_v3.business.user_female=image;image=img/lib/clip_art/people/Worker_Woman_128x128.png
 com.gliffy.shape.network.network_v3.business.user_male=image;image=img/lib/clip_art/people/Tech_Man_128x128.png
-#composite
-com.gliffy.shape.network.network_v3.business.user_group=rect
+com.gliffy.shape.network.network_v3.business.user_group=mxgraph.networks.users;strokeColor=#ffffff
 com.gliffy.shape.network.network_v3.business.server=image;image=img/lib/clip_art/computers/Server_Tower_128x128.png
 com.gliffy.shape.network.network_v3.business.database_server=image;image=img/lib/clip_art/computers/Server_Tower_128x128.png
 com.gliffy.shape.network.network_v3.business.mail_server=image;image=img/lib/clip_art/computers/Server_Tower_128x128.png
@@ -1141,7 +1140,7 @@ com.gliffy.shape.network.network_v3.business.mainframe=image;image=img/lib/clip_
 com.gliffy.shape.network.network_v3.business.rack_server_1u=image;image=img/lib/clip_art/computers/Server_128x128.png
 com.gliffy.shape.network.network_v3.business.multi_u_server=image;image=img/lib/clip_art/computers/Server_128x128.png
 com.gliffy.shape.network.network_v3.business.rack=image;image=img/lib/clip_art/computers/Server_Rack_Empty_128x128.png
-#com.gliffy.shape.network.network_v3.business.telephone=
+com.gliffy.shape.network.network_v3.business.telephone=image;image=img/lib/clip_art/telecommunication/iPhone_128x128.png
 #com.gliffy.shape.network.network_v3.business.flash_drive=
 #It needs a new modern icon to match the remaining icons
 com.gliffy.shape.network.network_v3.business.tape_backup=mxgraph.networks.tape_storage;strokeColor=#ffffff

binární
src/main/webapp/WEB-INF/lib/appengine-api-1.0-sdk-1.9.63.jar


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

@@ -1,7 +1,7 @@
 CACHE MANIFEST
 
 # THIS FILE WAS GENERATED. DO NOT MODIFY!
-# 03/31/2018 02:35 PM
+# 04/04/2018 01:26 PM
 
 app.html
 index.html?offline=1

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 235 - 233
src/main/webapp/js/app.min.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 238 - 236
src/main/webapp/js/atlas-viewer.min.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 216 - 214
src/main/webapp/js/atlas.min.js


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

@@ -4306,8 +4306,8 @@ App.prototype.convertFile = function(url, filename, mimeType, extension, success
 		gitHubUrl = true;
 	}
 	
-	// Workaround for wrong binary response with VSDX files
-	if (/\.vsdx$/i.test(filename) && Graph.fileSupport && new XMLHttpRequest().upload &&
+	// Workaround for wrong binary response with VSDX/VSD files
+	if ((/\.vsdx$/i.test(filename) || /\.vsd$/i.test(filename)) && Graph.fileSupport && new XMLHttpRequest().upload &&
 		typeof new XMLHttpRequest().responseType === 'string')
 	{
 		var req = new XMLHttpRequest();
@@ -4335,7 +4335,7 @@ App.prototype.convertFile = function(url, filename, mimeType, extension, success
 			this.importVisio(blob, mxUtils.bind(this, function(xml)
 			{
 				success(new LocalFile(this, xml, name, true));
-			}), error)
+			}), error, filename)
 		});
 
 		req.send();

+ 53 - 17
src/main/webapp/js/diagramly/EditorUi.js

@@ -2156,12 +2156,12 @@
 									}
 								});
 								
-								if  (file != null && img != null && ((/(\.vsdx)($|\?)/i.test(img)) || /(\.vssx)($|\?)/i.test(img)))
+								if  (file != null && img != null && ((/(\.vsdx)($|\?)/i.test(img)) || /(\.vssx)($|\?)/i.test(img) || (/(\.vsd)($|\?)/i.test(img))))
 								{
 									this.importVisio(file, function(xml)
 									{
 										doImport(xml, 'text/xml');
-									});
+									}, null, img);
 								}
 								else if (!this.isOffline() && new XMLHttpRequest().upload && this.isRemoteFileFormat(data, img) && file != null)
 								{
@@ -2745,12 +2745,8 @@
 				{
 					a.download = filename;
 				}
-				else
-				{
-					// Workaround for same window in Safari
-					a.setAttribute('target', '_blank');
-				}
 
+				a.setAttribute('target', '_blank');
 				document.body.appendChild(a);
 				
 				try
@@ -5389,8 +5385,10 @@
 	/**
 	 * Imports the given Visio file
 	 */
-	EditorUi.prototype.importVisio = function(file, done, onerror)
+	EditorUi.prototype.importVisio = function(file, done, onerror, filename)
 	{
+		filename = (filename != null) ? filename : file.name; 
+
 		onerror = (onerror != null) ? onerror : mxUtils.bind(this, function(e)
 		{
 			this.handleError(e);
@@ -5402,13 +5400,47 @@
 			
 			if (this.doImportVisio)
 			{
-				try
+				if (/(\.vsd)($|\?)/i.test(filename)) 
 				{
-					this.doImportVisio(file, done, onerror);
-				}
-				catch (e)
-				{
-					onerror(e);
+					 var formData = new FormData();
+			         formData.append("file1", file);
+					
+			         var xhr = new XMLHttpRequest();
+			 		 xhr.open('POST', VSD_CONVERT_URL);
+			 		 xhr.responseType = "blob";
+			 		
+			 		 xhr.onreadystatechange = mxUtils.bind(this, function()
+			 		 {
+			 			if (xhr.readyState == 4)
+			 			{	
+			 				if (xhr.status >= 200 && xhr.status <= 299)
+							{
+								try
+								{
+									this.doImportVisio(xhr.response, done, onerror);
+								}
+								catch (e)
+								{
+									onerror(e);
+								}
+							}
+			 				else
+		 					{
+			 					onerror({});
+		 					}
+			 			}
+			 		 });
+			 		
+			 		 xhr.send(formData);
+				} else {
+					try
+					{
+						this.doImportVisio(file, done, onerror);
+					}
+					catch (e)
+					{
+						onerror(e);
+					}
 				}
 			}
 		});
@@ -5874,7 +5906,11 @@
                 }
             }));
         }
-		else if  (file != null && filename != null && ((/(\.vsdx)($|\?)/i.test(filename)) || /(\.vssx)($|\?)/i.test(filename)))
+//		else if  (file != null && filename != null && ((/(\.vsdx)($|\?)/i.test(filename)) || /(\.vssx)($|\?)/i.test(filename) || /(\.vsd)($|\?)/i.test(filename)))
+//		{
+//			
+//        }
+		else if  (file != null && filename != null && ((/(\.vsdx)($|\?)/i.test(filename)) || /(\.vssx)($|\?)/i.test(filename) || /(\.vsd)($|\?)/i.test(filename)))
 		{
 			//  LATER: done and async are a hack before making this asynchronous
 			async = true;
@@ -6282,7 +6318,7 @@
 						});
 						
 						// Handles special cases
-						if (/(\.vsdx)($|\?)/i.test(file.name) || /(\.vssx)($|\?)/i.test(file.name))
+						if (/(\.vsdx)($|\?)/i.test(file.name) || /(\.vssx)($|\?)/i.test(file.name) || /(\.vsd)($|\?)/i.test(file.name))
 						{
 							fn(null, file.type, x + index * gs, y + index * gs, 240, 160, file.name, function(cells)
 							{
@@ -8091,7 +8127,7 @@
 								}
 							});
 							
-							if  (/(\.vsdx)($|\?)/i.test(name) || /(\.vssx)($|\?)/i.test(name))
+							if  (/(\.vsdx)($|\?)/i.test(name) || /(\.vssx)($|\?)/i.test(name) || /(\.vsd)($|\?)/i.test(name))
 							{
 								this.importVisio(file, mxUtils.bind(this, function(xml)
 								{

+ 1 - 0
src/main/webapp/js/diagramly/Init.js

@@ -9,6 +9,7 @@ window.isSvgBrowser = window.isSvgBrowser || (navigator.userAgent.indexOf('MSIE'
 
 // CUSTOM_PARAMETERS - URLs for save and export
 window.EXPORT_URL = window.EXPORT_URL || 'https://exp.draw.io/ImageExport4/export';
+window.VSD_CONVERT_URL = window.VSD_CONVERT_URL || "https://convert.draw.io/VsdConverter/api/converter";
 window.SAVE_URL = window.SAVE_URL || 'save';
 window.OPEN_URL = window.OPEN_URL || 'open';
 window.PROXY_URL = window.PROXY_URL || 'proxy';

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 1
src/main/webapp/js/embed-static.min.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 1
src/main/webapp/js/reader.min.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 238 - 236
src/main/webapp/js/viewer.min.js