drawio-offline
Version:
diagrams.net desktop
1,872 lines (1,617 loc) • 411 kB
JavaScript
/**
* Copyright (c) 2006-2017, JGraph Ltd
* Copyright (c) 2006-2017, Gaudenz Alder
*/
(function()
{
/**
* Version
*/
EditorUi.VERSION = '@DRAWIO-VERSION@';
/**
* Overrides compact UI setting.
*/
EditorUi.compactUi = uiTheme != 'atlas';
/**
* Overrides default grid color for dark mode
*/
if (Editor.isDarkMode())
{
mxGraphView.prototype.gridColor = mxGraphView.prototype.defaultDarkGridColor;
}
/**
* Switch to disable logging for mode and search terms.
*/
EditorUi.enableLogging = urlParams['stealth'] != '1' && urlParams['lockdown'] != '1' &&
(/.*\.draw\.io$/.test(window.location.hostname) ||
/.*\.diagrams\.net$/.test(window.location.hostname)) &&
window.location.hostname != 'support.draw.io';
/**
* Protocol and hostname to use for embedded files. Default is https://www.draw.io
*/
EditorUi.drawHost = window.DRAWIO_BASE_URL;
/**
* Protocol and hostname to use for embedded files. Default is https://www.draw.io
*/
EditorUi.lightboxHost = window.DRAWIO_LIGHTBOX_URL;
/**
* Switch to disable logging for mode and search terms.
*/
EditorUi.lastErrorMessage = null;
/**
* Switch to disable logging for mode and search terms.
*/
EditorUi.ignoredAnonymizedChars = '\n\t`~!@#$%^&*()_+{}|:"<>?-=[]\;\'.\/,\n\t';
/**
* Specifies the URL for the templates index file.
*/
EditorUi.templateFile = TEMPLATE_PATH + '/index.xml';
/**
* Specifies the URL for the diffsync cache.
*/
EditorUi.cacheUrl = (urlParams['dev'] == '1') ? '/cache' : window.REALTIME_URL;
if (EditorUi.cacheUrl == null && typeof DrawioFile !== 'undefined')
{
DrawioFile.SYNC = 'none'; //Disable real-time sync
}
/**
* Cache timeout is 10 seconds.
*/
Editor.cacheTimeout = 10000;
/**
* Switch to enable PlantUML in the insert from text dialog.
* NOTE: This must also be enabled on the server-side.
*/
EditorUi.enablePlantUml = EditorUi.enableLogging;
/**
* https://github.com/electron/electron/issues/2288
*/
EditorUi.isElectronApp = window != null && window.process != null &&
window.process.versions != null && window.process.versions['electron'] != null;
/**
* Shortcut for capability check.
*/
EditorUi.nativeFileSupport = !mxClient.IS_OP && !EditorUi.isElectronApp &&
'showSaveFilePicker' in window && 'showOpenFilePicker' in window;
/**
* Specifies if drafts should be saved in IndexedDB.
*/
EditorUi.enableDrafts = !mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp &&
isLocalStorage && urlParams['drafts'] != '0';
/**
* Link for scratchpad help.
*/
EditorUi.scratchpadHelpLink = 'https://www.diagrams.net/doc/faq/scratchpad';
/**
* Default Mermaid config without using foreign objects in flowcharts.
*/
EditorUi.defaultMermaidConfig = {
theme:'neutral',
arrowMarkerAbsolute:false,
flowchart:
{
htmlLabels:false
},
sequence:
{
diagramMarginX:50,
diagramMarginY:10,
actorMargin:50,
width:150,
height:65,
boxMargin:10,
boxTextMargin:5,
noteMargin:10,
messageMargin:35,
mirrorActors:true,
bottomMarginAdj:1,
useMaxWidth:true,
rightAngles:false,
showSequenceNumbers:false
},
gantt:{
titleTopMargin:25,
barHeight:20,
barGap:4,
topPadding:50,
leftPadding:75,
gridLineStartPadding:35,
fontSize:11,
fontFamily:'"Open-Sans", "sans-serif"',
numberSectionStyles:4,
axisFormat:'%Y-%m-%d'
}
};
/**
* Updates action states depending on the selection.
*/
EditorUi.logError = function(message, url, linenumber, colno, err, severity, quiet)
{
severity = ((severity != null) ? severity : (message.indexOf('NetworkError') >= 0 ||
message.indexOf('SecurityError') >= 0 || message.indexOf('NS_ERROR_FAILURE') >= 0 ||
message.indexOf('out of memory') >= 0) ? 'CONFIG' : 'SEVERE');
if (EditorUi.enableLogging && urlParams['dev'] != '1')
{
try
{
if (message == EditorUi.lastErrorMessage || (message != null && url != null &&
((message.indexOf('Script error') != -1) || (message.indexOf('extension') != -1))))
{
// TODO log external domain script failure "Script error." is
// reported when the error occurs in a script that is hosted
// on a domain other than the domain of the current page
}
// DocumentClosedError seems to be an FF bug an can be ignored for now
else if (message != null && message.indexOf('DocumentClosedError') < 0)
{
EditorUi.lastErrorMessage = message;
var logDomain = window.DRAWIO_LOG_URL != null ? window.DRAWIO_LOG_URL : '';
err = (err != null) ? err : new Error(message);
var img = new Image();
img.src = logDomain + '/log?severity=' + severity + '&v=' + encodeURIComponent(EditorUi.VERSION) +
'&msg=clientError:' + encodeURIComponent(message) + ':url:' + encodeURIComponent(window.location.href) +
':lnum:' + encodeURIComponent(linenumber) + ((colno != null) ? ':colno:' + encodeURIComponent(colno) : '') +
((err != null && err.stack != null) ? '&stack=' + encodeURIComponent(err.stack) : '');
}
}
catch (e)
{
// do nothing
}
}
try
{
if (!quiet && window.console != null)
{
console.error(severity, message, url, linenumber, colno, err);
}
}
catch (e)
{
// ignore
}
};
/**
* Updates action states depending on the selection.
*/
EditorUi.logEvent = function(data)
{
if (urlParams['dev'] == '1')
{
EditorUi.debug('logEvent', data);
}
else if (EditorUi.enableLogging)
{
try
{
var logDomain = window.DRAWIO_LOG_URL != null ? window.DRAWIO_LOG_URL : '';
var img = new Image();
img.src = logDomain + '/images/1x1.png?' +
'v=' + encodeURIComponent(EditorUi.VERSION) +
((data != null) ? '&data=' + encodeURIComponent(JSON.stringify(data)) : '');
}
catch (e)
{
// ignore
}
}
};
/**
* Sending error reports.
*/
EditorUi.sendReport = function(data, maxLength)
{
if (urlParams['dev'] == '1')
{
EditorUi.debug('sendReport', data);
}
else if (EditorUi.enableLogging)
{
try
{
maxLength = (maxLength != null) ? maxLength : 50000;
if (data.length > maxLength)
{
data = data.substring(0, maxLength) + '\n...[SHORTENED]'
}
mxUtils.post('/email', 'version=' + encodeURIComponent(EditorUi.VERSION) +
'&url=' + encodeURIComponent(window.location.href) +
'&data=' + encodeURIComponent(data));
}
catch (e)
{
// ignore
}
}
};
/**
* Adds the listener for automatically saving the diagram for local changes.
*/
EditorUi.debug = function()
{
try
{
if (window.console != null && urlParams['test'] == '1')
{
var args = [new Date().toISOString()];
for (var i = 0; i < arguments.length; i++)
{
if (arguments[i] != null)
{
args.push(arguments[i]);
}
}
console.log.apply(console, args);
}
}
catch (e)
{
// ignore
}
};
/**
* Static method for pasing PNG files.
*/
EditorUi.parsePng = function(f, fn, error)
{
var pos = 0;
function fread(d, count)
{
var start = pos;
pos += count;
return d.substring(start, pos);
};
// Reads unsigned long 32 bit big endian
function _freadint(d)
{
var bytes = fread(d, 4);
return bytes.charCodeAt(3) + (bytes.charCodeAt(2) << 8) +
(bytes.charCodeAt(1) << 16) + (bytes.charCodeAt(0) << 24);
};
// Checks signature
if (fread(f,8) != String.fromCharCode(137) + 'PNG' + String.fromCharCode(13, 10, 26, 10))
{
if (error != null)
{
error();
}
return;
}
// Reads header chunk
fread(f,4);
if (fread(f,4) != 'IHDR')
{
if (error != null)
{
error();
}
return;
}
fread(f, 17);
do
{
var n = _freadint(f);
var type = fread(f,4);
if (fn != null)
{
if (fn(pos - 8, type, n))
{
break;
}
}
value = fread(f,n);
fread(f,4);
if (type == 'IEND')
{
break;
}
}
while (n);
};
/**
* Removes any values, styles and geometries from the given XML node.
*/
EditorUi.removeChildNodes = function(node)
{
while (node.firstChild != null)
{
node.removeChild(node.firstChild);
}
};
/**
* Contains the default XML for an empty diagram.
*/
EditorUi.prototype.emptyDiagramXml = '<mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/></root></mxGraphModel>';
/**
*
*/
EditorUi.prototype.emptyLibraryXml = '<mxlibrary>[]</mxlibrary>';
/**
* Sets the delay for autosave in milliseconds. Default is 2000.
*/
EditorUi.prototype.mode = null;
/**
* General timeout is 25 seconds.
* LATER: Move to Editor
*/
EditorUi.prototype.timeout = Editor.prototype.timeout;
/**
* Allows for two buttons in the sidebar footer.
*/
EditorUi.prototype.sidebarFooterHeight = 38;
/**
* Specifies the default custom shape style.
*/
EditorUi.prototype.defaultCustomShapeStyle = 'shape=stencil(tZRtTsQgEEBPw1+DJR7AoN6DbWftpAgE0Ortd/jYRGq72R+YNE2YgTePloEJGWblgA18ZuKFDcMj5/Sm8boZq+BgjCX4pTyqk6ZlKROitwusOMXKQDODx5iy4pXxZ5qTHiFHawxB0JrQZH7lCabQ0Fr+XWC1/E8zcsT/gAi+Subo2/3Mh6d/oJb5nU1b5tW7r2knautaa3T+U32o7f7vZwpJkaNDLORJjcu7t59m2jXxqX9un+tt022acsfmoKaQZ+vhhswZtS6Ne/ThQGt0IV0N3Yyv6P3CeT9/tHO0XFI5cAE=);whiteSpace=wrap;html=1;';
/**
* Defines the maximum size for images.
*/
EditorUi.prototype.maxBackgroundSize = 1600;
/**
* Defines the maximum size for images.
*/
EditorUi.prototype.maxImageSize = 520;
/**
* Defines the maximum width for pasted text.
* Use 0 to disable check.
*/
EditorUi.prototype.maxTextWidth = 520;
/**
* Images above 100K should be resampled.
*/
EditorUi.prototype.resampleThreshold = 100000;
/**
* Maximum allowed size for images is 1 MB.
*/
EditorUi.prototype.maxImageBytes = 1000000;
/**
* Maximum size for background images is 2.5 MB.
*/
EditorUi.prototype.maxBackgroundBytes = 2500000;
/**
* Maximum size for text files in labels is 0.5 MB.
*/
EditorUi.prototype.maxTextBytes = 500000;
/**
* Holds the current file.
*/
EditorUi.prototype.currentFile = null;
/**
* Specifies if PDF export should be done via print dialog. Default is
* false which uses the PhantomJS backend to create the PDF.
*/
EditorUi.prototype.printPdfExport = false;
/**
* Specifies if PDF export with pages is enabled.
*/
EditorUi.prototype.pdfPageExport = true;
/**
* Restores app defaults for UI
*/
EditorUi.prototype.formatEnabled = urlParams['format'] != '0';
/**
* Whether template action should be shown in insert menu.
*/
EditorUi.prototype.insertTemplateEnabled = true;
/**
* Restores app defaults for UI
*/
EditorUi.prototype.closableScratchpad = true;
/**
* Capability check for canvas export
*/
(function()
{
EditorUi.prototype.useCanvasForExport = false;
EditorUi.prototype.jpgSupported = false;
// Checks if canvas is supported
try
{
var cnv = document.createElement('canvas');
EditorUi.prototype.canvasSupported = !!(cnv.getContext && cnv.getContext('2d'));
}
catch (e)
{
// ignore
}
try
{
var canvas = document.createElement('canvas');
var img = new Image();
// LATER: Capability check should not be async
img.onload = function()
{
try
{
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
// Works in Chrome, Firefox, Edge, Safari and Opera
var result = canvas.toDataURL('image/png');
EditorUi.prototype.useCanvasForExport = result != null && result.length > 6;
}
catch (e)
{
// ignore
}
};
// Checks if SVG with foreignObject can be exported
var svg = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1px" height="1px" version="1.1"><foreignObject pointer-events="all" width="1" height="1"><div xmlns="http://www.w3.org/1999/xhtml"></div></foreignObject></svg>';
img.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg)));
}
catch (e)
{
// ignore
}
// Checks for client-side JPG support
try
{
var canvas = document.createElement('canvas');
canvas.width = canvas.height = 1;
var uri = canvas.toDataURL('image/jpeg');
EditorUi.prototype.jpgSupported = (uri.match('image/jpeg') !== null);
}
catch (e)
{
// ignore
}
})();
/**
* Hook for subclassers.
*/
EditorUi.prototype.openLink = function(url, target, allowOpener)
{
// LATER: Replace this with direct calls to graph
return this.editor.graph.openLink(url, target, allowOpener);
};
/**
* Hook for subclassers.
*/
EditorUi.prototype.showSplash = function(force) { };
/**
* Abstraction for local storage access.
*/
EditorUi.prototype.getLocalData = function(key, fn)
{
fn(localStorage.getItem(key));
};
/**
* Abstraction for local storage access.
*/
EditorUi.prototype.setLocalData = function(key, data, fn)
{
localStorage.setItem(key, data);
if (fn != null)
{
fn();
}
};
/**
* Abstraction for local storage access.
*/
EditorUi.prototype.removeLocalData = function(key, fn)
{
localStorage.removeItem(key)
fn();
};
EditorUi.prototype.setMathEnabled = function(value)
{
this.editor.graph.mathEnabled = value;
this.editor.updateGraphComponents();
this.editor.graph.refresh();
this.fireEvent(new mxEventObject('mathEnabledChanged'));
};
EditorUi.prototype.isMathEnabled = function(value)
{
return this.editor.graph.mathEnabled;
};
/**
* Returns true if offline app, which isn't a defined thing
*/
EditorUi.prototype.isOfflineApp = function()
{
return urlParams['offline'] == '1';
};
/**
* Returns true if no external comms allowed or possible
*/
EditorUi.prototype.isOffline = function(ignoreStealth)
{
return this.isOfflineApp() || !navigator.onLine || (!ignoreStealth && (urlParams['stealth'] == '1' || urlParams['lockdown'] == '1'));
};
/**
* Translates this point by the given vector.
*
* @param {number} dx X-coordinate of the translation.
* @param {number} dy Y-coordinate of the translation.
*/
EditorUi.prototype.createSpinner = function(x, y, size)
{
var autoPosition = (x == null || y == null);
size = (size != null) ? size : 24;
var spinner = new Spinner({
lines: 12, // The number of lines to draw
length: size, // The length of each line
width: Math.round(size / 3), // The line thickness
radius: Math.round(size / 2), // The radius of the inner circle
rotate: 0, // The rotation offset
color: (Editor.isDarkMode()) ? '#c0c0c0' : '#000', // #rgb or #rrggbb
speed: 1.5, // Rounds per second
trail: 60, // Afterglow percentage
shadow: false, // Whether to render a shadow
hwaccel: false, // Whether to use hardware acceleration
zIndex: 2e9 // The z-index (defaults to 2000000000)
});
// Extends spin method to include an optional label
var oldSpin = spinner.spin;
spinner.spin = function(container, label)
{
var result = false;
if (!this.active)
{
oldSpin.call(this, container);
this.active = true;
if (label != null)
{
if (autoPosition)
{
y = Math.max(document.body.clientHeight || 0, document.documentElement.clientHeight || 0) / 2;
x = document.body.clientWidth / 2 - 2;
}
var status = document.createElement('div');
status.style.position = 'absolute';
status.style.whiteSpace = 'nowrap';
status.style.background = '#4B4243';
status.style.color = 'white';
status.style.fontFamily = 'Helvetica, Arial';
status.style.fontSize = '9pt';
status.style.padding = '6px';
status.style.paddingLeft = '10px';
status.style.paddingRight = '10px';
status.style.zIndex = 2e9;
status.style.left = Math.max(0, x) + 'px';
status.style.top = Math.max(0, y + 70) + 'px';
mxUtils.setPrefixedStyle(status.style, 'borderRadius', '6px');
mxUtils.setPrefixedStyle(status.style, 'transform', 'translate(-50%,-50%)');
if (!Editor.isDarkMode())
{
mxUtils.setPrefixedStyle(status.style, 'boxShadow', '2px 2px 3px 0px #ddd');
}
if (label.substring(label.length - 3, label.length) != '...' &&
label.charAt(label.length - 1) != '!')
{
label = label + '...';
}
status.innerHTML = label;
container.appendChild(status);
spinner.status = status;
}
// Pause returns a function to resume the spinner
this.pause = mxUtils.bind(this, function()
{
var fn = function() { };
if (this.active)
{
fn = mxUtils.bind(this, function()
{
this.spin(container, label);
});
}
this.stop();
return fn;
});
result = true;
}
return result;
};
// Extends stop method to remove the optional label
var oldStop = spinner.stop;
spinner.stop = function()
{
oldStop.call(this);
this.active = false;
if (spinner.status != null && spinner.status.parentNode != null)
{
spinner.status.parentNode.removeChild(spinner.status);
}
spinner.status = null;
};
spinner.pause = function()
{
return function() {};
};
return spinner;
};
/**
* Returns true if the given string contains a compatible graph model.
*/
EditorUi.prototype.isCompatibleString = function(data)
{
try
{
var doc = mxUtils.parseXml(data);
var node = this.editor.extractGraphModel(doc.documentElement, true);
return node != null && node.getElementsByTagName('parsererror').length == 0;
}
catch (e)
{
// ignore
}
return false;
};
/**
* Returns true if the given binary data is a Visio file.
*/
EditorUi.prototype.isVisioData = function(data)
{
return data.length > 8 && ((data.charCodeAt(0) == 0xD0 && data.charCodeAt(1) == 0xCF &&
data.charCodeAt(2) == 0x11 && data.charCodeAt(3) == 0xE0 && data.charCodeAt(4) == 0xA1 && data.charCodeAt(5) == 0xB1 &&
data.charCodeAt(6) == 0x1A && data.charCodeAt(7) == 0xE1) || (data.charCodeAt(0) == 0x50 && data.charCodeAt(1) == 0x4B &&
data.charCodeAt(2) == 0x03 && data.charCodeAt(3) == 0x04) || (data.charCodeAt(0) == 0x50 && data.charCodeAt(1) == 0x4B &&
data.charCodeAt(2) == 0x03 && data.charCodeAt(3) == 0x06));
};
/**
* Returns true if the given binary data is a Visio file that requires remote conversion.
* This code returns true for vss, vsd and vdx files.
*/
EditorUi.prototype.isRemoteVisioData = function(data)
{
return data.length > 8 && ((data.charCodeAt(0) == 0xD0 && data.charCodeAt(1) == 0xCF &&
data.charCodeAt(2) == 0x11 && data.charCodeAt(3) == 0xE0 && data.charCodeAt(4) == 0xA1 && data.charCodeAt(5) == 0xB1 &&
data.charCodeAt(6) == 0x1A && data.charCodeAt(7) == 0xE1) || (data.charCodeAt(0) == 0x3C && data.charCodeAt(1) == 0x3F &&
data.charCodeAt(2) == 0x78 && data.charCodeAt(3) == 0x6D && data.charCodeAt(3) == 0x6C));
};
/**
* Returns true if the given binary data is a PNG file.
*/
EditorUi.prototype.isPngData = function(data)
{
return data.length > 8 && data.charCodeAt(0) == 137 && data.charCodeAt(1) == 80 &&
data.charCodeAt(2) == 78 && data.charCodeAt(3) == 71 && data.charCodeAt(4) == 13 &&
data.charCodeAt(5) == 10 && data.charCodeAt(6) == 26 && data.charCodeAt(7) == 10;
};
/**
* Adds keyboard shortcuts for page handling.
*/
var editorUiCreateKeyHandler = EditorUi.prototype.createKeyHandler;
EditorUi.prototype.createKeyHandler = function(editor)
{
var keyHandler = editorUiCreateKeyHandler.apply(this, arguments);
if (!this.editor.chromeless || this.editor.editable)
{
var keyHandlerGetFunction = keyHandler.getFunction;
var graph = this.editor.graph;
var ui = this;
keyHandler.getFunction = function(evt)
{
if (graph.isSelectionEmpty() && ui.pages != null && ui.pages.length > 0)
{
var idx = ui.getSelectedPageIndex();
if (mxEvent.isShiftDown(evt))
{
if (evt.keyCode == 37)
{
return function()
{
if (idx > 0)
{
ui.movePage(idx, idx - 1);
}
};
}
else if (evt.keyCode == 38)
{
return function()
{
if (idx > 0)
{
ui.movePage(idx, 0);
}
};
}
else if (evt.keyCode == 39)
{
return function()
{
if (idx < ui.pages.length - 1)
{
ui.movePage(idx, idx + 1);
}
};
}
else if (evt.keyCode == 40)
{
return function()
{
if (idx < ui.pages.length - 1)
{
ui.movePage(idx, ui.pages.length - 1);
}
};
}
}
else if (mxEvent.isControlDown(evt) || (mxClient.IS_MAC && mxEvent.isMetaDown(evt)))
{
if (evt.keyCode == 37)
{
return function()
{
if (idx > 0)
{
ui.selectNextPage(false);
}
};
}
else if (evt.keyCode == 38)
{
return function()
{
if (idx > 0)
{
ui.selectPage(ui.pages[0]);
}
};
}
else if (evt.keyCode == 39)
{
return function()
{
if (idx < ui.pages.length - 1)
{
ui.selectNextPage(true);
}
};
}
else if (evt.keyCode == 40)
{
return function()
{
if (idx < ui.pages.length - 1)
{
ui.selectPage(ui.pages[ui.pages.length - 1]);
}
};
}
}
}
return keyHandlerGetFunction.apply(this, arguments);
};
}
return keyHandler;
};
/**
* Extracts the mxfile from the given HTML data from a data transfer event.
*/
var editorUiExtractGraphModelFromHtml = EditorUi.prototype.extractGraphModelFromHtml;
EditorUi.prototype.extractGraphModelFromHtml = function(data)
{
var result = editorUiExtractGraphModelFromHtml.apply(this, arguments);
if (result == null)
{
try
{
var idx = data.indexOf('<mxfile ');
if (idx >= 0)
{
var idx2 = data.lastIndexOf('</mxfile>');
if (idx2 > idx)
{
result = data.substring(idx, idx2 + 15).replace(/>/g, '>').
replace(/</g, '<').replace(/\\"/g, '"').replace(/\n/g, '');
}
}
else
{
// Gets compressed data from mxgraph element in HTML document
var doc = mxUtils.parseXml(data);
var node = this.editor.extractGraphModel(doc.documentElement, this.pages != null ||
this.diagramContainer.style.visibility == 'hidden');
result = (node != null) ? mxUtils.getXml(node) : '';
}
}
catch (e)
{
// ignore
}
}
return result;
};
/**
* Workaround for malformed xhtml meta element bug 07.08.16. The trailing slash was missing causing
* reopen to fail trying to parse. Used in replaceFileData, setFileData and importFile.
*/
EditorUi.prototype.validateFileData = function(data)
{
if (data != null && data.length > 0)
{
var index = data.indexOf('<meta charset="utf-8">');
if (index >= 0)
{
var replaceString = '<meta charset="utf-8"/>';
var replaceStrLen = replaceString.length;
data = data.slice(0, index) + replaceString + data.slice(index + replaceStrLen - 1, data.length);
}
data = Graph.zapGremlins(data);
}
return data;
};
/**
*
*/
EditorUi.prototype.replaceFileData = function(data)
{
data = this.validateFileData(data);
var node = (data != null && data.length > 0) ? mxUtils.parseXml(data).documentElement : null;
// Some nodes must be extracted here to find the mxfile node
// LATER: Remove duplicate call to extractGraphModel in overridden setGraphXml
var tmp = (node != null) ? this.editor.extractGraphModel(node, true) : null;
if (tmp != null)
{
node = tmp;
}
if (node != null)
{
var graph = this.editor.graph;
graph.model.beginUpdate();
try
{
var oldPages = (this.pages != null) ? this.pages.slice() : null;
var nodes = node.getElementsByTagName('diagram');
if (urlParams['pages'] != '0' || nodes.length > 1 ||
(nodes.length == 1 && nodes[0].hasAttribute('name')))
{
this.fileNode = node;
this.pages = (this.pages != null) ? this.pages : [];
// Wraps page nodes
for (var i = nodes.length - 1; i >= 0; i--)
{
var page = this.updatePageRoot(new DiagramPage(nodes[i]));
// Checks for invalid page names
if (page.getName() == null)
{
page.setName(mxResources.get('pageWithNumber', [i + 1]));
}
graph.model.execute(new ChangePage(this, page, (i == 0) ? page : null, 0));
}
}
else
{
// Creates tabbed file structure if enforced by URL
if (urlParams['pages'] != '0' && this.fileNode == null)
{
this.fileNode = node.ownerDocument.createElement('mxfile');
this.currentPage = new DiagramPage(node.ownerDocument.createElement('diagram'));
this.currentPage.setName(mxResources.get('pageWithNumber', [1]));
graph.model.execute(new ChangePage(this, this.currentPage, this.currentPage, 0));
}
// Avoids scroll offset when switching page
this.editor.setGraphXml(node);
// Avoids duplicate parsing of the XML stored in the node
if (this.currentPage != null)
{
this.currentPage.root = this.editor.graph.model.root;
}
}
if (oldPages != null)
{
for (var i = 0; i < oldPages.length; i++)
{
graph.model.execute(new ChangePage(this, oldPages[i], null));
}
}
}
finally
{
graph.model.endUpdate();
}
}
};
/**
* Translates this point by the given vector.
*
* @param {number} dx X-coordinate of the translation.
* @param {number} dy Y-coordinate of the translation.
*/
EditorUi.prototype.createFileData = function(node, graph, file, url, forceXml, forceSvg, forceHtml,
embeddedCallback, ignoreSelection, compact, uncompressed)
{
graph = (graph != null) ? graph : this.editor.graph;
forceXml = (forceXml != null) ? forceXml : false;
ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
var editLink = null;
var redirect = null;
if (file == null || file.getMode() == App.MODE_DEVICE || file.getMode() == App.MODE_BROWSER)
{
editLink = '_blank';
}
else
{
editLink = url;
redirect = editLink;
}
if (node == null)
{
return '';
}
else
{
var fileNode = node;
// Ignores case for possible HTML or XML nodes
if (fileNode.nodeName.toLowerCase() != 'mxfile')
{
if (uncompressed)
{
var diagramNode = node.ownerDocument.createElement('diagram');
diagramNode.setAttribute('id', Editor.guid());
diagramNode.appendChild(node);
fileNode = node.ownerDocument.createElement('mxfile');
fileNode.appendChild(diagramNode);
}
else
{
// Removes control chars in input for correct roundtrip check
var text = Graph.zapGremlins(mxUtils.getXml(node));
var data = Graph.compress(text);
// Fallback to plain XML for invalid compression
// TODO: Remove this fallback with active pages
if (Graph.decompress(data) != text)
{
return text;
}
else
{
var diagramNode = node.ownerDocument.createElement('diagram');
diagramNode.setAttribute('id', Editor.guid());
mxUtils.setTextContent(diagramNode, data);
fileNode = node.ownerDocument.createElement('mxfile');
fileNode.appendChild(diagramNode);
}
}
}
if (!compact)
{
// Removes old metadata
fileNode.removeAttribute('userAgent');
fileNode.removeAttribute('version');
fileNode.removeAttribute('editor');
fileNode.removeAttribute('pages');
fileNode.removeAttribute('type');
if (mxClient.IS_CHROMEAPP)
{
fileNode.setAttribute('host', 'Chrome');
}
else if (EditorUi.isElectronApp)
{
fileNode.setAttribute('host', 'Electron');
}
else
{
fileNode.setAttribute('host', window.location.hostname);
}
// Adds new metadata
fileNode.setAttribute('modified', new Date().toISOString());
fileNode.setAttribute('agent', navigator.appVersion);
fileNode.setAttribute('version', EditorUi.VERSION);
fileNode.setAttribute('etag', Editor.guid());
var md = (file != null) ? file.getMode() : this.mode;
if (md != null)
{
fileNode.setAttribute('type', md);
}
if (fileNode.getElementsByTagName('diagram').length > 1 && this.pages != null)
{
fileNode.setAttribute('pages', this.pages.length);
}
}
else
{
fileNode = fileNode.cloneNode(true);
fileNode.removeAttribute('modified');
fileNode.removeAttribute('host');
fileNode.removeAttribute('agent');
fileNode.removeAttribute('etag');
fileNode.removeAttribute('userAgent');
fileNode.removeAttribute('version');
fileNode.removeAttribute('editor');
fileNode.removeAttribute('type');
}
var xml = (uncompressed) ? mxUtils.getPrettyXml(fileNode) : mxUtils.getXml(fileNode);
// Writes the file as an embedded HTML file
if (!forceSvg && !forceXml && (forceHtml || (file != null && /(\.html)$/i.test(file.getTitle()))))
{
xml = this.getHtml2(mxUtils.getXml(fileNode), graph, (file != null) ? file.getTitle() : null, editLink, redirect);
}
// Maps the XML data to the content attribute in the SVG node
else if (forceSvg || (!forceXml && file != null && /(\.svg)$/i.test(file.getTitle())))
{
if (file != null && (file.getMode() == App.MODE_DEVICE || file.getMode() == App.MODE_BROWSER))
{
url = null;
}
xml = this.getEmbeddedSvg(xml, graph, url, null, embeddedCallback, ignoreSelection, redirect);
}
return xml;
}
};
/**
* Translates this point by the given vector.
*
* @param {number} dx X-coordinate of the translation.
* @param {number} dy Y-coordinate of the translation.
*/
EditorUi.prototype.getXmlFileData = function(ignoreSelection, currentPage, uncompressed)
{
ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
currentPage = (currentPage != null) ? currentPage : false;
uncompressed = (uncompressed != null) ? uncompressed : !Editor.compressXml;
// Generats graph model XML node for single page export
var node = this.editor.getGraphXml(ignoreSelection);
if (ignoreSelection && this.fileNode != null && this.currentPage != null)
{
// Updates current page XML if selection is ignored
EditorUi.removeChildNodes(this.currentPage.node);
mxUtils.setTextContent(this.currentPage.node, Graph.compressNode(node));
// Creates a clone of the file node for processing
node = this.fileNode.cloneNode(false);
// Appends the node of the page and applies compression
function appendPage(pageNode)
{
var models = pageNode.getElementsByTagName('mxGraphModel');
var modelNode = (models.length > 0) ? models[0] : null;
var clone = pageNode;
if (modelNode == null && uncompressed)
{
var text = mxUtils.trim(mxUtils.getTextContent(pageNode));
clone = pageNode.cloneNode(false);
if (text.length > 0)
{
var tmp = Graph.decompress(text);
if (tmp != null && tmp.length > 0)
{
clone.appendChild(mxUtils.parseXml(tmp).documentElement);
}
}
}
else if (modelNode != null && !uncompressed)
{
clone = pageNode.cloneNode(false);
mxUtils.setTextContent(clone, Graph.compressNode(modelNode));
}
else
{
clone = pageNode.cloneNode(true);
}
node.appendChild(clone);
};
if (currentPage)
{
appendPage(this.currentPage.node);
}
else
{
// Restores order of pages
for (var i = 0; i < this.pages.length; i++)
{
if (this.currentPage != this.pages[i])
{
if (this.pages[i].needsUpdate)
{
var enc = new mxCodec(mxUtils.createXmlDocument());
var temp = enc.encode(new mxGraphModel(this.pages[i].root));
this.editor.graph.saveViewState(this.pages[i].viewState, temp);
EditorUi.removeChildNodes(this.pages[i].node);
mxUtils.setTextContent(this.pages[i].node, Graph.compressNode(temp));
// Marks the page as up-to-date
delete this.pages[i].needsUpdate;
}
}
appendPage(this.pages[i].node);
}
}
}
return node;
};
/**
* Removes any values, styles and geometries from the given XML node.
*/
EditorUi.prototype.anonymizeString = function(text, zeros)
{
var result = [];
for (var i = 0; i < text.length; i++)
{
var c = text.charAt(i);
if (EditorUi.ignoredAnonymizedChars.indexOf(c) >= 0)
{
result.push(c);
}
else if (!isNaN(parseInt(c)))
{
result.push((zeros) ? '0' : Math.round(Math.random() * 9));
}
else if (c.toLowerCase() != c)
{
result.push(String.fromCharCode(65 + Math.round(Math.random() * 25)));
}
else if (c.toUpperCase() != c)
{
result.push(String.fromCharCode(97 + Math.round(Math.random() * 25)));
}
else if (/\s/.test(c))
{
/* any whitespace */
result.push(' ');
}
else
{
result.push('?');
}
}
return result.join('');
};
/**
* Removes any values, styles and geometries from the given XML node.
*/
EditorUi.prototype.anonymizePatch = function(patch)
{
if (patch[EditorUi.DIFF_INSERT] != null)
{
for (var i = 0; i < patch[EditorUi.DIFF_INSERT].length; i++)
{
try
{
var data = patch[EditorUi.DIFF_INSERT][i].data;
var doc = mxUtils.parseXml(data);
var clone = doc.documentElement.cloneNode(false);
if (clone.getAttribute('name') != null)
{
clone.setAttribute('name', this.anonymizeString(clone.getAttribute('name')));
}
patch[EditorUi.DIFF_INSERT][i].data = mxUtils.getXml(clone);
}
catch (e)
{
patch[EditorUi.DIFF_INSERT][i].data = e.message;
}
}
}
if (patch[EditorUi.DIFF_UPDATE] != null)
{
for (var pageId in patch[EditorUi.DIFF_UPDATE])
{
var diff = patch[EditorUi.DIFF_UPDATE][pageId];
if (diff.name != null)
{
diff.name = this.anonymizeString(diff.name);
}
if (diff.cells != null)
{
var anonymizeCellDiffs = mxUtils.bind(this, function(key)
{
var cellDiffs = diff.cells[key];
if (cellDiffs != null)
{
for (var cellId in cellDiffs)
{
if (cellDiffs[cellId].value != null)
{
cellDiffs[cellId].value = '[' +
cellDiffs[cellId].value.length + ']';
}
if (cellDiffs[cellId].xmlValue != null)
{
cellDiffs[cellId].xmlValue = '[' +
cellDiffs[cellId].xmlValue.length + ']';
}
if (cellDiffs[cellId].style != null)
{
cellDiffs[cellId].style = '[' +
cellDiffs[cellId].style.length + ']';
}
if (Object.keys(cellDiffs[cellId]).length == 0)
{
delete cellDiffs[cellId];
}
}
if (Object.keys(cellDiffs).length == 0)
{
delete diff.cells[key];
}
}
});
anonymizeCellDiffs(EditorUi.DIFF_INSERT);
anonymizeCellDiffs(EditorUi.DIFF_UPDATE);
if (Object.keys(diff.cells).length == 0)
{
delete diff.cells;
}
}
if (Object.keys(diff).length == 0)
{
delete patch[EditorUi.DIFF_UPDATE][pageId];
}
}
if (Object.keys(patch[EditorUi.DIFF_UPDATE]).length == 0)
{
delete patch[EditorUi.DIFF_UPDATE];
}
}
return patch;
};
/**
* Removes any values, styles and geometries from the given XML node.
*/
EditorUi.prototype.anonymizeAttributes = function(node, zeros)
{
if (node.attributes != null)
{
for (var i = 0; i < node.attributes.length; i++)
{
if (node.attributes[i].name != 'as')
{
node.setAttribute(node.attributes[i].name,
this.anonymizeString(node.attributes[i].value, zeros));
}
}
}
if (node.childNodes != null)
{
for (var i = 0; i < node.childNodes.length; i++)
{
this.anonymizeAttributes(node.childNodes[i], zeros);
}
}
};
/**
* Removes any values, styles and geometries from the given XML node.
*/
EditorUi.prototype.anonymizeNode = function(node, zeros)
{
var nodes = node.getElementsByTagName('mxCell');
for (var i = 0; i < nodes.length; i++)
{
if (nodes[i].getAttribute('value') != null)
{
nodes[i].setAttribute('value', '[' + nodes[i].getAttribute('value').length + ']');
}
if (nodes[i].getAttribute('xmlValue') != null)
{
nodes[i].setAttribute('xmlValue', '[' + nodes[i].getAttribute('xmlValue').length + ']');
}
if (nodes[i].getAttribute('style') != null)
{
nodes[i].setAttribute('style', '[' + nodes[i].getAttribute('style').length + ']');
}
if (nodes[i].parentNode != null && nodes[i].parentNode.nodeName != 'root' &&
nodes[i].parentNode.parentNode != null)
{
nodes[i].setAttribute('id', nodes[i].parentNode.getAttribute('id'));
nodes[i].parentNode.parentNode.replaceChild(nodes[i], nodes[i].parentNode);
}
}
return node;
};
/**
* Translates this point by the given vector.
*
* @param {number} dx X-coordinate of the translation.
* @param {number} dy Y-coordinate of the translation.
*/
EditorUi.prototype.synchronizeCurrentFile = function(forceReload)
{
var currentFile = this.getCurrentFile();
if (currentFile != null)
{
if (currentFile.savingFile)
{
this.handleError({message: mxResources.get('busy')});
}
else if (!forceReload && currentFile.invalidChecksum)
{
currentFile.handleFileError(null, true);
}
else if (this.spinner.spin(document.body, mxResources.get('updatingDocument')))
{
currentFile.clearAutosave();
this.editor.setStatus('');
if (forceReload)
{
currentFile.reloadFile(mxUtils.bind(this, function()
{
currentFile.handleFileSuccess(DrawioFile.SYNC == 'manual');
}), mxUtils.bind(this, function(err)
{
currentFile.handleFileError(err, true);
}));
}
else
{
currentFile.synchronizeFile(mxUtils.bind(this, function()
{
currentFile.handleFileSuccess(DrawioFile.SYNC == 'manual');
}), mxUtils.bind(this, function(err)
{
currentFile.handleFileError(err, true);
}));
}
}
}
};
/**
* Translates this point by the given vector.
*
* @param {number} dx X-coordinate of the translation.
* @param {number} dy Y-coordinate of the translation.
*/
EditorUi.prototype.getFileData = function(forceXml, forceSvg, forceHtml, embeddedCallback, ignoreSelection,
currentPage, node, compact, file, uncompressed)
{
ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
currentPage = (currentPage != null) ? currentPage : false;
var graph = this.editor.graph;
// Forces compression of embedded XML
if (forceSvg || (!forceXml && file != null && /(\.svg)$/i.test(file.getTitle())))
{
uncompressed = false;
var darkTheme = graph.themes != null && graph.defaultThemeName == 'darkTheme';
// Exports SVG for first page while other page is visible by creating a graph
// LATER: Add caching for the graph or SVG while not on first page
// Dark mode requires a refresh that would destroy all handlers
// LATER: Use dark theme here to bypass refresh
if (darkTheme || (this.pages != null && this.currentPage != this.pages[0]))
{
var graphGetGlobalVariable = graph.getGlobalVariable;
graph = this.createTemporaryGraph(graph.getStylesheet());
var page = this.pages[0];
graph.getGlobalVariable = function(name)
{
if (name == 'page')
{
return page.getName();
}
else if (name == 'pagenumber')
{
return 1;
}
return graphGetGlobalVariable.apply(this, arguments);
};
document.body.appendChild(graph.container);
graph.model.setRoot(page.root);
}
}
node = (node != null) ? node : this.getXmlFileData(ignoreSelection, currentPage, uncompressed);
file = (file != null) ? file : this.getCurrentFile();
var result = this.createFileData(node, graph, file, window.location.href,
forceXml, forceSvg, forceHtml, embeddedCallback, ignoreSelection, compact,
uncompressed);
// Removes temporary graph from DOM
if (graph != this.editor.graph)
{
graph.container.parentNode.removeChild(graph.container);
}
return result;
};
/**
*
*/
EditorUi.prototype.getHtml = function(node, graph, title, editLink, redirect, ignoreSelection)
{
ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
var bg = null;
var js = EditorUi.drawHost + '/js/embed-static.min.js';
// LATER: Merge common code with EmbedDialog
if (graph != null)
{
var bounds = (ignoreSelection) ? graph.getGraphBounds() : graph.getBoundingBox(graph.getSelectionCells());
var scale = graph.view.scale;
var x0 = Math.floor(bounds.x / scale - graph.view.translate.x);
var y0 = Math.floor(bounds.y / scale - graph.view.translate.y);
bg = graph.background;
// Embed script only used if no redirect
if (redirect == null)
{
var s = this.getBasenames().join(';');
if (s.length > 0)
{
js = EditorUi.drawHost + '/embed.js?s=' + s;
}
}
// Adds embed attributes
node.setAttribute('x0', x0);
node.setAttribute('y0', y0);
}
if (node != null)
{
node.setAttribute('pan', '1');
node.setAttribute('zoom', '1');
node.setAttribute('resize', '0');
node.setAttribute('fit', '0');
node.setAttribute('border', '20');
// Hidden attributes
node.setAttribute('links', '1');
if (editLink != null)
{
node.setAttribute('edit', editLink);
}
}
// Makes XHTML compatible
if (redirect != null)
{
redirect = redirect.replace(/&/g, '&');
}
// Removes control chars in input for correct roundtrip check
var text = (node != null) ? Graph.zapGremlins(mxUtils.getXml(node)) : '';
// Double compression for mxfile not fixed since it may cause imcompatibilites with
// embed clients that rely on this format. HTML files and export use getHtml2.
var data = Graph.compress(text);
// Fallback to URI encoded XML for invalid compression
if (Graph.decompress(data) != text)
{
data = encodeURIComponent(text);
}
var style = 'position:relative;overflow:auto;width:100%;';
return ((redirect == null) ? '<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5,IE=9" ><![endif]-->\n' : '') +
'<!DOCTYPE html>\n<html' + ((redirect != null) ? ' xmlns="http://www.w3.org/1999/xhtml">' : '>') +
'\n<head>\n' + ((redirect == null) ? ((title != null) ? '<title>' + mxUtils.htmlEntities(title) +
'</title>\n' : '') : '<title>diagrams.net</title>\n') +
((redirect != null) ? '<meta http-equiv="refresh" content="0;URL=\'' + redirect + '\'"/>\n' : '') +
'</head>\n<body' +
(((redirect == null && bg != null && bg != mxConstants.NONE) ? ' style="background-color:' + bg + ';">' : '>')) +
'\n<div class="mxgraph" style="' + style + '">\n' +
'<div style="width:1px;height:1px;overflow:hidden;">' + data + '</div>\n</div>\n' +
((redirect == null) ? '<script type="text/javascript" src="' + js + '"></script>' :
'<a style="position:absolute;top:50%;left:50%;margin-top:-128px;margin-left:-64px;" ' +
'href="' + redirect + '" target="_blank"><img border="0" ' +
'src="' + EditorUi.drawHost + '/images/drawlogo128.png"/></a>') +
'\n</body>\n</html>\n';
};
/**
* Same as above but using the new embed code.
*/
EditorUi.prototype.getHtml2 = function(xml, graph, title, editLink, redirect)
{
var js = window.DRAWIO_VIEWER_URL || EditorUi.drawHost + '/js/viewer-static.min.js';
// Makes XHTML compatible
if (redirect != null)
{
redirect = redirect.replace(/&/g, '&');
}
var data = {highlight: '#0000ff', nav: this.editor.graph.foldingEnabled, resize: true,
xml: Graph.zapGremlins(xml), toolbar: 'pages zoom layers lightbox'};
if (this.pages != null && this.currentPage != null)
{
data.page = mxUtils.indexOf(this.pages, this.currentPage);
}
var style = 'max-width:100%;border:1px solid transparent;';
return ((redirect == null) ? '<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5,IE=9" ><![endif]-->\n' : '') +
'<!DOCTYPE html>\n<html' + ((redirect != null) ? ' xmlns="http://www.w3.org/1999/xhtml">' : '>') +
'\n<head>\n' + ((redirect == null) ? ((title != null) ? '<title>' + mxUtils.htmlEntities(title) +
'</title>\n' : '') : '<title>diagrams.net</title>\n') +
((redirect != null) ? '<meta http-equiv="refresh" content="0;URL=\'' + redirect + '\'"/>\n' : '') +
'<meta charset="utf-8"/>\n</head>\n<body>' +
'\n<div class="mxgraph" style="' + style + '" data-mxgraph="' + mxUtils.htmlEntities(JSON.stringify(data)) + '"></div>\n' +
((redirect == null) ? '<script type="text/javascript" src="' + js + '"></script>' :
'<a style="position:absolute;top:50%;left:50%;margin-top:-128px;margin-left:-64px;" ' +
'href="' + redirect + '" target="_blank"><img border="0" ' +
'src="' + EditorUi.drawHost + '/images/drawlogo128.png"/></a>') +
'\n</body>\n</html>\n';
};
/**
*
*/
EditorUi.prototype.setFileData = function(data)
{
data = this.validateFileData(data);
this.currentPage = null;
this.fileNode = null;
this.pages = null;
var node = (data != null && data.length > 0) ? mxUtils.parseXml(data).documentElement : null;
// Checks for parser errors
var cause = Editor.extractParserError(node, mxResources.get('invalidOrMissingFile'));
if (cause)
{
throw new Error(mxResources.get('notADiagramFile') + ' (' + cause + ')');
}
else
{
// Some nodes must be extracted here to find the mxfile node
// LATER: Remove duplicate call to extractGraphModel in overridden setGraphXml
var tmp = (node != null) ? this.editor.extractGraphModel(node, true) : null;
if (tmp != null)
{
node = tmp;
}
if (node != null && node.nodeName == 'mxfile')
{
var nodes = node.getElementsByTagName('diagram');
if (urlParams['pages'] != '0' || nodes.length > 1 ||
(nodes.length == 1 && nodes[0].hasAttribute('name')))
{
var selectedPage = null;
this.fileNode = node;
this.pages = [];
// Wraps page nodes
for (var i = 0; i < nodes.length; i++)
{
// Adds page ID based on page order to match
// remote IDs given if IDs are missing here
if (nodes[i].getAttribute('id') == null)
{
nodes[i].setAttribute('id', i);
}
var page = new DiagramPage(nodes[i]);
// Checks for invalid page names
if (page.getName() == null)
{
page.setName(mxResources.get('pageWithNumber', [i + 1]));
}
this.pages.push(page);
if (urlParams['page-id'] != null && page.getId() == urlParams['page-id'])
{
selectedPage = page;
}
}
this.currentPage = (selectedPage != null) ? selectedPage :
this.pages[Math.max(0, Math.min(this.pages.length - 1, urlParams['page'] || 0))];
node = this.currentPage.node;
}
}
// Creates tabbed file structure if enforced by URL
if (urlParams['pages'] != '0' && this.fileNode == null && node != null)
{
this.fileNode = node.ownerDocument.createElement('mxfile');
this.currentPage = new DiagramPage(node.ownerDocument.createElement('diagram'));
this.currentPage.setName(mxResources.get('pageWithNumber', [1]));
this.pages = [this.currentPage];
}
// Avoids scroll offset when switching page
this.editor.setGraphXml(node);
// Avoids duplicate parsing of the XML stored in the node
if (this.currentPage != null)
{
this.currentPage.root = this.editor.graph.model.root;
}
if (urlParams['layer-ids'] != null)
{
try
{
var layerIds = urlParams['layer-ids'].split(' ');
var layerIdsMap = {};
for (var i = 0; i < layerIds.length; i++)
{
layerIdsMap[layerIds[i]] = true;
}
var model = this.editor.graph.getModel();
var children = model.getChildren(model.root);
// handle layers visibility
for (var i = 0; i < children.length; i++)
{
var child = children[i];
model.setVisible(child, layerIdsMap[child.id] || false);
}
}
catch(e){} //ignore
}
}
};
/**
* Translates this point by the given vector.
*
* @param {number} dx X-coordinate of the translation.
* @param {number} dy Y-coordinate of the translation.
*/
EditorUi.prototype.getBaseFilename = function(ignorePageName)
{
var file = this.getCurrentFile();
var basename = (file != null && file.getTitle() != null) ? file.getTitle() : this.defaultFilename;
if (/(\.xml)$/i.test(basename) || /(\.html)$/i.test(basename) ||
/(\.svg)$/i.test(basename) || /(\.png)$/i.test(basename) ||
/(\.drawio)$/i.test(basename))
{
basename = basename.substring(0, basename.lastIndexOf('.'));
}
if (!ignorePageName && this.pages != null && this.pages.length > 1 &&
this.currentPage != null && this.currentPage.node.getAttribute('name') != null &&
this.currentPage.getName().length > 0)
{
basename = basename + '-' + this.currentPage.getName();
}
return basen