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,34 +36,6 @@ 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 - }, 67 67 open: function() { 68 68 var graphXML = this.getData() || '<mxGraphModel/>'; 69 69 this.ui.setFileData(graphXML); ... ... @@ -105,19 +105,18 @@ 105 105 var attachmentReference = new XWiki.AttachmentReference(fileName, documentReference); 106 106 var uploadMethod = (diagramConfig.isTemporaryUploadSupported) ? xutils.temporaryUploadAttachment : xutils.uploadAttachment; 107 107 var uploadAttachment = $.proxy(uploadMethod, null, blob, attachmentReference); 108 - return uploadAttachment(); 80 + // Avoid creating too many versions of the attachment. Upload the attachment even if we failed to delete it first. 81 + return xutils.deleteAttachment(attachmentReference).then(uploadAttachment, uploadAttachment); 109 109 }; 110 110 111 111 var imageCache = {}; 112 - var saveFileAsPNGImageAttachment = function(file , index, originalPage) {85 + var saveFileAsPNGImageAttachment = function(file) { 113 113 var deferred = $.Deferred(); 114 - let page = file.ui.pages[index]; 115 - file.ui.selectPage(page, true, null); 116 - file.getUi().exportToCanvas(/* callback */ function (canvas) { 87 + file.getUi().exportToCanvas(/* callback */ function(canvas) { 117 117 if (canvas) { 118 118 try { 119 - canvas.toBlob(function 120 - pipeDeferred(saveBlobAsImageAttachment(blob, `${getXWikiAttachmentName(index)}.png`, file.documentReference), deferred);90 + canvas.toBlob(function(blob) { 91 + pipeDeferred(saveBlobAsImageAttachment(blob, 'diagram.png', file.documentReference), deferred); 121 121 }); 122 122 } catch(err) { 123 123 deferred.reject(); ... ... @@ -130,17 +130,13 @@ 130 130 $jsontool.serialize($services.localization.render('diagram.editor.saveAsImageAttachmentError')), 'error'); 131 131 deferred.reject(); 132 132 }, /* limitHeight */ null, /* ignoreSelection */ true, /* scale */ diagramConfig.pdfImageExportZoom); 133 - file.ui.selectPage(originalPage, true, null); 134 134 return deferred.promise(); 135 135 }; 136 136 137 - var saveFileAsSVGImageAttachment = function(file, index, originalPage) { 138 - var deferred = $.Deferred() 139 - let page = file.ui.pages[index]; 140 - file.ui.selectPage(page, true, null); 107 + var saveFileAsSVGImageAttachment = function(file) { 108 + var deferred = $.Deferred(); 141 141 var svgRoot = file.ui.editor.graph.getSvg(/* background: */ '#ffffff', /* scale: */ null, /* border: */ null, 142 142 /* nocrop: */ true, /* crisp: */ null, /* ignoreSelection: */ true); 143 - file.ui.selectPage(originalPage, true, null); 144 144 // Embed the images because the PDF exporter might not be able to access them. 145 145 file.ui.convertImages(svgRoot, function() { 146 146 var svg = '<?xml version="1.0" encoding="UTF-8"?>\n' + ... ... @@ -147,62 +147,28 @@ 147 147 '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' + 148 148 mxUtils.getXml(svgRoot); 149 149 var blob = new Blob([svg], {type: 'image/svg+xml'}); 150 - pipeDeferred(saveBlobAsImageAttachment(blob, `${getXWikiAttachmentName(index)}.svg`, file.documentReference), 151 - deferred); 117 + pipeDeferred(saveBlobAsImageAttachment(blob, 'diagram.svg', file.documentReference), deferred); 152 152 }, imageCache); 153 153 return deferred.promise(); 154 154 }; 155 155 156 - var saveFileAsImageAttachments = function(file , index, originalPage) {122 + var saveFileAsImageAttachments = function(file) { 157 157 // This is a workaround for https://github.com/jgraph/drawio/issues/490 158 158 // Stop editing for getting the latest content from diagram 159 159 file.ui.editor.graph.stopEditing(false); 160 160 // We upload the PNG image even if the SVG upload has failed. 161 - var pngUpload = $.proxy(saveFileAsPNGImageAttachment, null, file , index, originalPage);162 - return saveFileAsSVGImageAttachment(file , index, originalPage).then(pngUpload, pngUpload);127 + var pngUpload = $.proxy(saveFileAsPNGImageAttachment, null, file); 128 + return saveFileAsSVGImageAttachment(file).then(pngUpload, pngUpload); 163 163 }; 164 164 165 - var getXWikiAttachmentName = function (index) { 166 - if (index == 0) 167 - { 168 - // If is the first page we keep the old naming strategy -> diagram.png / diagram.svg 169 - return "diagram"; 170 - } 171 - return `diagram${index+1}`; 172 - }; 173 - 174 - var deleteAllDiagrams = function() { 175 - let baseUrl = `/xwiki/rest/diagram/${xm.documentReference.toString()}`; 176 - let queryString = $.param({"form_token": xm.form_token}); 177 - return $.post(`${baseUrl}?${queryString}`); 178 - }; 179 - 180 180 var saveFilesAsImageAttachments = function() { 181 - var uploadDeferredList = [] 182 - 183 - forEachOpenedFile(function (file) { 184 - var uploadDeferred = $.Deferred().resolve(); 185 - // Get the current page so we know where to return after saving. 186 - let originalPage = file.ui.currentPage; 187 - 188 - file.ui.pages.forEach(function (page, index) { 189 - // We do the next upload even if the previous uploads have failed. 190 - var nextUpload = $.proxy(saveFileAsImageAttachments, null, file, index,originalPage); 191 - uploadDeferred = uploadDeferred.then(nextUpload, nextUpload); 192 - uploadDeferredList.push(uploadDeferred.promise()); 193 - }); 132 + var uploadDeferred = $.Deferred().resolve(); 133 + forEachOpenedFile(function(file) { 134 + // We do the next upload even if the previous uploads have failed. 135 + var nextUpload = $.proxy(saveFileAsImageAttachments, null, file); 136 + uploadDeferred = uploadDeferred.then(nextUpload, nextUpload); 194 194 }); 195 - // Wait for all deferreds to complete. 196 - return $.when.apply($, uploadDeferredList).then( 197 - function () { 198 - // Resolve the overall promise if all succeeded. 199 - return { status: 'success' }; 200 - }, 201 - function () { 202 - // Reject the overall promise if any failed. 203 - return $.Deferred().reject({ status: 'fail' }); 204 - } 205 - ).promise(); 138 + return uploadDeferred.promise(); 206 206 }; 207 207 208 208 var uploadInProgress = false; ... ... @@ -214,13 +214,10 @@ 214 214 event.stopPropagation(); 215 215 var saveButton = $(event.target); 216 216 saveButton.prop('disabled', true); 217 - deleteAllDiagrams() 218 - .then(saveFilesAsImageAttachments) 219 - .catch(function(e) { 150 + saveFilesAsImageAttachments().fail(function(e) { 220 220 new XWiki.widgets.Notification( 221 221 $jsontool.serialize($services.localization.render('diagram.editor.saveAsImageAttachmentError')), 'error'); 222 - }) 223 - .always(function() { 153 + }).always(function() { 224 224 saveButton.prop('disabled', false).click(); 225 225 }); 226 226 } else { - Parser le contenu
-
... ... @@ -1,1 +1,1 @@ 1 - Non1 +Oui
- XWiki.JavaScriptExtension[2]
-
- Code
-
... ... @@ -1,3 +1,516 @@ 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 +}); 1 1 /** 2 2 * Overrides the link dialog in order to support creating links to wiki pages. 3 3 */ ... ... @@ -4,9 +4,10 @@ 4 4 define('diagram-link-editor', [ 5 5 'jquery', 6 6 'diagram-link-handler', 520 + 'svg-handler', 7 7 'draw.io', 8 8 'resourceSelector' 9 -], function($, diagramLinkHandler) { 523 +], function($, diagramLinkHandler, svgHandler) { 10 10 /** 11 11 * Override in order to change the Element type check from a.constructor !== Element, which was failing due to a 12 12 * collision with the PrototypeJS's own implementation of the standard Element. ... ... @@ -18,29 +18,6 @@ 18 18 a.nodeName.toLowerCase() != b.toLowerCase() ? !1 : null == c || a.getAttribute(c) == d; 19 19 }; 20 20 21 -/** 22 - * TODO: When upgrading, make sure to check if this method has changed. 23 - * This method was copied directly from draw.io, and the only change made was the call to this.showDialog. 24 - * Both the width and height were modified to fit better within the XWiki UI. 25 - */ 26 -EditorUi.prototype.showBackgroundImageDialog = function (apply, img, color, showColor) { 27 - apply = (apply != null) ? apply : mxUtils.bind(this, function (image, failed, color, shadowVisible) { 28 - if (!failed) { 29 - var change = new ChangePageSetup(this, (showColor) ? color : null, image); 30 - change.ignoreColor = !showColor; 31 - if (shadowVisible != null && showColor) { 32 - change.shadowVisible = shadowVisible; 33 - } 34 - this.editor.graph.model.execute(change); 35 - } 36 - }); 37 - 38 - var dlg = new BackgroundImageDialog(this, apply, img, color, showColor); 39 - this.showDialog(dlg.container, 420, (showColor) ? 260 : 240, true, true); 40 - dlg.init(); 41 -}; 42 - 43 - 44 44 EditorUi.prototype.showLinkDialog = function(value, selectLabel, callback, showNewWindowOption, linkTarget) { 45 45 var resourceReference = diagramLinkHandler.getResourceReferenceFromCustomLink(value); 46 46 // We append the modal to the body element in order to fix Issue #108: "Inserting a link in full screen mode is not ... ... @@ -65,32 +65,6 @@ 65 65 // Do nothing. 66 66 } 67 67 68 - // Override the Graph.updateSvgLinks method to ensure that the links are preserved in the final SVG. 69 - // We need to directly modify the original method because drawio does not provide a parameter to override the default 70 - // behavior or a smaller method that only performs the replacement. 71 - Graph.prototype.updateSvgLinks = function (node, target, removeCustom) { 72 - var links = node.getElementsByTagName('a'); 73 - for (var i = 0; i < links.length; i++) { 74 - if (links[i].getAttribute('target') == null) { 75 - var href = links[i].getAttribute('href'); 76 - if (href == null) { 77 - href = links[i].getAttribute('xlink:href'); 78 - } 79 - 80 - if (href != null) { 81 - if (target != null && /^https?:\/\//.test(href)) { 82 - // If the href starts with http or https, set the target attribute. 83 - links[i].setAttribute('target', target); 84 - } else if (this.isCustomLink(href)) { 85 - // Handle custom links 86 - let absoluteLink = $('<a/>').attr('href', diagramLinkHandler.getURLFromCustomLink(href)).prop('href'); 87 - links[i].setAttribute('href', absoluteLink); 88 - } 89 - } 90 - } 91 - } 92 - }; 93 - 94 94 // Overwrite Graph.getSvg in order to replace XWiki custom links with absolute URLs. 95 95 // Also fix the text fallback for viewers with no support for foreignObjects. 96 96 var originalGraphGetSVG = Graph.prototype.getSvg; ... ... @@ -116,8 +116,66 @@ 116 116 // Keep only the text content. 117 117 str = $('<div/>').html(str).text() || this.foAltText; 118 118 } 119 - return originalCreateAlternateContent.call(this, fo, x, y, w, h, str, align, valign, wrap, format, overflow, 120 - clip, rotation); 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 + } 121 121 }; 122 122 return originalDrawState.apply(this, arguments); 123 123 }; ... ... @@ -149,7 +149,326 @@ 149 149 }; 150 150 return converter; 151 151 }; 152 - 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 + 153 153 // Override for uploading the image as attachment instead of encode it to Base64. 154 154 var originalImportFiles = EditorUi.prototype.importFiles; 155 155 EditorUi.prototype.importFiles = function(files, x, y, maxSize, fn, resultFn, filterFn, barrierFn, resizeDialog, ... ... @@ -179,7 +179,7 @@ 179 179 } 180 180 }; 181 181 } 182 - originalImportFiles.apply(this, importFilesArgs);1024 + importFilesNew.apply(this, importFilesArgs); 183 183 }; 184 184 185 185 // Add support for inserting images by specifying the XWiki attachment reference. ... ... @@ -313,15 +313,6 @@ 313 313 }; 314 314 }); 315 315 316 -define('diagramMenuTranslations', { 317 - prefix: 'diagram.editor.menu.', 318 - keys: [ 319 - // File menu. 320 - 'print.label', 321 - 'print.title' 322 - ] 323 -}); 324 - 325 325 /** 326 326 * Integrates draw.io diagram editor in XWiki. 327 327 */ ... ... @@ -331,12 +331,11 @@ 331 331 'diagram-utils', 332 332 'diagram-url-io', 333 333 'diagram-config', 334 - 'xwiki-l10n!diagramMenuTranslations', 335 335 'diagram-graph-xml-filter', 336 336 'diagram-link-editor', 337 337 'diagram-image-editor', 338 338 'diagram-external-services' 339 -], function($, diagramStore, diagramUtils, diagramUrlIO, diagramConfig , l10n) {1171 +], function($, diagramStore, diagramUtils, diagramUrlIO, diagramConfig) { 340 340 341 341 // These variables are used to decide if an image should be uploaded at original resolution or 342 342 // should be declined for being too big. ... ... @@ -368,7 +368,6 @@ 368 368 369 369 var fixEditorUI = function(editorUI) { 370 370 cleanMenu(editorUI); 371 - renameMenu(editorUI); 372 372 fixKeyboardShortcutsAction(editorUI); 373 373 fixEditorButtons($(editorUI.container)); 374 374 removeThemeButton(); ... ... @@ -394,7 +394,8 @@ 394 394 // 395 395 // Change the service name in order to disable notifications. 396 396 // 397 - EditorUi.prototype.getServiceName = function() { 1228 + EditorUi.prototype.getServiceName = function() 1229 + { 398 398 return 'xwiki.com'; 399 399 }; 400 400 ... ... @@ -407,31 +407,11 @@ 407 407 var originalAddSubmenu = Menus.prototype.addSubmenu; 408 408 Menus.prototype.addSubmenu = function(name, menu, parent, label) { 409 409 var subMenu = this.get(name); 410 - if (subMenu && subMenu.is Enabled()!== false) {411 - returnoriginalAddSubmenu.apply(this, arguments);1242 + if (subMenu && subMenu.visible !== false) { 1243 + originalAddSubmenu.apply(this, arguments); 412 412 } 413 413 }; 414 414 415 - 416 - /* 417 - * Map with all the menu items that we want to have a title. 418 - */ 419 - const titleMap = new Map([ 420 - ['print', l10n['print.title']] 421 - ]); 422 - 423 - /* 424 - * Update the title of the menu items. 425 - */ 426 - var originalAddMenuItem = Menus.prototype.addMenuItem; 427 - Menus.prototype.addMenuItem = function(menu, key, parent, trigger, sprite, label) { 428 - let item = originalAddMenuItem.apply(this, arguments); 429 - if (item != null && titleMap.has(key)) { 430 - item.title = titleMap.get(key); 431 - } 432 - return item; 433 - }; 434 - 435 435 // Remove the language picker because the diagram editor is configured to use the XWiki language. 436 436 var originalCreateMenubar = Menus.prototype.createMenubar; 437 437 Menus.prototype.createMenubar = function(container) { ... ... @@ -451,24 +451,6 @@ 451 451 }; 452 452 453 453 // 454 - // Rename menu options to fit our needs. 455 - // 456 - var renameMenu = function(editorUI) { 457 - const menuItems = [ 458 - // File menu 459 - ['print', l10n['print.label']] 460 - ]; 461 - 462 - // Iterate over the array of tuples 463 - menuItems.forEach(function([menuKey, newLabel]) { 464 - var action = editorUI.actions.actions[menuKey]; 465 - if (action) { 466 - action.label = newLabel; 467 - } 468 - }); 469 - }; 470 - 471 - // 472 472 // Clean the editor menu by removing the features that are not needed. 473 473 // 474 474 var cleanMenu = function(editorUI) { ... ... @@ -481,7 +481,7 @@ 481 481 // Help menu 482 482 'downloadDesktop', 'useOffline', 483 483 // ExportAs 484 - 'exportHtml' , 'exportPdf'1278 + 'exportHtml' 485 485 ].forEach(function(actionName) { 486 486 var action = editorUI.actions.actions[actionName]; 487 487 if (action) {