Änderungen von Dokument DiagramSheet
Zuletzt geändert von Daniel Herrmann am 2026/02/04 20:25
Von Version 1.1
bearbeitet von Mike Schneider
am 2025/12/30 10:37
am 2025/12/30 10:37
Änderungskommentar:
Install extension [org.xwiki.contrib:application-diagram/1.3]
Auf Version
2.1
bearbeitet von Daniel Herrmann
am 2026/02/04 20:25
am 2026/02/04 20:25
Änderungskommentar:
Install extension [com.xwiki.diagram:application-diagram/1.22.11]
Zusammenfassung
Details
- Seiteneigenschaften
-
- Dokument-Autor
-
... ... @@ -1,1 +1,1 @@ 1 -XWiki. mschneide1 +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
- 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
- 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
- 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