Découvrez les nouveautés de cette version : Fonctionnalités, améliorations et évolutions vous attendent ! 👉 Cliquez ici pour en savoir plus

Modifications pour le document DiagramEditSheet

Modifié par Admin le 19/03/2025 - 19:24

Depuis la version 6.1
modifié par Admin
sur 25/06/2024 - 07:09
Commentaire de modification : Install extension [com.xwiki.diagram:application-diagram/1.20.5]
À la version 8.1
modifié par Admin
sur 10/02/2025 - 13:05
Commentaire de modification : Install extension [com.xwiki.diagram:application-diagram/1.22.0]

Résumé

Détails

XWiki.JavaScriptExtension[1]
Code
... ... @@ -36,6 +36,34 @@
36 36   isCompressed: function() {
37 37   return false;
38 38   },
39 + // TODO: When upgrading the drawio version, ensure that this method is copied from the drawio code and the
40 + // `resolveReferences` parameter of the getFileData is set to `true` to prevent incorrect formatting of links to
41 + // wiki pages in the diagram content. https://github.com/xwikisas/application-diagram/issues/295
42 + createData: function() {
43 + var actualPages = this.ui.pages;
44 +
45 + if (this.isRealtime()) {
46 + // Uses ownPages for getting file data below
47 + this.ui.pages = this.ownPages;
48 +
49 + // Updates view state in own current page
50 + if (this.ui.currentPage != null) {
51 + var ownPage = this.ui.getPageById(
52 + this.ui.currentPage.getId(),
53 + this.ownPages);
54 +
55 + if (ownPage != null) {
56 + ownPage.viewState = this.ui.editor.graph.getViewState();
57 + ownPage.needsUpdate = true;
58 + }
59 + }
60 + }
61 +
62 + var result = this.ui.getFileData(null, null, null, null, null, null, null, null, this,
63 + !this.isCompressed(), true);
64 + this.ui.pages = actualPages;
65 + return result;
66 + },
39 39   open: function() {
40 40   var graphXML = this.getData() || '<mxGraphModel/>';
41 41   this.ui.setFileData(graphXML);
XWiki.JavaScriptExtension[2]
Code
... ... @@ -1,516 +514,3 @@
1 -define('svg-handler', ['jquery'], function($) {
2 - var lineHeight = 15;
3 - var spaceWidth = 5;
4 - var listBorder = 35;
5 - var addTagSpecificStyle = function(element, s, w, h, htmlConverter, str, alt) {
6 - switch (getTagName(element)){
7 - case 'h1':
8 - alt.setAttribute('font-size', Math.round(s.fontSize + 0.6 * s.fontSize) + 'px');
9 - alt.setAttribute('font-weight', 'bold');
10 - break;
11 - case 'h2':
12 - alt.setAttribute('font-size', Math.round(s.fontSize + 0.3 * s.fontSize) + 'px');
13 - alt.setAttribute('font-weight', 'bold');
14 - break;
15 - case 'h3':
16 - alt.setAttribute('font-size', Math.round(s.fontSize + 0.15 * s.fontSize) + 'px');
17 - alt.setAttribute('font-weight', 'bold');
18 - break;
19 - case 'h4':
20 - alt.setAttribute('font-weight', 'bold');
21 - break;
22 - case 'h5':
23 - alt.setAttribute('font-size', Math.round(s.fontSize - 0.15 * s.fontSize) + 'px');
24 - alt.setAttribute('font-weight', 'bold');
25 - break;
26 - case 'h6':
27 - alt.setAttribute('font-size', Math.round(s.fontSize - 0.3 * s.fontSize) + 'px');
28 - alt.setAttribute('font-weight', 'bold');
29 - break;
30 - case 'b':
31 - alt.setAttribute('font-weight', 'bold');
32 - break;
33 - case 'i':
34 - alt.setAttribute('font-style', 'italic');
35 - break;
36 - case 'li':
37 - // Because of the space that is reserved for the bullets delimiters, the list's text is moved more to the
38 - // right. For simulating the same behavior in svg, the text-anchor is removed for all the elements inside this
39 - // container, meaning also for ul and ol elements.
40 - alt.removeAttribute('text-anchor');
41 -
42 - // Adapt elements for XWiki PDF export since links are not displayed when 'a' tag is placed inside a 'text'
43 - // element.
44 - var isLinkTag = getTagName(alt) === 'a';
45 -
46 - // Have a bullet for each row.
47 - var delimiter = document.createElementNS('http://www.w3.org/2000/svg', isLinkTag ? 'text' : 'tspan');
48 - delimiter.textContent = "•";
49 - delimiter.setAttribute('x', w - 2 * spaceWidth);
50 - delimiter.setAttribute('y', h);
51 -
52 - // Move all to a container.
53 - var container = document.createElementNS('http://www.w3.org/2000/svg', isLinkTag ? 'g' : 'text');
54 - container.setAttribute('x', w);
55 - container.setAttribute('y', h);
56 - container.appendChild(delimiter);
57 -
58 - if (isLinkTag) {
59 - // The text inside alt is distributed to 'tspan' elements and since they are showned only if are placed
60 - // under a 'text' parent, we add it between them and the link.
61 - var text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
62 - // Copy only first child since it refers to the actual text content which, dependending on whether the
63 - // text needed wrapping or not, is a 'tspan' with either just text inside or with multiple children
64 - // representing the wrapped text.
65 - text.appendChild($(alt).children()[0]);
66 - $(alt).empty();
67 - alt.appendChild(text);
68 - }
69 -
70 - // If alt comes from a container, we should preserve the original parent.
71 - var parent = alt.parentElement;
72 - container.appendChild(alt);
73 - if (parent != null) {
74 - parent.appendChild(container);
75 - parent.removeAttribute('text-anchor');
76 - }
77 - return container;
78 - case 'ul':
79 - // See comment from case 'li'.
80 - alt.removeAttribute('text-anchor');
81 - break;
82 - case 'ol':
83 - // See comment from care 'li'.
84 - alt.removeAttribute('text-anchor');
85 - break;
86 - case 'u':
87 - alt.setAttribute('text-decoration', 'underline');
88 - break;
89 - case 'a':
90 - var link = document.createElementNS('http://www.w3.org/2000/svg', 'a');
91 - link.setAttribute('x', w);
92 - link.setAttribute('y', h);
93 - link.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', element.href);
94 - // Add link decorations for the text. Some styles added to a 'g' are not distributed to it's children by FOP,
95 - // so we do it manually.
96 - if (getTagName(alt) == 'g') {
97 - var childNodes = $(alt).children();
98 - childNodes.each(function() {
99 - this.setAttribute('fill', '#0000EE');
100 - this.setAttribute('text-decoration', 'underline');
101 - });
102 - } else {
103 - alt.setAttribute('fill', '#0000EE');
104 - alt.setAttribute('text-decoration', 'underline');
105 - }
106 - alt.removeAttribute('text-anchor');
107 - // If alt comes from a container, we should preserve the original parent.
108 - var parent = alt.parentElement;
109 - link.appendChild(alt);
110 - if (parent != null) {
111 - parent.appendChild(link);
112 - }
113 - return link;
114 - default:
115 - return alt;
116 - }
117 - return alt;
118 - };
119 -
120 - var getStrWidth = function(str, s) {
121 - return $('<span></span>')
122 - .css({display: 'none', whiteSpace: 'nowrap'})
123 - .appendTo($('body'))
124 - .text(str)
125 - .width();
126 - };
127 -
128 - var takenCoordinates;
129 - // Creates a row inside a text element that should be wrapped. The coordinates of the first row are already saved.
130 - var createRow = function(w, h, rowNb) {
131 - var row = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
132 - row.setAttribute('x', w);
133 - row.setAttribute('y', h);
134 - if (rowNb > 0) {
135 - takenCoordinates.push({'w': w, 'h': h});
136 - }
137 - return row;
138 - };
139 -
140 - var wrapText = function(alt, s, w, h, content, contentWidth, maxWidth) {
141 - var words = content.split(/[\s+]/);
142 - var wordWidth, rowWidth = 0, rowNb = 0;
143 - var row = createRow(w, h, rowNb);
144 - alt.appendChild(row);
145 - words.each(function(word) {
146 - word += ' ';
147 - wordWidth = getStrWidth(word, s);
148 - if (rowWidth + wordWidth > maxWidth) {
149 - h += lineHeight;
150 - row = createRow(w, h, ++rowNb);
151 - row.textContent = word;
152 - alt.appendChild(row);
153 - rowWidth = wordWidth;
154 - } else {
155 - row.textContent += word;
156 - rowWidth += wordWidth;
157 - }
158 - });
159 - };
160 -
161 - /*
162 - * Consider containerWidth as the maximal width to do the word wrap when needed. Add the inner html only if the
163 - * element is not a list, since the text should be distributed to children instead of being shown at once. The same
164 - * is applied for when the text is longer then the containerWidth, since it will be distributed to tspan elements.
165 - * Shorten the containerWidth send to text wrapper for 'li' elements since in their case an adittional space is
166 - * added for the bullet delimiter and it shouldn't be included.
167 - */
168 - var addTextContent= function(element, htmlConverter, s, containerWidth, alt, w, h) {
169 - htmlConverter.innerHTML = element.innerText;
170 - var content = htmlConverter.value;
171 - var contentWidth = getStrWidth(content, s);
172 - if (!isListElement(element)) {
173 - if (contentWidth > containerWidth) {
174 - containerWidth = isListItem(element) ? containerWidth - listBorder : containerWidth;
175 - wrapText(alt, s, w, h, content, contentWidth, containerWidth);
176 - } else {
177 - alt.textContent = htmlConverter.value;
178 - }
179 - }
180 - };
181 -
182 - var getTagName = function(element) {
183 - return element && element.tagName && element.tagName.toLowerCase();
184 - };
185 -
186 - var isTextNode = function(node) {
187 - return node && node.nodeType === 3;
188 - };
189 -
190 - var isHeadingElement = function(element) {
191 - return ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].indexOf(getTagName(element)) >= 0;
192 - };
193 -
194 - var isBlockElement = function(element) {
195 - return ['p', 'div'].indexOf(getTagName(element)) >= 0 || isHeadingElement(element);
196 - };
197 -
198 - var isListElement = function(element) {
199 - return ['ul', 'ol'].indexOf(getTagName(element)) >= 0;
200 - };
201 -
202 - var isInlineElement = function(element) {
203 - return ['b', 'i', 'span'].indexOf(getTagName(element)) >= 0;
204 - };
205 -
206 - var isListItem = function(element) {
207 - return getTagName(element) === 'li';
208 - };
209 -
210 - var isNodeWithPartialStyle = function(node, nbOfSiblings) {
211 - return isTextNode(node) || ((isInlineElement(node) || isBlockElement(node)) && nbOfSiblings > 1);
212 - };
213 -
214 - var isParagraphWithPartialStyle = function(node, nbOfSiblings) {
215 - if (isNodeWithPartialStyle(node, nbOfSiblings)) {
216 - return true;
217 - }
218 -
219 - // Consider also indirect children.
220 - var childNodes = $(node).contents();
221 - if ($(node).children().length > 0) {
222 - for (var i = 0; i < childNodes.length; i++) {
223 - var child = childNodes[i];
224 - if (isParagraphWithPartialStyle(child, childNodes.length) == true) {
225 - return true;
226 - }
227 - }
228 - }
229 - return false;
230 - };
231 -
232 - var isStyleElement = function(element) {
233 - if (element == null) { return false; }
234 - return ['b', 'i', 'u', 'a'].indexOf(getTagName(element)) != -1 || isHeadingElement(element);
235 - };
236 -
237 - /*
238 - * Get multiple styles of an element (i.e. b, i, u, a tags)
239 - */
240 - var getStylesFromElement = function(element) {
241 - var styles = [];
242 - // Partially styled elements are not supported, so consider only the styles of the main element.
243 - var parentTextLength = element.parentElement.textContent.length;
244 - if (element.textContent.length == parentTextLength) {
245 - styles.push(element);
246 - }
247 - // Consider also indirect children.
248 - var childElements = element.getElementsByTagName("*");
249 - for (i = 0; i < childElements.length; i++) {
250 - var child = childElements[i];
251 - if (child.textContent.length == parentTextLength) {
252 - styles.push(child);
253 - }
254 - }
255 - return styles;
256 - };
257 -
258 - // For the current block of text take into consideration if those coordinates are already in use.
259 - // This is used when we are manually computing the container of children elements.
260 - var maybeChangeHeight = function(w, h, element) {
261 - // They didn't took into consideration the size of an heading tag.
262 - h = isHeadingElement(element) ? h - lineHeight : h;
263 - var thisCoordinates = {'w': w, 'h': h};
264 - takenCoordinates.each(function(coordinates) {
265 - if (coordinates['w'] == thisCoordinates['w'] && coordinates['h'] == thisCoordinates['h']) {
266 - h += lineHeight;
267 - thisCoordinates['h'] = h;
268 - }
269 - });
270 - takenCoordinates.push(thisCoordinates);
271 - return h;
272 - };
273 -
274 - var getAdjustedInitialCoordinates = function(w, h, fontSize) {
275 - w = Math.round(w / 2);
276 - // For smaller heights the result seems to corespond to the one in the original view, but for the others the font
277 - // size is relevant.
278 - // TODO: investigate the reasons behind this behavior.
279 - if (h < lineHeight) {
280 - h = Math.round(h / 2);
281 - } else {
282 - h = Math.round(h / 2 - fontSize / 2);
283 - }
284 - return {'w': w, 'h': h};
285 - }
286 -
287 - // Create the basis of a svg element. Calculate new coordinates if is needed.
288 - var createSvgElement = function(s, w, h, tag, element, addAnchor, isPartiallyStyledElement) {
289 - var alt = document.createElementNS('http://www.w3.org/2000/svg', tag);
290 - if (element && (isBlockElement(element) || isListItem(element) || isPartiallyStyledElement)) {
291 - h = maybeChangeHeight(w, h, element);
292 - }
293 - alt.setAttribute('x', w);
294 - alt.setAttribute('y', h);
295 - alt.setAttribute('fill', s.fontColor || 'black');
296 - if (addAnchor) {
297 - alt.setAttribute('text-anchor', 'middle');
298 - }
299 - alt.setAttribute('font-size', Math.round(s.fontSize) + 'px');
300 - alt.setAttribute('font-family', s.fontFamily);
301 -
302 - if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
303 - {
304 - alt.setAttribute('font-weight', 'bold');
305 - }
306 -
307 - if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
308 - {
309 - alt.setAttribute('font-style', 'italic');
310 - }
311 -
312 - if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
313 - {
314 - alt.setAttribute('text-decoration', 'underline');
315 - }
316 - return alt;
317 - };
318 -
319 - var convertTextElementToSvg = function(element, s, w, h, htmlConverter, str, containerWidth, isFirstChild) {
320 - if (getTagName(element) == 'br') {
321 - return document.createElementNS('http://www.w3.org/2000/svg', "text");
322 - }
323 - if (isFirstChild) {
324 - takenCoordinates = [];
325 - }
326 - var coordinates = getAdjustedInitialCoordinates(w, h, s.fontSize);
327 - w = coordinates.w;
328 - h = coordinates.h;
329 -
330 - // Use 'tspan' for 'li' elements since they will be move inside a 'text' container along with a row delimiter.
331 - // Update h in case it was manually modified to take into consideration other inserted elements.
332 - var tag = isListItem(element) ? 'tspan' : 'text';
333 - var alt = createSvgElement(s, w, h, tag, element, true);
334 - h = parseInt(alt.getAttribute('y'));
335 -
336 - addTextContent(element, htmlConverter, s, containerWidth, alt, w, h);
337 -
338 - // Convert recursively the children. Have a container that will hold the elements in case there are other children.
339 - // Consider the case when multiple styles are added to an element (e.g. <i><b>txt</b></i>).
340 - // Traverse all children and keep the styles to just one element.
341 - if (element.childElements().length > 0) {
342 - var container = createSvgElement(s, w, h, 'g', null, !isListElement(element));
343 - // TODO: Don't append alt element, unless it has a content.
344 - container.appendChild(alt);
345 - element.childElements().each(function(child) {
346 - if (isStyleElement(child)) {
347 - var styles = getStylesFromElement(child);
348 - styles.each(function(style) {
349 - alt = addTagSpecificStyle(style, s, w, h, htmlConverter, str, alt, true);
350 - });
351 - } else {
352 - var childSvg = convertTextElementToSvg(child, s, w, h, htmlConverter, str, containerWidth, false);
353 - if (childSvg != undefined) {
354 - container.appendChild(childSvg);
355 - }
356 - }
357 - });
358 - }
359 -
360 - // TODO: apply the defined style of the element.
361 - alt = addTagSpecificStyle(element, s, w, h, htmlConverter, str, alt);
362 - if (element.childElements().length > 0) {
363 - return container;
364 - } else {
365 - return alt;
366 - }
367 - };
368 -
369 -
370 - /*
371 - * Converts a partially styled element (e.g. link added to just a word of a phrase) to svg kepping as much as
372 - * possible from the original element.
373 - * When different types of elements are separated vertically (see Example 1), as long as each element is not a
374 - * composite one, the result is mainly as the input. But, for cases when the styled element is inside a phrase (see
375 - * Example 2), the result is a fallback to simple text.
376 - * TODO: In the case of Example 3, when a child of a partially styled paragraph is itself a partially styled
377 - * paragraph with "Text" nodes a fallback must be added. For now, elementsContainer.contents() separates the nodes
378 - * that are connected and there isn't a smooth solution to get which elements were on the same line (maybe consider
379 - * </br> as divider). Even though it looks the same in html, Example 4 works compared to 3 since there are
380 - * no text nodes, but this depends on how the user inserts the text.
381 - *
382 - * Example 1: <div> "Simple text" </br> <b>Bolded text</b> </div>
383 - * Example 2: <div> "Simple text" <b>Bolded text</b> </div>
384 - * Example 3: <div> "Simple text" </br> "Simple text" <b>Bolded text</b> </div>
385 - * Example 4:
386 - * <div>
387 - * "Simple text" </br>
388 - * <span>"Simple text" <b>Bolded text</b></span>
389 - * </div>
390 - */
391 - var convertPartiallyStyledParagraphToSVG = function(childNodes, s, w, h, htmlConverter, str, containerWidth) {
392 - takenCoordinates = [];
393 - var initialWidth = w;
394 - var initialHeight = h;
395 - var coordinates = getAdjustedInitialCoordinates(w, h, s.fontSize);
396 - w = coordinates.w;
397 - h = coordinates.h;
398 -
399 - var container = createSvgElement(s, w, h, /* tag */ 'g', /* element */ null,
400 - /* addAnchor */ !isListElement(childNodes[0]));
401 - // Go to last level of child nodes in case there is a style added to the root element. Keep the styles to add
402 - // them after assembling the element with its children.
403 - var styles = [];
404 - while (childNodes.length == 1 && $(childNodes[0]).children().length > 0) {
405 - var rootNode = childNodes[0];
406 - if (isStyleElement(rootNode)) {
407 - styles.push(rootNode);
408 - }
409 - childNodes = $(rootNode).contents();
410 - }
411 -
412 - // Resolve the basic case where the element is just a phrase with a styled element. Remove this after fixing
413 - // the TODO described in the comment above.
414 - var simpleNodes= childNodes.filter(function() {
415 - return isTextNode(this) || isInlineElement(this);
416 - });
417 -
418 - if(simpleNodes.length == childNodes.length) {
419 - var element = $('<span></span>').html(str)[0];
420 - // We use the unmodified coordinates to prevent adjusting them twice.
421 - var alt = convertTextElementToSvg(element, s, initialWidth, initialHeight, htmlConverter, str, containerWidth,
422 - /* isFirstChild */ true);
423 - return alt;
424 - }
425 -
426 - // Look only on first-level children since for now it is not supported to partially style paragraphs inside a
427 - // partially styled paragraph.
428 - childNodes.each(function(index) {
429 - var element = $(this).clone()[0];
430 - // Convert text content to element for consistency.
431 - if (isTextNode(element)) {
432 - element = $('<span></span>').html(element.textContent)[0];
433 - }
434 -
435 - // Don't do anything for </br> tags or empty elements.
436 - if (getTagName(element) == 'br' || element.innerText == '') {
437 - return;
438 - }
439 -
440 - // Consider that list elements will be moved to a 'text' container and in this case 'tspan' should be used as
441 - // child tag. Specify that it is a partially styled element because in this case it could contain '<br>'
442 - // tags and the children would need to have changed coordinates (heights) when they are created.
443 - var alt = createSvgElement(s, w, h, /* tag */ isListItem(element) ? 'tspan' : 'text', /* element */ element,
444 - /* addAnchor */ true, /* isPartiallyStyledElement */ true);
445 - h = parseInt(alt.getAttribute('y'));
446 -
447 - addTextContent(element, htmlConverter, s, containerWidth, alt, w, h);
448 -
449 - alt = addTagSpecificStyle(element, s, w, h, htmlConverter, str, alt);
450 - container.appendChild(alt);
451 - });
452 - // Consider the case when multiple styles are added to the root element.
453 - styles.each(function(style) {
454 - container = addTagSpecificStyle(style, s, w, h, htmlConverter, str, container, true);
455 - });
456 - return container;
457 -
458 - };
459 -
460 - // Fix the transform attribute of the element that is used when foreignObjects are not supported and the view
461 - // fallbacks to svg. This code is taken from an older draw.io version, since starting with 12.5.0 the transform
462 - // attribute doesn't take into consideration the coordinates, but only the foOffset.
463 - var adjustAlternateTextCoordinates = function(group, x, y , w , h, s, align, valign, overflow, rotation, foOffset) {
464 - x += s.dx;
465 - y += s.dy;
466 - var dx = 0;
467 - var dy = 0;
468 -
469 - if (align == mxConstants.ALIGN_CENTER) {
470 - dx -= w / 2;
471 - } else if (align == mxConstants.ALIGN_RIGHT) {
472 - dx -= w;
473 - }
474 - x += dx;
475 -
476 - if (valign == mxConstants.ALIGN_MIDDLE) {
477 - dy -= h / 2;
478 - } else if (valign == mxConstants.ALIGN_BOTTOM) {
479 - dy -= h;
480 - }
481 -
482 - if (overflow != 'fill' && mxClient.IS_FF && mxClient.IS_WIN) {
483 - dy -= 2;
484 - }
485 - y += dy;
486 -
487 - var tr = (s.scale != 1) ? 'scale(' + s.scale + ')' : '';
488 -
489 - if (s.rotation != 0 && this.rotateHtml) {
490 - tr += 'rotate(' + (s.rotation) + ',' + (w / 2) + ',' + (h / 2) + ')';
491 - var pt = this.rotatePoint((x + w / 2) * s.scale, (y + h / 2) * s.scale, s.rotation, s.rotationCx, s.rotationCy);
492 - x = pt.x - w * s.scale / 2;
493 - y = pt.y - h * s.scale / 2;
494 - } else {
495 - x *= s.scale;
496 - y *= s.scale;
497 - }
498 -
499 - if (rotation != 0) {
500 - tr += 'rotate(' + (rotation) + ',' + (-dx) + ',' + (-dy) + ')';
501 - }
502 -
503 - var transform = 'translate(' + (Math.round(x) + foOffset) + ',' + (Math.round(y) + foOffset) + ')' + tr;
504 - group.setAttribute('transform', transform);
505 - };
506 -
507 - return {
508 - convertTextElementToSvg: convertTextElementToSvg,
509 - isParagraphWithPartialStyle: isParagraphWithPartialStyle,
510 - convertPartiallyStyledParagraphToSVG: convertPartiallyStyledParagraphToSVG,
511 - adjustAlternateTextCoordinates: adjustAlternateTextCoordinates
512 - };
513 -});
514 514  /**
515 515   * Overrides the link dialog in order to support creating links to wiki pages.
516 516   */
... ... @@ -517,10 +517,9 @@
517 517  define('diagram-link-editor', [
518 518   'jquery',
519 519   'diagram-link-handler',
520 - 'svg-handler',
521 521   'draw.io',
522 522   'resourceSelector'
523 -], function($, diagramLinkHandler, svgHandler) {
9 +], function($, diagramLinkHandler) {
524 524   /**
525 525   * Override in order to change the Element type check from a.constructor !== Element, which was failing due to a
526 526   * collision with the PrototypeJS's own implementation of the standard Element.
... ... @@ -556,6 +556,32 @@
556 556   // Do nothing.
557 557   }
558 558  
45 + // Override the Graph.updateSvgLinks method to ensure that the links are preserved in the final SVG.
46 + // We need to directly modify the original method because drawio does not provide a parameter to override the default
47 + // behavior or a smaller method that only performs the replacement.
48 + Graph.prototype.updateSvgLinks = function (node, target, removeCustom) {
49 + var links = node.getElementsByTagName('a');
50 + for (var i = 0; i < links.length; i++) {
51 + if (links[i].getAttribute('target') == null) {
52 + var href = links[i].getAttribute('href');
53 + if (href == null) {
54 + href = links[i].getAttribute('xlink:href');
55 + }
56 +
57 + if (href != null) {
58 + if (target != null && /^https?:\/\//.test(href)) {
59 + // If the href starts with http or https, set the target attribute.
60 + links[i].setAttribute('target', target);
61 + } else if (this.isCustomLink(href)) {
62 + // Handle custom links
63 + let absoluteLink = $('<a/>').attr('href', diagramLinkHandler.getURLFromCustomLink(href)).prop('href');
64 + links[i].setAttribute('href', absoluteLink);
65 + }
66 + }
67 + }
68 + }
69 + };
70 +
559 559   // Overwrite Graph.getSvg in order to replace XWiki custom links with absolute URLs.
560 560   // Also fix the text fallback for viewers with no support for foreignObjects.
561 561   var originalGraphGetSVG = Graph.prototype.getSvg;
... ... @@ -581,66 +581,8 @@
581 581   // Keep only the text content.
582 582   str = $('<div/>').html(str).text() || this.foAltText;
583 583   }
584 - // Update links inside foreignObject to use absolute URLs, since the generated SVG needs to be portable.
585 - $(fo).find('a').each(function() {
586 - var oldLink = $(this).attr('href');
587 - if (diagramLinkHandler.isXWikiCustomLink(oldLink)) {
588 - var newLink = diagramLinkHandler.getURLFromCustomLink(oldLink);
589 - $(this).attr('href', newLink);
590 - }
591 - });
592 -
593 - if (this.foAltText != null) {
594 - var s = this.state;
595 - var htmlConverter = document.createElement('textarea');
596 - var content = document.createElementNS('http://www.w3.org/2000/svg', 'g');
597 - var temporaryContent = document.createElementNS('http://www.w3.org/2000/svg', 'g');
598 - var elementsContainer = $(fo).children().first().children().first();
599 - var childNodes = elementsContainer.contents();
600 - // Consider the container width to be the width of the closest rect parent node. For some shapes, like Actor
601 - // or any non squared shape, there is no rect and we fallback to the initial width of the text node, which is
602 - // less accurate.
603 - var containerWidth = this.state.initialWidth;
604 - var prevNode = $(fo.parentNode).prev()[0];
605 - if (prevNode && prevNode.nodeName == 'rect') {
606 - var containerWidth = prevNode.width.baseVal.valueAsString;
607 - }
608 - try {
609 - if (childNodes.length > 0) {
610 - var isPartiallyStyledElement = false;
611 - var alt;
612 - childNodes.each(function(index) {
613 - // Make a clone of the node, to not alter the original child.
614 - var node = $(this).clone()[0];
615 - // If this paragraph has partially styled nodes (i.e. link added to just a part of the text) create a
616 - // fallback version of it, since not all the cases are supported and the created svg could not be
617 - // compatible with the already created elements. This is done since is too complex to calculate the
618 - // coordinates of the styled node, considering the last element added and that it may be needed to wrap
619 - // the text.
620 - if (svgHandler.isParagraphWithPartialStyle(node, childNodes.length)) {
621 - alt = svgHandler.convertPartiallyStyledParagraphToSVG(childNodes, s, w, h, htmlConverter, str,
622 - containerWidth);
623 - isPartiallyStyledElement = true;
624 - return false;
625 - } else {
626 - alt = svgHandler.convertTextElementToSvg(node, s, w, h, htmlConverter, str, containerWidth, index == 0);
627 - temporaryContent.appendChild(alt);
628 - }
629 - });
630 - content.appendChild(isPartiallyStyledElement ? alt : temporaryContent)
631 - svgHandler.adjustAlternateTextCoordinates(content, x, y, w, h, s, align, valign, overflow, rotation,
632 - this.foOffset);
633 - return content;
634 - } else {
635 - return originalCreateAlternateContent.call(this, fo, x, y, w, h, str, align, valign, wrap, format,
636 - overflow, clip, rotation);
637 - }
638 - } catch (e) {
639 - return originalCreateAlternateContent.apply(this, arguments);
640 - }
641 - } else {
642 - return originalCreateAlternateContent.apply(this, arguments);
643 - }
96 + return originalCreateAlternateContent.call(this, fo, x, y, w, h, str, align, valign, wrap, format, overflow,
97 + clip, rotation);
644 644   };
645 645   return originalDrawState.apply(this, arguments);
646 646   };
... ... @@ -672,326 +672,7 @@
672 672   };
673 673   return converter;
674 674   };
675 -
676 - // Copied from the drawio version 24.5.4 to include a fix for importing a file in an empty diagram. This method should
677 - // be removed once we upgrade to a drawio version >= 24.4.0. Being a big method, the structure was altered just to
678 - // make it shorter.
679 - var importFilesNew = function(files, x, y, maxSize, fn, resultFn, filterFn, barrierFn,
680 - resizeDialog, maxBytes, resampleThreshold, ignoreEmbeddedXml, evt) {
681 - maxSize = (maxSize != null) ? maxSize : this.maxImageSize;
682 - maxBytes = (maxBytes != null) ? maxBytes : this.maxImageBytes;
683 - var crop = x != null && y != null;
684 - var resizeImages = true;
685 - x = (x != null) ? x : 0;
686 - y = (y != null) ? y : 0;
687 - // Checks if large images are imported
688 - var largeImages = false;
689 - if (!mxClient.IS_CHROMEAPP && files != null) {
690 - var thresh = resampleThreshold || this.resampleThreshold;
691 - for (var i = 0; i < files.length; i++) {
692 - if (files[i].type.substring(0, 9) !== 'image/svg' &&
693 - files[i].type.substring(0, 6) === 'image/' &&
694 - files[i].size > thresh) {
695 - largeImages = true;
696 - break;
697 - }
698 - }
699 - }
700 -
701 - var doImportFiles = mxUtils.bind(this, function() {
702 - var graph = this.editor.graph;
703 - var gs = graph.gridSize;
704 -
705 - fn = (fn != null) ? fn : mxUtils.bind(this, function(data, mimeType, x, y, w, h, filename, done, file) {
706 - try {
707 - if (data != null && data.substring(0, 10) == '<mxlibrary') {
708 - this.spinner.stop();
709 - this.loadLibrary(new LocalLibrary(this, data, filename));
710 - this.showSidebar();
711 -
712 - return null;
713 - }
714 - else {
715 - // Drop on empty file ignores drop location
716 - if (this.isCompatibleString(data) && files.length == 1 && evt != null &&
717 - evt.type == 'drop' && this.isBlankFile() && !this.canUndo()) {
718 - crop = false;
719 - x = 0;
720 - y = 0;
721 - }
722 - return this.importFile(data, mimeType, x, y, w, h, filename,
723 - done, file, crop, ignoreEmbeddedXml, evt);
724 - }
725 - }
726 - catch (e) {
727 - this.handleError(e);
728 - return null;
729 - }
730 - });
731 -
732 - resultFn = (resultFn != null) ? resultFn : mxUtils.bind(this, function(cells) {
733 - graph.setSelectionCells(cells);
734 - });
735 -
736 - if (this.spinner.spin(document.body, mxResources.get('loading'))) {
737 - var count = files.length;
738 - var remain = count;
739 - var queue = [];
740 -
741 - // Barrier waits for all files to be loaded asynchronously
742 - var barrier = mxUtils.bind(this, function(index, fnc) {
743 - queue[index] = fnc;
744 - if (--remain == 0) {
745 - this.spinner.stop();
746 - if (barrierFn != null) {
747 - barrierFn(queue);
748 - }
749 - else {
750 - var cells = [];
751 - graph.getModel().beginUpdate();
752 - try {
753 - for (var j = 0; j < queue.length; j++) {
754 - var tmp = queue[j]();
755 - if (tmp != null) {
756 - cells = cells.concat(tmp);
757 - }
758 - }
759 - }
760 - finally {
761 - graph.getModel().endUpdate();
762 - }
763 - }
764 - resultFn(cells);
765 - }
766 - });
767 -
768 - for (var i = 0; i < count; i++) {
769 - (mxUtils.bind(this, function(index) {
770 - var file = files[index];
771 - if (file != null) {
772 - var reader = new FileReader();
773 - reader.onload = mxUtils.bind(this, function(e) {
774 - if (filterFn == null || filterFn(file)) {
775 - try {
776 - if (file.type.substring(0, 6) == 'image/') {
777 - if (file.type.substring(0, 9) == 'image/svg') {
778 - // Checks if SVG contains content attribute
779 - var data = Graph.clipSvgDataUri(e.target.result);
780 - var comma = data.indexOf(',');
781 - var svgText = decodeURIComponent(escape(atob(data.substring(comma + 1))));
782 - var root = mxUtils.parseXml(svgText);
783 - var svgs = root.getElementsByTagName('svg');
784 - if (svgs.length > 0) {
785 - var svgRoot = svgs[0];
786 - var cont = (ignoreEmbeddedXml) ? null : svgRoot.getAttribute('content');
787 - if (cont != null && cont.charAt(0) != '<' && cont.charAt(0) != '%') {
788 - cont = unescape((window.atob) ? atob(cont) : Base64.decode(cont, true));
789 - }
790 - if (cont != null && cont.charAt(0) == '%') {
791 - cont = decodeURIComponent(cont);
792 - }
793 - if (cont != null && (cont.substring(0, 8) === '<mxfile ' ||
794 - cont.substring(0, 14) === '<mxGraphModel ')) {
795 - barrier(index, mxUtils.bind(this, function() {
796 - return fn(cont, 'text/xml', x + index * gs, y + index * gs, 0, 0, file.name);
797 - }));
798 - }
799 - else {
800 - // SVG needs special handling to add viewbox if missing and
801 - // find initial size from SVG attributes (only for IE11)
802 - barrier(index, mxUtils.bind(this, function() {
803 - try {
804 - // Parses SVG and find width and height
805 - if (root != null) {
806 - var svgs = root.getElementsByTagName('svg');
807 - if (svgs.length > 0) {
808 - var svgRoot = svgs[0];
809 - var w = svgRoot.getAttribute('width');
810 - var h = svgRoot.getAttribute('height');
811 - if (w != null && w.charAt(w.length - 1) != '%') {
812 - w = parseFloat(w);
813 - }
814 - else {
815 - w = NaN;
816 - }
817 - if (h != null && h.charAt(h.length - 1) != '%') {
818 - h = parseFloat(h);
819 - }
820 - else {
821 - h = NaN;
822 - }
823 - // Check if viewBox attribute already exists
824 - var vb = svgRoot.getAttribute('viewBox');
825 - if (vb == null || vb.length == 0) {
826 - svgRoot.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
827 - }
828 - // Uses width and height from viewbox for
829 - // missing width and height attributes
830 - else if (isNaN(w) || isNaN(h)) {
831 - var tokens = vb.split(' ');
832 - if (tokens.length > 3) {
833 - w = parseFloat(tokens[2]);
834 - h = parseFloat(tokens[3]);
835 - }
836 - }
837 - data = Editor.createSvgDataUri(mxUtils.getXml(svgRoot));
838 - var s = Math.min(1, Math.min(maxSize / Math.max(1, w)), maxSize / Math.max(1, h));
839 - var cells = fn(data, file.type, x + index * gs, y + index * gs, Math.max(
840 - 1, Math.round(w * s)), Math.max(1, Math.round(h * s)), file.name);
841 - // Hack to fix width and height asynchronously
842 - if (cells != null && (isNaN(w) || isNaN(h))) {
843 - var img = new Image();
844 - img.onload = mxUtils.bind(this, function() {
845 - w = Math.max(1, img.width);
846 - h = Math.max(1, img.height);
847 - cells[0].geometry.width = w;
848 - cells[0].geometry.height = h;
849 - svgRoot.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
850 - data = Editor.createSvgDataUri(mxUtils.getXml(svgRoot));
851 - var semi = data.indexOf(';');
852 - if (semi > 0) {
853 - data = data.substring(0, semi) + data.substring(data.indexOf(',', semi + 1));
854 - }
855 - graph.setCellStyles('image', data, [cells[0]]);
856 - });
857 - img.src = Editor.createSvgDataUri(mxUtils.getXml(svgRoot));
858 - }
859 - return cells;
860 - }
861 - }
862 - }
863 - catch (e) {
864 - // ignores any SVG parsing errors
865 - }
866 - return null;
867 - }));
868 - }
869 - }
870 - else {
871 - barrier(index, mxUtils.bind(this, function() {
872 - return null;
873 - }));
874 - }
875 - }
876 - else {
877 - // Checks if PNG+XML is available to bypass code below
878 - var containsModel = false;
879 - if (file.type == 'image/png') {
880 - var xml = (ignoreEmbeddedXml) ? null : this.extractGraphModelFromPng(e.target.result);
881 - if (xml != null && xml.length > 0) {
882 - var img = new Image();
883 - img.src = e.target.result;
884 - barrier(index, mxUtils.bind(this, function() {
885 - return fn(xml, 'text/xml', x + index * gs, y + index * gs,
886 - img.width, img.height, file.name);
887 - }));
888 - containsModel = true;
889 - }
890 - }
891 - // Additional asynchronous step for finding image size
892 - if (!containsModel) {
893 - // Cannot load local files in Chrome App
894 - if (mxClient.IS_CHROMEAPP) {
895 - this.spinner.stop();
896 - this.showError(mxResources.get('error'), mxResources.get('dragAndDropNotSupported'),
897 - mxResources.get('cancel'), mxUtils.bind(this, function()
898 - {
899 - // Hides the dialog
900 - }), null, mxResources.get('ok'), mxUtils.bind(this, function()
901 - {
902 - // Redirects to import function
903 - this.actions.get('import').funct();
904 - })
905 - );
906 - }
907 - else {
908 - this.loadImage(e.target.result, mxUtils.bind(this, function(img) {
909 - this.resizeImage(img, e.target.result, mxUtils.bind(this, function(data2, w2, h2) {
910 - barrier(index, mxUtils.bind(this, function() {
911 - // Refuses to insert images above a certain size as they kill the app
912 - if (data2 != null && data2.length < maxBytes) {
913 - var s = (!resizeImages || !this.isResampleImageSize(
914 - file.size, resampleThreshold)) ? 1 :
915 - Math.min(1, Math.min(maxSize / w2, maxSize / h2));
916 - return fn(data2, file.type, x + index * gs, y + index * gs,
917 - Math.round(w2 * s), Math.round(h2 * s), file.name);
918 - }
919 - else {
920 - this.handleError({message: mxResources.get('imageTooBig')});
921 - return null;
922 - }
923 - }));
924 - }), resizeImages, maxSize, resampleThreshold, file.size);
925 - }), mxUtils.bind(this, function() {
926 - this.handleError({message: mxResources.get('invalidOrMissingFile')});
927 - }));
928 - }
929 - }
930 - }
931 - }
932 - else {
933 - var data = e.target.result;
934 - fn(data, file.type, x + index * gs, y + index * gs, 240, 160, file.name, function(cells) {
935 - barrier(index, function() {
936 - return cells;
937 - });
938 - }, file);
939 - }
940 - }
941 - catch (e) {
942 - // Ignores file parsing error
943 - barrier(index, mxUtils.bind(this, function() {
944 - return null;
945 - }));
946 - if (window.console != null) {
947 - console.error(e, file);
948 - }
949 - }
950 - }
951 - else {
952 - // Ignores file and decrements counter
953 - barrier(index, mxUtils.bind(this, function()
954 - {
955 - return null;
956 - }));
957 - }
958 - });
959 - // Handles special cases
960 - if (/(\.v(dx|sdx?))($|\?)/i.test(file.name) || /(\.vs(x|sx?))($|\?)/i.test(file.name)) {
961 - fn(null, file.type, x + index * gs, y + index * gs, 240, 160, file.name, function(cells) {
962 - barrier(index, function() {
963 - return cells;
964 - });
965 - }, file);
966 - }
967 - else if (file.type.substring(0, 5) == 'image' || file.type == 'application/pdf') {
968 - reader.readAsDataURL(file);
969 - }
970 - else {
971 - reader.readAsText(file);
972 - }
973 - }
974 - }))(i);
975 - }
976 - }
977 - });
978 - if (largeImages) {
979 - // Workaround for lost files array in async code
980 - var tmp = [];
981 - for (var i = 0; i < files.length; i++) {
982 - tmp.push(files[i]);
983 - }
984 - files = tmp;
985 - this.confirmImageResize(function(doResize) {
986 - resizeImages = doResize;
987 - doImportFiles();
988 - }, resizeDialog);
989 - }
990 - else {
991 - doImportFiles();
992 - }
993 - };
994 -
129 +
995 995   // Override for uploading the image as attachment instead of encode it to Base64.
996 996   var originalImportFiles = EditorUi.prototype.importFiles;
997 997   EditorUi.prototype.importFiles = function(files, x, y, maxSize, fn, resultFn, filterFn, barrierFn, resizeDialog,
... ... @@ -1021,7 +1021,7 @@
1021 1021   }
1022 1022   };
1023 1023   }
1024 - importFilesNew.apply(this, importFilesArgs);
159 + originalImportFiles.apply(this, importFilesArgs);
1025 1025   };
1026 1026  
1027 1027   // Add support for inserting images by specifying the XWiki attachment reference.
... ... @@ -1155,6 +1155,15 @@
1155 1155   };
1156 1156  });
1157 1157  
293 +define('diagramMenuTranslations', {
294 + prefix: 'diagram.editor.menu.',
295 + keys: [
296 + // File menu.
297 + 'print.label',
298 + 'print.title'
299 + ]
300 +});
301 +
1158 1158  /**
1159 1159   * Integrates draw.io diagram editor in XWiki.
1160 1160   */
... ... @@ -1164,11 +1164,12 @@
1164 1164   'diagram-utils',
1165 1165   'diagram-url-io',
1166 1166   'diagram-config',
311 + 'xwiki-l10n!diagramMenuTranslations',
1167 1167   'diagram-graph-xml-filter',
1168 1168   'diagram-link-editor',
1169 1169   'diagram-image-editor',
1170 1170   'diagram-external-services'
1171 -], function($, diagramStore, diagramUtils, diagramUrlIO, diagramConfig) {
316 +], function($, diagramStore, diagramUtils, diagramUrlIO, diagramConfig, l10n) {
1172 1172  
1173 1173   // These variables are used to decide if an image should be uploaded at original resolution or
1174 1174   // should be declined for being too big.
... ... @@ -1200,6 +1200,7 @@
1200 1200  
1201 1201   var fixEditorUI = function(editorUI) {
1202 1202   cleanMenu(editorUI);
348 + renameMenu(editorUI);
1203 1203   fixKeyboardShortcutsAction(editorUI);
1204 1204   fixEditorButtons($(editorUI.container));
1205 1205   removeThemeButton();
... ... @@ -1239,11 +1239,31 @@
1239 1239   var originalAddSubmenu = Menus.prototype.addSubmenu;
1240 1240   Menus.prototype.addSubmenu = function(name, menu, parent, label) {
1241 1241   var subMenu = this.get(name);
1242 - if (subMenu && subMenu.visible !== false) {
1243 - originalAddSubmenu.apply(this, arguments);
388 + if (subMenu && subMenu.isEnabled() !== false) {
389 + return originalAddSubmenu.apply(this, arguments);
1244 1244   }
1245 1245   };
1246 1246  
393 +
394 + /*
395 + * Map with all the menu items that we want to have a title.
396 + */
397 + const titleMap = new Map([
398 + ['print', l10n['print.title']]
399 + ]);
400 +
401 + /*
402 + * Update the title of the menu items.
403 + */
404 + var originalAddMenuItem = Menus.prototype.addMenuItem;
405 + Menus.prototype.addMenuItem = function(menu, key, parent, trigger, sprite, label) {
406 + let item = originalAddMenuItem.apply(this, arguments);
407 + if (item != null && titleMap.has(key)) {
408 + item.title = titleMap.get(key);
409 + }
410 + return item;
411 + };
412 +
1247 1247   // Remove the language picker because the diagram editor is configured to use the XWiki language.
1248 1248   var originalCreateMenubar = Menus.prototype.createMenubar;
1249 1249   Menus.prototype.createMenubar = function(container) {
... ... @@ -1263,6 +1263,24 @@
1263 1263   };
1264 1264  
1265 1265   //
432 + // Rename menu options to fit our needs.
433 + //
434 + var renameMenu = function(editorUI) {
435 + const menuItems = [
436 + // File menu
437 + ['print', l10n['print.label']]
438 + ];
439 +
440 + // Iterate over the array of tuples
441 + menuItems.forEach(function([menuKey, newLabel]) {
442 + var action = editorUI.actions.actions[menuKey];
443 + if (action) {
444 + action.label = newLabel;
445 + }
446 + });
447 + };
448 +
449 + //
1266 1266   // Clean the editor menu by removing the features that are not needed.
1267 1267   //
1268 1268   var cleanMenu = function(editorUI) {
... ... @@ -1275,7 +1275,7 @@
1275 1275   // Help menu
1276 1276   'downloadDesktop', 'useOffline',
1277 1277   // ExportAs
1278 - 'exportHtml'
462 + 'exportHtml', 'exportPdf'
1279 1279   ].forEach(function(actionName) {
1280 1280   var action = editorUI.actions.actions[actionName];
1281 1281   if (action) {