Changes for page InplaceEditing

From version 3.1
edited by Nazzareno Pompei
on 22/12/2021 09:31
Change comment: Install extension [org.xwiki.platform:xwiki-platform-edit-ui/13.10.1]
To version 1.1
edited by N Pompei
on 08/06/2020 17:32
Change comment: Install extension [org.xwiki.platform:xwiki-platform-edit-ui/12.4]

Summary

Details

Page properties
Author
... ... @@ -1,1 +1,1 @@
1 -XWiki.NazzarenoPompei
1 +XWiki.NPompei
Content
... ... @@ -9,15 +9,11 @@
9 9   #jsonResponse($editConfirmation)
10 10   #else
11 11   ## Lock the document for editing.
12 - #set ($lockParams = {
12 + #set ($discard = $response.sendRedirect($tdoc.getURL('lock', $escapetool.url({
13 13   'ajax': 1,
14 14   'action': $request.lockAction,
15 15   'language': $tdoc.realLocale
16 - })
17 - #if ($request.force == 'true')
18 - #set ($lockParams.force = 1)
19 - #end
20 - #set ($discard = $response.sendRedirect($tdoc.getURL('lock', $escapetool.url($lockParams))))
16 + }))))
21 21   #end
22 22  #end
23 23  {{/velocity}}
XWiki.JavaScriptExtension[0]
Code
... ... @@ -1,11 +7,9 @@
1 -(function(config) {
2 - "use strict";
3 -
4 -const paths = config.paths;
5 -const l10n = config.l10n;
6 -
7 7  require.config({
8 - paths: paths.js
2 + paths: {
3 + 'actionButtons': $jsontool.serialize($xwiki.getSkinFile('js/xwiki/actionbuttons/actionButtons.js', true)),
4 + // Required in case the user needs to resolve merge conflicts on save.
5 + 'diff': $jsontool.serialize($xwiki.getSkinFile('uicomponents/viewers/diff.js'))
6 + }
9 9  });
10 10  
11 11  define('xwiki-document-api', ['jquery'], function($) {
... ... @@ -16,36 +16,13 @@
16 16  
17 17   return {
18 18   /**
19 - * @return this document's plain title
20 - */
21 - getPlainTitle() {
22 - return $('<div/>').html(this.renderedTitle || this.title).text();
23 - },
24 -
25 - /**
26 26   * @return this document's real locale
27 27   */
28 28   getRealLocale: function() {
29 - var realLocale = this.language;
30 - if (typeof realLocale !== 'string' || realLocale === '') {
31 - realLocale = this.getDefaultLocale();
32 - }
33 - return realLocale;
20 + return this.language || (this.translations && this.translations['default']) || $('html').attr('lang');
34 34   },
35 35  
36 36   /**
37 - * @return this document's default locale
38 - */
39 - getDefaultLocale: function() {
40 - if (this.translations && typeof this.translations['default'] === 'string') {
41 - return this.translations['default'];
42 - } else {
43 - // The default locale is not specified. Use the UI locale.
44 - return $('html').attr('lang');
45 - }
46 - },
47 -
48 - /**
49 49   * @return the URL that can be used to perform the specified action on this document
50 50   */
51 51   getURL: function(action, queryString, fragment) {
... ... @@ -80,33 +80,25 @@
80 80   */
81 81   render: function(forView) {
82 82   var queryString = {
83 - xpage: 'get',
58 + xpage: 'rendercontent',
84 84   outputTitle: true,
60 + outputSyntax: forView ? null : 'annotatedxhtml',
85 85   language: this.getRealLocale(),
86 86   // Make sure the response is not retrieved from cache (IE11 doesn't obey the caching HTTP headers).
87 87   timestamp: new Date().getTime()
88 88   };
89 - if (!forView) {
90 - // We need the annotated XHTML when editing in order to be able to protect the rendering transformations and to
91 - // be able to recreate the wiki syntax.
92 - queryString.outputSyntax = 'annotatedxhtml';
93 - // Currently, only the macro transformations are protected and thus can be edited.
94 - // See XRENDERING-78: Add markers to modified XDOM by Transformations/Macros
95 - queryString.transformations = 'macro';
96 - }
97 97   var thisXWikiDocument = this;
98 98   return $.get(this.getURL('view'), queryString).fail(function() {
99 - new XWiki.widgets.Notification(l10n['edit.inplace.page.renderFailed'], 'error');
67 + new XWiki.widgets.Notification(
68 + $jsontool.serialize($services.localization.render('edit.inplace.page.renderFailed')),
69 + 'error'
70 + );
100 100   }).then(function(html) {
101 - // Render succeeded.
102 102   var container = $('<div/>').html(html);
103 103   return $.extend(thisXWikiDocument, {
104 104   renderedTitle: container.find('#document-title h1').html(),
105 105   renderedContent: container.find('#xwikicontent').html()
106 106   });
107 - }, function() {
108 - // Render failed.
109 - return thisXWikiDocument;
110 110   });
111 111   },
112 112  
... ... @@ -121,15 +121,11 @@
121 121   // Make sure the response is not retrieved from cache (IE11 doesn't obey the caching HTTP headers).
122 122   timestamp: new Date().getTime()
123 123   }).then(function(newXWikiDocument) {
124 - // Reload succeeded.
125 125   // Resolve the document reference.
126 126   thisXWikiDocument.documentReference = XWiki.Model.resolve(newXWikiDocument.id, XWiki.EntityType.DOCUMENT);
127 127   // We were able to load the document so it's not new.
128 128   thisXWikiDocument.isNew = false;
129 129   return $.extend(thisXWikiDocument, newXWikiDocument);
130 - }, function() {
131 - // Reload failed.
132 - return thisXWikiDocument;
133 133   });
134 134   },
135 135  
... ... @@ -152,19 +152,8 @@
152 152   // Make sure the response is not retrieved from cache (IE11 doesn't obey the caching HTTP headers).
153 153   timestamp: new Date().getTime()
154 154   }).then(function() {
155 - // Lock succeeded.
156 156   thisXWikiDocument.locked = action;
157 157   return thisXWikiDocument;
158 - }, function(response) {
159 - // Lock failed.
160 - delete thisXWikiDocument.locked;
161 - // Check if the user can force the lock.
162 - var lockConfirmation = response.responseJSON;
163 - if (response.status === 423 && lockConfirmation) {
164 - // The user can force the lock, but needs confirmation.
165 - thisXWikiDocument.lockConfirmation = lockConfirmation;
166 - }
167 - return thisXWikiDocument;
168 168   });
169 169   },
170 170  
... ... @@ -187,25 +187,6 @@
187 187   // See https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Synchronous_and_Asynchronous_Requests
188 188   $.ajax({type: 'GET', url: url, async: false});
189 189   }
190 - },
191 -
192 - /**
193 - * Makes sure this document matches the current UI locale.
194 - */
195 - translate: function() {
196 - const realLocale = this.getRealLocale();
197 - const uiLocale = $('html').attr('lang');
198 - if (realLocale && realLocale !== uiLocale) {
199 - this.language = uiLocale;
200 - // Set the original document locale.
201 - this.translations = this.translations || {};
202 - this.translations['default'] = realLocale;
203 - // Update the document fields that are not 'shared' with the original document.
204 - this.isNew = true;
205 - delete this.version;
206 - delete this.majorVersion;
207 - delete this.minorVersion;
208 - }
209 209   }
210 210   };
211 211  });
... ... @@ -222,7 +222,9 @@
222 222   'xwiki-events-bridge'
223 223  ], function($, xcontext, xwikiDocumentAPI) {
224 224   var preload = function() {
225 - paths.css.forEach(loadCSS);
158 + loadCSS($jsontool.serialize($xwiki.getSkinFile('js/xwiki/actionbuttons/actionButtons.css', true)));
159 + // Required in case the user needs to resolve merge conflicts on save.
160 + loadCSS($jsontool.serialize($xwiki.getSkinFile('uicomponents/viewers/diff.css', true)));
226 226   return initActionButtons();
227 227   };
228 228  
... ... @@ -242,12 +242,6 @@
242 242   });
243 243   };
244 244  
245 - var translatePage = function() {
246 - return editInPlace({
247 - afterEdit: createTranslation
248 - });
249 - };
250 -
251 251   var editSection = function(sectionId) {
252 252   return editInPlace({
253 253   lockFailed: function() {
... ... @@ -296,38 +296,26 @@
296 296   lockFailed: function() {}
297 297   }, options);
298 298   $('#xwikicontent').addClass('loading');
299 - // Lock the document first.
300 300   return lock(currentXWikiDocument).fail(options.lockFailed)
301 - // Then load the document only if we managed to lock it.
302 302   .then(load)
303 - // Then load the editors only if we managed to load the document.
304 304   .then(edit).done(options.afterEdit).always(function() {
305 305   $('#xwikicontent').removeClass('loading');
306 - // Then wait for an action (save, cancel, reload) only if the editors were loaded successfuly.
307 307   }).then(maybeSave)
308 - // Then unlock the document both when the edit ended with success and with a failure.
309 - .then(unlock, unlock)
310 - // Finally view the document both when the edit ended with success and with a failure.
311 - .then(view, view)
312 - .always(function(xwikiDocument) {
313 - // Update the current document for the next edit session.
314 - currentXWikiDocument = xwikiDocument;
315 - });
233 + .then(unlock)
234 + .then(view);
316 316   };
317 317  
318 318   var lock = function(xwikiDocument) {
319 - return xwikiDocument.lock().then(null, function(xwikiDocument) {
238 + return xwikiDocument.lock().then(null, function(response) {
239 + var confirmation = response.responseJSON;
320 320   // If the document was already locked then we need to ask the user if they want to force the lock.
321 - if (xwikiDocument.lockConfirmation) {
322 - var confirmation = xwikiDocument.lockConfirmation;
323 - delete xwikiDocument.lockConfirmation;
324 - return maybeForceLock(confirmation).then($.proxy(xwikiDocument, 'lock', 'edit', true), function() {
325 - // Cancel the edit action.
326 - return xwikiDocument;
327 - });
241 + if (response.status === 423 && confirmation) {
242 + return maybeForceLock(confirmation).then($.proxy(xwikiDocument, 'lock', 'edit', true));
328 328   } else {
329 - new XWiki.widgets.Notification(l10n['edit.inplace.page.lockFailed'], 'error');
330 - return xwikiDocument;
244 + new XWiki.widgets.Notification(
245 + $jsontool.serialize($services.localization.render('edit.inplace.page.lockFailed')),
246 + 'error'
247 + );
331 331   }
332 332   });
333 333   };
... ... @@ -375,7 +375,7 @@
375 375   '</div>',
376 376   '</div>'
377 377   ].join(''));
378 - modal.find('.close').attr('aria-label', l10n['edit.inplace.close']);
295 + modal.find('.close').attr('aria-label', $jsontool.serialize($services.localization.render('edit.inplace.close')));
379 379   modal.find('.modal-footer .btn-warning').click(function() {
380 380   // The user has confirmed they want to force the lock.
381 381   modal.data('deferred').resolve();
... ... @@ -400,7 +400,8 @@
400 400   renderedContent: $('#xwikicontent').html()
401 401   }, xwikiDocument);
402 402   }).fail(function() {
403 - new XWiki.widgets.Notification(l10n['edit.inplace.page.loadFailed'], 'error');
320 + new XWiki.widgets.Notification($jsontool.serialize($services.localization.render('edit.inplace.page.loadFailed')),
321 + 'error');
404 404   // Render the document for edit, in order to have the annotated content HTML. The annotations are used to protect
405 405   // the rendering transformations (e.g. macros) when editing the content.
406 406   }).then($.proxy(render, null, false));
... ... @@ -439,7 +439,7 @@
439 439   'xwiki:actions:save',
440 440   'xwiki:actions:reload',
441 441   'xwiki:actions:cancel',
442 - ].join(' '), '.xcontent.form', function(event, data) {
360 + ].join(' '), function(event, data) {
443 443   deferred.resolve({
444 444   name: event.type.substring('xwiki:actions:'.length),
445 445   document: xwikiDocument,
... ... @@ -450,18 +450,24 @@
450 450   };
451 451  
452 452   var save = function(data) {
453 - // Push the changes to the server.
454 - return push(data.document).then(function(xwikiDocument) {
371 + // Push the changes to the server then render the document for view. We need the view HTML both if we stop editing
372 + // now and if we continue but cancel the edit later.
373 + return push(data.document).then($.proxy(render, null, true)).then(function(xwikiDocument) {
455 455   // Save succeeded.
456 - return shouldReload(xwikiDocument).then(
457 - // The document was saved with merge and thus if we want to continue eding we need to reload the editor (because
458 - // its content doesn't match the saved content).
459 - reload,
460 - // No need to reload the editor because either the action was Save & View or there was no merge on save.
461 - $.proxy(maybeContinueEditing, null, data['continue'])
462 - );
463 - // Save failed. Continue editing because we may have unsaved content.
464 - }, maybeSave);
375 + if (data['continue']) {
376 + // Update the original version in order to be able to restore it on cancel.
377 + delete xwikiDocument.originalDocument;
378 + xwikiDocument.originalDocument = $.extend(true, {}, xwikiDocument);
379 + // Continue editing.
380 + return maybeSave(xwikiDocument);
381 + } else {
382 + // This is the final version.
383 + return xwikiDocument;
384 + }
385 + }, function(xwikiDocument) {
386 + // Save failed. Continue editing.
387 + return maybeSave(xwikiDocument);
388 + });
465 465   };
466 466  
467 467   var push = function(xwikiDocument) {
... ... @@ -472,35 +472,11 @@
472 472   // once it was resolved or rejected. So the first event that fires will resolve/reject the promise and the remaining
473 473   // event won't be able to change that. The remaining event listener could be called later but it won't have any
474 474   // effect.
475 - $(document).one('xwiki:document:saved', '.xcontent.form', $.proxy(deferred, 'resolve', xwikiDocument));
476 - $(document).one('xwiki:document:saveFailed', '.xcontent.form', $.proxy(deferred, 'reject', xwikiDocument));
477 - return deferred.promise();
399 + $(document).one('xwiki:document:saved', $.proxy(deferred, 'resolve', xwikiDocument));
400 + $(document).one('xwiki:document:saveFailed', $.proxy(deferred, 'reject', xwikiDocument));
401 + return deferred.promise().then($.proxy(xwikiDocument, 'reload'));
478 478   };
479 479  
480 - var maybeContinueEditing = function(continueEditing, xwikiDocument) {
481 - var afterReloadAndRender = function(success, xwikiDocument) {
482 - if (continueEditing) {
483 - if (success) {
484 - // Update the original version in order to be able to restore it on cancel.
485 - delete xwikiDocument.originalDocument;
486 - xwikiDocument.originalDocument = $.extend(true, {}, xwikiDocument);
487 - }
488 - // Continue editing.
489 - return maybeSave(xwikiDocument);
490 - } else {
491 - // This is the final version. We stop editing even if the reload / render failed.
492 - return xwikiDocument;
493 - }
494 - };
495 -
496 - // Reload the document JSON data (to have the new version) and render the document for view. We need the view HTML
497 - // both if we stop editing now and if we continue but cancel the edit later.
498 - return xwikiDocument.reload().then($.proxy(render, null, true)).then(
499 - $.proxy(afterReloadAndRender, null, /* success: */ true),
500 - $.proxy(afterReloadAndRender, null, /* success: */ false)
501 - );
502 - };
503 -
504 504   var cancel = function(xwikiDocument) {
505 505   // Simply return the original version to be restored.
506 506   return xwikiDocument.originalDocument;
... ... @@ -518,25 +518,6 @@
518 518   // Make sure we unlock the document when the user navigates to another page.
519 519   $(window).on('unload pagehide', $.proxy(unlock, null, currentXWikiDocument));
520 520  
521 - var shouldReload = function(xwikiDocument) {
522 - var reloadEventFired = false;
523 - $(document).one('xwiki:actions:reload.maybe', '.xcontent.form', function() {
524 - reloadEventFired = true;
525 - });
526 - var deferred = $.Deferred();
527 - // Wait a bit to see if the reload event is fired.
528 - setTimeout(function() {
529 - // Remove the listener in case the reload event wasn't fired.
530 - $(document).off('xwiki:actions:reload.maybe');
531 - if (reloadEventFired) {
532 - deferred.resolve(xwikiDocument);
533 - } else {
534 - deferred.reject(xwikiDocument);
535 - }
536 - }, 0);
537 - return deferred.promise();
538 - };
539 -
540 540   var reload = function(xwikiDocument) {
541 541   // Leave the edit mode and then re-enter.
542 542   return view(xwikiDocument, true).then(editInPlace);
... ... @@ -543,33 +543,21 @@
543 543   };
544 544  
545 545   var view = function(xwikiDocument, reload) {
546 - var viewContent = $('#xwikicontent');
547 547   // Destroy the editors before returning to view.
548 - viewContent.trigger('xwiki:actions:view', {document: xwikiDocument});
428 + $(document).trigger('xwiki:actions:view', {document: xwikiDocument});
549 549   $('#document-title h1').html(xwikiDocument.renderedTitle);
550 - viewContent.html(xwikiDocument.renderedContent);
430 + $('#xwikicontent').html(xwikiDocument.renderedContent);
551 551   if (!reload) {
552 552   // If the user has canceled the edit then the restored page content may include the section edit links. Show them
553 553   // in case they were hidden.
554 - viewContent.children(':header').children('.edit_section').removeClass('hidden');
434 + $('#xwikicontent').children(':header').children('.edit_section').removeClass('hidden');
555 555   // Let others know that the DOM has been updated, in order to enhance it.
556 - $(document).trigger('xwiki:dom:updated', {'elements': viewContent.toArray()});
436 + $(document).trigger('xwiki:dom:updated', {'elements': $('#xwikicontent').toArray()});
557 557   }
558 - // Remove the action events scope.
559 - viewContent.closest('.form').removeClass('form');
560 - // Update the URL.
561 - if (window.location.hash === '#edit' || window.location.hash === '#translate') {
562 - history.replaceState(null, null, '#');
563 - }
564 564   return $.Deferred().resolve(xwikiDocument).promise();
565 565   };
566 566  
567 567   var edit = function(xwikiDocument) {
568 - // By adding the 'form' CSS class we set the scope of the action events (e.g. xwiki:actions:beforeSave or
569 - // xwiki:actions:cancel). We need this because in view mode we can have multiple forms active on the page (e.g. one
570 - // for editing the document content in place and one for editing the document syntax in-place) and we don't want
571 - // them to interfere (e.g. canceling one form shouldn't cancel the other forms).
572 - $('#xwikicontent').closest('.xcontent').addClass('form');
573 573   return initActionButtons(xwikiDocument).then(initTitleEditor).then(initContentEditor)
574 574   .then(startRealTimeEditingSession);
575 575   };
... ... @@ -576,7 +576,7 @@
576 576  
577 577   var initActionButtons = function(xwikiDocument) {
578 578   if (xwikiDocument) {
579 - initTranslateButton(xwikiDocument);
448 + maybeShowTranslateButton(xwikiDocument);
580 580   }
581 581   var editContent = $('#xwikicontent');
582 582   // We need the wrapper because #xwikicontent uses Bootstrap grid (col-xs-12) which is implemented with CSS float.
... ... @@ -586,86 +586,59 @@
586 586   var actionButtonsWrapper = editContent.nextAll('.sticky-buttons-wrapper');
587 587   if (actionButtonsWrapper.length === 0) {
588 588   actionButtonsWrapper = $('<div class="sticky-buttons-wrapper col-xs-12">' +
589 - '<div class="inplace-editing-buttons sticky-buttons"/></div>').insertAfter(editContent).toggle(!!xwikiDocument);
590 - var actionButtons = actionButtonsWrapper.children('.sticky-buttons')
591 - .data('xwikiDocument', xwikiDocument)
592 - // Expose the fake form if an extension needs to manipulate it.
593 - .data('fakeForm', fakeForm);
458 + '<div class="inplace-editing-buttons sticky-buttons"/></div>').insertAfter(editContent);
459 + var actionButtons = actionButtonsWrapper.children('.sticky-buttons').data('xwikiDocument', xwikiDocument)
460 + .toggle(!!xwikiDocument);
594 594   return loadActionButtons(actionButtons);
595 595   } else {
596 596   // If we're editing a page..
597 597   if (xwikiDocument) {
598 598   // ..then make sure the action buttons are displayed right away (don't wait for the user to scroll).
599 - actionButtonsWrapper.show().children('.sticky-buttons')
600 - .data('xwikiDocument', xwikiDocument)
601 - // Expose the fake form if an extension needs to manipulate it.
602 - .data('fakeForm', fakeForm)
603 - // but make sure the position of the action buttons is updated.
604 - .trigger('xwiki:dom:refresh');
466 + actionButtonsWrapper.children('.sticky-buttons').data('xwikiDocument', xwikiDocument).show();
605 605   // The action buttons are disabled on Save & View. We don't reload the page on Save & View and we reuse the
606 606   // action buttons so we need to re-enable them each time we enter the edit mode.
607 607   fakeForm.enable();
470 + $(document).trigger('xwiki:dom:refresh');
608 608   }
609 609   return $.Deferred().resolve(xwikiDocument).promise();
610 610   }
611 611   };
612 612  
613 - var createTranslation = function(xwikiDocument) {
614 - xwikiDocument.translate();
615 - $('#document-title-input').focus().select();
616 - // Let the user know that they are now editing the translation of this page in the current locale.
617 - $('#document-title-input').popover({
618 - content: l10n['edit.inplace.page.translate.messageAfter'],
619 - placement: 'bottom',
620 - trigger: 'manual'
621 - }).popover('show').one('blur', function() {
622 - // Hide the popover when the title input loses the focus.
623 - $(this).popover('hide');
624 - });
476 + var maybeShowTranslateButton = function(xwikiDocument) {
477 + var xwikiDocumentLocale = xwikiDocument.getRealLocale();
478 + var uiLocale = $('html').attr('lang');
479 + if (xwikiDocumentLocale && xwikiDocumentLocale !== uiLocale) {
480 + $('#tmTranslate').off('click.translate').on('click.translate', function(event) {
481 + event.preventDefault();
482 + $(this).addClass('hidden');
483 + xwikiDocument.language = uiLocale;
484 + // Update the document translation fields that are not 'shared' with the original document.
485 + xwikiDocument.isNew = true;
486 + delete xwikiDocument.version;
487 + delete xwikiDocument.majorVersion;
488 + delete xwikiDocument.minorVersion;
489 + $('#document-title-input').focus().select();
490 + var message = $jsontool.serialize($services.localization.render('edit.inplace.page.translation',
491 + ['__locale__']));
492 + new XWiki.widgets.Notification(
493 + message.replace('__locale__', uiLocale),
494 + 'info'
495 + );
496 + }).removeClass('hidden');
497 + var message = $jsontool.serialize($services.localization.render('edit.inplace.page.original', ['__locale__']));
498 + new XWiki.widgets.Notification(
499 + message.replace('__locale__', xwikiDocumentLocale),
500 + 'info'
501 + );
502 + }
625 625   };
626 626  
627 - var initTranslateButton = function(xwikiDocument) {
628 - // Initialize the translate button only if it's visible.
629 - const translateButton = $(config.translateButtonSelector).filter('[data-toggle="popover"]').filter(':visible');
630 - translateButton.off('click.translate').on('click.translate', function(event) {
631 - event.preventDefault();
632 - translateButton.parent().addClass('hidden');
633 - createTranslation(xwikiDocument);
634 - // Let the user know that they are editing the original version of the page and not the translation corresponding
635 - // to the current locale because there isn't one created yet.
636 - }).attr({
637 - // Backup the initial popover message to be able to restore it on view.
638 - 'data-content-view': translateButton.attr('data-content'),
639 - // Use a custom popover message dedicated to the edit action.
640 - 'data-content': l10n['edit.inplace.page.translate.messageBefore']
641 - }).popover('show')
642 - // Hide the popover on the next click. The user can still see the message by hovering the translate button.
643 - .closest('html').one('click', function() {
644 - translateButton.popover('hide');
645 - });
646 - };
647 -
648 648   var loadActionButtons = function(actionButtons) {
649 - $(document).on('xwiki:actions:view', '.xcontent.form', function(event, data) {
650 - // Blur the action buttons first to re-enable the "disabled in inputs" shortcut keys (e.g. the page edit
651 - // shortcut), then disable the action buttons in order to disable their shortcut keys while we're not editing
652 - // in-place (e.g. prevent the Save shortcut while the user is only viewing the page). Finally hide the action
653 - // buttons to have them ready for the next editing session (the user can save or cancel and then edit again
654 - // without reloading the page).
655 - actionButtons.find(':input').blur().prop('disabled', true).end().parent().hide();
656 - // Restore the Translate button if the locale of the viewed document doesn't match the current user interface
657 - // locale (because the viewed document doesn't have a translation in the current locale).
658 - var xwikiDocumentLocale = data.document.getRealLocale();
659 - var uiLocale = $('html').attr('lang');
660 - if (xwikiDocumentLocale && xwikiDocumentLocale !== uiLocale) {
661 - const translateButton = $(config.translateButtonSelector).filter('[data-toggle="popover"]');
662 - // Restore the translation button behavior for view action.
663 - translateButton.off('click.translate')
664 - // Restore the popover text for view action.
665 - .attr('data-content', translateButton.attr('data-content-view') || translateButton.attr('data-content'))
666 - // Restore the visibility.
667 - .parent().removeClass('hidden');
668 - }
506 + $(document).on('xwiki:actions:view', function() {
507 + // Hide the action buttons and disable the shortcut keys (by disabling the buttons).
508 + actionButtons.hide().find(':input').prop('disabled', true);
509 + // Hide the translate button because it can be used only in edit mode for the moment.
510 + $('#tmTranslate').addClass('hidden');
669 669   });
670 670   return $.get(XWiki.currentDocument.getURL('get'), {
671 671   xpage: 'editactions'
... ... @@ -677,16 +677,10 @@
677 677   $('<input type="hidden" name="form_token" />').val(xcontext.form_token).appendTo(actionButtons);
678 678   // We need a place where actionButtons.js can add more hidden inputs.
679 679   actionButtons.append('<div class="hidden extra"/>');
680 - // Let the others know that the DOM has been updated, in order to enhance it.
681 - $(document).trigger('xwiki:dom:updated', {'elements': actionButtons.toArray()});
682 682   var deferred = $.Deferred();
683 - require(['xwiki-actionButtons', 'xwiki-diff', 'xwiki-autoSave'], function() {
523 + require(['actionButtons'], function() {
684 684   overrideEditActions();
685 685   overrideAjaxSaveAndContinue();
686 - // Activate the auto-save feature passing our fake edit form. Note that autosave.js also creates an instance of
687 - // AutoSave but it doesn't do anything because it doesn't find a real edit form in the page. This is why we have
688 - // to create our own instance of AutoSave passing the right (fake) form.
689 - new XWiki.editors.AutoSave({form: fakeForm});
690 690   var xwikiDocument = actionButtons.data('xwikiDocument');
691 691   // Enable the action buttons (and their shortcut keys) only if we're editing a document.
692 692   actionButtons.find(':input').prop('disabled', !xwikiDocument);
... ... @@ -694,7 +694,10 @@
694 694   });
695 695   return deferred.promise();
696 696   }, function() {
697 - new XWiki.widgets.Notification(l10n['edit.inplace.actionButtons.loadFailed'], 'error');
533 + new XWiki.widgets.Notification(
534 + $jsontool.serialize($services.localization.render('edit.inplace.actionButtons.loadFailed')),
535 + 'error'
536 + );
698 698   });
699 699   };
700 700  
... ... @@ -720,13 +720,6 @@
720 720   insert: function(element) {
721 721   this._getActionButtons().find('.hidden.extra').append(element);
722 722   },
723 - // Note that this method only works with single argument.
724 - append: function(element) {
725 - this.insert(element);
726 - },
727 - down: function(selector) {
728 - return this._getActionButtons().find(selector)[0];
729 - },
730 730   serialize: function() {
731 731   var extra = this._getActionButtons().find(':input').serializeArray().reduce(function(extra, entry) {
732 732   var value = extra[entry.name] || [];
... ... @@ -736,28 +736,20 @@
736 736   }, {});
737 737   var xwikiDocument = this._getActionButtons().data('xwikiDocument');
738 738   var formData = {
739 - title: xwikiDocument.rawTitle,
571 + title: xwikiDocument.title,
572 + content: xwikiDocument.renderedContent,
573 + RequiresHTMLConversion: 'content',
574 + content_syntax: xwikiDocument.syntax,
740 740   language: xwikiDocument.getRealLocale(),
741 741   isNew: xwikiDocument.isNew
742 742   };
743 - if (xwikiDocument.content != xwikiDocument.originalDocument.content) {
744 - // Submit the raw (source) content. No syntax conversion is needed in this case.
745 - formData.content = xwikiDocument.content;
746 - } else {
747 - // Submit the rendered content (HTML), but make sure it is converted to the document syntax on the server.
748 - $.extend(formData, {
749 - content: xwikiDocument.renderedContent,
750 - RequiresHTMLConversion: 'content',
751 - content_syntax: xwikiDocument.syntax
752 - });
753 - }
754 754   // Check for merge conflicts only if the document is not new and we know the current version.
755 755   if (!xwikiDocument.isNew && xwikiDocument.version) {
756 756   formData.previousVersion = xwikiDocument.version;
757 - formData.editingVersionDate = new Date(xwikiDocument.modified).getTime();
581 + // It would have been easier to send the timestamp but that's what the Save action expects.
582 + formData.editingVersionDate = new Date(xwikiDocument.modified).toISOString();
758 758   }
759 - // Ensure that formData information has priority over extra information.
760 - return $.extend({}, extra, formData);
584 + return $.extend(formData, extra);
761 761   }
762 762   };
763 763  
... ... @@ -779,21 +779,17 @@
779 779   var originalAjaxSaveAndContinue = $.extend({}, XWiki.actionButtons.AjaxSaveAndContinue.prototype);
780 780   $.extend(XWiki.actionButtons.AjaxSaveAndContinue.prototype, {
781 781   reloadEditor: function() {
782 - var actionButtons = $('.inplace-editing-buttons');
783 - if (actionButtons.is(':visible')) {
784 - actionButtons.trigger('xwiki:actions:reload');
606 + if ($('.inplace-editing-buttons').is(':visible')) {
607 + $(document).trigger('xwiki:actions:reload');
785 785   } else {
786 786   return originalAjaxSaveAndContinue.reloadEditor.apply(this, arguments);
787 787   }
788 788   },
789 - maybeRedirect: function(continueEditing) {
612 + maybeRedirect: function() {
790 790   if ($('.inplace-editing-buttons').is(':visible')) {
791 - // Overwrite the default behavior so that we don't redirect when leaving the edit mode because we're already
792 - // in view mode. We still need to report a redirect (return true) if we don't continue editing, so that
793 - // actionButtons.js behaves as if a redirect was done.
794 - return !continueEditing;
614 + // Never redirect when leaving the edit mode because we're already in view mode.
615 + return false;
795 795   } else {
796 - // Fallback on the default behavior if the in-place editing buttons are hidden.
797 797   return originalAjaxSaveAndContinue.maybeRedirect.apply(this, arguments);
798 798   }
799 799   }
... ... @@ -802,8 +802,8 @@
802 802  
803 803   var initTitleEditor = function(xwikiDocument) {
804 804   var label = $('<label for="document-title-input" class="sr-only"/>')
805 - .text(l10n['core.editors.content.titleField.label']);
806 - var input = $('<input type="text" id="document-title-input"/>').val(xwikiDocument.rawTitle);
625 + .text($jsontool.serialize($services.localization.render('core.editors.content.titleField.label')));
626 + var input = $('<input type="text" id="document-title-input"/>').val(xwikiDocument.title);
807 807   var placeholder = xwikiDocument.documentReference.name;
808 808   if (placeholder === 'WebHome') {
809 809   placeholder = xwikiDocument.documentReference.parent.name;
... ... @@ -810,13 +810,13 @@
810 810   }
811 811   input.attr('placeholder', placeholder);
812 812   $('#document-title h1').addClass('editable').empty().append([label, input]);
813 - $(document).on('xwiki:actions:beforeSave.titleEditor', '.xcontent.form', function(event) {
814 - xwikiDocument.rawTitle = input.val();
633 + $(document).on('xwiki:actions:beforeSave.titleEditor', function(event) {
634 + xwikiDocument.title = input.val();
815 815   });
816 - $(document).one('xwiki:actions:view', '.xcontent.form', function(event, data) {
636 + $(document).one('xwiki:actions:view', function(event, data) {
817 817   // Destroy the title editor.
818 818   $(document).off('xwiki:actions:beforeSave.titleEditor');
819 - $('#document-title h1').removeClass('editable').text(xwikiDocument.rawTitle);
639 + $('#document-title h1').removeClass('editable').text(xwikiDocument.title);
820 820   });
821 821   return xwikiDocument;
822 822   };
... ... @@ -833,24 +833,24 @@
833 833   // Keep the focus while the edit content is being prepared.
834 834   viewContent.focus();
835 835   }
836 - var data = $.extend({}, config, {
656 + var data = {
657 + contentType: 'org.xwiki.rendering.syntax.SyntaxContent',
658 + editMode: 'wysiwyg',
837 837   document: xwikiDocument,
838 838   // The content editor is loaded on demand, asynchronously.
839 839   deferred: $.Deferred()
840 - });
841 - editContent.trigger('xwiki:actions:edit', data);
842 - return data.deferred.done(function() {
662 + };
663 + var editContentPromise = data.deferred.promise();
664 + editContentPromise.done(function() {
843 843   editContent.show();
844 844   viewContent.remove();
845 845   if (withFocus) {
846 - // Restore the focus when the edit content is ready but make sure we don't scroll the page. We don't restore the
847 - // focus right away because we just made the content visible so it may not be editable yet (e.g. the WYSIWYG
848 - // editor can make the content editable only if it is visible).
849 - setTimeout(function() {
850 - editContent[0].focus({preventScroll: true});
851 - }, 0);
668 + // Restore the focus when the edit content is ready but make sure we don't scroll the page.
669 + editContent[0].focus({preventScroll: true});
852 852   }
853 - }).promise();
671 + });
672 + editContent.trigger('xwiki:actions:edit', data);
673 + return editContentPromise;
854 854   };
855 855  
856 856   var startRealTimeEditingSession = function(xwikiDocument) {
... ... @@ -869,21 +869,16 @@
869 869   };
870 870  
871 871   return {
872 - preload,
873 - editPage,
874 - editSection,
875 - translatePage
692 + preload: preload,
693 + editPage: editPage,
694 + editSection: editSection
876 876   };
877 877  });
878 878  
879 879  require(['jquery'], function($) {
880 - // We can edit in-place only if the #xwikicontent element is present.
881 - if (!$('#xwikicontent').length) {
882 - return;
883 - }
699 + var inplaceEditingConfig = $('div[data-inplace-editing-config]').data('inplaceEditingConfig') || {};
700 + var wysiwygEditorModule = 'xwiki-' + inplaceEditingConfig.wysiwygEditor + '-inline';
884 884  
885 - var wysiwygEditorModule = 'xwiki-' + config.wysiwygEditor + '-inline';
886 -
887 887   var preloadEditor = function() {
888 888   require(['editInPlace', wysiwygEditorModule], function(editInPlace) {
889 889   editInPlace.preload();
... ... @@ -900,186 +900,44 @@
900 900   });
901 901   }
902 902  
903 - var onInPlaceEditing = function(event) {
718 + var editButton = $('#tmEdit > a');
719 + editButton.on('click.inPlaceEditing', function(event) {
720 + event.preventDefault();
904 904   // Make sure the user doesn't try to re-activate the edit mode while we are in edit mode.
905 - if (editButton.hasClass('disabled')) {
906 - return;
907 - }
908 - // Disable the edit buttons and hide the section edit links.
909 - editButton.add(translateButton).addClass('disabled');
910 - $('#xwikicontent').children(':header').children('.edit_section').addClass('hidden');
722 + editButton.addClass('disabled');
723 + // Load the code needed to edit in place only when the edit button is clicked.
724 + require(['editInPlace', wysiwygEditorModule], function(editInPlace) {
725 + editInPlace.editPage().always(function() {
726 + editButton.removeClass('disabled');
727 + });
728 + // Fallback on the standalone edit mode if we fail to load the required modules.
729 + }, $.proxy(disableInPlaceEditing, event.target));
730 + });
731 +
732 + // Section in-place editing.
733 + $('#xwikicontent').on('click.inPlaceEditing', '> :header > a.edit_section:not(.disabled)', function(event) {
911 911   event.preventDefault();
912 - const handler = event.data;
913 - const data = handler.beforeEdit?.(event);
735 + // Make sure the user doesn't try to re-activate the edit mode while we are in edit mode.
736 + editButton.addClass('disabled');
737 + // Hide the section editing links and focus the content right away. We could have replaced the section editing icon
738 + // with a loading animation / spinner but giving instant visual feedback about what is going to happen is perceived
739 + // better by the users (it feels faster).
740 + $('#xwikicontent').attr('tabindex', '0').focus().children(':header').children('.edit_section').addClass('hidden');
741 + var heading = $(event.target).closest(':header');
914 914   // Load the code needed to edit in place only when the edit button is clicked.
915 915   require(['editInPlace', wysiwygEditorModule], function(editInPlace) {
916 - // Re-enable the translate button because it can be used while editing to create the missing translation.
917 - translateButton.removeClass('disabled');
918 - handler.edit(editInPlace, data).always(function() {
919 - // Restore only the edit button at the end because:
920 - // * the translate button is restored (if needed) by the editInPlace module
921 - // * the section edit links are restored when the document is rendered for view
744 + editInPlace.editSection(heading.attr('id')).always(function() {
922 922   editButton.removeClass('disabled');
923 923   });
924 924   // Fallback on the standalone edit mode if we fail to load the required modules.
925 925   }, $.proxy(disableInPlaceEditing, event.target));
926 - };
749 + });
927 927  
928 928   var disableInPlaceEditing = function() {
929 - editButton.add(translateButton).off('click.inPlaceEditing').removeClass('disabled');
752 + editButton.off('click.inPlaceEditing').removeClass('disabled');
930 930   $('#xwikicontent').off('click.inPlaceEditing').removeAttr('tabindex').children(':header').children('.edit_section')
931 931   .removeClass('hidden');
932 932   // Fallback on the standalone edit mode.
933 933   $(this).click();
934 934   };
935 -
936 - var editButton = $(config.editButtonSelector);
937 - editButton.on('click.inPlaceEditing', {
938 - beforeEdit: function() {
939 - history.replaceState(null, null, '#edit');
940 - },
941 - edit: function(editInPlace) {
942 - return editInPlace.editPage();
943 - }
944 - }, onInPlaceEditing).attr('data-editor', 'inplace');
945 -
946 - var translateButton = $(config.translateButtonSelector);
947 - translateButton.on('click.inPlaceEditing', {
948 - beforeEdit: function() {
949 - history.replaceState(null, null, '#translate');
950 - translateButton.parent().addClass('hidden');
951 - },
952 - edit: function(editInPlace) {
953 - return editInPlace.translatePage();
954 - }
955 - }, onInPlaceEditing);
956 -
957 - // Section in-place editing.
958 - $('#xwikicontent').on('click.inPlaceEditing', '> :header > a.edit_section:not(.disabled)', {
959 - beforeEdit: function(event) {
960 - // Focus the content right away to give the user instant visual feedback about what is going to happen.
961 - $('#xwikicontent').attr('tabindex', '0').focus();
962 - // Return the id of the edited section.
963 - return $(event.target).closest(':header').attr('id');
964 - },
965 - edit: function(editInPlace, sectionId) {
966 - return editInPlace.editSection(sectionId);
967 - }
968 - }, onInPlaceEditing);
969 -
970 - if (window.location.hash === '#edit') {
971 - editButton.click();
972 - } else if (window.location.hash === '#translate') {
973 - translateButton.click();
974 - }
975 975  });
976 -
977 -require(['jquery'], function($) {
978 - // Backup the document title before each editing session in order to catch changes.
979 - var previousPlainTitle;
980 - $('#xwikicontent').on('xwiki:actions:edit', function(event, data) {
981 - previousPlainTitle = data.document.getPlainTitle();
982 - });
983 -
984 - // Update the UI after each editing session.
985 - $(document).on('xwiki:actions:view', function(event, data) {
986 - var xwikiDocument = data.document;
987 - updateDocAuthorAndDate(xwikiDocument);
988 - updateDocExtraTabs(xwikiDocument);
989 - updateDrawer(xwikiDocument);
990 - updateContentMenu(xwikiDocument);
991 - if (xwikiDocument.getPlainTitle() !== previousPlainTitle) {
992 - updateDocTrees(xwikiDocument);
993 - updateLinks(xwikiDocument);
994 - }
995 - });
996 -
997 - var updateDocAuthorAndDate = function(xwikiDocument) {
998 - var urlWithSelector = xwikiDocument.getURL('get', 'xpage=contentheader') + ' .xdocLastModification';
999 - $('.xdocLastModification').load(urlWithSelector, function() {
1000 - // load() replaces the content of the specified container but we want to replace the container itself. We can't do
1001 - // this from the selector, e.g. by using '.xdocLastModification > *' because we lose the text nodes.
1002 - $(this).children().unwrap();
1003 - });
1004 - };
1005 -
1006 - var updateDocExtraTabs = function(xwikiDocument) {
1007 - // Reload the selected tab and force the reload of the hidden tabs next time they are selected.
1008 - $('#docextrapanes').children().addClass('empty').empty();
1009 - var selectedTab = $('#docExtraTabs .active[data-template]');
1010 - if (selectedTab.length) {
1011 - var docExtraId = selectedTab.attr('id');
1012 - docExtraId = docExtraId.substring(0, docExtraId.length - 'tab'.length);
1013 - XWiki.displayDocExtra(docExtraId, selectedTab.data('template'), false);
1014 - }
1015 - };
1016 -
1017 - // Update the document trees (e.g. breadcrumb, navigation) if they have nodes that correspond to the edited document.
1018 - // Note that we want to update the internal tree data not just the link label. This is especially useful if we're
1019 - // going to implement refactoring operations (rename) in the document tree.
1020 - var updateDocTrees = function(xwikiDocument) {
1021 - var plainTitle = xwikiDocument.getPlainTitle();
1022 - $('.jstree-xwiki').each(function() {
1023 - $(this).jstree?.(true)?.set_text?.('document:' + xwikiDocument.id, plainTitle);
1024 - });
1025 - };
1026 -
1027 - // Update the links that target the edited document and whose label matches the document title. Note that this can
1028 - // update links whose label was not generated dynamically (e.g. with server side scripting) based on the document
1029 - // title. For instance there could be links with hard-coded labels or with labels generated using a translatin key
1030 - // (like in the Applications panel). For simplicity we assume that if the link matches the document URL and its
1031 - // previous title then it needs to be updated, but this happens only at the UI level.
1032 - var updateLinks = function(xwikiDocument) {
1033 - var docURL = xwikiDocument.getURL();
1034 - var newPlainTitle = xwikiDocument.getPlainTitle();
1035 - // Exclude the links from the document content.
1036 - // Update the links that contain only text (no child elements) otherwise we can lose UI elements (e.g. icons).
1037 - $('a').not('#xwikicontent a').not(':has(*)').filter(function() {
1038 - var linkURL = $(this).attr('href')?.split(/[?#]/, 1)[0];
1039 - return linkURL === docURL && $(this).text() === previousPlainTitle;
1040 - }).text(newPlainTitle);
1041 - };
1042 -
1043 - // Update the list of available document translations in the drawer menu. This is needed for instance when a new
1044 - // translation is created using the in-place editor.
1045 - var updateDrawer = function(xwikiDocument) {
1046 - var languageMenu = $('#tmLanguages_menu');
1047 - var locale = xwikiDocument.getRealLocale();
1048 - // Look for the language query string parameter, either inside or at the end.
1049 - var localeSelector = 'a[href*="language=' + locale + '&"], a[href$="language=' + locale + '"]';
1050 - // Check if the language menu is present (multilingual is on) and the document locale is not listed.
1051 - if (languageMenu.length && !languageMenu.find(localeSelector).length) {
1052 - // If we get here then it means a new document translation was created and it needs to be listed in the drawer.
1053 - $('<div/>').load(xwikiDocument.getURL('get', $.param({
1054 - 'xpage': 'xpart',
1055 - 'vm': 'drawer.vm',
1056 - 'useLayoutVars': true
1057 - // Pass the query string from the current URL so that it gets included in the translation URL.
1058 - // XWIKI-11314: Changing the current language from the UI does not preserve the query string of the current URL
1059 - })) + '&' + location.search.substring(1) + ' #tmLanguages_menu', function() {
1060 - $(this).find('a').each(function() {
1061 - // Clean the query string.
1062 - $(this).attr('href', $(this).attr('href').replace(/&?(xpage=xpart|vm=drawer\.vm|useLayoutVars=true)/g, '')
1063 - .replace('?&', '?'));
1064 - });
1065 - languageMenu.replaceWith($(this).children());
1066 - });
1067 - }
1068 - };
1069 -
1070 - // Update the links from the content menu to point to the real document locale. This is needed especially when a new
1071 - // document translation is created in-place.
1072 - var updateContentMenu = function(xwikiDocument) {
1073 - var realLocale = xwikiDocument.getRealLocale();
1074 - var defaultLocale = xwikiDocument.getDefaultLocale();
1075 - if (realLocale != defaultLocale) {
1076 - var defaultLocaleRegex = new RegExp('(\\blanguage=)' + defaultLocale + '($|&|#)');
1077 - $('#contentmenu a[href*="language=' + defaultLocale + '"]').each(function() {
1078 - $(this).attr('href', $(this).attr('href').replace(defaultLocaleRegex, '$1' + realLocale + '$2'));
1079 - });
1080 - }
1081 - };
1082 -});
1083 -
1084 -})(JSON.parse(document.querySelector('[data-inplace-editing-config]')?.getAttribute('data-inplace-editing-config')) ||
1085 - {});
Parse content
... ... @@ -1,1 +1,1 @@
1 -No
1 +Yes
XWiki.StyleSheetExtension[0]
Code
... ... @@ -6,7 +6,6 @@
6 6   margin-bottom: @line-height-computed / 4;
7 7  }
8 8  
9 -@document-title-input-padding-vertical: @line-height-computed / 4 - 1;
10 10  input#document-title-input {
11 11   /* Preserve the heading styles. */
12 12   border: 1px solid transparent;
... ... @@ -13,10 +13,9 @@
13 13   box-shadow: none;
14 14   color: inherit;
15 15   font-size: inherit;
16 - /* It seems it's not enough to set the line height for the text input. We also need to set its height. */
17 - height: @font-size-document-title * @headings-line-height + 2 * (1 + @document-title-input-padding-vertical);
15 + height: auto;
18 18   line-height: @headings-line-height;
19 - padding: @document-title-input-padding-vertical (ceil(@grid-gutter-width / 2) - 1);
17 + padding: (@line-height-computed / 4 - 1) (ceil(@grid-gutter-width / 2) - 1);
20 20   width: 100%;
21 21  }
22 22  
... ... @@ -49,8 +49,3 @@
49 49  #xwikicontent {
50 50   padding-top: @line-height-computed * 0.75;
51 51  }
52 -
53 -.sticky-buttons-wrapper {
54 - /* Leave some space for the bottom box shadow of the editing area. */
55 - margin-top: 7px;
56 -}
XWiki.UIExtensionClass[0]
Executed Content
... ... @@ -1,67 +9,12 @@
1 -{{velocity output="false"}}
2 -## TODO: Remove this when XWIKI-18511 (Add support for passing a query string when calling getSkinFile) is implemented.
3 -#macro (getSkinFileWithParams $file $params)
4 -#set ($url = $xwiki.getSkinFile($file, true))
5 -$url#if ($url.contains('?'))&#else?#end$escapetool.url($params)
6 -#end
7 -{{/velocity}}
8 -
9 9  {{velocity}}
10 10  {{html clean="false"}}
11 -#if ($services.edit.document.inPlaceEditingEnabled() && $hasEdit && $xcontext.action == 'view' && !$doc.isNew())
3 +#if ($xcontext.action == 'view' && !$doc.isNew())
12 12   ## We support in-place editing only for the WYSIWYG edit mode ATM.
13 13   #getDefaultDocumentEditor($defaultEditMode)
14 14   #if ($defaultEditMode == 'wysiwyg')
15 - #set ($l10nKeys = [
16 - 'edit.inplace.page.renderFailed',
17 - 'edit.inplace.page.lockFailed',
18 - 'edit.inplace.close',
19 - 'edit.inplace.page.loadFailed',
20 - 'edit.inplace.actionButtons.loadFailed',
21 - 'core.editors.content.titleField.label',
22 - ['edit.inplace.page.translate.messageBefore', $doc.realLocale.getDisplayName($xcontext.locale),
23 - $xcontext.locale.getDisplayName($xcontext.locale)],
24 - ['edit.inplace.page.translate.messageAfter', $xcontext.locale.getDisplayName($xcontext.locale)]
25 - ])
26 - #set ($l10n = {})
27 - #foreach ($key in $l10nKeys)
28 - #set ($params = $key.subList(1, $key.size()))
29 - #if ($params)
30 - #set ($discard = $l10n.put($key[0], $services.localization.render($key[0], $params)))
31 - #else
32 - #set ($discard = $l10n.put($key, $services.localization.render($key)))
33 - #end
34 - #end
35 - ## See stylesheets.vm
36 - #set ($cssParams = {
37 - 'skin': $xwiki.skin,
38 - 'colorTheme': $services.model.serialize($themeDoc.documentReference, 'default')
39 - })
40 - #set ($jsParams = {'language': $xcontext.locale})
41 - ## We have to explicitly enable the source mode for in-line edit because the latest version of the content editor
42 - ## could be installed on an older version of XWiki where the in-place editor didn't support the source mode (so the
43 - ## content editor cannot enable the source mode by default).
44 44   #set ($inplaceEditingConfig = {
45 - 'contentType': 'org.xwiki.rendering.syntax.SyntaxContent',
46 46   'editMode': $defaultEditMode,
47 - 'wysiwygEditor': $services.edit.syntaxContent.defaultWysiwygEditor.descriptor.id,
48 - 'editButtonSelector': '#tmEdit > a',
49 - 'translateButtonSelector': '#tmTranslate > a',
50 - 'enableSourceMode': true,
51 - 'enableOfficeImport': $services.officemanager.isConnected(),
52 - 'paths': {
53 - 'js': {
54 - 'xwiki-actionButtons': "#getSkinFileWithParams('js/xwiki/actionbuttons/actionButtons.js' $jsParams)",
55 - 'xwiki-autoSave': "#getSkinFileWithParams('js/xwiki/editors/autosave.js' $jsParams)",
56 - 'xwiki-diff': $xwiki.getSkinFile('uicomponents/viewers/diff.js')
57 - },
58 - 'css': [
59 - "#getSkinFileWithParams('js/xwiki/actionbuttons/actionButtons.css' $cssParams)",
60 - "#getSkinFileWithParams('js/xwiki/editors/autosave.css' $cssParams)",
61 - "#getSkinFileWithParams('uicomponents/viewers/diff.css' $cssParams)"
62 - ]
63 - },
64 - 'l10n': $l10n
9 + 'wysiwygEditor': $services.edit.syntaxContent.defaultWysiwygEditor.descriptor.id
65 65   })
66 66   <div class="hidden" data-inplace-editing-config="$escapetool.xml($jsontool.serialize($inplaceEditingConfig))"></div>
67 67   ## We didn't move this to the file system because it uses LESS and we didn't want to include it in the skin.
XWiki.UIExtensionClass[1]
Cached
... ... @@ -1,0 +1,1 @@
1 +No
Asynchronous rendering
... ... @@ -1,0 +1,1 @@
1 +No
Executed Content
... ... @@ -1,0 +1,22 @@
1 +{{velocity}}
2 +{{html clean="false"}}
3 +## Output the translation button if all the following conditions are met:
4 +## * multilingual is on
5 +## * we're loading the original document version
6 +## * the original document version has a locale specified (it doesn't make sense to translate technical documents)
7 +## * the current UI locale doesn't match the original document locale
8 +#if ($xwiki.isMultiLingual() && $tdoc.realLocale == $doc.realLocale && "$!doc.realLocale" != ''
9 + && $doc.realLocale != $xcontext.locale)
10 + #set ($url = $doc.getURL('edit', $escapetool.url({'language': $xcontext.locale})))
11 + #set ($hint = $services.localization.render('edit.inplace.page.translate.hint',
12 + [$xcontext.locale.getDisplayName($xcontext.locale)]))
13 + ## We show the translate button only while editing in-place.
14 + <div class="btn-group hidden" id="tmTranslate">
15 + <a class="btn btn-default" href="$url" role="button" title="$escapetool.xml($hint)">
16 + $services.icon.renderHTML('translate')
17 + <span class="btn-label">$escapetool.xml($services.localization.render('edit.inplace.page.translate'))</span>
18 + </a>
19 + </div>
20 +#end
21 +{{/html}}
22 +{{/velocity}}
Extension Point ID
... ... @@ -1,0 +1,1 @@
1 +org.xwiki.plaftorm.menu.content
Extension ID
... ... @@ -1,0 +1,1 @@
1 +org.xwiki.plaftorm.menu.content.translate
Extension Parameters
... ... @@ -1,0 +1,1 @@
1 +order=5000
Extension Scope
... ... @@ -1,0 +1,1 @@
1 +wiki