1/**
2 * Plugin for embed mode in Confluence Connect post version 1.4.8
3 */
4Draw.loadPlugin(function(ui)
5{
6	// Handle data governess by modifying external services URLs
7	var allowedRegions = {
8		eu: 1,
9		us: 1
10	};
11
12	if (allowedRegions[urlParams['dataGov']])
13	{
14		var region = urlParams['dataGov'];
15		var urls = {
16			'EXPORT_URL': 'export',
17			'PLANT_URL': 'plant',
18			'VSD_CONVERT_URL': 'vsd',
19			'EMF_CONVERT_URL': 'emf',
20			'OPEN_URL': 'import'
21		};
22
23		for (var key in urls)
24		{
25			var val = window[key];
26
27			if (val)
28			{
29				window[key] = '/region-' + urls[key] + '-' + region;
30			}
31		}
32	}
33
34	// Extracts macro data from JSON protocol
35	var macroData = {};
36
37	mxEvent.addListener(window, 'message', mxUtils.bind(this, function(evt)
38	{
39		var data = evt.data;
40
41		try
42		{
43			data = JSON.parse(data);
44
45			if (data.action == 'load')
46			{
47				if (data.macroData != null)
48				{
49					macroData = data.macroData;
50
51					if (ui.format != null)
52					{
53						ui.format.refresh();
54					}
55				}
56
57				ui.initComments(macroData.contentId || macroData.custContentId);
58				macroData.diagramDisplayName = data.title;
59
60				//Fetch notifications
61				ui.fetchAndShowNotification('conf');
62			}
63		}
64		catch (e)
65		{
66			data = null;
67		}
68	}));
69
70	var renameAction = ui.actions.get("rename");
71
72	renameAction.visible = true;
73
74	renameAction.isEnabled = function()
75	{
76		return macroData.diagramDisplayName != null;
77	}
78
79	function descriptorChangedListener()
80	{
81		var curFile = ui.getCurrentFile();
82		var fileTitle = curFile.getTitle();
83
84		//Update file name in the UI
85		var tmp = document.createElement('span');
86		mxUtils.write(tmp, mxUtils.htmlEntities(fileTitle));
87
88		if (ui.embedFilenameSpan != null)
89		{
90			ui.embedFilenameSpan.parentNode.removeChild(ui.embedFilenameSpan);
91		}
92
93		ui.buttonContainer.appendChild(tmp);
94		ui.embedFilenameSpan = tmp;
95		macroData.diagramDisplayName = fileTitle;
96
97		var vSettings = curFile.desc.viewerSettings;
98
99		if (vSettings != null)
100		{
101			macroData.tbstyle = vSettings.tbstyle;
102			macroData.links = vSettings.links;
103			macroData.simple = vSettings.simple;
104			macroData.lbox = vSettings.lbox;
105			macroData.zoom = vSettings.zoom;
106			macroData.pCenter = vSettings.pCenter;
107			macroData.aspect =	vSettings.aspect;
108			macroData.hiResPreview = vSettings.hiResPreview;
109
110			if (ui.format != null)
111			{
112				ui.format.refresh();
113			}
114		}
115	};
116
117	var xdm_e = decodeURIComponent(urlParams['site']);
118	var license = urlParams['atlas-lic'];
119
120	ui.remoteInvoke('checkConfLicense', [license, xdm_e], null, function(licenseValid)
121	{
122	    if (!licenseValid)
123	    {
124			ui.menus.get('file').funct = function(menu, parent)
125			{
126				menu.addItem(mxResources.get('licenseRequired'), null, function()
127				{
128					// do nothing
129				}, parent, null, false);
130			}
131
132			ui.menus.get('insertAdvanced').funct = function(menu, parent)
133			{
134				menu.addItem(mxResources.get('licenseRequired'), null, function()
135				{
136					// do nothing
137				}, parent, null, false);
138			}
139
140			if (typeof(MathJax) !== 'undefined')
141			{
142				ui.actions.get('mathematicalTypesetting').funct = function()
143				{
144					ui.alert(mxResources.get('licenseRequired'));
145				};
146			}
147
148			EditorUi.prototype.insertPage = function(page, index)
149			{
150				this.alert(mxResources.get('licenseRequired'));
151			};
152
153			Sidebar.prototype.searchEntries = function(searchTerms, count, page, success, error)
154			{
155				success();
156			};
157
158			Sidebar.prototype.insertSearchHint = function(div, searchTerm, count, page, results, len, more, terms)
159			{
160				var link = document.createElement('div');
161				link.className = 'geTitle';
162				link.style.cssText = 'background-color:#ffd350;border-radius:6px;color:black;' +
163					'border:1px solid black !important;text-align:center;white-space:normal;' +
164					'padding:6px 0px 6px 0px !important;margin:4px 4px 8px 2px;font-size:12px;';
165				mxUtils.write(link, mxResources.get('licenseRequired'));
166				div.appendChild(link);
167			};
168
169			DrawioFileSync.prototype.fileChangedNotify = function()
170			{
171				//Disable RT syncing
172			};
173
174			ui.importFiles = function()
175			{
176				//Disable DnD and file import
177				ui.alert(mxResources.get('licenseRequired'));
178			}
179
180			//Disable comments
181			ui.getComments = function(success, error)
182			{
183				error({message: mxResources.get('licenseRequired')});
184			}
185
186			ui.addComment = function(comment, success, error)
187			{
188				error();
189			}
190	    }
191	},
192	function(){});
193
194	renameAction.funct = function()
195	{
196		var dlg = new FilenameDialog(ui, macroData.diagramDisplayName || "",
197				mxResources.get('rename'), function(newName)
198		{
199			if (newName != null && newName.length > 0)
200			{
201				//TODO This is not needed with RT since title is added to desc
202				macroData.diagramDisplayName = newName;
203				var parent = window.opener || window.parent;
204				parent.postMessage(JSON.stringify({event: 'rename', name: newName}), '*');
205
206				//Update and sync new name
207				ui.getCurrentFile().rename(newName);
208			}
209		}, mxResources.get('rename'), function(name)
210		{
211			var err = "";
212			if (name == null || name.length == 0)
213			{
214				err = 'Filename too short';
215			}
216			else if (/[&\*+=\\;/{}|\":<>\?~]/g.test(name))
217			{
218				err = 'Invalid characters \\ / | : { } < > & + ? = ; * " ~';
219			}
220			else
221			{
222				return true;
223			}
224
225			ui.showError(mxResources.get('error'), err, mxResources.get('ok'));
226			return false;
227		});
228		ui.showDialog(dlg.container, 300, 80, true, true);
229		dlg.init();
230	}
231
232	// Returns modified macro data to client
233	var uiCreateLoadMessage = ui.createLoadMessage;
234
235	ui.createLoadMessage = function(eventName)
236	{
237		var msg = uiCreateLoadMessage.apply(this, arguments);
238
239		if (eventName == 'export')
240		{
241			msg.macroData = macroData;
242
243			var desc = ui.getCurrentFile().getDescriptor();
244
245			//Until app.min.js is propagated, this code is necessary
246			if (desc != null)
247			{
248				if (desc.key == null)
249				{
250					desc.key = Editor.guid(32);
251					desc.channel = Editor.guid(32);
252					desc.etagP = Editor.guid(32);
253					desc.title = macroData.diagramDisplayName;
254				}
255				else if (desc.title)
256				{
257					macroData.diagramDisplayName = desc.title;
258				}
259
260				msg.desc = desc;
261			}
262			else
263			{
264				msg.desc = {};
265			}
266		}
267
268		return msg;
269	};
270
271	// Adds new section for confluence cloud
272	var diagramFormatPanelInit = DiagramFormatPanel.prototype.init;
273
274	DiagramFormatPanel.prototype.init = function()
275	{
276		this.container.appendChild(this.addViewerOptions(this.createPanel()));
277
278		diagramFormatPanelInit.apply(this, arguments);
279	};
280
281	// Adds viewer config to style options and refreshes
282	DiagramFormatPanel.prototype.addViewerOptions = function(div)
283	{
284		var ui = this.editorUi;
285		var editor = ui.editor;
286		var graph = editor.graph;
287
288		div.appendChild(this.createTitle(mxResources.get('viewerSettings')));
289
290		// Viewer simple
291		div.appendChild(this.createOption(mxResources.get('simpleViewer'), function()
292		{
293			return macroData.simple == '1';
294		}, function(checked)
295		{
296			macroData.simple = (checked) ? '1' : '0';
297		}));
298
299		// Viewer lightbox
300		div.appendChild(this.createOption(mxResources.get('lightbox'), function()
301		{
302			return macroData.lbox != '0';
303		}, function(checked)
304		{
305			macroData.lbox = (checked) ? '1' : '0';
306		}));
307
308		// Viewer centering
309		div.appendChild(this.createOption(mxResources.get('center'), function()
310		{
311			return macroData.pCenter == '1';
312		}, function(checked)
313		{
314			macroData.pCenter = (checked) ? '1' : '0';
315		}));
316
317		// High Resolution Preview
318		div.appendChild(this.createOption(mxResources.get('hiResPreview', null, 'High Res Preview'), function()
319		{
320			return (macroData.hiResPreview == null && Editor.config != null && Editor.config.hiResPreview) || macroData.hiResPreview == '1';
321		}, function(checked)
322		{
323			macroData.hiResPreview = (checked) ? '1' : '0';
324			ui.remoteInvoke('setHiResPreview', [checked], null, function(){}, function(){}); //Notify plugin of the change, ignoring both success and error callbacks
325		}));
326
327		// Toolbar
328		var stylePanel = this.createPanel();
329		stylePanel.style.position = 'relative';
330		stylePanel.style.borderWidth = '0px';
331		stylePanel.style.marginLeft = '0px';
332		stylePanel.style.paddingTop = '8px';
333		stylePanel.style.paddingBottom = '4px';
334		stylePanel.style.fontWeight = 'normal';
335		stylePanel.className = 'geToolbarContainer';
336
337		mxUtils.write(stylePanel, mxResources.get('toolbar'));
338
339		// Adds toolbar options
340		var tbSelect = document.createElement('select');
341		tbSelect.style.position = 'absolute';
342		tbSelect.style.right = '20px';
343		tbSelect.style.width = '97px';
344		tbSelect.style.marginTop = '-2px';
345
346		var opts = [{value: 'top', title: mxResources.get('top')},
347			{value: 'inline', title: mxResources.get('embed')},
348			{value: 'hidden', title: mxResources.get('hidden')}]
349		var validTb = false;
350
351		for (var i = 0; i < opts.length; i++)
352		{
353			validTb = validTb || macroData.tbstyle == opts[i].value;
354			var tbOption = document.createElement('option');
355			tbOption.setAttribute('value', opts[i].value);
356			mxUtils.write(tbOption, opts[i].title);
357			tbSelect.appendChild(tbOption);
358		}
359
360		tbSelect.value = (validTb) ? macroData.tbstyle : 'top';
361		stylePanel.appendChild(tbSelect);
362		div.appendChild(stylePanel);
363
364		mxEvent.addListener(tbSelect, 'change', function(evt)
365		{
366			macroData.tbstyle = tbSelect.value;
367			mxEvent.consume(evt);
368		});
369
370		// Links
371		stylePanel = stylePanel.cloneNode(false);
372		stylePanel.style.paddingTop = '4px';
373		mxUtils.write(stylePanel, mxResources.get('links'));
374
375		// Adds links options
376		var linksSelect = document.createElement('select');
377		linksSelect.style.position = 'absolute';
378		linksSelect.style.right = '20px';
379		linksSelect.style.width = '97px';
380		linksSelect.style.marginTop = '-2px';
381
382		var opts = [{value: 'auto', title: mxResources.get('automatic')},
383			{value: 'blank', title: mxResources.get('openInNewWindow')},
384			{value: 'self', title: mxResources.get('openInThisWindow')}]
385		var validLinks = false;
386
387		for (var i = 0; i < opts.length; i++)
388		{
389			validLinks = validLinks || macroData.links == opts[i].value;
390			var linkOption = document.createElement('option');
391			linkOption.setAttribute('value', opts[i].value);
392			mxUtils.write(linkOption, opts[i].title);
393			linksSelect.appendChild(linkOption);
394		}
395
396		linksSelect.value = (validLinks) ? macroData.links : 'auto';
397		stylePanel.appendChild(linksSelect);
398		div.appendChild(stylePanel);
399
400		mxEvent.addListener(linksSelect, 'change', function(evt)
401		{
402			macroData.links = linksSelect.value;
403			mxEvent.consume(evt);
404		});
405
406		// Zoom
407		var zoomOpt = this.createRelativeOption(mxResources.get('zoom'), null, null, function(input)
408		{
409			var value = (input.value == '') ? 100 : parseInt(input.value);
410			value = Math.max(0, (isNaN(value)) ? 100 : value);
411			input.value = value + ' %';
412			macroData.zoom = value / 100;
413		}, function(input)
414		{
415			input.value = (parseFloat(macroData.zoom || 1) * 100) + '%';
416		});
417
418		zoomOpt.style.fontWeight = 'normal';
419		zoomOpt.style.paddingBottom = '6px';
420		zoomOpt.style.paddingTop = '6px';
421		zoomOpt.style.border = 'none';
422
423		div.appendChild(zoomOpt);
424
425		//Page and layers settings
426		div.appendChild(this.createTitle(mxResources.get('pageLayers', null, 'Page and Layers')));
427
428		var hasAspect = false;
429		var pageId = null, layerIds = null;
430
431		var customizeBtn = mxUtils.button(mxResources.get('customize', null, 'Customize'), function()
432		{
433
434			var dlg = new AspectDialog(ui, pageId, layerIds, function(info)
435			{
436				pageId = info.pageId;
437				layerIds = info.layerIds;
438				macroData.aspect = pageId + ' ' + layerIds.join(' ');
439				ui.remoteInvoke('setAspect', [macroData.aspect], null, function(){}, function(){}); //Notify plugin of the change, ignoring both success and error callbacks
440			});
441
442			ui.showDialog(dlg.container, 700, 465, true, true);
443			dlg.init();
444		});
445
446		customizeBtn.className = 'geColorBtn';
447		customizeBtn.style.marginLeft = '10px';
448		customizeBtn.style.padding = '2px';
449		customizeBtn.setAttribute('disabled', 'disabled');
450
451		if (macroData.aspect != null)
452		{
453			var aspectArray = macroData.aspect.split(' ');
454
455			if (aspectArray.length > 0)
456			{
457				pageId = aspectArray[0];
458				layerIds = aspectArray.slice(1);
459				hasAspect = true;
460				customizeBtn.removeAttribute('disabled');
461			}
462		}
463
464		var firstPageRadio = ui.addRadiobox(div, 'pageLayers', mxResources.get('firstPage', null, 'First Page (All Layers)'), !hasAspect);
465		firstPageRadio.style.marginTop = '4px';
466
467		mxEvent.addListener(firstPageRadio, 'change', function()
468		{
469			if (this.checked)
470			{
471				macroData.aspect = null;
472				ui.remoteInvoke('setAspect', [macroData.aspect], null, function(){}, function(){}); //Notify plugin of the change, ignoring both success and error callbacks
473				customizeBtn.setAttribute('disabled', 'disabled');
474			}
475		});
476
477		var currentStateRadio = ui.addRadiobox(div, 'pageLayers', mxResources.get('curEditorState', null, 'Current Editor State'), false);
478		currentStateRadio.style.marginTop = '8px';
479
480		mxEvent.addListener(currentStateRadio, 'change', function()
481		{
482			if (this.checked)
483			{
484				var curPage = ui.updatePageRoot(ui.currentPage);
485				var layerIds = [], layers = curPage.root.children;
486
487				for (var i = 0; i < layers.length; i++)
488				{
489					if (layers[i].visible != false)
490					{
491						layerIds.push(layers[i].id);
492					}
493				}
494
495				macroData.aspect = curPage.getId() + ' ' + layerIds.join(' ');
496				ui.remoteInvoke('setAspect', [macroData.aspect], null, function(){}, function(){}); //Notify plugin of the change, ignoring both success and error callbacks
497				customizeBtn.setAttribute('disabled', 'disabled');
498			}
499		});
500
501		var customStateRadio = ui.addRadiobox(div, 'pageLayers', mxResources.get('custom', null, 'Custom'), hasAspect, false, true);
502		customStateRadio.style.marginTop = '8px';
503
504		mxEvent.addListener(customStateRadio, 'change', function()
505		{
506			if (this.checked)
507			{
508				customizeBtn.removeAttribute('disabled');
509			}
510		});
511
512		div.appendChild(customizeBtn);
513
514		return div;
515	};
516
517	if (ui.format != null)
518	{
519		ui.format.refresh();
520	}
521
522	//Adding Link to Confluence Page Anchor
523
524	var origLinkDialog = LinkDialog;
525
526	LinkDialog = function(editorUi, initialValue, btnLabel, fn, showPages)
527	{
528		function modFn(link, selDoc)
529		{
530			if (anchorRadio.checked)
531			{
532				fn('data:confluence/anchor,' + anchorSelect.value);
533			}
534			else
535			{
536				fn(link, selDoc);
537			}
538		};
539
540		origLinkDialog.call(this, editorUi, initialValue, btnLabel, modFn, showPages);
541
542		var baseUrl = '';
543
544		ui.remoteInvoke('getBaseUrl', null, null, function(url)
545		{
546			baseUrl = url;
547		},
548		function()
549		{
550			//Extremely rare, we can safely ignore since the editor won't work
551		});
552
553		var inner = this.container.querySelector('.geTitle'), urlInput = inner.querySelector('input[type="text"]'), urlCheck = urlInput.previousSibling;
554
555		var lbl = document.createElement('div');
556		mxUtils.write(lbl, mxResources.get('confAnchor') + ':');
557		inner.appendChild(lbl);
558
559		function addOption(select, name, value, isDisabled, isSelected)
560		{
561			var opt = document.createElement('option');
562
563			if (isDisabled)
564			{
565				opt.setAttribute('disabled', 'disabled');
566			}
567
568			if (isSelected)
569			{
570				opt.setAttribute('selected', 'selected');
571			}
572
573			if (value)
574			{
575				opt.setAttribute('value', value);
576			}
577
578			mxUtils.write(opt, name);
579			select.appendChild(opt);
580		}
581
582		var anchorRadio = document.createElement('input');
583		anchorRadio.style.cssText = 'margin-right:8px;margin-bottom:8px;';
584		anchorRadio.setAttribute('value', 'url');
585		anchorRadio.setAttribute('type', 'radio');
586
587		var anchorSelect = document.createElement('select');
588		anchorSelect.style.marginTop = '6px';
589		anchorSelect.style.width = '680px';
590
591		var anchorBusyIcn = document.createElement('img');
592		anchorBusyIcn.src = '/images/spin.gif';
593		anchorBusyIcn.style.position = 'absolute';
594
595		var selAnchor = null;
596
597		if (initialValue != null && initialValue.substring(0, 23) == 'data:confluence/anchor,')
598		{
599			urlInput.value = '';
600			selAnchor = initialValue.substring(23);
601			anchorRadio.setAttribute('checked', 'checked');
602			anchorRadio.defaultChecked = true;
603		}
604
605		ui.remoteInvoke('getCurPageAnchors', null, null, function(headings)
606		{
607			addOption(anchorSelect, headings.length == 0? mxResources.get('noAnchorsFound') : mxResources.get('confAnchor'), null, true, selAnchor == null);
608
609			if (headings.length == 0)
610			{
611				anchorSelect.setAttribute('disabled', 'disabled');
612				anchorRadio.setAttribute('disabled', 'disabled');
613			}
614			else
615			{
616				for(var i = 0; i < headings.length; i++)
617				{
618					addOption(anchorSelect, headings[i], headings[i], false, selAnchor == headings[i]);
619				}
620			}
621
622			anchorBusyIcn.style.display = 'none';
623		}, function()
624		{
625			anchorSelect.style.border = '1px solid red';
626			anchorSelect.setAttribute('disabled', 'disabled');
627			anchorRadio.setAttribute('disabled', 'disabled');
628			anchorBusyIcn.style.display = 'none';
629		});
630
631		mxEvent.addListener(anchorSelect, 'focus', function()
632		{
633			anchorRadio.setAttribute('checked', 'checked');
634			anchorRadio.checked = true;
635		});
636
637		inner.appendChild(anchorRadio);
638		inner.appendChild(anchorSelect);
639		inner.appendChild(anchorBusyIcn);
640
641		//Attachments select
642		lbl = document.createElement('div');
643		mxUtils.write(lbl, mxResources.get('attachments') + ':');
644		inner.appendChild(lbl);
645
646		var attSelect = document.createElement('select');
647		attSelect.style.margin = '6px 0 5px 0';
648		attSelect.style.width = '705px';
649
650		var attBusyIcn = document.createElement('img');
651		attBusyIcn.src = '/images/spin.gif';
652		attBusyIcn.style.position = 'absolute';
653
654		var attMap = {};
655
656		ui.remoteInvoke('getCurPageAttachments', null, null, function(atts)
657		{
658			addOption(attSelect, atts.length == 0? mxResources.get('noAttachments') : mxResources.get('attachments'), null, true, true);
659
660			if (atts.length == 0)
661			{
662				attSelect.setAttribute('disabled', 'disabled');
663			}
664			else
665			{
666				atts = atts.filter(function(a)
667				{
668					//Exclude draft and temp files
669					return a.metadata.mediaType != 'application/vnd.jgraph.mxfile.cached' && !/^\~.+\.tmp$/.test(a.title);
670				});
671
672				for(var i = 0; i < atts.length; i++)
673				{
674					attMap[atts[i].id] = atts[i];
675					addOption(attSelect, atts[i].title, atts[i].id, false, false);
676				}
677			}
678
679			attBusyIcn.style.display = 'none';
680		}, function()
681		{
682			attSelect.style.border = '1px solid red';
683			attSelect.setAttribute('disabled', 'disabled');
684			attBusyIcn.style.display = 'none';
685		});
686
687		function setUrlValue(content)
688		{
689			//Attachment webui link doesn't work, so build it
690			if (content.type == 'attachment')
691			{
692				var pageId = content._expandable.container.match(/\d+/)[0];
693				urlInput.value = baseUrl + '/pages/viewpageattachments.action?pageId='
694								+ pageId
695								+ '&preview=/' + pageId + '/' + content.id.replace('att', '') + '/'
696								+ encodeURIComponent(content.title);
697			}
698			else
699			{
700				urlInput.value = baseUrl + content._links.webui;
701			}
702
703			urlCheck.checked = true;
704		};
705
706		mxEvent.addListener(attSelect, 'change', function()
707		{
708			var att = attMap[attSelect.value];
709
710			if (att.metadata.mediaType == 'application/vnd.jgraph.mxfile')
711			{
712				attBusyIcn.style.display = '';
713				var pageId = att._expandable.container;
714				pageId = pageId.substr(pageId.lastIndexOf('/') + 1);
715
716				ui.remoteInvoke('getPageDrawioDiagrams', [pageId], null, function(drawioCCs)
717				{
718					var attCC = drawioCCs.filter(function(c)
719					{
720						return c.info.name == att.title;
721					})[0];
722
723					if (attCC)
724					{
725						setUrlValue(attCC.obj);
726					}
727					else
728					{
729						setUrlValue(att);
730					}
731
732					attBusyIcn.style.display = 'none';
733				}, function()
734				{
735					attSelect.style.border = '1px solid red';
736					attBusyIcn.style.display = 'none';
737				});
738			}
739			else
740			{
741				setUrlValue(att);
742			}
743		});
744
745		inner.appendChild(attSelect);
746		inner.appendChild(attBusyIcn);
747
748		//Search
749		lbl = document.createElement('div');
750		mxUtils.write(lbl, mxResources.get('search') + ':');
751		inner.appendChild(lbl);
752
753		var searchInput = document.createElement('input');
754		searchInput.placeholder = mxResources.get('search');
755		searchInput.style.margin = '6px 5px 5px 0';
756		searchInput.style.width = '490px';
757
758		var spaceSelect = document.createElement('select');
759		spaceSelect.style.marginTop = '6px';
760		spaceSelect.style.width = '202px';
761
762		var spaceBusyIcn = document.createElement('img');
763		spaceBusyIcn.src = '/images/spin.gif';
764		spaceBusyIcn.style.position = 'absolute';
765
766		var searchResult = document.createElement('div');
767		searchResult.style.cssText = 'border: 1px solid black;width: 705px;height:200px;overflow-y:auto; overflow-x:hidden';
768
769		addOption(spaceSelect, mxResources.get('allSpaces'), '*', false, true);
770
771		var typesMap = {
772			'page': mxResources.get('page'),
773			'attachment': mxResources.get('attachment', null, 'Attachment'),
774			'blogpost': mxResources.get('blog'),
775			'ac:com.mxgraph.confluence.plugins.diagramly:drawio-diagram': mxResources.get('drawDiag')
776		};
777
778		ui.remoteInvoke('getAvailableSpaces', null, null, function(spaces)
779		{
780			for(var i = 0; i < spaces.length; i++)
781			{
782				addOption(spaceSelect, spaces[i].title, spaces[i].space.key, false, false);
783			}
784
785			spaceBusyIcn.style.display = 'none';
786		}, function()
787		{
788			//We'll use all spaces and ignore error
789			spaceBusyIcn.style.display = 'none';
790		});
791
792		var searchTimeout = null, searchResultsMap = {};
793
794		function resultRowClick()
795		{
796			var cId = this.getAttribute('data-url');
797			setUrlValue(searchResultsMap[cId]);
798		};
799
800		function doSearch()
801		{
802			clearTimeout(searchTimeout);
803
804			if(searchInput.value != '')
805			{
806				searchResult.innerHTML = '<img src="/images/spin.gif">';
807				searchResultsMap = {};
808
809				ui.remoteInvoke('contentSearch', [searchInput.value, spaceSelect.value == '*'? null : [spaceSelect.value]], null, function(results)
810				{
811					searchResult.innerHTML = '';
812
813					results = results.filter(function(r)
814					{
815						//Exclude draft files and diagram files (since it is returned as custom contents)
816						return r.metadata.mediaType != 'application/vnd.jgraph.mxfile.cached' && r.metadata.mediaType != 'application/vnd.jgraph.mxfile';
817					});
818
819					if (results.length == 0)
820					{
821						searchResult.innerHTML = mxResources.get('noSearchResults');
822					}
823					else
824					{
825						var table = document.createElement('table');
826						table.className = 'geStripedTable';
827						table.innerHTML = '<tr><th style="width:335px;">' + mxResources.get('title') + '</th><th style="width:105px;">' + mxResources.get('type')
828											 + '</th><th style="width:130px;">' + mxResources.get('space') + '</th><th>' + mxResources.get('lastModified') + '</th></tr>';
829
830						for(var i = 0; i < results.length; i++)
831						{
832							var res = results[i];
833							searchResultsMap[res.id] = res;
834							var spaceName =  res.space? res.space.name : '';
835
836							var tr = document.createElement('tr');
837							tr.setAttribute('data-url', res.id);
838							var type = typesMap[res.type];
839							tr.innerHTML = '<td>' + mxUtils.htmlEntities(res.title) + '</td><td>' + (type? type : mxResources.get('other'))
840												 + '</td><td>' + mxUtils.htmlEntities(spaceName) + '</td><td>' + mxUtils.htmlEntities(res.version.friendlyWhen) + '</td></tr>';
841
842							mxEvent.addListener(tr, 'click', resultRowClick);
843							table.appendChild(tr);
844						}
845
846						searchResult.appendChild(table);
847					}
848				}, function()
849				{
850					searchResult.innerHTML = mxResources.get('confAErrOccured');
851				});
852			}
853		};
854
855		mxEvent.addListener(searchInput, 'keypress', function(e)
856		{
857	        if(e.which == 13)
858			{
859	            doSearch();
860	        }
861	    });
862
863		mxEvent.addListener(searchInput, 'input', function(e)
864		{
865			clearTimeout(searchTimeout);
866			searchTimeout = setTimeout(doSearch, 1000);
867		});
868
869		inner.appendChild(searchInput);
870		inner.appendChild(spaceSelect);
871		inner.appendChild(spaceBusyIcn);
872		inner.appendChild(searchResult);
873
874		var origInit = this.init;
875
876		this.init = function()
877		{
878			origInit.apply(this, arguments);
879
880			if (anchorRadio.checked)
881			{
882				anchorSelect.focus();
883			}
884		};
885	};
886
887	mxUtils.extend(LinkDialog, origLinkDialog);
888
889	ui.showLinkDialog = function(value, btnLabel, fn)
890	{
891		var dlg = new LinkDialog(this, value, btnLabel, fn, true);
892		this.showDialog(dlg.container, 700, 470, true, true);
893		dlg.init();
894	};
895
896	//Viewer also had to change this in viewer (Graph.prototype.customLinkClicked)
897	var origHandleCustomLink = ui.handleCustomLink;
898
899	//This code is similar to AC.gotoAnchor but we don't have access to AC here
900	ui.handleCustomLink = function(href)
901	{
902		if (href.substring(0, 19) == 'data:confluence/id,')
903		{
904			var id = href.substring(19);
905
906			var newWin = window.open();
907
908			if (id)
909			{
910				ui.remoteInvoke('getContentInfo', [id], null, function(info)
911				{
912					ui.remoteInvoke('getBaseUrl', null, null, function(url)
913					{
914						newWin.location = url + info._links.webui;
915					},
916					function(){});
917				}, function()
918				{
919					newWin.document.writeln(mxResources.get('objectNotFound'));
920				});
921			}
922			else
923			{
924				throw new Error('Empty ID');
925			}
926		}
927		else if (href.substring(0, 23) == 'data:confluence/anchor,')
928		{
929			var anchor = href.substring(23);
930
931			var newWin = window.open();
932
933			if (anchor)
934			{
935				ui.remoteInvoke('getPageInfo', [true], null, function(info)
936				{
937					var url = info.url;
938
939					if (url != null)
940					{
941						//remove any hash
942						var hash = url.indexOf('#');
943
944						if (hash > -1)
945						{
946							url = url.substring(0, hash);
947						}
948
949						//We assume the new editor for simplicity
950						newWin.location = url + '#' + encodeURIComponent(anchor.replace(/\s/g, '-'));
951					}
952				}, function()
953				{
954					throw new Error('Unexpected Error');
955				});
956			}
957			else
958			{
959				throw new Error('Empty Anchor');
960			}
961		}
962		else
963		{
964			origHandleCustomLink.apply(ui, arguments);
965		}
966	};
967
968	var origGetLinkTitle = ui.getLinkTitle;
969
970	ui.getLinkTitle = function(href)
971	{
972		if (href.substring(0, 19) == 'data:confluence/id,')
973		{
974			return mxResources.get('link'); //We only have the id which is not helpful
975		}
976		else if (href.substring(0, 23) == 'data:confluence/anchor,')
977		{
978			return mxResources.get('anchor') + ': ' + href.substring(23);
979		}
980		else
981		{
982			return origGetLinkTitle.apply(ui, arguments);
983		}
984	};
985
986	//======================== Revisions ========================
987
988	ui.isRevisionHistoryEnabled = function()
989	{
990		return macroData.pageId != null;
991	};
992
993	ui.isRevisionHistorySupported = function()
994	{
995		return true;
996	};
997
998	/**
999	 * Get revisions of current file
1000	 */
1001	ui.getRevisions = function(success, error)
1002	{
1003		function getXml(success, error)
1004		{
1005			ui.remoteInvoke('getFileContent', [this.downloadUrl], null, success, error);
1006		};
1007
1008		function restoreFn(xml)
1009		{
1010			if (ui.spinner.spin(document.body, mxResources.get('restoring')))
1011			{
1012				ui.replaceFileData(xml);
1013				ui.spinner.stop();
1014				ui.hideDialog();
1015			}
1016		};
1017
1018		ui.remoteInvoke('getDiagramRevisions', [macroData.diagramName, macroData.pageId], null, function(revisions)
1019		{
1020			//convert to editor format and add getXml function
1021			var revs = [];
1022
1023			for (var i = 0; i < revisions.length; i++)
1024			{
1025				var rev = revisions[i];
1026				rev.getXml = mxUtils.bind(rev, getXml);
1027				revs.push(rev);
1028			}
1029
1030			success(revs, restoreFn);
1031		}, error);
1032	};
1033
1034	//============= Support Action ===============
1035	ui.actions.addAction('support...', function()
1036	{
1037		ui.remoteInvoke('getPageInfo', [true], null, function(info)
1038		{
1039			var url = info.url;
1040
1041			if (url != null)
1042			{
1043				var wikiPos = url.indexOf('/wiki/');
1044
1045				if (wikiPos > -1)
1046				{
1047					url = url.substring(0, wikiPos);
1048				}
1049
1050				ui.openLink(url + '/wiki/plugins/servlet/ac/com.mxgraph.confluence.plugins.diagramly/support');
1051			}
1052			else
1053			{
1054				ui.openLink('https://about.draw.io/support/');
1055			}
1056		}, function()
1057		{
1058			ui.openLink('https://about.draw.io/support/');
1059		});
1060	});
1061
1062	//=============Custom Libraries in More Shapes ===================
1063	function addImage(container, data, w, h, img)
1064	{
1065		var ew = 100;
1066		var eh = 100;
1067
1068		var iw = w;
1069		var ih = h;
1070
1071		if (w > ui.maxImageSize || h > ui.maxImageSize)
1072		{
1073			var s = Math.min(1, Math.min(ui.maxImageSize / Math.max(1, w)), ui.maxImageSize / Math.max(1, h));
1074			w *= s;
1075			h *= s;
1076		}
1077
1078		if (iw > ih)
1079		{
1080			ih = Math.round(ih * ew / iw);
1081			iw = ew;
1082		}
1083		else
1084		{
1085			iw = Math.round(iw * eh / ih);
1086			ih = eh;
1087		}
1088
1089		var wrapper = document.createElement('div');
1090		wrapper.setAttribute('draggable', 'true');
1091		wrapper.style.display = 'inline-block';
1092		wrapper.style.cursor = 'move';
1093
1094		if (data != null)
1095		{
1096            var elt = document.createElement('img');
1097            elt.setAttribute('src', data);
1098			elt.style.width = iw + 'px';
1099			elt.style.height = ih + 'px';
1100			elt.style.margin = '10px';
1101
1102			elt.style.paddingBottom = Math.floor((eh - ih) / 2) + 'px';
1103			elt.style.paddingLeft = Math.floor((ew - iw) / 2) + 'px';
1104
1105			wrapper.appendChild(elt);
1106		}
1107		else if (img != null)
1108		{
1109			var cells = ui.stringToCells(Graph.decompress(img.xml));
1110
1111			if (cells.length > 0)
1112			{
1113				ui.sidebar.createThumb(cells, ew, eh, wrapper, null, true, false);
1114
1115				// Needs inline block on SVG for delete icon to appear on same line
1116				wrapper.firstChild.style.display = 'inline-block';
1117				wrapper.firstChild.style.cursor = '';
1118			}
1119		}
1120
1121		container.appendChild(wrapper);
1122	};
1123
1124	var customLibraries = [];
1125
1126	ui.actions.addAction('shapes...', mxUtils.bind(this, function()
1127	{
1128		ui.remoteInvoke('getCustomLibraries', null, null, function(libs)
1129		{
1130			customLibraries = libs;
1131
1132			for(var i = 0; i < libs.length; i++)
1133			{
1134				libs[i].imageCallback = mxUtils.bind(libs[i], function(preview)
1135				{
1136					preview.innerHTML = '<img src="/images/spin.gif">';
1137
1138					ui.remoteInvoke('getFileContent', [this.downloadUrl], null, function(libContent)
1139					{
1140						try
1141						{
1142							preview.innerHTML = '';
1143							doc = mxUtils.parseXml(libContent);
1144							var images = JSON.parse(mxUtils.getTextContent(doc.documentElement));
1145
1146							for(var i = 0; i < images.length; i++)
1147							{
1148								addImage(preview, images[i].data, images[i].w, images[i].h, images[i]);
1149							}
1150						}
1151						catch(e)
1152						{
1153							preview.innerHTML = mxResources.get('confAErrOccured');
1154							console.log(e);
1155						}
1156					}, function(err)
1157					{
1158						preview.innerHTML = mxResources.get('errorLoadingFile');
1159						console.log(err);
1160					});
1161				});
1162			}
1163
1164			var customLibsEntry = libs.length > 0? [{title : mxResources.get('customLib'), entries : libs}] : [];
1165			ui.showDialog(new MoreShapesDialog(ui, true, ui.sidebar.entries.concat(customLibsEntry)).container, 640, (isLocalStorage) ?
1166					((mxClient.IS_IOS) ? 650 : 630) : 650, true, true);
1167		}, function(err)
1168		{
1169			console.log(err);
1170			ui.showDialog(new MoreShapesDialog(ui, true, ui.sidebar.entries).container, 640, (isLocalStorage) ?
1171					((mxClient.IS_IOS) ? 650 : 630) : 650, true, true);
1172		});
1173	}));
1174
1175    var showEntriesOld =  Sidebar.prototype.showEntries;
1176
1177	Sidebar.prototype.showEntries = function(stc, remember, force)
1178	{
1179		showEntriesOld.apply(this, arguments);
1180
1181		if(stc == null)
1182			return;
1183
1184		var libIds = stc.split(';');
1185
1186		for(var i = 0; i < customLibraries.length; i++)
1187		{
1188			lib = customLibraries[i];
1189
1190			if(mxUtils.indexOf(libIds, lib.id) != -1)
1191			{
1192				ui.remoteInvoke('getFileContent', [lib.downloadUrl], null, mxUtils.bind(lib, function(libContent)
1193				{
1194					try
1195					{
1196						ui.loadLibrary(new RemoteLibrary(ui, libContent, this));
1197					}
1198					catch (e)
1199					{
1200						//Ignore
1201					}
1202				}), function()
1203				{
1204					//Ignore
1205				});
1206			}
1207			else
1208			{
1209				ui.closeLibrary(new RemoteLibrary(ui, '', lib));
1210			}
1211		};
1212	};
1213
1214    var isEntryVisibleOld = Sidebar.prototype.isEntryVisible;
1215
1216	Sidebar.prototype.isEntryVisible = function(key)
1217	{
1218		var visible = isEntryVisibleOld.apply(this, arguments);
1219		var cVisible = false;
1220
1221		var customLibSelection = mxSettings.getCustomLibraries();
1222
1223		for(var i = 0; i < customLibSelection.length; i++)
1224		{
1225			try
1226			{
1227				var hash = customLibSelection[i];
1228
1229				if (hash.charAt(0) == 'R')
1230				{
1231					if(JSON.parse(decodeURIComponent(hash.substr(1)))[0] == key)
1232					{
1233						cVisible = true;
1234					}
1235				}
1236			}
1237			catch(e){} //ignore
1238		}
1239
1240		return visible || cVisible;
1241	};
1242
1243	//Show custom templates in templates dialog
1244	//Overriding the action here is too late as the ui button is created using the action function
1245	//The solution is to override the NewDialog itself but it's tricky and hacky
1246	var origNewDialog = NewDialog;
1247
1248	NewDialog = function(editorUi, compact, showName, callback, createOnly, cancelCallback,
1249		leftHighlight, rightHighlight, rightHighlightBorder, itemPadding, templateFile,
1250		recentDocsCallback, searchDocsCallback, openExtDocCallback, showImport, createButtonLabel, customTempCallback, withoutType)
1251	{
1252		if (!showName && recentDocsCallback == null &&
1253			searchDocsCallback == null && openExtDocCallback == null && customTempCallback == null)
1254		{
1255			openExtDocCallback = function(url, info, name)
1256			{
1257				ui.remoteInvoke('getFileContent', [url], null, callback, function()
1258				{
1259					ui.showError(mxResources.get('error'), mxResources.get('cantReadChckPerms'), mxResources.get('ok'));
1260				});
1261			};
1262
1263			customTempCallback = function(customTempCallback)
1264			{
1265				ui.remoteInvoke('getCustomTemplates', null, null, customTempCallback, function()
1266				{
1267					customTempCallback({}, 0); //ignore error by sending empty templates
1268				});
1269			};
1270		}
1271
1272		origNewDialog.call(this, editorUi, compact, showName, callback, createOnly, cancelCallback,
1273					leftHighlight, rightHighlight, rightHighlightBorder, itemPadding, templateFile,
1274					recentDocsCallback, searchDocsCallback, openExtDocCallback, showImport, createButtonLabel, customTempCallback, withoutType);
1275	};
1276
1277	mxUtils.extend(NewDialog, origNewDialog);
1278
1279	for (var key in origNewDialog)
1280	{
1281		NewDialog[key] = origNewDialog[key];
1282	}
1283	//=============Embed File with real-time collab support (based on remote invocation)
1284	//Until app.min.js is propagated, this code is necessary
1285	if (typeof EmbedFile === 'undefined')
1286	{
1287		var origInstallMessageHandler = ui.installMessageHandler;
1288
1289		ui.installMessageHandler = function()
1290		{
1291			var parent = window.opener || window.parent;
1292			parent.postMessage(JSON.stringify({event: 'disableRT'}), '*');
1293
1294			origInstallMessageHandler.apply(this, arguments);
1295		}
1296
1297		return;
1298	}
1299
1300	/**
1301	 * Workaround for changing etag after save is higher autosave delay to allow
1302	 * for preflight etag update and decrease possible conflicts on file save.
1303	 */
1304	EmbedFile.prototype.autosaveDelay = 500;
1305
1306	/**
1307	 * Delay for last save in ms.
1308	 */
1309	EmbedFile.prototype.saveDelay = 0;
1310
1311	/**
1312	 *
1313	 */
1314	EmbedFile.prototype.isConflict = function(err)
1315	{
1316		return err != null && err.status == 409;
1317	};
1318
1319	/**
1320	 * Returns the current user.
1321	 */
1322	EmbedFile.prototype.getCurrentUser = function()
1323	{
1324		return ui.getCurrentUser();
1325	};
1326
1327	/**
1328	 * Returns true if an autosave is required at the time of execution.
1329	 */
1330	EmbedFile.prototype.isAutosave = function()
1331	{
1332		return this.desc.id != null;
1333	};
1334
1335	/**
1336	 * Specifies if the autosave checkbox should be shown in the document
1337	 * properties dialog. Default is false.
1338	 */
1339	EmbedFile.prototype.isAutosaveOptional = function()
1340	{
1341		return this.desc.id == null;
1342	};
1343
1344	/**
1345	 *
1346	 */
1347	EmbedFile.prototype.isRenamable = function()
1348	{
1349		return this.isEditable() && DrawioFile.prototype.isEditable.apply(this, arguments);
1350	};
1351
1352	/**
1353	 *
1354	 */
1355	EmbedFile.prototype.save = function(revision, success, error, unloading, overwrite)
1356	{
1357		this.saveStarted = true;
1358
1359		DrawioFile.prototype.save.apply(this, [revision, mxUtils.bind(this, function()
1360		{
1361			this.saveFile(null, revision, success, error, unloading, overwrite);
1362			this.saveStarted = false;
1363		}), error, unloading, overwrite]);
1364	};
1365
1366	/**
1367	 *
1368	 */
1369	EmbedFile.prototype.setModified = function(value)
1370	{
1371		DrawioFile.prototype.setModified.apply(this, arguments);
1372
1373		//Set editor modified also to prevent accidental closure or exiting without saving
1374		ui.editor.modified = value;
1375	};
1376
1377	/**
1378	 *
1379	 */
1380	EmbedFile.prototype.saveFile = function(title, revision, success, error, unloading, overwrite)
1381	{
1382		try
1383		{
1384			if (!this.isEditable())
1385			{
1386				if (success != null)
1387				{
1388					success();
1389				}
1390			}
1391			else if (!this.savingFile)
1392			{
1393				// Sets shadow modified state during save
1394				this.savingFileTime = new Date();
1395				this.setShadowModified(false);
1396				this.savingFile = true;
1397
1398				this.createSecret(mxUtils.bind(this, function(secret, token)
1399				{
1400					var doSave = mxUtils.bind(this, function(realOverwrite, realRevision)
1401					{
1402						try
1403						{
1404							var lastDesc = this.desc;
1405							var savedData = this.getData();
1406							this.desc.secret = secret;
1407							this.desc.key = this.desc.key? this.desc.key : Editor.guid(32);
1408							this.desc.channel = this.desc.channel? this.desc.channel : Editor.guid(32);
1409							this.desc.etagP = this.desc.etagP? this.desc.etagP : Editor.guid(32);
1410							this.desc.title = this.desc.title? this.desc.title : macroData.diagramDisplayName;
1411
1412							ui.remoteInvoke('saveDraftWithFileDesc', [savedData, this.desc], null, mxUtils.bind(this, function(resp)
1413							{
1414								try
1415								{
1416									this.savingFile = false;
1417
1418									// Handles special case where resp is false eg
1419									// if the old file was converted to realtime
1420									if (resp != false)
1421									{
1422										// Checks for changes during save
1423										this.setModified(this.getShadowModified());
1424
1425										if (revision)
1426										{
1427											this.lastAutosaveRevision = new Date().getTime();
1428										}
1429
1430										// Adaptive autosave delay
1431										this.autosaveDelay = Math.min(8000,
1432											Math.max(this.saveDelay + 500,
1433											EmbedFile.prototype.autosaveDelay));
1434										this.desc = resp;
1435
1436										// Shows possible errors but keeps the modified flag as the
1437										// file was saved but the cache entry could not be written
1438										if (token != null)
1439										{
1440											this.fileSaved(savedData, lastDesc, mxUtils.bind(this, function()
1441											{
1442												this.contentChanged();
1443
1444												if (success != null)
1445												{
1446													success(resp);
1447												}
1448											}), error, token);
1449										}
1450										else
1451										{
1452											success(resp);
1453										}
1454									}
1455									else if (error != null)
1456									{
1457										error(resp);
1458									}
1459								}
1460								catch (e)
1461								{
1462									this.savingFile = false;
1463
1464									if (error != null)
1465									{
1466										error(e);
1467									}
1468									else
1469									{
1470										throw e;
1471									}
1472								}
1473							}),
1474							mxUtils.bind(this, function(err, desc)
1475							{
1476								//TODO EMBED desc is null here
1477								try
1478								{
1479									this.savingFile = false;
1480
1481									if (this.isConflict(err))
1482									{
1483										this.inConflictState = true;
1484
1485										if (this.sync != null)
1486										{
1487											//Reduce number of retials since we already retry in AC.saveDraftWithFileDesc
1488											this.sync.maxCatchupRetries = 2;
1489											this.savingFile = true;
1490
1491											this.sync.fileConflict(desc, mxUtils.bind(this, function()
1492											{
1493												// Adds random cool-off
1494												window.setTimeout(mxUtils.bind(this, function()
1495												{
1496													this.updateFileData();
1497													this.setShadowModified(false);
1498													doSave(realOverwrite, true);
1499												}), 100 + Math.random() * 500 + (err.isLocked? 500 : 0));
1500											}), mxUtils.bind(this, function()
1501											{
1502												this.savingFile = false;
1503
1504												if (error != null)
1505												{
1506													error();
1507												}
1508											}));
1509										}
1510										else if (error != null)
1511										{
1512											error();
1513										}
1514									}
1515									else if (error != null)
1516									{
1517										error(err);
1518									}
1519								}
1520								catch (e)
1521								{
1522									this.savingFile = false;
1523
1524									if (error != null)
1525									{
1526										error(e);
1527									}
1528									else
1529									{
1530										throw e;
1531									}
1532								}
1533							}));
1534						}
1535						catch (e)
1536						{
1537							this.savingFile = false;
1538
1539							if (error != null)
1540							{
1541								error(e);
1542							}
1543							else
1544							{
1545								throw e;
1546							}
1547						}
1548					});
1549
1550					doSave(overwrite, revision);
1551				}));
1552			}
1553		}
1554		catch (e)
1555		{
1556			if (error != null)
1557			{
1558				error(e);
1559			}
1560			else
1561			{
1562				throw e;
1563			}
1564		}
1565	};
1566
1567	/**
1568	 *
1569	 */
1570	EmbedFile.prototype.copyFile = function(success, error)
1571	{
1572		//Download a copy of the file since it is difficult to add a copy to current confluence page
1573		this.updateFileData();
1574		ui.doSaveLocalFile(this.data, this.getTitle(), 'text/xml');
1575		error(); //Since the problem is not fixed //TODO Confirm this is OK??
1576	};
1577
1578	/**
1579	 *
1580	 */
1581	EmbedFile.prototype.rename = function(title, success, error)
1582	{
1583		var etag = this.getCurrentEtag();
1584		this.desc.title = title;
1585
1586		ui.remoteInvoke('setFileDescriptor', [this.desc], null, mxUtils.bind(this, function(desc)
1587		{
1588			this.desc = desc;
1589			this.descriptorChanged();
1590
1591			if (this.sync != null)
1592			{
1593				this.sync.descriptorChanged(etag);
1594			}
1595
1596			if (success != null)
1597			{
1598				success(desc);
1599			}
1600		}), error);
1601	};
1602
1603	/**
1604	 *
1605	 */
1606	EmbedFile.prototype.getTitle = function()
1607	{
1608		return this.desc.title || macroData.diagramDisplayName;
1609	};
1610
1611	/**
1612	 *
1613	 */
1614	EmbedFile.prototype.getHash = function()
1615	{
1616		return 'E' + this.getId();
1617	};
1618
1619	/**
1620	 *
1621	 */
1622	EmbedFile.prototype.getId = function()
1623	{
1624		return this.desc.id;
1625	};
1626
1627	/**
1628	 *
1629	 */
1630	EmbedFile.prototype.isSyncSupported = function()
1631	{
1632		return this.desc.id != null;
1633	};
1634
1635	/**
1636	 *
1637	 */
1638	EmbedFile.prototype.isRevisionHistorySupported = function()
1639	{
1640		return true;
1641	};
1642
1643	/**
1644	 *
1645	 */
1646	EmbedFile.prototype.getLatestVersion = function(success, error)
1647	{
1648		ui.remoteInvoke('getDraftFileContent', null, null, mxUtils.bind(this, function(data, desc)
1649		{
1650			success(new EmbedFile(ui, data, desc));
1651		}), error);
1652	};
1653
1654	/**
1655	 * Gets the channel ID from the given descriptor.
1656	 */
1657	EmbedFile.prototype.getChannelId = function()
1658	{
1659		var chan = this.desc.channel;
1660
1661		if (chan != null)
1662		{
1663			chan = 'E-' + this.getId() + '.' + chan;
1664		}
1665
1666		return chan;
1667	};
1668
1669	/**
1670	 * Gets the channel key from the given descriptor.
1671	 */
1672	EmbedFile.prototype.getChannelKey = function()
1673	{
1674		return this.desc.key;
1675	};
1676
1677	/**
1678	 *
1679	 */
1680	EmbedFile.prototype.getLastModifiedDate = function()
1681	{
1682		return new Date(this.desc.modifiedDate);
1683	};
1684
1685	/**
1686	 *
1687	 */
1688	EmbedFile.prototype.getDescriptor = function()
1689	{
1690		return this.desc;
1691	};
1692
1693	/**
1694	* Updates the descriptor of this file with the one from the given file.
1695	*/
1696	EmbedFile.prototype.setDescriptor = function(desc)
1697	{
1698		this.desc = desc;
1699	};
1700
1701	/**
1702	 * Returns the secret from the given descriptor.
1703	 */
1704	EmbedFile.prototype.getDescriptorSecret = function(desc)
1705	{
1706		return desc.secret;
1707	};
1708
1709	/**
1710	 * Updates the revision ID on the given descriptor.
1711	 */
1712	EmbedFile.prototype.setDescriptorRevisionId = function(desc, id)
1713	{
1714		desc.headRevisionId = id;
1715	};
1716
1717	/**
1718	 * Returns the revision ID from the given descriptor.
1719	 */
1720	EmbedFile.prototype.getDescriptorRevisionId = function(desc)
1721	{
1722		return desc.headRevisionId;
1723	};
1724
1725	/**
1726	 *
1727	 */
1728	EmbedFile.prototype.getDescriptorEtag = function(desc)
1729	{
1730		return desc.etag;
1731	};
1732
1733	/**
1734	 *
1735	 */
1736	EmbedFile.prototype.setDescriptorEtag = function(desc, etag)
1737	{
1738		desc.etag = etag;
1739	};
1740
1741	/**
1742	 *
1743	 */
1744	EmbedFile.prototype.patchDescriptor = function(desc, patch)
1745	{
1746		DrawioFile.prototype.patchDescriptor.apply(this, arguments);
1747
1748		desc.headRevisionId = patch.headRevisionId;
1749		desc.modifiedDate = patch.modifiedDate;
1750	};
1751
1752	/**
1753	 *
1754	 */
1755	EmbedFile.prototype.loadDescriptor = function(success, error)
1756	{
1757		ui.remoteInvoke('getFileDescriptor', null, null, success, error);
1758	};
1759
1760	var allowAutoSave = true;
1761
1762	EmbedFile.prototype.isAutosaveNow = function(success, error)
1763	{
1764		return allowAutoSave;
1765	};
1766
1767	//Ensure saving of draft before publishing
1768	var origSaveAction = ui.actions.get('save').funct;
1769
1770	ui.actions.get('save').funct = function(exit)
1771	{
1772		var actArgs = arguments;
1773		var curFile = ui.getCurrentFile();
1774		var desc = curFile.getDescriptor();
1775		var isNewFile = desc == null || desc.key == null;
1776
1777		if (exit)
1778		{
1779			//Prevent stpping the spinner early by creating our own spinner
1780			var spinner = new Spinner({
1781				lines: 12, // The number of lines to draw
1782				length: 24, // The length of each line
1783				width: 8, // The line thickness
1784				radius: 12, // The radius of the inner circle
1785				rotate: 0, // The rotation offset
1786				color: '#000', // #rgb or #rrggbb
1787				speed: 1.5, // Rounds per second
1788				trail: 60, // Afterglow percentage
1789				shadow: false, // Whether to render a shadow
1790				hwaccel: false, // Whether to use hardware acceleration
1791				zIndex: 2e9 // The z-index (defaults to 2000000000)
1792			});
1793
1794			if (!isNewFile)
1795			{
1796				spinner.spin(document.body);
1797			}
1798
1799			allowAutoSave = false;
1800
1801			if (desc != null)
1802			{
1803				desc.viewerSettings = {
1804					tbstyle: macroData.tbstyle,
1805					links: macroData.links,
1806					simple: macroData.simple,
1807					lbox: macroData.lbox,
1808					zoom: macroData.zoom,
1809					pCenter: macroData.pCenter,
1810					aspect:	macroData.aspect,
1811					hiResPreview: macroData.hiResPreview
1812				};
1813			}
1814
1815			var etag = curFile.getCurrentEtag();
1816		}
1817
1818		function doActions()
1819		{
1820			origSaveAction.apply(ui, actArgs);
1821
1822			if (exit && curFile.sync != null)
1823			{
1824				curFile.sync.descriptorChanged(etag);
1825			}
1826		};
1827
1828		function doSave()
1829		{
1830			if (curFile.saveStarted || curFile.savingFile)
1831			{
1832				setTimeout(doSave, 100);
1833				return;
1834			}
1835
1836			if (curFile.isModified())
1837			{
1838				//Save file (draft) first
1839				ui.saveFile(null, doActions);
1840			}
1841			else if (exit) //Save descriptor only to update the viewer settings
1842			{
1843				ui.remoteInvoke('setFileDescriptor', [desc], null, doActions, doActions);
1844			}
1845			else
1846			{
1847				doActions();
1848			}
1849		};
1850
1851		if (isNewFile)
1852		{
1853			//New files are saved directly and descriptor is added during publishing after creating the custom content
1854			doActions();
1855		}
1856		else
1857		{
1858			doSave();
1859		}
1860	};
1861
1862	var p2pCollab = null;
1863	//Add file opening here (or it should be for all in EditorUi?)
1864	var origInstallMessageHandler =  ui.installMessageHandler;
1865
1866	ui.installMessageHandler = function(callback)
1867	{
1868		origInstallMessageHandler.call(this, function()
1869		{
1870			callback.apply(this, arguments);
1871
1872			var file = ui.getCurrentFile();
1873
1874			file.loadDescriptor(function(desc)
1875			{
1876				file.desc = desc;
1877				ui.fileLoaded(file, true);
1878
1879				if (file.desc)
1880				{
1881					var descChangedNeeded = false;
1882
1883					if (file.desc.title && file.desc.title != macroData.diagramDisplayName)
1884					{
1885						macroData.diagramDisplayName = file.desc.title;
1886						descChangedNeeded = true;
1887					}
1888
1889					if (file.desc.viewerSettings != null)
1890					{
1891						descChangedNeeded = true;
1892					}
1893
1894					if (descChangedNeeded)
1895					{
1896						descriptorChangedListener();
1897					}
1898
1899					//RT Cursors
1900					if (urlParams['rtCursors'] == '1' && p2pCollab != null)
1901					{
1902						p2pCollab.joinFile(file.getChannelId());
1903						file.p2pCollab = p2pCollab;
1904					}
1905				}
1906			});
1907
1908			file.addListener('descriptorChanged', descriptorChangedListener);
1909		});
1910	}
1911
1912	ui.editor.setModified = function()
1913	{
1914		//Cancel set modified of the editor and use the file's one
1915	};
1916
1917	//P2P RT
1918	if (urlParams['rtCursors'] == '1')
1919	{
1920		p2pCollab = new P2PCollab(ui);
1921	}
1922});
1923