Änderungen von Dokument DiagramViewSheet

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,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}}
Icon 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
Icon 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  });
Icon 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]));
Icon 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