Changes for page InplaceEditing
From version 6.1
edited by Nazzareno Pompei
on 19/01/2024 08:57
on 19/01/2024 08:57
Change comment:
Install extension [org.xwiki.platform:xwiki-platform-edit-ui/15.10.5]
To version 5.1
edited by Nazzareno Pompei
on 30/01/2023 10:31
on 30/01/2023 10:31
Change comment:
Install extension [org.xwiki.platform:xwiki-platform-edit-ui/14.10.3]
Summary
-
Objects (3 modified, 0 added, 0 removed)
Details
- XWiki.JavaScriptExtension[0]
-
- Code
-
... ... @@ -292,8 +292,7 @@ 292 292 options.afterEdit(xwikiDocument); 293 293 return xwikiDocument; 294 294 }).finally(() => { 295 - // Remove the aria-expanded attribute which is incorrect for role=textbox 296 - $('#xwikicontent').removeClass('loading').removeAttr('aria-expanded'); 295 + $('#xwikicontent').removeClass('loading'); 297 297 // Then wait for an action (save, cancel, reload) only if the editors were loaded successfuly. 298 298 }).then(maybeSave) 299 299 // Then unlock the document both when the edit ended with success and with a failure. ... ... @@ -581,41 +581,28 @@ 581 581 // Thus we need to use the grid for the sticky buttons also otherwise the postion is badly computed when scrolling 582 582 // (because of the float on the previous element). This wouldn't be needed if we were using position:sticky, which 583 583 // we can't use yet because it's not implemented on IE11 which we still have to support. 584 - let inplaceEditingForm = editContent.nextAll('form#inplace-editing'); 585 - if (!inplaceEditingForm.length) { 586 - // The 'xwikieditcontent' id is needed for the auto-save feature (otherwise it doesn't find the form). 587 - inplaceEditingForm = $(` 588 - <form id="inplace-editing" class="col-xs-12"> 589 - <div hidden> 590 - <input type="hidden" name="form_token" /> 591 - <input type="hidden" name="async" value="true" /> 592 - <input type="hidden" name="content" /> 593 - <input type="hidden" name="RequiresHTMLConversion" value="content" /> 594 - <input type="hidden" name="content_syntax" /> 595 - <input type="hidden" name="language" /> 596 - </div> 597 - <fieldset id="xwikieditcontent" class="xform inplace-editing-buttons sticky-buttons"></fieldset> 598 - </form> 599 - `).attr('action', XWiki.currentDocument.getURL('save')) 600 - .insertAfter(editContent).toggle(!!xwikiDocument); 601 - inplaceEditingForm.find('input[name="form_token"]').val(xcontext.form_token); 602 - var actionButtons = inplaceEditingForm.children('.sticky-buttons').data('xwikiDocument', xwikiDocument); 583 + var actionButtonsWrapper = editContent.nextAll('.sticky-buttons-wrapper'); 584 + if (actionButtonsWrapper.length === 0) { 585 + actionButtonsWrapper = $('<div class="sticky-buttons-wrapper col-xs-12">' + 586 + '<div class="inplace-editing-buttons sticky-buttons"/></div>').insertAfter(editContent).toggle(!!xwikiDocument); 587 + var actionButtons = actionButtonsWrapper.children('.sticky-buttons') 588 + .data('xwikiDocument', xwikiDocument) 589 + // Expose the fake form if an extension needs to manipulate it. 590 + .data('fakeForm', fakeForm); 603 603 return loadActionButtons(actionButtons); 604 604 } else { 605 605 // If we're editing a page.. 606 606 if (xwikiDocument) { 607 607 // ..then make sure the action buttons are displayed right away (don't wait for the user to scroll). 608 - inplaceEditingForm.show().children('.sticky-buttons')596 + actionButtonsWrapper.show().children('.sticky-buttons') 609 609 .data('xwikiDocument', xwikiDocument) 610 - // Make sure the position of the action buttons is updated. 611 - .trigger('xwiki:dom:refresh') 612 - // The action buttons are disabled on Save & View. We don't reload the page on Save & View and we reuse the 613 - // action buttons so we need to re-enable them each time we enter the edit mode. 614 - .prop('disabled', false); 615 - // Cleanup the extra hidden input fields that actionButtons.js might have appended to the form. We have to do 616 - // this each time the form is (re)enabled (i.e. after a failed Save & View or before entering the edit mode) 617 - // because they are designed to be used once. 618 - inplaceEditingForm.children('fieldset').nextAll().remove(); 598 + // Expose the fake form if an extension needs to manipulate it. 599 + .data('fakeForm', fakeForm) 600 + // but make sure the position of the action buttons is updated. 601 + .trigger('xwiki:dom:refresh'); 602 + // The action buttons are disabled on Save & View. We don't reload the page on Save & View and we reuse the 603 + // action buttons so we need to re-enable them each time we enter the edit mode. 604 + fakeForm.enable(); 619 619 } 620 620 return Promise.resolve(xwikiDocument); 621 621 } ... ... @@ -657,17 +657,6 @@ 657 657 }; 658 658 659 659 var loadActionButtons = function(actionButtons) { 660 - // We want to update the form data as late as possible (but still before the form is validated), in order to allow 661 - // the title and content editors to update their values and the 'xwikiDocument' instance. We do this by catching the 662 - // event early (lower in the DOM, at the start of the event bubbling phase) and adding a one time event listener for 663 - // the end of the event bubbling phase at the top level of the DOM document. 664 - actionButtons.on('xwiki:actions:beforeSave', function() { 665 - $(document).one('xwiki:actions:beforeSave', updateFormDataBeforeSave); 666 - }); 667 - actionButtons.on('xwiki:actions:cancel', function(event) { 668 - // We are already in view mode so there's no need to leave the page. 669 - event.preventDefault(); 670 - }); 671 671 $(document).on('xwiki:actions:view', '.xcontent.form', function(event, data) { 672 672 // Blur the action buttons first to re-enable the "disabled in inputs" shortcut keys (e.g. the page edit 673 673 // shortcut), then disable the action buttons in order to disable their shortcut keys while we're not editing ... ... @@ -674,7 +674,7 @@ 674 674 // in-place (e.g. prevent the Save shortcut while the user is only viewing the page). Finally hide the action 675 675 // buttons to have them ready for the next editing session (the user can save or cancel and then edit again 676 676 // without reloading the page). 677 - actionButtons.find(':input').blur(). end().prop('disabled', true).parent().hide();652 + actionButtons.find(':input').blur().prop('disabled', true).end().parent().hide(); 678 678 // Restore the Translate button if the locale of the viewed document doesn't match the current user interface 679 679 // locale (because the viewed document doesn't have a translation in the current locale). 680 680 var xwikiDocumentLocale = data.document.getRealLocale(); ... ... @@ -695,14 +695,23 @@ 695 695 actionButtons.html(html); 696 696 // Fix the name of the Save & View action. 697 697 actionButtons.find('.btn-primary').first().attr('name', 'action_save'); 673 + // Append the hidden input field that keeps the CSRF token. 674 + $('<input type="hidden" name="form_token" />').val(xcontext.form_token).appendTo(actionButtons); 675 + // We need a place where actionButtons.js can add more hidden inputs. 676 + actionButtons.append('<div class="hidden extra"/>'); 698 698 // Let the others know that the DOM has been updated, in order to enhance it. 699 699 $(document).trigger('xwiki:dom:updated', {'elements': actionButtons.toArray()}); 700 700 return new Promise((resolve, reject) => { 701 701 require(['xwiki-actionButtons', 'xwiki-diff', 'xwiki-autoSave'], function() { 681 + overrideEditActions(); 702 702 overrideAjaxSaveAndContinue(); 683 + // Activate the auto-save feature passing our fake edit form. Note that autosave.js also creates an instance of 684 + // AutoSave but it doesn't do anything because it doesn't find a real edit form in the page. This is why we have 685 + // to create our own instance of AutoSave passing the right (fake) form. 686 + new XWiki.editors.AutoSave({form: fakeForm}); 703 703 var xwikiDocument = actionButtons.data('xwikiDocument'); 704 704 // Enable the action buttons (and their shortcut keys) only if we're editing a document. 705 - actionButtons.prop('disabled', !xwikiDocument); 689 + actionButtons.find(':input').prop('disabled', !xwikiDocument); 706 706 resolve(xwikiDocument); 707 707 }); 708 708 }); ... ... @@ -711,30 +711,86 @@ 711 711 }); 712 712 }; 713 713 714 - var updateFormDataBeforeSave = function() { 715 - const form = $('form#inplace-editing'); 716 - const xwikiDocument = form.children('.sticky-buttons').data('xwikiDocument'); 698 + function addToFormData(formData, inputs) { 699 + inputs.serializeArray().forEach(function(entry) { 700 + formData.append(entry.name, entry.value); 701 + }); 702 + } 717 717 718 - form.find('input[name="language"]').val(xwikiDocument.getRealLocale()); 719 - form.find('input[name="isNew"]').val(xwikiDocument.isNew); 704 + // actionButtons.js expects a form so we use a fake form. Refactoring actionButtons.js is too dangerous ATM. 705 + var fakeForm = { 706 + action: XWiki.currentDocument.getURL('save'), 707 + async: true, 708 + _getActionButtons: function() { 709 + if (!this._actionButtons) { 710 + this._actionButtons = $('#xwikicontent').nextAll('.sticky-buttons-wrapper').children('.sticky-buttons'); 711 + } 712 + return this._actionButtons; 713 + }, 714 + disable: function() { 715 + this._getActionButtons().find(':input').prop('disabled', true); 716 + }, 717 + enable: function() { 718 + // Clear the extra hidden input fields, that actionButtons.js might have added, each time the form is (re)enabled 719 + // (i.e. after a failed Save & View or before entering the edit mode) because they are designed to be used once. 720 + this._getActionButtons().find('.hidden.extra').empty(); 721 + this._getActionButtons().find(':input').prop('disabled', false); 722 + }, 723 + insert: function(element) { 724 + this._getActionButtons().find('.hidden.extra').append(element); 725 + }, 726 + // Note that this method only works with single argument. 727 + append: function(element) { 728 + this.insert(element); 729 + }, 730 + down: function(selector) { 731 + return this._getActionButtons().find(selector)[0]; 732 + }, 733 + serialize: function() { 734 + var formData = new FormData(); 735 + addToFormData(formData, this._getActionButtons().find(':input')); 736 + // retrieve all input fields listing the temporary uploaded files. 737 + var xwikiDocument = this._getActionButtons().data('xwikiDocument'); 738 + formData.set('title', xwikiDocument.rawTitle); 739 + formData.set('language', xwikiDocument.getRealLocale()); 740 + formData.set('isNew', xwikiDocument.isNew); 720 720 721 - // Submit either the raw (source) content (no syntax conversion needed in this case) or the rendered content (HTML) 722 - // in which case we have to force the conversion to the document syntax on the server. 723 - const submitRawContent = xwikiDocument.content !== xwikiDocument.originalDocument.content; 724 - form.find('input[name="content"]').val(submitRawContent ? xwikiDocument.content : xwikiDocument.renderedContent); 725 - form.find('input[name="RequiresHTMLConversion"]').prop('disabled', submitRawContent); 726 - form.find('input[name="content_syntax"]').val(xwikiDocument.syntax).prop('disabled', submitRawContent); 742 + if (xwikiDocument.content !== xwikiDocument.originalDocument.content) { 743 + // Submit the raw (source) content. No syntax conversion is needed in this case. 744 + formData.set('content', xwikiDocument.content); 745 + } else { 746 + // Submit the rendered content (HTML), but make sure it is converted to the document syntax on the server. 747 + formData.set('content', xwikiDocument.renderedContent); 748 + formData.set('RequiresHTMLConversion', 'content'); 749 + formData.set('content_syntax', xwikiDocument.syntax); 750 + } 751 + // Add the temporary uploaded files to the form. 752 + addToFormData(formData, $('#xwikicontent').nextAll('input[name="uploadedFiles"]')); 727 727 728 - // Add thetemporaryuploadedfiles to theform.729 - $('#xwikicontent').nextAll('input[name="uploadedFiles"]').attr('form', 'inplace-editing');730 - 731 - //Checkforerge conflictsonly iftheocumentsnot new and wenow thecurrentversion.732 - if(!xwikiDocument.isNew&& xwikiDocument.version) {733 - for m.find('input[name="previousVersion"]').val(xwikiDocument.version);734 - form.find('input[name="editingVersionDate"]').val(newDate(xwikiDocument.modified).getTime());754 + // Check for merge conflicts only if the document is not new and we know the current version. 755 + if (!xwikiDocument.isNew && xwikiDocument.version) { 756 + formData.set('previousVersion', xwikiDocument.version); 757 + formData.set('editingVersionDate', new Date(xwikiDocument.modified).getTime()); 758 + } 759 + // Note: if you refactor, please make sure that actionsButtons inputs have the lowest priority. 760 + return formData; 735 735 } 736 736 }; 737 737 764 + var overrideEditActions = function() { 765 + // Override the EditActions.notify() function in order to pass a fake form in the event parameters. 766 + var originalNotify = XWiki.actionButtons.EditActions.prototype.notify; 767 + XWiki.actionButtons.EditActions.prototype.notify = function(originalEvent, action, params) { 768 + if (params && $(originalEvent.element()).closest('.inplace-editing-buttons').length > 0) { 769 + // actionButtons.js expects a form so we use a fake form. Refactoring actionButtons.js is too dangerous ATM. 770 + // Note that we do this only when the event has parameters because we want to exclude the cancel event for which 771 + // actionButtons.js changes the window location if a form is specified, and we want to prevent that. 772 + params.form = fakeForm; 773 + } 774 + return originalNotify.apply(this, arguments); 775 + }; 776 + }; 777 + 738 738 var overrideAjaxSaveAndContinue = function() { 739 739 var originalAjaxSaveAndContinue = $.extend({}, XWiki.actionButtons.AjaxSaveAndContinue.prototype); 740 740 $.extend(XWiki.actionButtons.AjaxSaveAndContinue.prototype, { ... ... @@ -741,13 +741,7 @@ 741 741 reloadEditor: function() { 742 742 var actionButtons = $('.inplace-editing-buttons'); 743 743 if (actionButtons.is(':visible')) { 744 - // This function is called after the document save confirmation is received, if the save was done by merge. We 745 - // register our reload listener from a document saved listener, but we're using promises which are 746 - // asynchronous so the reload listener is actually registered with a delay. For this reason we trigger the 747 - // reload event with a delay to ensure our reload listener is called. 748 - setTimeout(function() { 749 - actionButtons.trigger('xwiki:actions:reload'); 750 - }, 0); 784 + actionButtons.trigger('xwiki:actions:reload'); 751 751 } else { 752 752 return originalAjaxSaveAndContinue.reloadEditor.apply(this, arguments); 753 753 } ... ... @@ -769,20 +769,12 @@ 769 769 var initTitleEditor = function(xwikiDocument) { 770 770 var label = $('<label for="document-title-input" class="sr-only"/>') 771 771 .text(l10n['core.editors.content.titleField.label']); 772 - var input = $('<input type="text" id="document-title-input" name="title" form="inplace-editing" />') 773 - .val(xwikiDocument.rawTitle); 774 - if (config.titleIsMandatory) { 775 - input.attr({ 776 - 'required': '', 777 - 'data-validation-value-missing': l10n['core.validation.required.message'] 778 - }); 779 - } else { 780 - var placeholder = xwikiDocument.documentReference.name; 781 - if (placeholder === 'WebHome') { 782 - placeholder = xwikiDocument.documentReference.parent.name; 783 - } 784 - input.attr('placeholder', placeholder); 806 + var input = $('<input type="text" id="document-title-input"/>').val(xwikiDocument.rawTitle); 807 + var placeholder = xwikiDocument.documentReference.name; 808 + if (placeholder === 'WebHome') { 809 + placeholder = xwikiDocument.documentReference.parent.name; 785 785 } 811 + input.attr('placeholder', placeholder); 786 786 $('#document-title h1').addClass('editable').empty().append([label, input]); 787 787 $(document).on('xwiki:actions:beforeSave.titleEditor', '.xcontent.form', function(event) { 788 788 xwikiDocument.rawTitle = input.val(); ... ... @@ -882,10 +882,6 @@ 882 882 } 883 883 // Disable the edit buttons and hide the section edit links. 884 884 editButton.add(translateButton).addClass('disabled'); 885 - editButton.attr('aria-disabled', 'true'); 886 - var reference = editButton.attr('href'); 887 - editButton.removeAttr('href'); 888 - editButton.attr('role', 'link'); 889 889 $('#xwikicontent').children(':header').children('.edit_section').addClass('hidden'); 890 890 event.preventDefault(); 891 891 const handler = event.data; ... ... @@ -899,9 +899,6 @@ 899 899 // * the translate button is restored (if needed) by the editInPlace module 900 900 // * the section edit links are restored when the document is rendered for view 901 901 editButton.removeClass('disabled'); 902 - editButton.removeAttr('aria-disabled'); 903 - editButton.removeAttr('role'); 904 - editButton.attr('href', reference); 905 905 }); 906 906 // Fallback on the standalone edit mode if we fail to load the required modules. 907 907 }, disableInPlaceEditing.bind(event.target));
- XWiki.StyleSheetExtension[0]
-
- Code
-
... ... @@ -9,9 +9,10 @@ 9 9 @document-title-input-padding-vertical: @line-height-computed / 4 - 1; 10 10 input#document-title-input { 11 11 /* Preserve the heading styles. */ 12 + border: 1px solid transparent; 13 + box-shadow: none; 12 12 color: inherit; 13 13 font-size: inherit; 14 - background-color: @body-bg; 15 15 /* It seems it's not enough to set the line height for the text input. We also need to set its height. */ 16 16 height: @font-size-document-title * @headings-line-height + 2 * (1 + @document-title-input-padding-vertical); 17 17 line-height: @headings-line-height; ... ... @@ -18,16 +18,12 @@ 18 18 padding: @document-title-input-padding-vertical (ceil(@grid-gutter-width / 2) - 1); 19 19 width: 100%; 20 20 } 21 -input#document-title-input:valid { 22 - border: 1px solid transparent; 23 - box-shadow: none; 24 -} 25 25 26 -input#document-title-input: valid:hover {23 +input#document-title-input:hover { 27 27 border-color: @input-border; 28 28 } 29 29 30 -input#document-title-input: valid:focus,27 +input#document-title-input:focus, 31 31 #xwikicontent[contenteditable]:focus, 32 32 #xwikicontent[tabindex]:focus { 33 33 .form-control-focus(); ... ... @@ -53,7 +53,7 @@ 53 53 padding-top: @line-height-computed * 0.75; 54 54 } 55 55 56 - form#inplace-editing{53 +.sticky-buttons-wrapper { 57 57 /* Leave some space for the bottom box shadow of the editing area. */ 58 58 margin-top: 7px; 59 59 }
- XWiki.UIExtensionClass[0]
-
- Executed Content
-
... ... @@ -19,7 +19,6 @@ 19 19 'edit.inplace.page.loadFailed', 20 20 'edit.inplace.actionButtons.loadFailed', 21 21 'core.editors.content.titleField.label', 22 - 'core.validation.required.message', 23 23 ['edit.inplace.page.translate.messageBefore', $doc.realLocale.getDisplayName($xcontext.locale), 24 24 $xcontext.locale.getDisplayName($xcontext.locale)], 25 25 ['edit.inplace.page.translate.messageAfter', $xcontext.locale.getDisplayName($xcontext.locale)] ... ... @@ -63,7 +63,6 @@ 63 63 }, 64 64 'l10n': $l10n 65 65 }) 66 - #set ($inplaceEditingConfig.titleIsMandatory = $xwiki.getSpacePreference('xwiki.title.mandatory') == 1) 67 67 <div class="hidden" data-inplace-editing-config="$escapetool.xml($jsontool.serialize($inplaceEditingConfig))"></div> 68 68 ## We didn't move this to the file system because it uses LESS and we didn't want to include it in the skin. 69 69 #set ($discard = $xwiki.ssx.use('XWiki.InplaceEditing'))