Modifications pour le document DiagramEditSheet
Modifié par Admin le 19/03/2025 - 19:24
Résumé
-
Objets (2 modifications, 0 ajouts, 0 suppressions)
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 - importFiles New.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) {