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

Modifications pour le document DiagramEditSheet

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

Depuis la version 7.1
modifié par Admin
sur 08/11/2024 - 07:52
Commentaire de modification : Install extension [com.xwiki.diagram:application-diagram/1.21]
À la version 9.1
modifié par Admin
sur 19/03/2025 - 19:24
Commentaire de modification : Install extension [com.xwiki.diagram:application-diagram/1.22.1]

Résumé

Détails

XWiki.JavaScriptExtension[1]
Code
... ... @@ -105,18 +105,19 @@
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 - // Avoid creating too many versions of the attachment. Upload the attachment even if we failed to delete it first.
109 - return xutils.deleteAttachment(attachmentReference).then(uploadAttachment, uploadAttachment);
108 + return uploadAttachment();
110 110   };
111 111  
112 112   var imageCache = {};
113 - var saveFileAsPNGImageAttachment = function(file) {
112 + var saveFileAsPNGImageAttachment = function(file, index, originalPage) {
114 114   var deferred = $.Deferred();
115 - file.getUi().exportToCanvas(/* callback */ function(canvas) {
114 + let page = file.ui.pages[index];
115 + file.ui.selectPage(page, true, null);
116 + file.getUi().exportToCanvas(/* callback */ function (canvas) {
116 116   if (canvas) {
117 117   try {
118 - canvas.toBlob(function(blob) {
119 - pipeDeferred(saveBlobAsImageAttachment(blob, 'diagram.png', file.documentReference), deferred);
119 + canvas.toBlob(function (blob) {
120 + pipeDeferred(saveBlobAsImageAttachment(blob, `${getXWikiAttachmentName(index)}.png`, file.documentReference), deferred);
120 120   });
121 121   } catch(err) {
122 122   deferred.reject();
... ... @@ -129,13 +129,17 @@
129 129   $jsontool.serialize($services.localization.render('diagram.editor.saveAsImageAttachmentError')), 'error');
130 130   deferred.reject();
131 131   }, /* limitHeight */ null, /* ignoreSelection */ true, /* scale */ diagramConfig.pdfImageExportZoom);
133 + file.ui.selectPage(originalPage, true, null);
132 132   return deferred.promise();
133 133   };
134 134  
135 - var saveFileAsSVGImageAttachment = function(file) {
136 - var deferred = $.Deferred();
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);
137 137   var svgRoot = file.ui.editor.graph.getSvg(/* background: */ '#ffffff', /* scale: */ null, /* border: */ null,
138 138   /* nocrop: */ true, /* crisp: */ null, /* ignoreSelection: */ true);
143 + file.ui.selectPage(originalPage, true, null);
139 139   // Embed the images because the PDF exporter might not be able to access them.
140 140   file.ui.convertImages(svgRoot, function() {
141 141   var svg = '<?xml version="1.0" encoding="UTF-8"?>\n' +
... ... @@ -142,28 +142,62 @@
142 142   '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' +
143 143   mxUtils.getXml(svgRoot);
144 144   var blob = new Blob([svg], {type: 'image/svg+xml'});
145 - pipeDeferred(saveBlobAsImageAttachment(blob, 'diagram.svg', file.documentReference), deferred);
150 + pipeDeferred(saveBlobAsImageAttachment(blob, `${getXWikiAttachmentName(index)}.svg`, file.documentReference),
151 + deferred);
146 146   }, imageCache);
147 147   return deferred.promise();
148 148   };
149 149  
150 - var saveFileAsImageAttachments = function(file) {
156 + var saveFileAsImageAttachments = function(file, index, originalPage) {
151 151   // This is a workaround for https://github.com/jgraph/drawio/issues/490
152 152   // Stop editing for getting the latest content from diagram
153 153   file.ui.editor.graph.stopEditing(false);
154 154   // We upload the PNG image even if the SVG upload has failed.
155 - var pngUpload = $.proxy(saveFileAsPNGImageAttachment, null, file);
156 - return saveFileAsSVGImageAttachment(file).then(pngUpload, pngUpload);
161 + var pngUpload = $.proxy(saveFileAsPNGImageAttachment, null, file, index, originalPage);
162 + return saveFileAsSVGImageAttachment(file, index, originalPage).then(pngUpload, pngUpload);
157 157   };
158 158  
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 +
159 159   var saveFilesAsImageAttachments = function() {
160 - var uploadDeferred = $.Deferred().resolve();
161 - forEachOpenedFile(function(file) {
162 - // We do the next upload even if the previous uploads have failed.
163 - var nextUpload = $.proxy(saveFileAsImageAttachments, null, file);
164 - uploadDeferred = uploadDeferred.then(nextUpload, nextUpload);
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 + });
165 165   });
166 - return uploadDeferred.promise();
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();
167 167   };
168 168  
169 169   var uploadInProgress = false;
... ... @@ -175,10 +175,13 @@
175 175   event.stopPropagation();
176 176   var saveButton = $(event.target);
177 177   saveButton.prop('disabled', true);
178 - saveFilesAsImageAttachments().fail(function(e) {
217 + deleteAllDiagrams()
218 + .then(saveFilesAsImageAttachments)
219 + .catch(function(e) {
179 179   new XWiki.widgets.Notification(
180 180   $jsontool.serialize($services.localization.render('diagram.editor.saveAsImageAttachmentError')), 'error');
181 - }).always(function() {
222 + })
223 + .always(function() {
182 182   saveButton.prop('disabled', false).click();
183 183   });
184 184   } else {
Parser le contenu
... ... @@ -1,1 +1,1 @@
1 -Oui
1 +Non
XWiki.JavaScriptExtension[2]
Code
... ... @@ -18,6 +18,29 @@
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 +
21 21   EditorUi.prototype.showLinkDialog = function(value, selectLabel, callback, showNewWindowOption, linkTarget) {
22 22   var resourceReference = diagramLinkHandler.getResourceReferenceFromCustomLink(value);
23 23   // We append the modal to the body element in order to fix Issue #108: "Inserting a link in full screen mode is not
... ... @@ -42,6 +42,32 @@
42 42   // Do nothing.
43 43   }
44 44  
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 +
45 45   // Overwrite Graph.getSvg in order to replace XWiki custom links with absolute URLs.
46 46   // Also fix the text fallback for viewers with no support for foreignObjects.
47 47   var originalGraphGetSVG = Graph.prototype.getSvg;
... ... @@ -101,318 +101,6 @@
101 101   return converter;
102 102   };
103 103  
104 - // Copied from the drawio version 24.5.4 to include a fix for importing a file in an empty diagram. This method should
105 - // be removed once we upgrade to a drawio version >= 24.4.0. Being a big method, the structure was altered just to
106 - // make it shorter.
107 - var importFilesNew = function(files, x, y, maxSize, fn, resultFn, filterFn, barrierFn,
108 - resizeDialog, maxBytes, resampleThreshold, ignoreEmbeddedXml, evt) {
109 - maxSize = (maxSize != null) ? maxSize : this.maxImageSize;
110 - maxBytes = (maxBytes != null) ? maxBytes : this.maxImageBytes;
111 - var crop = x != null && y != null;
112 - var resizeImages = true;
113 - x = (x != null) ? x : 0;
114 - y = (y != null) ? y : 0;
115 - // Checks if large images are imported
116 - var largeImages = false;
117 - if (!mxClient.IS_CHROMEAPP && files != null) {
118 - var thresh = resampleThreshold || this.resampleThreshold;
119 - for (var i = 0; i < files.length; i++) {
120 - if (files[i].type.substring(0, 9) !== 'image/svg' &&
121 - files[i].type.substring(0, 6) === 'image/' &&
122 - files[i].size > thresh) {
123 - largeImages = true;
124 - break;
125 - }
126 - }
127 - }
128 - var doImportFiles = mxUtils.bind(this, function() {
129 - var graph = this.editor.graph;
130 - var gs = graph.gridSize;
131 - fn = (fn != null) ? fn : mxUtils.bind(this, function(data, mimeType, x, y, w, h, filename, done, file) {
132 - try {
133 - if (data != null && data.substring(0, 10) == '<mxlibrary') {
134 - this.spinner.stop();
135 - this.loadLibrary(new LocalLibrary(this, data, filename));
136 - this.showSidebar();
137 - return null;
138 - }
139 - else {
140 - // Drop on empty file ignores drop location
141 - if (this.isCompatibleString(data) && files.length == 1 && evt != null &&
142 - evt.type == 'drop' && this.isBlankFile() && !this.canUndo()) {
143 - crop = false;
144 - x = 0;
145 - y = 0;
146 - }
147 - return this.importFile(data, mimeType, x, y, w, h, filename,
148 - done, file, crop, ignoreEmbeddedXml, evt);
149 - }
150 - }
151 - catch (e) {
152 - this.handleError(e);
153 - return null;
154 - }
155 - });
156 - resultFn = (resultFn != null) ? resultFn : mxUtils.bind(this, function(cells) {
157 - graph.setSelectionCells(cells);
158 - });
159 - if (this.spinner.spin(document.body, mxResources.get('loading'))) {
160 - var count = files.length;
161 - var remain = count;
162 - var queue = [];
163 - // Barrier waits for all files to be loaded asynchronously
164 - var barrier = mxUtils.bind(this, function(index, fnc) {
165 - queue[index] = fnc;
166 - if (--remain == 0) {
167 - this.spinner.stop();
168 - if (barrierFn != null) {
169 - barrierFn(queue);
170 - }
171 - else {
172 - var cells = [];
173 - graph.getModel().beginUpdate();
174 - try {
175 - for (var j = 0; j < queue.length; j++) {
176 - var tmp = queue[j]();
177 - if (tmp != null) {
178 - cells = cells.concat(tmp);
179 - }
180 - }
181 - }
182 - finally {
183 - graph.getModel().endUpdate();
184 - }
185 - }
186 - resultFn(cells);
187 - }
188 - });
189 - for (var i = 0; i < count; i++) {
190 - (mxUtils.bind(this, function(index) {
191 - var file = files[index];
192 - if (file != null) {
193 - var reader = new FileReader();
194 - reader.onload = mxUtils.bind(this, function(e) {
195 - if (filterFn == null || filterFn(file)) {
196 - try {
197 - if (file.type.substring(0, 6) == 'image/') {
198 - if (file.type.substring(0, 9) == 'image/svg') {
199 - // Checks if SVG contains content attribute
200 - var data = Graph.clipSvgDataUri(e.target.result);
201 - var comma = data.indexOf(',');
202 - var svgText = decodeURIComponent(escape(atob(data.substring(comma + 1))));
203 - var root = mxUtils.parseXml(svgText);
204 - var svgs = root.getElementsByTagName('svg');
205 - if (svgs.length > 0) {
206 - var svgRoot = svgs[0];
207 - var cont = (ignoreEmbeddedXml) ? null : svgRoot.getAttribute('content');
208 - if (cont != null && cont.charAt(0) != '<' && cont.charAt(0) != '%') {
209 - cont = unescape((window.atob) ? atob(cont) : Base64.decode(cont, true));
210 - }
211 - if (cont != null && cont.charAt(0) == '%') {
212 - cont = decodeURIComponent(cont);
213 - }
214 - if (cont != null && (cont.substring(0, 8) === '<mxfile ' ||
215 - cont.substring(0, 14) === '<mxGraphModel ')) {
216 - barrier(index, mxUtils.bind(this, function() {
217 - return fn(cont, 'text/xml', x + index * gs, y + index * gs, 0, 0, file.name);
218 - }));
219 - }
220 - else {
221 - // SVG needs special handling to add viewbox if missing and
222 - // find initial size from SVG attributes (only for IE11)
223 - barrier(index, mxUtils.bind(this, function() {
224 - try {
225 - // Parses SVG and find width and height
226 - if (root != null) {
227 - var svgs = root.getElementsByTagName('svg');
228 - if (svgs.length > 0) {
229 - var svgRoot = svgs[0];
230 - var w = svgRoot.getAttribute('width');
231 - var h = svgRoot.getAttribute('height');
232 - if (w != null && w.charAt(w.length - 1) != '%') {
233 - w = parseFloat(w);
234 - }
235 - else {
236 - w = NaN;
237 - }
238 - if (h != null && h.charAt(h.length - 1) != '%') {
239 - h = parseFloat(h);
240 - }
241 - else {
242 - h = NaN;
243 - }
244 - // Check if viewBox attribute already exists
245 - var vb = svgRoot.getAttribute('viewBox');
246 - if (vb == null || vb.length == 0) {
247 - svgRoot.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
248 - }
249 - // Uses width and height from viewbox for
250 - // missing width and height attributes
251 - else if (isNaN(w) || isNaN(h)) {
252 - var tokens = vb.split(' ');
253 - if (tokens.length > 3) {
254 - w = parseFloat(tokens[2]);
255 - h = parseFloat(tokens[3]);
256 - }
257 - }
258 - data = Editor.createSvgDataUri(mxUtils.getXml(svgRoot));
259 - var s = Math.min(1, Math.min(maxSize / Math.max(1, w)), maxSize / Math.max(1, h));
260 - var cells = fn(data, file.type, x + index * gs, y + index * gs, Math.max(
261 - 1, Math.round(w * s)), Math.max(1, Math.round(h * s)), file.name);
262 - // Hack to fix width and height asynchronously
263 - if (cells != null && (isNaN(w) || isNaN(h))) {
264 - var img = new Image();
265 - img.onload = mxUtils.bind(this, function() {
266 - w = Math.max(1, img.width);
267 - h = Math.max(1, img.height);
268 - cells[0].geometry.width = w;
269 - cells[0].geometry.height = h;
270 - svgRoot.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
271 - data = Editor.createSvgDataUri(mxUtils.getXml(svgRoot));
272 - var semi = data.indexOf(';');
273 - if (semi > 0) {
274 - data = data.substring(0, semi) + data.substring(data.indexOf(',', semi + 1));
275 - }
276 - graph.setCellStyles('image', data, [cells[0]]);
277 - });
278 - img.src = Editor.createSvgDataUri(mxUtils.getXml(svgRoot));
279 - }
280 - return cells;
281 - }
282 - }
283 - }
284 - catch (e) {
285 - // ignores any SVG parsing errors
286 - }
287 - return null;
288 - }));
289 - }
290 - }
291 - else {
292 - barrier(index, mxUtils.bind(this, function() {
293 - return null;
294 - }));
295 - }
296 - }
297 - else {
298 - // Checks if PNG+XML is available to bypass code below
299 - var containsModel = false;
300 - if (file.type == 'image/png') {
301 - var xml = (ignoreEmbeddedXml) ? null : this.extractGraphModelFromPng(e.target.result);
302 - if (xml != null && xml.length > 0) {
303 - var img = new Image();
304 - img.src = e.target.result;
305 - barrier(index, mxUtils.bind(this, function() {
306 - return fn(xml, 'text/xml', x + index * gs, y + index * gs,
307 - img.width, img.height, file.name);
308 - }));
309 - containsModel = true;
310 - }
311 - }
312 - // Additional asynchronous step for finding image size
313 - if (!containsModel) {
314 - // Cannot load local files in Chrome App
315 - if (mxClient.IS_CHROMEAPP) {
316 - this.spinner.stop();
317 - this.showError(mxResources.get('error'), mxResources.get('dragAndDropNotSupported'),
318 - mxResources.get('cancel'), mxUtils.bind(this, function()
319 - {
320 - // Hides the dialog
321 - }), null, mxResources.get('ok'), mxUtils.bind(this, function()
322 - {
323 - // Redirects to import function
324 - this.actions.get('import').funct();
325 - })
326 - );
327 - }
328 - else {
329 - this.loadImage(e.target.result, mxUtils.bind(this, function(img) {
330 - this.resizeImage(img, e.target.result, mxUtils.bind(this, function(data2, w2, h2) {
331 - barrier(index, mxUtils.bind(this, function() {
332 - // Refuses to insert images above a certain size as they kill the app
333 - if (data2 != null && data2.length < maxBytes) {
334 - var s = (!resizeImages || !this.isResampleImageSize(
335 - file.size, resampleThreshold)) ? 1 :
336 - Math.min(1, Math.min(maxSize / w2, maxSize / h2));
337 - return fn(data2, file.type, x + index * gs, y + index * gs,
338 - Math.round(w2 * s), Math.round(h2 * s), file.name);
339 - }
340 - else {
341 - this.handleError({message: mxResources.get('imageTooBig')});
342 - return null;
343 - }
344 - }));
345 - }), resizeImages, maxSize, resampleThreshold, file.size);
346 - }), mxUtils.bind(this, function() {
347 - this.handleError({message: mxResources.get('invalidOrMissingFile')});
348 - }));
349 - }
350 - }
351 - }
352 - }
353 - else {
354 - var data = e.target.result;
355 - fn(data, file.type, x + index * gs, y + index * gs, 240, 160, file.name, function(cells) {
356 - barrier(index, function() {
357 - return cells;
358 - });
359 - }, file);
360 - }
361 - }
362 - catch (e) {
363 - // Ignores file parsing error
364 - barrier(index, mxUtils.bind(this, function() {
365 - return null;
366 - }));
367 - if (window.console != null) {
368 - console.error(e, file);
369 - }
370 - }
371 - }
372 - else {
373 - // Ignores file and decrements counter
374 - barrier(index, mxUtils.bind(this, function()
375 - {
376 - return null;
377 - }));
378 - }
379 - });
380 - // Handles special cases
381 - if (/(\.v(dx|sdx?))($|\?)/i.test(file.name) || /(\.vs(x|sx?))($|\?)/i.test(file.name)) {
382 - fn(null, file.type, x + index * gs, y + index * gs, 240, 160, file.name, function(cells) {
383 - barrier(index, function() {
384 - return cells;
385 - });
386 - }, file);
387 - }
388 - else if (file.type.substring(0, 5) == 'image' || file.type == 'application/pdf') {
389 - reader.readAsDataURL(file);
390 - }
391 - else {
392 - reader.readAsText(file);
393 - }
394 - }
395 - }))(i);
396 - }
397 - }
398 - });
399 - if (largeImages) {
400 - // Workaround for lost files array in async code
401 - var tmp = [];
402 - for (var i = 0; i < files.length; i++) {
403 - tmp.push(files[i]);
404 - }
405 - files = tmp;
406 - this.confirmImageResize(function(doResize) {
407 - resizeImages = doResize;
408 - doImportFiles();
409 - }, resizeDialog);
410 - }
411 - else {
412 - doImportFiles();
413 - }
414 - };
415 -
416 416   // Override for uploading the image as attachment instead of encode it to Base64.
417 417   var originalImportFiles = EditorUi.prototype.importFiles;
418 418   EditorUi.prototype.importFiles = function(files, x, y, maxSize, fn, resultFn, filterFn, barrierFn, resizeDialog,
... ... @@ -442,7 +442,7 @@
442 442   }
443 443   };
444 444   }
445 - importFilesNew.apply(this, importFilesArgs);
182 + originalImportFiles.apply(this, importFilesArgs);
446 446   };
447 447  
448 448   // Add support for inserting images by specifying the XWiki attachment reference.
... ... @@ -576,6 +576,15 @@
576 576   };
577 577  });
578 578  
316 +define('diagramMenuTranslations', {
317 + prefix: 'diagram.editor.menu.',
318 + keys: [
319 + // File menu.
320 + 'print.label',
321 + 'print.title'
322 + ]
323 +});
324 +
579 579  /**
580 580   * Integrates draw.io diagram editor in XWiki.
581 581   */
... ... @@ -585,11 +585,12 @@
585 585   'diagram-utils',
586 586   'diagram-url-io',
587 587   'diagram-config',
334 + 'xwiki-l10n!diagramMenuTranslations',
588 588   'diagram-graph-xml-filter',
589 589   'diagram-link-editor',
590 590   'diagram-image-editor',
591 591   'diagram-external-services'
592 -], function($, diagramStore, diagramUtils, diagramUrlIO, diagramConfig) {
339 +], function($, diagramStore, diagramUtils, diagramUrlIO, diagramConfig, l10n) {
593 593  
594 594   // These variables are used to decide if an image should be uploaded at original resolution or
595 595   // should be declined for being too big.
... ... @@ -621,6 +621,7 @@
621 621  
622 622   var fixEditorUI = function(editorUI) {
623 623   cleanMenu(editorUI);
371 + renameMenu(editorUI);
624 624   fixKeyboardShortcutsAction(editorUI);
625 625   fixEditorButtons($(editorUI.container));
626 626   removeThemeButton();
... ... @@ -646,8 +646,7 @@
646 646   //
647 647   // Change the service name in order to disable notifications.
648 648   //
649 - EditorUi.prototype.getServiceName = function()
650 - {
397 + EditorUi.prototype.getServiceName = function() {
651 651   return 'xwiki.com';
652 652   };
653 653  
... ... @@ -660,11 +660,31 @@
660 660   var originalAddSubmenu = Menus.prototype.addSubmenu;
661 661   Menus.prototype.addSubmenu = function(name, menu, parent, label) {
662 662   var subMenu = this.get(name);
663 - if (subMenu && subMenu.visible !== false) {
664 - originalAddSubmenu.apply(this, arguments);
410 + if (subMenu && subMenu.isEnabled() !== false) {
411 + return originalAddSubmenu.apply(this, arguments);
665 665   }
666 666   };
667 667  
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 +
668 668   // Remove the language picker because the diagram editor is configured to use the XWiki language.
669 669   var originalCreateMenubar = Menus.prototype.createMenubar;
670 670   Menus.prototype.createMenubar = function(container) {
... ... @@ -684,6 +684,24 @@
684 684   };
685 685  
686 686   //
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 + //
687 687   // Clean the editor menu by removing the features that are not needed.
688 688   //
689 689   var cleanMenu = function(editorUI) {
... ... @@ -696,7 +696,7 @@
696 696   // Help menu
697 697   'downloadDesktop', 'useOffline',
698 698   // ExportAs
699 - 'exportHtml'
484 + 'exportHtml', 'exportPdf'
700 700   ].forEach(function(actionName) {
701 701   var action = editorUI.actions.actions[actionName];
702 702   if (action) {