Änderungen von Dokument DiagramViewSheet
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,23 +1,41 @@ 1 +{{include reference="Diagram.CommonCode"/}} 2 + 1 1 {{velocity}} 2 -#if ($doc.getObject('Diagram.DiagramClass')) 3 - #if ($xcontext.action == 'get' && $request.data == 'svg') 4 - #set ($svg = $doc.getValue('svg')) 5 - #if ("$!svg" == '') 6 - #set ($svg = '<svg/>') 7 - #end 8 - #set ($discard = $response.setContentType('image/svg+xml')) 9 - #set ($discard = $response.setContentLength($svg.length())) 10 - #set ($discard = $response.writer.write($svg)) 11 - #elseif ($xcontext.action == 'export') 12 - {{html clean="false"}} 13 - <img src="$doc.getURL('get', "data=svg&v=$!doc.version")" alt="$escapetool.xml($doc.plainTitle)" /> 14 - {{/html}} 15 - #else 16 - #set ($discard = $xwiki.ssx.use('Diagram.DiagramViewSheet')) 17 - #set ($discard = $xwiki.jsx.use('Diagram.DiagramViewSheet')) 18 - {{html clean="false"}} 19 - <div class="diagram loading" data-model="$escapetool.xml($tdoc.content)"></div> 20 - {{/html}} 4 +#set ($diagramObj = $doc.getObject('Diagram.DiagramClass')) 5 +#if ($diagramObj || "$!request.source" != '') 6 + #set ($discard = $xwiki.ssx.use('Diagram.DiagramSheet')) 7 + #set ($discard = $xwiki.jsx.use('Diagram.DiagramViewSheet')) 8 + #set ($toolbar = 'zoom layers pages lightbox') 9 + #if ("$!request.source" == '' && $services.security.authorization.hasAccess('edit')) 10 + #set ($toolbar = "edit $toolbar") 21 21 #end 12 + {{html clean="false"}} 13 + ## Check if the query contains the parameter for getting the diagram from URL. 14 + #set ($displayDiv = "") 15 + #set ($fileName = 'diagram.svg') 16 + #if ($xcontext.action == 'export') 17 + #set ($displayDiv = 'hidden') 18 + #showDiagramSVGInHTML($doc, $fileName) 19 + #end 20 + <div class="diagram $displayDiv" 21 + data-diagram-config="$escapetool.xml($jsontool.serialize($diagramConfig))" 22 + data-toolbar="$escapetool.xml($toolbar)" 23 + #if ("$!request.source" == '') 24 + data-model="$escapetool.xml($tdoc.content)" 25 + data-reference="$escapetool.xml($services.model.serialize($tdoc.documentReference, 'default'))" 26 + data-title="$escapetool.xml($tdoc.plainTitle)" 27 + #end 28 + > 29 + ## Show a preview of the diagram until the diagram viewer is loaded. This is also useful for export and WYSIWYG edit 30 + ## mode where the JavaScript code is not executed and thus the diagram viewer is never loaded. 31 + #set ($pngFileName = 'diagram.png') 32 + #if ($xcontext.action == 'export' && $diagramObj.getValue('exportUsingSVG') == 0 33 + && $doc.getAttachment($pngFileName)) 34 + #set ($fileName = $pngFileName) 35 + #end 36 + #set ($diagramURL = $doc.getAttachmentURL($fileName, 'download', "v=$!doc.version")) 37 + <img src="$diagramURL" alt="$escapetool.xml($doc.plainTitle)" /> 38 + </div> 39 + {{/html}} 22 22 #end 23 23 {{/velocity}}
- XWiki.JavaScriptExtension[0]
-
- Pufferstrategie
-
... ... @@ -1,1 +1,0 @@ 1 -long - Code
-
... ... @@ -1,32 +1,0 @@ 1 -// mxGraph Client Configuration 2 -var mxBasePath = "$services.webjars.url('org.xwiki.contrib:mxgraph-client', '')"; 3 -var mxLoadResources = false; 4 - 5 -var mxGraphViewerBasePath = "$services.webjars.url('org.xwiki.contrib:mxgraph-editor', '')"; 6 - 7 -// Diagram Viewer Configuration 8 -var diagramViewerBasePath = "$services.webjars.url('org.xwiki.contrib:draw.io', '')"; 9 -var STENCIL_PATH = diagramViewerBasePath + 'stencils'; 10 -var IMAGE_PATH = diagramViewerBasePath + 'images'; 11 -var STYLE_PATH = CSS_PATH = diagramViewerBasePath + 'styles'; 12 -var SHAPES_PATH = diagramViewerBasePath + 'shapes'; 13 -var GRAPH_IMAGE_PATH = diagramViewerBasePath + 'img'; 14 - 15 -var urlParams = {}; 16 - 17 -require.config({ 18 - paths: { 19 - 'mxgraph-client': mxBasePath + 'mxClient.min', 20 - 'sanitizer': mxGraphViewerBasePath + 'sanitizer/sanitizer.min', 21 - 'mxgraph-viewer': mxGraphViewerBasePath + 'mxGraphViewer.min', 22 - 'draw.io.viewer': diagramViewerBasePath + 'js/draw.io.viewer.min' 23 - }, 24 - shim: { 25 - 'mxgraph-viewer': { 26 - deps: ['mxgraph-client', 'sanitizer'] 27 - }, 28 - 'draw.io.viewer': { 29 - deps: ['mxgraph-viewer'] 30 - } 31 - } 32 -}); - Name
-
... ... @@ -1,1 +1,0 @@ 1 -Configuration - Inhalt parsen
-
... ... @@ -1,1 +1,0 @@ 1 -Ja - Benutze diese Erweiterung
-
... ... @@ -1,1 +1,0 @@ 1 -onDemand
- XWiki.JavaScriptExtension[1]
-
- Code
-
... ... @@ -1,4 +1,21 @@ 1 -define('diagramViewer', ['jquery', 'draw.io.viewer'], function($) { 1 +define('xwiki-diagram-messages', { 2 + prefix: 'diagram.macro.', 3 + keys: [ 4 + 'loadFailed.externalDiagramUrl', 5 + 'loadFailed.externalDiagramUrl.guestUser', 6 + 'loadFailed' 7 + ] 8 +}); 9 + 10 +define('diagram-viewer', [ 11 + 'jquery', 12 + 'xwiki-l10n!xwiki-diagram-messages', 13 + 'diagram-utils', 14 + 'diagram-link-handler', 15 + 'xwiki-meta', 16 + 'diagram-graph-xml-filter', 17 + 'draw.io.viewer' 18 +], function($, l10n, diagramUtils, diagramLinkHandler, xm) { 2 2 var getOrigin = function() { 3 3 var origin = window.location.origin; 4 4 if (!origin) { ... ... @@ -15,27 +15,237 @@ 15 15 // This is required for the clipart images. 16 16 GraphViewer.prototype.imageBaseUrl = getOrigin(); 17 17 18 - $.fn.viewDiagram = function(options) { 19 - options = $.extend({ 20 - lightbox: false 21 - }, options); 35 + // Fix the diagram printing from the lightbox. mxPrintPreview#addGraphFragment optimizes the diagram rendering when 36 + // previewing the print by rendering only the shapes that intersect the clip associated with the print page. The 37 + // computations are wrong in view mode (either when computing the bounding rect of the shapes or the clip of the print 38 + // page) and it's not easy to spot the problem unfortunately. The consequence is that the print is empty. Disabling 39 + // the clipping fixes the issue apparently, but we loose the optimization which may cause problems for large diagrams. 40 + // Note that starting with version 3.9.9 of mxGraph the mxPrintPreview#addGraphFragment function doesn't skip the 41 + // shapes that don't interset the clip anymore because the return line has been commented out. 42 + // See https://github.com/jgraph/mxgraph/commit/d5c1345ec946b9d55a9c2a8c1c5df0f154561edf 43 + mxPrintPreview.prototype.clipping = false; 44 + 45 + // Overwrite in order to fix: Links with heterogeneous inner styles are not entirely clickable #107 46 + var graphLabelLinkClicked = Graph.prototype.labelLinkClicked; 47 + Graph.prototype.labelLinkClicked = function() { 48 + var newArguments = arguments; 49 + var link = arguments[1]; 50 + var href = link.getAttribute('href'); 51 + var originalLinkPolicy = this.linkPolicy; 52 + if (diagramLinkHandler.isXWikiCustomLink(href)) { 53 + newArguments = Array.prototype.slice.apply(arguments); 54 + newArguments[1] = $('<a/>').attr('href', diagramLinkHandler.getURLFromCustomLink(href))[0]; 55 + // Behave as if the link is clicked directly. 56 + this.linkPolicy = 'blank'; 57 + } 58 + try { 59 + graphLabelLinkClicked.apply(this, newArguments); 60 + } catch (e) { 61 + // Restore the link policy. 62 + this.linkPolicy = originalLinkPolicy; 63 + } 64 + }; 65 + // Overwrite in order to fix the same issue but when clicking the links from the light box. 66 + var graphAddClickHandler = Graph.prototype.addClickHandler; 67 + Graph.prototype.addClickHandler = function() { 68 + var originalBeforeClick = arguments[1]; 69 + var newArguments = Array.prototype.slice.apply(arguments); 70 + if (typeof originalBeforeClick === 'function') { 71 + var newBeforeClick = function(event, href) { 72 + if (href == null) { 73 + href = $(mxEvent.getSource(event)).closest('a').attr('href'); 74 + } 75 + originalBeforeClick.call(this, event, href); 76 + }; 77 + newArguments[1] = newBeforeClick; 78 + } 79 + graphAddClickHandler.apply(this, newArguments); 80 + }; 81 + 82 + var getDiagramViewerConfig = function(diagramContainer) { 83 + var config = { 84 + title: diagramContainer.data('title'), 85 + lightbox: false, 86 + toolbar: 'zoom layers pages lightbox', 87 + 'toolbar-buttons': { 88 + edit: { 89 + title: mxResources.get('edit'), 90 + image: Editor.editImage 91 + } 92 + } 93 + }; 94 + if (diagramContainer.data('reference')) { 95 + config.toolbar = 'edit ' + config.toolbar; 96 + var diagramReference = XWiki.Model.resolve(diagramContainer.data('reference'), XWiki.EntityType.DOCUMENT); 97 + config.reference = diagramReference; 98 + var diagramEditURL = new XWiki.Document(diagramReference).getURL('edit'); 99 + config['toolbar-buttons'].edit.handler = function() { 100 + window.location.href = diagramEditURL; 101 + }; 102 + } 103 + var toolbar = diagramContainer.data('toolbar'); 104 + if (toolbar === 'false') { 105 + delete config.toolbar; 106 + } else if (typeof toolbar === 'string') { 107 + config.toolbar = toolbar; 108 + } 109 + return config; 110 + }; 111 + 112 + const observer = new MutationObserver((mutations) => { 113 + mutations.forEach((mutation) => { 114 + mutation.addedNodes.forEach((node) => { 115 + if ($(node).hasClass('xwiki-diagram-drawio-toolbar')) { 116 + node.style.width = Math.min($('#xwikicontent').width(), parseInt(node.style.width)) + 'px'; 117 + } 118 + }); 119 + if (mutation.type === 'attributes') { 120 + if ($(mutation.target).hasClass('xwiki-diagram-drawio-toolbar')) { 121 + mutation.target.style.width = 122 + Math.min($('#xwikicontent').width(), parseInt(mutation.target.style.width)) + 'px'; 123 + } 124 + } 125 + }); 126 + }); 127 + 128 + // Enforce the maximum width of the diagram toolbar to prevent overflows. 129 + observer.observe(document.body, { 130 + childList: true, 131 + subtree: true, 132 + attributes: true, 133 + attributeFilter: ['style'] 134 + }); 135 + 136 + // Fix position of a dialog at the top of the window. 137 + Dialog.prototype.fixDialogPosition = function() { 138 + this.container.style.top = '0px'; 139 + this.container.style.position = 'fixed'; 140 + }; 141 + 142 + // If a diagram is inserted in a page with a lot of content before it, then PrintDialog won't be visible, since the 143 + // top of the dialog is computed to consider a page that only holds the diagram. 144 + var originalShowDialog = EditorUi.prototype.showDialog; 145 + EditorUi.prototype.showDialog = function(elt, w, h, modal, closable, onClose, noScroll, transparent, onResize, 146 + ignoreBgClick) { 147 + originalShowDialog.apply(this, arguments); 148 + var dialog = this.dialog; 149 + if ($(dialog.container).find("input[name='printZoom']")) { 150 + dialog.fixDialogPosition(); 151 + var originalResizeListener = dialog.resizeListener; 152 + dialog.resizeListener = mxUtils.bind(dialog, function() { 153 + originalResizeListener.apply(this, arguments); 154 + this.fixDialogPosition(); 155 + }); 156 + // Since the listener is already added at construction step, we need to remove it and reattach the new function. 157 + mxEvent.removeListener(window, 'resize', originalResizeListener); 158 + mxEvent.addListener(window, 'resize', dialog.resizeListener); 159 + } 160 + }; 161 + 162 + var getProxyUrl = function(url) { 163 + const queryParams = $.param({ 164 + 'url': url, 165 + 'xpage': 'plain', 166 + 'outputSyntax': 'plain', 167 + 'importDiagram': 'true', 168 + }); 169 + let ProxyUrl = new URL(window.PROXY_URL + '?' + queryParams, window.location); 170 + return ProxyUrl; 171 + }; 172 + 173 + var getDiagramXML = function(element) { 174 + let fromStorage = false; 175 + let diagramXML; 176 + let externalDiagramUrl = element.getAttribute('data-external-diagram-url'); 177 + if (externalDiagramUrl) { 178 + if (!xm.userReference) { 179 + // Protect against spamming the proxy. 180 + throw "Diagram preview from URL is not available for unregistered users"; 181 + } 182 + // If the diagram should be fetched from an external url, use a cache to reduce the amount of requests. For the 183 + // inplace WYSIWYG editor it's especially helpful, where there are 5-10 requests for the same source. 184 + if (!window.externalDiagramUrlCache) { 185 + window.externalDiagramUrlCache = new Map(); 186 + } 187 + diagramXML = window.externalDiagramUrlCache.get(externalDiagramUrl); 188 + if (!diagramXML) { 189 + // Use a proxy to get around CORS. 190 + diagramXML = mxUtils.load(getProxyUrl(externalDiagramUrl)).getXml().documentElement; 191 + window.externalDiagramUrlCache.set(externalDiagramUrl, diagramXML); 192 + } 193 + // Add the XML so drawio can access it. 194 + element.setAttribute('data-model', diagramXML.outerHTML); 195 + } else { 196 + diagramXML = diagramUtils.getDiagramXMLFromURL(window.location.href); 197 + if (!diagramXML) { 198 + diagramXML = $(element).data('model') || '<mxGraphModel/>'; 199 + // fromStorage is used to signal when to transform links from XWiki references to URLs. 200 + fromStorage = true; 201 + } 202 + diagramXML = mxUtils.parseXml(diagramXML).documentElement; 203 + } 204 + return [fromStorage, diagramXML]; 205 + }; 206 + 207 + var renderDiagram = function(element, diagramXML, fromStorage, configOverride) { 208 + element.innerHTML = ''; 209 + var diagramRootNode = diagramXML; 210 + if (!diagramRootNode || (diagramRootNode.localName != 'mxfile' && diagramRootNode.localName != 'mxGraphModel')) { 211 + throw "Invalid diagram XML."; 212 + } 213 + diagramRootNode.fromStorage = fromStorage; 214 + var config = $.extend(getDiagramViewerConfig($(element)), configOverride); 215 + var graphViewer = new GraphViewer(element, diagramRootNode, config); 216 + // Make the diagram title from the tool bar a link to the diagram page. 217 + if (config.title && config.reference) { 218 + var diagramViewURL = new XWiki.Document(config.reference).getURL(); 219 + var diagramLink = $('<a/>').attr('href', diagramViewURL).text(config.title); 220 + $(graphViewer.toolbar).children().last().empty().append(diagramLink); 221 + } 222 + // Identify the toolbar by class. 223 + $(graphViewer.toolbar).addClass('xwiki-diagram-drawio-toolbar'); 224 + } 225 + 226 + function getErrorKey(externalDiagramUrl, hasUser) { 227 + if (!externalDiagramUrl) return 'loadFailed'; 228 + return hasUser 229 + ? 'loadFailed.externalDiagramUrl' 230 + : 'loadFailed.externalDiagramUrl.guestUser'; 231 + } 232 + 233 + var renderError = function(element, text) { 234 + element.innerHTML = ''; 235 + $(element).html('<div class="box warningmessage"></div>'); 236 + $(element).children().text(text); 237 + }; 238 + 239 + const initializedDiagramMarkerClass = 'diagram-initialized'; 240 + $.fn.viewDiagram = function(configOverride) { 22 22 return this.each(function() { 23 - var xmlDoc = mxUtils.parseXml($(this).data('model')); 24 - new GraphViewer(this, xmlDoc.documentElement, options); 25 - }).removeClass('loading'); 242 + try { 243 + // Guard against re-rendering an existing diagram or one which is in the process of displaying. 244 + if (!this.classList.contains(initializedDiagramMarkerClass)) { 245 + this.classList.add(initializedDiagramMarkerClass); 246 + let [fromStorage, diagramXML] = getDiagramXML(this); 247 + renderDiagram(this, diagramXML, fromStorage, configOverride); 248 + } 249 + } catch (e) { 250 + console.error(e); 251 + const externalDiagramUrl = this.getAttribute('data-external-diagram-url'); 252 + const errorMessage = getErrorKey(externalDiagramUrl, xm.userReference); 253 + renderError(this, l10n.get(errorMessage)); 254 + } 255 + }); 26 26 }; 27 27 28 - var diagramViewerDeferred = $.Deferred(); 29 - // Load the default theme. The theme controls how the shapes are rendered. 30 - mxUtils.get(STYLE_PATH + '/default.xml', function(response) { 31 - // Configures the default viewer theme. 32 - Graph.prototype.defaultThemes = Graph.prototype.defaultThemes || {}; 33 - Graph.prototype.defaultThemes[Graph.prototype.defaultThemeName] = response.getDocumentElement(); 34 - diagramViewerDeferred.resolve(); 35 - }, function() { 36 - // Failed to load the theme. 37 - diagramViewerDeferred.reject(); 258 + $(document).on('xwiki:dom:updated', function() { 259 + // When using the externalDiagramUrl parameter, and in the in-place WYSIWYG editor, reload the diagram on the client side. 260 + $('.diagram').viewDiagram(); 38 38 }); 39 39 40 - return diagramViewerDeferred.promise(); 263 + return diagramUtils.loadTranslationAndTheme().done(function(theme) { 264 + // Configure the default viewer theme. 265 + Graph.prototype.defaultThemes = Graph.prototype.defaultThemes || {}; 266 + Graph.prototype.defaultThemes[Graph.prototype.defaultThemeName] = theme; 267 + }); 41 41 });
- XWiki.JavaScriptExtension[2]
-
- Code
-
... ... @@ -1,5 +1,37 @@ 1 -require(['jquery', 'diagramViewer'], function($, diagramViewerPromise) { 2 - diagramViewerPromise.done(function() { 3 - $('.diagram').viewDiagram(); 1 +/*! 2 +## Make sure that the version loaded with RequireJS is not a cached one. 3 +#set ($version = $services.extension.installed.getInstalledExtension('com.xwiki.diagram:application-diagram', 4 + "wiki:$xcontext.database").version.value) 5 +#set ($params = $escapetool.url({ 6 + 'minify': $!services.debug.minify, 7 + 'appVersion': $version 8 +})) 9 +#[[*/ 10 +// Start JavaScript-only code. 11 +(function(params) { 12 + "use strict"; 13 + 14 +require.config({ 15 + paths: { 16 + 'diagram-setup': new XWiki.Document('DiagramSheet', 'Diagram').getURL('jsx', params) 17 + }, 18 + map: { 19 + 'diagram-utils': { 20 + 'mxgraph-common': 'mxgraph-viewer' 21 + }, 22 + 'diagram-link-handler': { 23 + 'draw.io.common': 'draw.io.viewer' 24 + } 25 + } 26 +}); 27 + 28 +require(['diagram-setup'], function() { 29 + require(['jquery', 'diagram-viewer'], function($, diagramViewerPromise) { 30 + diagramViewerPromise.done(function() { 31 + $('.diagram').viewDiagram(); 32 + }); 4 4 }); 5 5 }); 35 + 36 +// End JavaScript-only code. 37 +}).apply(']]#', $jsontool.serialize([$params]));
- XWiki.StyleSheetExtension[0]
-
- Pufferstrategie
-
... ... @@ -1,1 +1,0 @@ 1 -long - Code
-
... ... @@ -1,23 +1,0 @@ 1 -.diagram { 2 - min-height: 25px; 3 -} 4 - 5 -/** 6 - * Diagram Macro 7 - */ 8 -.geDiagramContainer, 9 -.diagram-container > .thumbnail { 10 - max-width: 100%; 11 -} 12 - 13 -.diagram-container > .thumbnail { 14 - display: inline-block; 15 -} 16 - 17 -.diagram-container > .thumbnail .box { 18 - margin-bottom: 0; 19 -} 20 - 21 -.diagram-container > .thumbnail svg { 22 - max-width: 100%; 23 -} - Content Type
-
... ... @@ -1,1 +1,0 @@ 1 -CSS - Inhalt parsen
-
... ... @@ -1,1 +1,0 @@ 1 -Nein - Benutze diese Erweiterung
-
... ... @@ -1,1 +1,0 @@ 1 -onDemand