Änderungen von Dokument DiagramSheet

Zuletzt geändert von Daniel Herrmann am 2026/02/04 20:25

Von Version 1.1 Icon
bearbeitet von Mike Schneider
am 2025/12/30 10:37
Änderungskommentar: Install extension [org.xwiki.contrib:application-diagram/1.3]
Auf Version Icon 2.1
bearbeitet von Daniel Herrmann
am 2026/02/04 20:25
Änderungskommentar: Install extension [com.xwiki.diagram:application-diagram/1.22.11]

Zusammenfassung

Details

Icon Seiteneigenschaften
Dokument-Autor
... ... @@ -1,1 +1,1 @@
1 -XWiki.mschneide
1 +XWiki.dherrman
Inhalt
... ... @@ -1,5 +1,15 @@
1 1  {{velocity}}
2 -#if ($doc.getObject('Diagram.DiagramClass'))
2 +#set ($diagramObj = $doc.getObject('Diagram.DiagramClass'))
3 +#if ($diagramObj)
4 + #set ($temporaryUploadSupported = ("$!services.temporaryAttachments" != ''))
5 + #set ($diagramConfig = {
6 + "debug": $services.debug.minify.equals(false),
7 + "locale": $xcontext.locale,
8 + "drawIOBasePath": $services.webjars.url('org.xwiki.contrib:draw.io', ''),
9 + "pdfImageExportZoom": $diagramObj.getValue('pdfImageExportZoom'),
10 + "disableExternalServices": $xwiki.getDocument('Diagram.DiagramConfig').getValue('disableExternalServices'),
11 + "isTemporaryUploadSupported": $temporaryUploadSupported
12 + })
3 3   #if ($xcontext.action == 'edit')
4 4   {{include reference="Diagram.DiagramEditSheet" /}}
5 5   #else
Icon XWiki.JavaScriptExtension[1]
Pufferstrategie
... ... @@ -1,0 +1,1 @@
1 +long
Code
... ... @@ -1,0 +1,715 @@
1 +/**
2 + * Generic helper methods.
3 + */
4 +define('xwiki-utils', ['jquery', 'xwiki-meta'], function($, xm) {
5 + var getAttachmentURL = function(attachment, action, queryString) {
6 + var docRef = xm.documentReference;
7 + if (typeof attachment !== 'string') {
8 + docRef = attachment.parent;
9 + attachment = attachment.name;
10 + }
11 + if (typeof queryString === 'object') {
12 + queryString = $.param(queryString);
13 + }
14 + var attachmentURL = new XWiki.Document(docRef).getURL(action || 'download') +
15 + (docRef.name == 'WebHome' ? docRef.name : '') + '/' + attachment +
16 + (queryString ? '?' + queryString : '');
17 + return attachmentURL;
18 + };
19 +
20 + var temporaryUploadAttachment = function (blob, attachment) {
21 + var documentReference = xm.documentReference;
22 + if (typeof attachment !== 'string') {
23 + documentReference = attachment.parent;
24 + attachment = attachment.name;
25 + }
26 + var doc = new XWiki.Document(documentReference);
27 + var formData = new FormData();
28 + formData.append('upload', blob);
29 + const params = {
30 + 'form_token': xm.form_token,
31 + 'sheet': 'CKEditor.FileUploader',
32 + 'outputSyntax': 'plain',
33 + 'filename': attachment
34 + };
35 + var form = $('form#inline');
36 + return $.ajax({
37 + url : doc.getURL('get', new URLSearchParams(params).toString()),
38 + data : formData,
39 + method: 'POST',
40 + processData : false,
41 + contentType : false, // Sets the 'multipart/form-data' automatically
42 + headers: {
43 + 'X-XWiki-Temporary-Attachment-Support': true
44 + }
45 + }).then(function (response) {
46 + form.append($('<input>')
47 + .prop('type', 'hidden')
48 + .prop('name', 'uploadedFiles')
49 + .prop('value', response.fileName))
50 + }, null);
51 + };
52 +
53 + var uploadAttachment = function(blob, attachment) {
54 + var documentReference = xm.documentReference;
55 + if (typeof attachment !== 'string') {
56 + documentReference = attachment.parent;
57 + attachment = attachment.name;
58 + }
59 + var doc = new XWiki.Document(documentReference);
60 + var url = doc.getURL('upload');
61 + var formData = new FormData();
62 + formData.append('filepath', blob);
63 + formData.append('filename', attachment);
64 + formData.append('form_token', xm.form_token);
65 + // The default redirect adds more time for the upload and we don't need to render those pages.
66 + formData.append('xredirect', doc.getURL('get', 'outputSyntax=plain'));
67 + return $.post({
68 + url : url,
69 + data : formData,
70 + processData : false,
71 + contentType : false
72 + });
73 + };
74 +
75 + var deleteAttachment = function(attachmentName) {
76 + var attachmentURL = getAttachmentURL(attachmentName, 'delattachment', {
77 + 'form_token': xm.form_token,
78 + 'xredirect': XWiki.currentDocument.getURL('get', 'outputSyntax=plain')
79 + });
80 + return $.post(attachmentURL);
81 + };
82 +
83 + return {
84 + getAttachmentURL: getAttachmentURL,
85 + uploadAttachment: uploadAttachment,
86 + deleteAttachment: deleteAttachment,
87 + temporaryUploadAttachment: temporaryUploadAttachment
88 + };
89 +});
90 +
91 +/**
92 + * Diagram application configuration received from the server side.
93 + */
94 +define('diagram-config', ['jquery'], function($) {
95 + return $('[data-diagram-config]').data('diagramConfig');
96 +});
97 +
98 +/**
99 + * Diagram application setup.
100 + */
101 +define('diagram-setup', ['diagram-config'], function(diagramConfig) {
102 + window.mxIsElectron = false;
103 + var drawIOBasePath = diagramConfig.drawIOBasePath;
104 + // mxGraph client setup
105 + window.mxBasePath = drawIOBasePath + 'mxgraph';
106 + window.mxImageBasePath = drawIOBasePath + 'mxgraph/images';
107 + window.mxLanguage = diagramConfig.locale;
108 +
109 + // draw.io setup
110 + window.DRAWIO_SERVER_URL = "https://app.diagrams.net/";
111 + window.RESOURCES_PATH = drawIOBasePath + 'resources';
112 + window.RESOURCE_BASE = RESOURCES_PATH + '/dia';
113 + window.STENCIL_PATH = drawIOBasePath + 'stencils';
114 + window.IMAGE_PATH = drawIOBasePath + 'images';
115 + window.STYLE_PATH = window.CSS_PATH = drawIOBasePath + 'styles';
116 + window.SHAPES_PATH = drawIOBasePath + 'shapes';
117 + window.GRAPH_IMAGE_PATH = drawIOBasePath + 'img';
118 + window.TEMPLATE_PATH = drawIOBasePath + 'templates';
119 + // This is used by File > Open Library from > Browser...
120 + window.OPEN_FORM = drawIOBasePath + 'open.html';
121 + window.OPEN_URL = new XWiki.Document('DiagramImporter', 'Diagram').getURL('get', 'outputSyntax=plain');
122 + window.EXPORT_URL = ' ';
123 + window.PROXY_URL = new XWiki.Document('DiagramProxy', 'Diagram').getURL('get');
124 + window.isLocalStorage = true;
125 + // mxClient is not considering the basePath of the stylesheet files, so we load them after.
126 + window.mxLoadStylesheets = false;
127 +
128 + window.urlParams = (function(params) {
129 + var pairs = window.location.search.substr(1).split('&');
130 + pairs.forEach(function(pair) {
131 + var parts = pair.split('=', 2);
132 + if (parts.length === 2) {
133 + params[parts[0]] = decodeURIComponent(parts[1].replace(/\+/g, " "));
134 + }
135 + });
136 + return params;
137 + })({
138 + // Don't show the splash screen.
139 + 'splash': '0',
140 + // Disable the GitHub integration.
141 + 'gh': '0',
142 + // Disable the GitLab integration.
143 + 'gl': '0',
144 + // Explicitly enable the tabbed UI.
145 + 'pages': '1',
146 + // Disable the Dropbox integration.
147 + 'db': '0',
148 + // Disable the Google Drive integration.
149 + 'gapi': '0',
150 + // Disable Google Analytics.
151 + 'analytics': '0',
152 + // Disable the One Drive integration.
153 + 'od': '0',
154 + // Disable the Trello integration.
155 + 'tr': '0',
156 + // Disable realtime collaboration.
157 + 'sync': 'none'
158 + });
159 +
160 + // Disabling the integration with these external services is not enough because the draw.io code has hard-coded
161 + // references.
162 + window.DriveFile = window.DropboxFile = window.GitHubFile = window.OneDriveFile = window.TrelloFile = false;
163 + window.DriveLibrary = window.DropboxLibrary = window.GitHubLibrary = window.OneDriveLibrary = window.TrelloLibrary
164 + = false;
165 +
166 + require.config({
167 + paths: {
168 + 'base64': drawIOBasePath + 'js/deflate/base64.min',
169 + 'jscolor': drawIOBasePath + 'js/jscolor/jscolor.min',
170 + 'pako': drawIOBasePath + 'js/deflate/pako.min',
171 + 'sanitizer': drawIOBasePath + 'js/sanitizer/purify.min',
172 + 'spin': drawIOBasePath + 'js/spin/spin.min',
173 + 'jszip': drawIOBasePath + 'js/jszip/jszip.min',
174 + 'mermaid': drawIOBasePath + 'js/mermaid/mermaid.min',
175 + 'freehand': drawIOBasePath + 'js/freehand/perfect-freehand',
176 + 'rough': drawIOBasePath + 'js/rough/rough.min',
177 + 'mxgraph-init': drawIOBasePath + 'js/draw.io.init.min',
178 + 'mxgraph-client': drawIOBasePath + 'mxgraph/mxClient',
179 + 'mxgraph-editor': drawIOBasePath + 'js/draw.io.grapheditor.min',
180 + 'mxgraph-viewer': drawIOBasePath + 'js/draw.io.graphviewer.min',
181 + 'draw.io': drawIOBasePath + 'js/draw.io.min',
182 + 'draw.io.viewer': drawIOBasePath + 'js/draw.io.viewer.min',
183 + 'resourceSelector': new XWiki.Document('WebHome', 'Diagram.ResourceSelector').getURL('jsx', 'minify=' + !diagramConfig.debug)
184 + },
185 + shim: {
186 + 'mxgraph-client': ['mxgraph-init'],
187 + 'mxgraph-editor': ['noDocWrite!mxgraph-client', 'jscolor', 'sanitizer-global'],
188 + 'mxgraph-viewer': ['noDocWrite!mxgraph-client', 'sanitizer-global'],
189 + 'draw.io': ['mxgraph-editor', 'base64', 'pako-global', 'spin-global', 'jszip-global', 'mermaid-global', 'freehand', 'rough'],
190 + 'draw.io.viewer': ['mxgraph-viewer', 'pako-global', 'spin-global', 'rough']
191 + }
192 + });
193 +
194 + define('pako-global', ['pako'], function(pako) {
195 + // draw.io expects a global variable.
196 + window.pako = pako;
197 + });
198 +
199 + define('spin-global', ['spin'], function(spin) {
200 + // draw.io expects a global variable.
201 + window.Spinner = spin;
202 + });
203 +
204 + define('jszip-global', ['jszip'], function(JSZip) {
205 + // draw.io expects a global variable.
206 + window.JSZip = JSZip;
207 + });
208 +
209 + define('sanitizer-global', ['sanitizer'], function(sanitizer) {
210 + // draw.io expects a global variable.
211 + window.DOMPurify = sanitizer;
212 + });
213 +
214 + define('mermaid-global', ['mermaid'], function(mermaid) {
215 + // draw.io expects a global variable.
216 + window.mermaid = mermaid;
217 + });
218 +
219 + // Disable document.write() while loading modules that call this API in order to prevent warning messages in the
220 + // JavaScript console.
221 + define('noDocWrite', {
222 + load: function (name, req, onload, config) {
223 + var originalDocWrite = document.write;
224 + document.write = function() {};
225 + req([name], function (value) {
226 + document.write = originalDocWrite;
227 + onload(value);
228 + });
229 + }
230 + });
231 +});
232 +
233 +/**
234 + * Handles links to wiki pages (this is used by both the diagram viewer and the diagram editor).
235 + */
236 +define('diagram-link-handler', ['xwiki-utils', 'draw.io.common'], function(xutils) {
237 + // See https://about.draw.io/interactive-diagrams-with-custom-links-and-actions/
238 + // See https://desk.draw.io/support/solutions/articles/16000080137
239 + // See https://jgraph.github.io/drawio-tools/tools/link.html
240 + var xwikiCustomLinkPrefix = 'data:xwiki/reference,';
241 + var isXWikiCustomLink = function(href) {
242 + return typeof href === 'string' && href.substring(0, xwikiCustomLinkPrefix.length) === xwikiCustomLinkPrefix;
243 + };
244 +
245 + var getResourceReferenceFromCustomLink = function(href) {
246 + if (isXWikiCustomLink(href)) {
247 + var resourceReference = href.substring(xwikiCustomLinkPrefix.length);
248 + var typeSeparatorIndex = resourceReference.indexOf(':');
249 + if (typeSeparatorIndex >= 0) {
250 + return {
251 + type: resourceReference.substring(0, typeSeparatorIndex),
252 + reference: resourceReference.substring(typeSeparatorIndex + 1)
253 + }
254 + }
255 + }
256 + if (href) {
257 + return {
258 + type: 'url',
259 + reference: href
260 + };
261 + }
262 + };
263 +
264 + var getCustomLinkFromResourceReference = function(resourceReference) {
265 + if (resourceReference.type === 'url') {
266 + return resourceReference.reference || '';
267 + } else if (resourceReference.type) {
268 + return xwikiCustomLinkPrefix + resourceReference.type + ':' + (resourceReference.reference || '');
269 + } else {
270 + // An empty link usually means that the link should be removed.
271 + return '';
272 + }
273 + };
274 +
275 + var getURLFromResourceReference = function(resourceReference, diagramReference) {
276 + switch (resourceReference.type) {
277 + case 'doc':
278 + let documentReference = XWiki.Model.resolve(resourceReference.reference, XWiki.EntityType.DOCUMENT,
279 + XWiki.currentDocument.documentReference);
280 + return new XWiki.Document(documentReference).getURL();
281 + case 'attach':
282 + // The diagram reference is used for the attachment reference only if is specified. This is needed when the
283 + // diagram is displayed on another page. See https://github.com/xwikisas/application-diagram/issues/121
284 + let attachmentReference = XWiki.Model.resolve(resourceReference.reference, XWiki.EntityType.ATTACHMENT,
285 + diagramReference || XWiki.currentDocument.documentReference);
286 + return xutils.getAttachmentURL(attachmentReference);
287 + default:
288 + return resourceReference.reference;
289 + }
290 + };
291 +
292 + var getURLFromCustomLink = function(href, diagramReference) {
293 + let resourceReference = getResourceReferenceFromCustomLink(href);
294 + return getURLFromResourceReference(resourceReference, diagramReference);
295 + };
296 +
297 + var originalHandleCustomLink = Graph.prototype.handleCustomLink;
298 + Graph.prototype.handleCustomLink = function(href) {
299 + let actualHref = href;
300 + if (isXWikiCustomLink(href)) {
301 + actualHref = 'data:action/json,' + JSON.stringify({'actions': [{'open': getURLFromCustomLink(href)}]});
302 + }
303 + return originalHandleCustomLink.call(this, actualHref);
304 + };
305 +
306 + var originalGetLinkTitle = EditorUi.prototype.getLinkTitle;
307 + EditorUi.prototype.getLinkTitle = function(href) {
308 + if (isXWikiCustomLink(href)) {
309 + let resourceReference = getResourceReferenceFromCustomLink(href);
310 + return resourceReference.type + ':' + resourceReference.reference;
311 + } else {
312 + return originalGetLinkTitle.apply(this, arguments);
313 + }
314 + };
315 +
316 + var getFragmentIdentifierFromURL = function(url) {
317 + if (typeof url === 'string' && url.substring(0, 5) !== 'data:') {
318 + fragmentIdentifierPosition = url.indexOf('#');
319 + if (fragmentIdentifierPosition >= 0) {
320 + var fragmentIdentifier = url.substring(fragmentIdentifierPosition + 1);
321 + try {
322 + return decodeURIComponent(fragmentIdentifier);
323 + } catch (e) {
324 + // Malformed URI sequence. Return the fragment identifier as is.
325 + return fragmentIdentifier;
326 + }
327 + }
328 + }
329 + };
330 +
331 + var getCustomLinkFromURL = function(url) {
332 + if (isXWikiCustomLink(url)) {
333 + return url;
334 + } else {
335 + var fragmentIdentifier = getFragmentIdentifierFromURL(url);
336 + if (isXWikiCustomLink(fragmentIdentifier)) {
337 + return fragmentIdentifier;
338 + }
339 + }
340 + };
341 +
342 + return {
343 + isXWikiCustomLink: isXWikiCustomLink,
344 + getResourceReferenceFromCustomLink: getResourceReferenceFromCustomLink,
345 + getCustomLinkFromResourceReference: getCustomLinkFromResourceReference,
346 + getCustomLinkFromURL: getCustomLinkFromURL,
347 + getURLFromCustomLink: getURLFromCustomLink,
348 + getURLFromResourceReference: getURLFromResourceReference
349 + };
350 +});
351 +
352 +/**
353 + * Filters the Graph XML when:
354 + * <ul>
355 + * <li>
356 + * the XML is <b>loaded</b> from the XWiki database
357 + * <ul>
358 + * <li>convert the draw.io WAR paths to draw.io WebJar paths (e.g. include the draw.io version)</li>
359 + * <li>replace the attachment reference from the image source with the attachment URL</li>
360 + * </ul>
361 + * </li>
362 + * <li>
363 + * the XML is <b>saved</b> to the XWiki database
364 + * <ul>
365 + * <li>convert the draw.io WebJar paths to draw.io WAR paths (e.g. remove the draw.io version)</li>
366 + * <li>internal links should be saved using the entity reference instead of the entity URL</li>
367 + * <li>internal images should be saved using the attachment reference instead of the attachment URL</li>
368 + * </ul>
369 + * </li>
370 + * <li>
371 + * the XML is <b>imported</b> from an external source
372 + * <ul>
373 + * <li>convert the draw.io WAR paths to draw.io WebJar paths (e.g. include the draw.io version)</li>
374 + * <li>replace absolute URLs with internal custom links, if the target entity reference is indicated</li>
375 + * </ul>
376 + * </li>
377 + * <li>
378 + * the XML is <b>exported</b> to an external source
379 + * <ul>
380 + * <li>convert the draw.io WebJar paths to draw.io WAR paths (e.g. remove the draw.io version)</li>
381 + * <li>convert attached images to data URI (but keep some metadata to idicate the attachment reference)</li>
382 + * <li>replace custom internal links with absolute URLs (but keep some metadata to indicate the entity reference)</li>
383 + * </ul>
384 + * </li>
385 + * </ul>
386 + */
387 +define('diagram-graph-xml-filter', ['jquery', 'diagram-config', 'diagram-link-handler'], function($, diagramConfig, diagramLinkHandler) {
388 + var originalGetGraphXml = Editor.prototype.getGraphXml;
389 + Editor.prototype.getGraphXml = function(ignoreSelection, forStorage) {
390 + var node = originalGetGraphXml.apply(this, arguments);
391 + var filterImage = forStorage === true ? onSaveImage : onExportImage;
392 + var filterLink = forStorage === true ? onSaveLink : onExportLink;
393 + var filterBackgroundImage = forStorage === true ? onSaveBackgroundImage : onExportBackgroundImage;
394 + filter(this.graph, node, filterImage, filterLink, filterBackgroundImage);
395 + return node;
396 + };
397 +
398 + var originalSetGraphXml = Editor.prototype.setGraphXml;
399 + Editor.prototype.setGraphXml = function(node) {
400 + var filterImage = node.fromStorage === true ? onLoadImage : onImportImage;
401 + var filterLink = node.fromStorage === true ? onLoadLink : onImportLink;
402 + var filterBackgroundImage = node.forStorage === true ? onLoadBackgroundImage : onImportBackgroundImage;
403 + filter(this.graph, node, filterImage, filterLink, filterBackgroundImage);
404 + originalSetGraphXml.call(this, node);
405 + };
406 +
407 + var originalImportGraphModel = Graph.prototype.importGraphModel;
408 + Graph.prototype.importGraphModel = function(node, dx, dy, crop) {
409 + filter(this, node, onImportImage, onImportLink, onImportBackgroundImage);
410 + return originalImportGraphModel.call(this, node, dx, dy, crop);
411 + };
412 +
413 + var filter = function(graph, node, filterImage, filterLink, filterBackgroundImage) {
414 + findImageNodes(node).each(function() {
415 + filterImage(this, graph);
416 + });
417 + findLinkNodes(node).each(function() {
418 + filterLink(this, graph);
419 + });
420 + filterBackgroundImage(node);
421 + };
422 +
423 + var findImageNodes = function(node) {
424 + return $(node).find('mxCell[style*="image;"], mxCell[style*="shape=image;"]');
425 + };
426 +
427 + var findLinkNodes = function(node) {
428 + return $(node).find('UserObject[link], mxCell[value*="\\<a href"]');
429 + };
430 +
431 + // Convert the draw.io WebJar paths to draw.io WAR paths (e.g. remove the draw.io version from the path).
432 + // Internal images should be saved using the attachment reference instead of the attachment URL.
433 + var drawIOBasePath = diagramConfig.drawIOBasePath;
434 + var onSaveImage = function(node, graph) {
435 + var style = $(node).attr('style') || '';
436 + var styleObject = graph.stylesheet.getCellStyle(style, {});
437 + var oldSource = styleObject.image || '';
438 + var newSource = oldSource;
439 + if (oldSource.substring(0, drawIOBasePath.length) === drawIOBasePath) {
440 + // Convert draw.io WebJar URL to draw.io WAR path.
441 + // See https://github.com/xwikisas/application-diagram/issues/11
442 + newSource = oldSource.substring(drawIOBasePath.length);
443 + } else {
444 + var customLink = diagramLinkHandler.getCustomLinkFromURL(oldSource);
445 + if (customLink) {
446 + // Save the XWiki attachment reference instead of the attachment URL.
447 + // We have to encode the value in order to avoid breaking the style string.
448 + newSource = encodeURIComponent(customLink);
449 + }
450 + }
451 + if (newSource !== oldSource) {
452 + $(node).attr('style', style.replace(oldSource, newSource));
453 + }
454 + };
455 +
456 + // Convert the draw.io WAR paths to draw.io WebJar paths (e.g. include the draw.io version in the path).
457 + // Replace the attachment reference from the image source with the attachment URL.
458 + var onLoadImage = function(node, graph) {
459 + var style = $(node).attr('style') || '';
460 + var styleObject = graph.stylesheet.getCellStyle(style, {});
461 + var oldSource = styleObject.image || '';
462 + var newSource = oldSource;
463 + if (oldSource.substring(0, 4) === 'img/') {
464 + // Convert draw.io WAR path to draw.io WebJar URL.
465 + // See https://github.com/xwikisas/application-diagram/issues/11
466 + newSource = drawIOBasePath + oldSource;
467 + } else {
468 + try {
469 + var decodedOldSource = decodeURIComponent(oldSource);
470 + if (diagramLinkHandler.isXWikiCustomLink(decodedOldSource)) {
471 + // Replace the attachment reference from the image source with the attachment URL, but keep the attachment
472 + // reference in the fragment identifier in order to be able to restore it on save.
473 + var diagramReference = XWiki.Model.resolve($(graph.container).data('reference'), XWiki.EntityType.DOCUMENT);
474 + newSource = diagramLinkHandler.getURLFromCustomLink(decodedOldSource, diagramReference) + '#' + oldSource;
475 + }
476 + } catch (e) {
477 + // Ignore.
478 + }
479 + }
480 + if (newSource !== oldSource) {
481 + $(node).attr('style', style.replace(oldSource, newSource));
482 + }
483 + };
484 +
485 + // Convert the draw.io WebJar paths to draw.io WAR paths (e.g. remove the draw.io version).
486 + // Convert attached images to data URI (but keep some metadata to idicate the attachment reference).
487 + var onExportImage = function(node, graph) {
488 + var style = $(node).attr('style') || '';
489 + var styleObject = graph.stylesheet.getCellStyle(style, {});
490 + var oldSource = styleObject.image || '';
491 + var newSource = convertImageLink(oldSource, true);
492 + var customLink = diagramLinkHandler.getCustomLinkFromURL(oldSource);
493 + if (customLink) {
494 + // For image attachments, keep some metadata to indicate the original source.
495 + style = mxUtils.setStyle(style, 'xwikiImage', encodeURIComponent(customLink));
496 + }
497 + if (newSource !== oldSource) {
498 + $(node).attr('style', style.replace(oldSource, newSource));
499 + }
500 + };
501 +
502 + var onImportBackgroundImage = function(node) {
503 + // Do nothing.
504 + };
505 +
506 + var onSaveBackgroundImage = function(node) {
507 + // Do nothing.
508 + };
509 +
510 + var onLoadBackgroundImage = function(node) {
511 + // Do nothing.
512 + };
513 +
514 + var onExportBackgroundImage = function(node) {
515 + if (node.getAttribute('backgroundImage') == undefined) {
516 + return;
517 + }
518 + var backgroundImage = JSON.parse(node.getAttribute('backgroundImage'));
519 + var oldSource = backgroundImage.src;
520 + var newSource = convertImageLink(oldSource);
521 + var customLink = diagramLinkHandler.getCustomLinkFromURL(oldSource);
522 + if (customLink) {
523 + // For image attachments, keep some metadata to indicate the original source.
524 + backgroundImage.xwikiImage = encodeURIComponent(customLink);
525 + }
526 + if (newSource !== oldSource) {
527 + backgroundImage.src = newSource;
528 + node.setAttribute('backgroundImage', JSON.stringify(backgroundImage));
529 + }
530 + };
531 +
532 + var convertImageLink = function(oldSource, removeBase) {
533 + var newSource = oldSource;
534 + if (oldSource.substring(0, drawIOBasePath.length) === drawIOBasePath) {
535 + // Convert the draw.io WebJar paths to draw.io WAR paths (e.g. remove the draw.io version).
536 + newSource = oldSource.substring(drawIOBasePath.length);
537 + } else if (oldSource.substring(0, 5) !== 'data:') {
538 + // Convert other images to data URI.
539 + newSource = maybeConvertToDataURI(oldSource, removeBase);
540 + }
541 + return newSource;
542 + };
543 +
544 + var maybeConvertToDataURI = function(url, removeBase) {
545 + var image = getCachedImageByURL(url);
546 + if (image) {
547 + var canvas = document.createElement('canvas');
548 + var context2D = canvas.getContext('2d');
549 + canvas.height = image.naturalHeight;
550 + canvas.width = image.naturalWidth;
551 + context2D.drawImage(image, 0, 0);
552 + try {
553 + url = canvas.toDataURL();
554 + var semicolonIndex = url.indexOf(';');
555 + // Remove temporarily 'base64,' when the url is used in a style attribute, since a split is done by ',' afterwards.
556 + if (semicolonIndex > 0 && removeBase ) {
557 + url = url.substring(0, semicolonIndex) + url.substring(url.indexOf(',', semicolonIndex + 1));
558 + }
559 + } catch (e) {
560 + // Ignore.
561 + }
562 + }
563 + return url;
564 + };
565 +
566 + var diagramImages = {};
567 + var collectImages = function() {
568 + $(this.diagramContainer).find('image').each(function() {
569 + var absoluteURL = $(this).attr('xlink:href');
570 + if (diagramImages.hasOwnProperty(absoluteURL)) {
571 + return;
572 + }
573 + var image = diagramImages[absoluteURL] = new Image();
574 + image.crossOrigin = 'use-credentials';
575 + image.src = absoluteURL;
576 + });
577 + };
578 + $(document).on('diagramEditorCreated', function(event, editorUI) {
579 + editorUI.editor.addListener('fileLoaded', $.proxy(collectImages, editorUI));
580 + editorUI.editor.graph.model.addListener(mxEvent.CHANGE, $.proxy(collectImages, editorUI));
581 + });
582 +
583 + var getCachedImageByURL = function(url) {
584 + var absoluteURL = $('<a/>').attr('href', url).prop('href');
585 + var image = diagramImages[absoluteURL];
586 + if (image && image.complete && image.naturalWidth) {
587 + return image;
588 + }
589 + };
590 +
591 + // Convert the draw.io WAR paths to draw.io WebJar paths (e.g. include the draw.io version in the path).
592 + var onImportImage = function(node, graph) {
593 + onLoadImage(node, graph);
594 + };
595 +
596 + var onSaveLink = function(node) {
597 + // Nothing to do here (save the link as is).
598 + };
599 +
600 + var onLoadLink = function(node) {
601 + // Nothing to do here (keep the link as is).
602 + };
603 +
604 + // Replace custom internal links with absolute URLs (but keep some metadata to indicate the entity reference).
605 + // Consider both the cases when the node is UserObject or mxCell.
606 + var onExportLink = function(node) {
607 + var tagName = node.tagName.toLowerCase();
608 + var value = $(node).attr('value');
609 + var href = $('<div></div>').html(value).find('a[href]').attr('href');
610 + var link = tagName == 'userobject' ? $(node).attr('link') : href;
611 + if (diagramLinkHandler.isXWikiCustomLink(link)) {
612 + var url = diagramLinkHandler.getURLFromCustomLink(link);
613 + var absoluteURL = $('<a/>').attr('href', url).prop('href');
614 + updateLinkAttributes(node, value, link, absoluteURL);
615 + }
616 + };
617 +
618 + // Replace absolute URLs with internal custom links, if the target entity reference is indicated.
619 + var onImportLink = function(node) {
620 + var link = $(node).attr('data-link');
621 + if (diagramLinkHandler.isXWikiCustomLink(link)) {
622 + var value = $(node).attr('value');
623 + var href = $('<div></div>').html(value).find('a[href]').attr('href');
624 + updateLinkAttributes(node, value, href, link);
625 + }
626 + };
627 +
628 + var updateLinkAttributes = function(node, value, oldURL, newURL) {
629 + var attributesMap = {'data-link': null};
630 + if (node.tagName.toLowerCase() == 'userobject') {
631 + attributesMap.link = newURL;
632 + } else {
633 + attributesMap.value = value.replace(oldURL, newURL);
634 + }
635 + $(node).attr(attributesMap);
636 + };
637 +});
638 +
639 +/**
640 + * Utility functions used in both view and edit modes.
641 + */
642 +define('diagram-utils', ['jquery', 'diagram-link-handler'], function($, diagramLinkHandler) {
643 + //
644 + // Get a XML diagram from hash.
645 + // The hash begins with 'R' & continues with the encoded diagram.
646 + //
647 + var getDiagramXMLFromURL = function(url) {
648 + let hashIndex = url.indexOf('#R');
649 + if (hashIndex > -1) {
650 + // Exclude first 2 letters from hash (#R).
651 + let hash = url.substring(hashIndex+2);
652 + try {
653 + let decodedData = decodeURIComponent(hash);
654 + // Drawio uses a combination of URL encoding and base64 encoding in the hash and this breaks the import.
655 + // Since we decompress the diagram in our code when we save the page we can get rid of the
656 + // graph.decompress method and just return the serialized DOM with the imported diagram.
657 + const parser = new DOMParser();
658 + const xmlDoc = parser.parseFromString(decodedData, "application/xml");
659 + return new XMLSerializer().serializeToString(xmlDoc);
660 + } catch (e) {
661 + console.error(e.stack)
662 + }
663 + }
664 + return null;
665 + };
666 +
667 + //
668 + // Load the translation file and the default theme.
669 + //
670 + var loadTranslationAndTheme = function() {
671 + var deferred = $.Deferred();
672 + mxResources.loadDefaultBundle = false;
673 + var bundle = mxResources.getDefaultBundle(RESOURCE_BASE, mxLanguage) ||
674 + mxResources.getSpecialBundle(RESOURCE_BASE, mxLanguage);
675 + // The theme is important because it controls how the shapes are rendered.
676 + mxUtils.getAll([bundle, STYLE_PATH + '/default.xml'], function(response) {
677 + // Adds bundle text to resources.
678 + mxResources.parse(response[0].getText());
679 + deferred.resolve(response[1].getDocumentElement());
680 + }, function() {
681 + // Failed to load the translation file or the theme.
682 + deferred.reject();
683 + });
684 + return deferred.promise();
685 + };
686 +
687 + //
688 + // Fix XWiki custom links in print preview.
689 + //
690 + var originalPrintPreviewAddGraphFragment = mxPrintPreview.prototype.addGraphFragment;
691 + mxPrintPreview.prototype.addGraphFragment = function(dx, dy, scale, pageNumber, div, clip) {
692 + var result = originalPrintPreviewAddGraphFragment.apply(this, arguments);
693 + // Convert XWiki custom links to absolute URLs.
694 + $(div).find('a').each(function() {
695 + // We have to update both SVG links and HTML links (within foreign objects).
696 + ['xlink:href', 'href'].forEach(function(name) {
697 + var value = $(this).attr(name);
698 + if (diagramLinkHandler.isXWikiCustomLink(value)) {
699 + var url = diagramLinkHandler.getURLFromCustomLink(value);
700 + var absoluteURL = $('<a/>').attr('href', url).prop('href');
701 + $(this).attr(name, absoluteURL);
702 + }
703 + }, this);
704 + });
705 + return result;
706 + };
707 +
708 + // Add the required stylesheets, since in mxClient the basePath of the stylesheets is not considered.
709 + mxClient.link('stylesheet', mxClient.basePath + '/css/common.css');
710 +
711 + return {
712 + getDiagramXMLFromURL: getDiagramXMLFromURL,
713 + loadTranslationAndTheme: loadTranslationAndTheme
714 + };
715 +});
Name
... ... @@ -1,0 +1,1 @@
1 +Code used in both view and edit modes
Inhalt parsen
... ... @@ -1,0 +1,1 @@
1 +Nein
Benutze diese Erweiterung
... ... @@ -1,0 +1,1 @@
1 +onDemand
Icon XWiki.StyleSheetExtension[0]
Pufferstrategie
... ... @@ -1,0 +1,1 @@
1 +long
Code
... ... @@ -1,0 +1,182 @@
1 +/* The diagram styles should be loaded after the skin and before our overwrites. */
2 +@import url("$services.webjars.url('org.xwiki.contrib:draw.io', 'styles/grapheditor.css')");
3 +
4 +/**
5 + * Diagram Editor and Dialogs
6 + */
7 +.diagram-editor {
8 + height: 600px;
9 + min-height: 20px;
10 + position: relative;
11 +}
12 +
13 +.diagram-editor input[type="checkbox"], .diagram-editor input[type="radio"],
14 +.mxPopupMenu input[type="checkbox"], .mxPopupMenu input[type="radio"],
15 +.mxWindow input[type="checkbox"], .mxWindow input[type="radio"],
16 +.geDialog input[type="checkbox"], .geDialog input[type="radio"] {
17 + vertical-align: text-bottom;
18 +}
19 +
20 +.fullScreenWrapper .buttons > .buttonwrapper:first-child {
21 + /* Hide the "Exit Full Screen" button. We have a tool bar entry for this. */
22 + display: none !important;
23 +}
24 +
25 +/**
26 + * Overwrite XWiki skin styles
27 + */
28 +.diagram-editor *,
29 +.mxPopupMenu *,
30 +.mxWindow *,
31 +.geDialog,
32 +.geDialog * {
33 + box-sizing: content-box;
34 +}
35 +
36 +.mxPopupMenu,
37 +.mxWindow,
38 +.geDialog {
39 + /* We need the same font size as on draw.io because the dialog height is hard-coded. */
40 + font-size: 10pt;
41 +}
42 +
43 +.diagram-editor button, .diagram-editor select,
44 +.mxPopupMenu button, .mxPopupMenu select,
45 +.mxWindow button, .mxWindow select,
46 +.geDialog button, .geDialog select {
47 + box-sizing: border-box;
48 +}
49 +
50 +.diagram-editor input[type="text"],
51 +.mxPopupMenu input[type="text"],
52 +.mxWindow input[type="text"],
53 +.geDialog input[type="text"] {
54 + font-size: inherit;
55 + height: auto;
56 + padding: 1px;
57 +}
58 +
59 +.diagram-editor img,
60 +.mxPopupMenu img,
61 +.mxWindow img,
62 +.geDialog img {
63 + vertical-align: baseline;
64 +}
65 +
66 +.diagram-editor hr,
67 +.mxPopupMenu hr,
68 +.mxWindow hr,
69 +.geDialog hr {
70 + margin: 0;
71 +}
72 +
73 +.mxPopupMenu table,
74 +.mxWindow table,
75 +.geDialog table {
76 + margin-bottom: 0;
77 + width: auto;
78 +}
79 +
80 +.diagram-editor table > tbody > tr > td,
81 +.mxPopupMenu table > tbody > tr > td,
82 +.mxWindow table > tbody > tr > td,
83 +.geDialog table > tbody > tr > td {
84 + border-top: 0 none;
85 +}
86 +
87 +.geDialog table > tbody > tr > td {
88 + padding: 0;
89 + vertical-align: baseline;
90 +}
91 +
92 +.geDialog h3 {
93 + font-size: 1.17em;
94 + font-weight: bold;
95 +}
96 +
97 +.geDialog input[type='checkbox'] {
98 + vertical-align: baseline;
99 +}
100 +
101 +.geDialog label {
102 + display: inline;
103 +}
104 +
105 +.geDialog textarea {
106 + padding: inherit;
107 +}
108 +/**
109 + * Diagram Viewer
110 + */
111 +.diagram {
112 + /* Leave space for the toolbar. */
113 + margin-top: 30px;
114 + /* Make sure the diagram container always has some width (when not hidden) otherwise draw.io will think it's hidden
115 + and thus will delay the diagram rendering until it becomes visible. */
116 + min-width: 1px;
117 +}
118 +
119 +/**
120 + * Diagram Macro
121 + */
122 +.geDiagramContainer,
123 +.diagram-container > .thumbnail {
124 + max-width: 100%;
125 +}
126 +
127 +.diagram-container > .thumbnail {
128 + display: inline-block;
129 +}
130 +
131 +.diagram-container > .thumbnail .box {
132 + margin-bottom: 0;
133 +}
134 +
135 +.externalServicesDialog {
136 + text-align: center;
137 + padding: 2%;
138 + overflow: auto;
139 + line-height: 1.3em;
140 +}
141 +
142 +/* TODO when upgrading the parent
143 +Right now, there is no defined LESS value for the background color. If, in the future, a LESS variable is defined in https://github.com/xwiki/xwiki-platform/blob/master/xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-resources/src/main/resources/flamingo/less/variablesInit.vm#L22-L27, we should swap to that. */
144 +.geEditor button.btn-primary:hover, .geEditor #tmDrawerActivator:hover {
145 + background-color: #2F6EAD;
146 +}
147 +
148 +/* This is not the ideal solution, but it's the only one that worked. I also tried using revert, revert-layer, and unset, but nothing else worked except this. */
149 +.geEditor button.close {
150 + font-size: x-large;
151 +}
152 +
153 +/* Solves the weird placement of buttons inside the More Shapes modal */
154 +.geEditor .geDialogFooter button {
155 + line-height: revert;
156 +}
157 +
158 +/* Draw.io added a gray border to all the panels, and this rule reverts the color back to transparent. */
159 +.geEditor .panel {
160 + border-color: transparent;
161 +}
162 +
163 +/* Disabled the button that changed the light/dark mode because I couldn't position it to make it look good. You can still modify the appearance in the Extras -> Appearance tab. */
164 +.geToolbarButton.geAdaptiveAsset {
165 + display: none;
166 +}
167 +
168 +/* Extend the width of the inputs that are located in the pdf export modal. */
169 +.geDialog tbody input {
170 + /* The original width which is too is directly on the element so we need the !imporant directive to make it work. */
171 + width: 70px !important;
172 +}
173 +
174 +/* Disables the highlighting of the panel with the figures, allowing us to drag and drop elements. */
175 +.geSidebarContainer {
176 + user-select: none;
177 +}
178 +
179 +/* Properly align the radio buttons in the change background popup. */
180 +input[name="geBackgroundImageDialogOption"] {
181 + margin-bottom: 0px !important;
182 +}
Content Type
... ... @@ -1,0 +1,1 @@
1 +CSS
Name
... ... @@ -1,0 +1,1 @@
1 +CSS
Inhalt parsen
... ... @@ -1,0 +1,1 @@
1 +Ja
Benutze diese Erweiterung
... ... @@ -1,0 +1,1 @@
1 +onDemand
Icon XWiki.StyleSheetExtension[1]
Pufferstrategie
... ... @@ -1,0 +1,1 @@
1 +long
Code
... ... @@ -1,0 +1,11 @@
1 +/* Overwrite the graph editor styles that affect the XWiki UI */
2 +body.geEditor {
3 + font-family: @font-family-base;
4 + font-size: @font-size-base;
5 +}
6 +
7 +/* Overwrite the .geEditor button to make all the primary buttons and the drawer button the right color. */
8 +.geEditor button.btn-primary, .geEditor #tmDrawerActivator {
9 + background-color: @btn-primary-bg;
10 +}
11 +
Content Type
... ... @@ -1,0 +1,1 @@
1 +LESS
Name
... ... @@ -1,0 +1,1 @@
1 +LESS
Inhalt parsen
... ... @@ -1,0 +1,1 @@
1 +Nein
Benutze diese Erweiterung
... ... @@ -1,0 +1,1 @@
1 +onDemand