Changes for page InplaceEditing
Last modified by SuperNico Laub on 2025/09/18 17:55
From version 1.1
edited by superadmin
on 2025/05/22 17:45
on 2025/05/22 17:45
Change comment:
Install extension [org.xwiki.platform:xwiki-platform-edit-ui/16.10.8]
To version 2.1
edited by SuperNico Laub
on 2025/09/18 17:55
on 2025/09/18 17:55
Change comment:
Install extension [org.xwiki.platform:xwiki-platform-edit-ui/17.7.0]
Summary
-
Page properties (1 modified, 0 added, 0 removed)
-
Objects (3 modified, 0 added, 0 removed)
Details
- Page properties
-
- Author
-
... ... @@ -1,1 +1,1 @@ 1 -XWiki.super admin1 +xwiki:XWiki.supernico
- XWiki.JavaScriptExtension[0]
-
- Code
-
... ... @@ -92,12 +92,12 @@ 92 92 /** 93 93 * Render the title and the content of this document. 94 94 * 95 - * @param forView whether to render thedocumentforview(without the renderingannotations) or for edit (with the96 - * rendering annotations); whenrendering for edit some transformationsmightnotbeexecuted95 + * @param {object} params - optional rendering parameters, that may include the output syntax and the rendering 96 + * transformations to execute 97 97 * @return a promise that resolves to this document instance if the render request succeeds 98 98 */ 99 - render(for View) {100 - varqueryString = {99 + render({outputSyntax, transformations} = {}) { 100 + const queryString = { 101 101 xpage: 'get', 102 102 outputTitle: true, 103 103 // Render the default translation if this is a new document: ... ... @@ -108,21 +108,21 @@ 108 108 // Make sure the response is not retrieved from cache (IE11 doesn't obey the caching HTTP headers). 109 109 timestamp: new Date().getTime() 110 110 }; 111 - if (!forView) { 112 - // We need the annotated HTML when editing in order to be able to protect the rendering transformations and to 113 - // be able to recreate the wiki syntax. 114 - queryString.outputSyntax = 'annotatedhtml'; 115 - queryString.outputSyntaxVersion = '5.0' 116 - // Currently, only the macro transformations are protected and thus can be edited. 117 - // See XRENDERING-78: Add markers to modified XDOM by Transformations/Macros 118 - queryString.transformations = 'macro'; 111 + if (outputSyntax) { 112 + queryString.outputSyntax = outputSyntax.type; 113 + queryString.outputSyntaxVersion = outputSyntax.version; 119 119 } 120 - return Promise.resolve($.get(this.getURL('view'), queryString)).then(html => { 115 + if (transformations) { 116 + queryString.transformations = transformations; 117 + } 118 + return Promise.resolve($.get(this.getURL('view'), $.param(queryString, true))).then(html => { 121 121 // Render succeeded. 122 - var container = $('<div/>').html(html); 120 + const container = $('<div/>').html(html); 121 + const contentWrapper = container.find('#xwikicontent'); 122 + const isContentRenderedToHTML = !outputSyntax || outputSyntax.type.includes('html'); 123 123 return $.extend(this, { 124 124 renderedTitle: container.find('#document-title h1').html(), 125 - renderedContent: container.find('#xwikicontent').html()125 + renderedContent: isContentRenderedToHTML ? contentWrapper.html() : contentWrapper.text() 126 126 }); 127 127 }).catch(() => { 128 128 new XWiki.widgets.Notification(l10n['edit.inplace.page.renderFailed'], 'error'); ... ... @@ -195,7 +195,6 @@ 195 195 lockAction: action, 196 196 force: force, 197 197 language: this.getRealLocale(), 198 - outputSyntax: 'plain', 199 199 // Make sure the response is not retrieved from cache (IE11 doesn't obey the caching HTTP headers). 200 200 timestamp: new Date().getTime() 201 201 })).then(() => { ... ... @@ -485,18 +485,18 @@ 485 485 }).catch(xwikiDocument => { 486 486 new XWiki.widgets.Notification(l10n['edit.inplace.page.loadFailed'], 'error'); 487 487 return Promise.reject(xwikiDocument); 488 - // Render the document for edit, in ordertohavetheannotated contentHTML. Theannotations areused toprotect489 - // therenderingtransformations(e.g. macros) when editing the content.490 - }).then(render.bind(null, false));487 + // Render the document for edit, using the provided rendering configuration, which may depend on the editor used to 488 + // edit the content. 489 + }).then(render.bind(null, module.contentEditor.getRenderingConfig())); 491 491 }; 492 492 493 493 /** 494 - * @param forView whether to render thedocumentforview(without the renderingannotations) or for edit (with the495 - * rendering annotations); whenrendering for edit some transformationsmightnotbeexecuted493 + * @param {object} params - optional rendering parameters, that may include the output syntax and the rendering 494 + * transformations to execute 496 496 * @param xwikiDocument the document to render 497 497 */ 498 - var render = function( forView, xwikiDocument) {499 - return xwikiDocument.render( forView);497 + var render = function(params, xwikiDocument) { 498 + return xwikiDocument.render(params); 500 500 }; 501 501 502 502 var maybeSave = function(xwikiDocument) { ... ... @@ -585,7 +585,7 @@ 585 585 586 586 // Reload the document JSON data (to have the new version) and render the document for view. We need the view HTML 587 587 // both if we stop editing now and if we continue but cancel the edit later. 588 - return xwikiDocument.reload().then(render.bind(null, true)).then(587 + return xwikiDocument.reload().then(render.bind(null, {})).then( 589 589 afterReloadAndRender.bind(null, /* success: */ true), 590 590 afterReloadAndRender.bind(null, /* success: */ false) 591 591 ); ... ... @@ -634,7 +634,7 @@ 634 634 return view(xwikiDocument, true).then(editInPlace); 635 635 }; 636 636 637 - varview =function(xwikiDocument, reload) {636 + async function view(xwikiDocument, reload) { 638 638 if (xwikiDocument.isNew && xwikiDocument.language && xwikiDocument.defaultTranslation) { 639 639 // The user tried to translate the current document in the UI locale and either canceled the edit without saving 640 640 // or there was an error. Display the default translation to the user. ... ... @@ -641,29 +641,51 @@ 641 641 xwikiDocument = xwikiDocument.defaultTranslation; 642 642 } 643 643 644 - var viewContent = $('#xwikicontent'); 645 - // Destroy the editors before returning to view. 646 - viewContent.trigger('xwiki:actions:view', {document: xwikiDocument}); 643 + const viewContent = $('#xwikicontent'); 644 + // Destroy the editors and restore the view mode. 645 + const promises = []; 646 + viewContent.trigger('xwiki:actions:view', {document: xwikiDocument, promises}); 647 + // Some listeners may need to perform asynchronous operations. We wait for them, but we restore the view mode even 648 + // if they fail. 649 + logRejectedPromises( 650 + 'We encountered some errors while restoring the view mode: ', 651 + await Promise.allSettled(promises) 652 + ); 647 647 $('#document-title h1').html(xwikiDocument.renderedTitle); 648 648 viewContent.html(xwikiDocument.renderedContent); 655 + 649 649 // Reset the editor to let others know that we're not editing anymore. 650 650 XWiki.editor = ''; 658 + 651 651 if (!reload) { 652 652 // If the user has canceled the edit then the restored page content may include the section edit links. Show them 653 653 // in case they were hidden. 654 654 viewContent.children(':header').children('.edit_section').removeClass('hidden'); 663 + 655 655 // Let others know that the DOM has been updated, in order to enhance it. 656 656 $(document).trigger('xwiki:dom:updated', {'elements': viewContent.toArray()}); 657 657 } 667 + 658 658 // Remove the action events scope. 659 659 viewContent.closest('.form').removeClass('form'); 670 + 660 660 // Update the URL. 661 661 if (window.location.hash === '#edit' || window.location.hash === '#translate') { 662 662 history.replaceState(null, null, '#'); 663 663 } 664 - return Promise.resolve(xwikiDocument); 665 - }; 666 666 676 + return xwikiDocument; 677 + } 678 + 679 + function logRejectedPromises(message, results) { 680 + const errors = results 681 + .filter(result => result.status === 'rejected') 682 + .map(result => result.reason); 683 + if (errors.length) { 684 + console.warn(message, errors); 685 + } 686 + } 687 + 667 667 var edit = function(xwikiDocument) { 668 668 // By adding the 'form' CSS class we set the scope of the action events (e.g. xwiki:actions:beforeSave or 669 669 // xwiki:actions:cancel). We need this because in view mode we can have multiple forms active on the page (e.g. one ... ... @@ -692,9 +692,6 @@ 692 692 <div hidden> 693 693 <input type="hidden" name="form_token" /> 694 694 <input type="hidden" name="async" value="true" /> 695 - <input type="hidden" name="content" /> 696 - <input type="hidden" name="RequiresHTMLConversion" value="content" /> 697 - <input type="hidden" name="content_syntax" /> 698 698 <input type="hidden" name="language" /> 699 699 </div> 700 700 <fieldset id="xwikieditcontent" class="xform inplace-editing-buttons sticky-buttons"></fieldset> ... ... @@ -815,16 +815,6 @@ 815 815 form.find('input[name="language"]').val(xwikiDocument.getRealLocale()); 816 816 form.find('input[name="isNew"]').val(xwikiDocument.isNew); 817 817 818 - // Submit either the raw (source) content (no syntax conversion needed in this case) or the rendered content (HTML) 819 - // in which case we have to force the conversion to the document syntax on the server. 820 - const submitRawContent = typeof xwikiDocument.renderedContent !== 'string'; 821 - form.find('input[name="content"]').val(submitRawContent ? xwikiDocument.content : xwikiDocument.renderedContent); 822 - form.find('input[name="RequiresHTMLConversion"]').prop('disabled', submitRawContent); 823 - form.find('input[name="content_syntax"]').val(xwikiDocument.syntax).prop('disabled', submitRawContent); 824 - 825 - // Add the temporary uploaded files to the form. 826 - $('#xwikicontent').nextAll('input[name="uploadedFiles"]').attr('form', 'inplace-editing'); 827 - 828 828 // Check for merge conflicts only if the document is not new and we know the current version. 829 829 if (!xwikiDocument.isNew && xwikiDocument.version) { 830 830 form.find('input[name="previousVersion"]').val(xwikiDocument.version); ... ... @@ -836,14 +836,13 @@ 836 836 var originalAjaxSaveAndContinue = $.extend({}, XWiki.actionButtons.AjaxSaveAndContinue.prototype); 837 837 $.extend(XWiki.actionButtons.AjaxSaveAndContinue.prototype, { 838 838 reloadEditor: function() { 839 - var actionButtons = $('.inplace-editing-buttons'); 840 - if (actionButtons.is(':visible')) { 847 + if (XWiki.editor === config.editMode) { 841 841 // This function is called after the document save confirmation is received, if the save was done by merge. We 842 842 // register our reload listener from a document saved listener, but we're using promises which are 843 843 // asynchronous so the reload listener is actually registered with a delay. For this reason we trigger the 844 844 // reload event with a delay to ensure our reload listener is called. 845 845 setTimeout(function() { 846 - acti onButtons.trigger('xwiki:actions:reload');853 + $('.inplace-editing-buttons').trigger('xwiki:actions:reload'); 847 847 }, 0); 848 848 } else { 849 849 return originalAjaxSaveAndContinue.reloadEditor.apply(this, arguments); ... ... @@ -850,13 +850,13 @@ 850 850 } 851 851 }, 852 852 maybeRedirect: function(continueEditing) { 853 - if ( $('.inplace-editing-buttons').is(':visible')){860 + if (XWiki.editor === config.editMode) { 854 854 // Overwrite the default behavior so that we don't redirect when leaving the edit mode because we're already 855 855 // in view mode. We still need to report a redirect (return true) if we don't continue editing, so that 856 856 // actionButtons.js behaves as if a redirect was done. 857 857 return !continueEditing; 858 858 } else { 859 - // Fallback on the default behavior if thein-placeeditingbuttonsarehidden.866 + // Fallback on the default behavior if we're not editing in-place. 860 860 return originalAjaxSaveAndContinue.maybeRedirect.apply(this, arguments); 861 861 } 862 862 } ... ... @@ -899,9 +899,7 @@ 899 899 var withFocus = document.activeElement && document.activeElement === editContent[0]; 900 900 // Keep showing the view content until the edit content is ready in order to avoid UI flicker. 901 901 var viewContent = editContent.clone().insertAfter(editContent); 902 - // Note that we don't trigger the xwiki:dom:updated event here because we want to let the editor trigger the event 903 - // only for the content areas that are safe to be updated from JavaScript (i.e. the macro output). 904 - editContent.hide().html(xwikiDocument.renderedContent); 909 + editContent.hide().empty(); 905 905 if (withFocus) { 906 906 // Keep the focus while the edit content is being prepared. 907 907 viewContent.focus(); ... ... @@ -910,6 +910,8 @@ 910 910 // Use the same name as for the standalone editor, in order to be consistent. 911 911 editorName: 'content', 912 912 document: xwikiDocument, 918 + startupFocus: withFocus, 919 + formId: 'inplace-editing', 913 913 // The content editor is loaded on demand, asynchronously. 914 914 deferred: $.Deferred() 915 915 }); ... ... @@ -917,14 +917,6 @@ 917 917 return data.deferred.promise().then(() => { 918 918 editContent.show(); 919 919 viewContent.remove(); 920 - if (withFocus) { 921 - // Restore the focus when the edit content is ready but make sure we don't scroll the page. We don't restore the 922 - // focus right away because we just made the content visible so it may not be editable yet (e.g. the WYSIWYG 923 - // editor can make the content editable only if it is visible). 924 - setTimeout(function() { 925 - editContent[0].focus({preventScroll: true}); 926 - }, 0); 927 - } 928 928 return xwikiDocument; 929 929 }); 930 930 }; ... ... @@ -939,12 +939,15 @@ 939 939 } 940 940 }; 941 941 942 - return { 941 + let module = { 942 + contentEditor: undefined, 943 943 preload, 944 944 editPage, 945 945 editSection, 946 946 translatePage 947 947 }; 948 + 949 + return module; 948 948 }); 949 949 950 950 require(['jquery'], function($) { ... ... @@ -953,16 +953,15 @@ 953 953 return; 954 954 } 955 955 956 - var wysiwygEditorModule = 'xwiki-' + config.wysiwygEditor + '-inline'; 957 - 958 958 var preloadEditor = function() { 959 - require(['editInPlace', wysiwygEditorModule], function(editInPlace) { 959 + require(['editInPlace', config.contentEditor], function(editInPlace, contentEditor) { 960 + editInPlace.contentEditor = contentEditor; 960 960 editInPlace.preload(); 961 961 // Fallback on the standalone edit mode if we fail to load the required modules. 962 962 }, disableInPlaceEditing); 963 963 }; 964 964 965 - // Preload the WYSIWYGeditor code without slowing down the page view.966 + // Preload the content editor code without slowing down the page view. 966 966 if (document.readyState === 'complete') { 967 967 setTimeout(preloadEditor, 0); 968 968 } else { ... ... @@ -1016,7 +1016,8 @@ 1016 1016 const data = handler.beforeEdit?.(event); 1017 1017 // Load the code needed to edit in place only when the edit button is clicked. 1018 1018 currentlyEditing = new Promise((resolve, reject) => { 1019 - require(['editInPlace', wysiwygEditorModule], (editInPlace) => { 1020 + require(['editInPlace', config.contentEditor], (editInPlace, contentEditor) => { 1021 + editInPlace.contentEditor = contentEditor; 1020 1020 // Re-enable the translate button because it can be used while editing to create the missing translation. 1021 1021 translateButton.removeClass('disabled'); 1022 1022 handler.edit(editInPlace, data).finally(() => {
- XWiki.StyleSheetExtension[0]
-
- Code
-
... ... @@ -1,29 +27,3 @@ 1 -#document-title h1.editable { 2 - /* Move the title heading a bit to the top and to the left in order to accomodate the input border and padding. */ 3 - margin-top: -@line-height-computed / 4; 4 - margin-left: -(ceil(@grid-gutter-width / 2)); 5 - /* Reduce the bottom margin in order to accomodate the input border and bottom padding. */ 6 - margin-bottom: @line-height-computed / 4; 7 -} 8 - 9 -@document-title-input-padding-vertical: @line-height-computed / 4 - 1; 10 -.document-header input#document-title-input { 11 - /* Preserve the heading styles. */ 12 - color: inherit; 13 - font-size: inherit; 14 - background-color: transparent; 15 - /* It seems it's not enough to set the line height for the text input. We also need to set its height. */ 16 - height: ~"calc(@{font-size-document-title} * @{headings-line-height} - -2 * (1px - -@{document-title-input-padding-vertical}))"; 17 - line-height: @headings-line-height; 18 - padding: @document-title-input-padding-vertical (ceil(@grid-gutter-width / 2) - 1); 19 - width: 100%; 20 -} 21 -input#document-title-input:valid { 22 - border: 1px solid transparent; 23 - box-shadow: none; 24 -} 25 - 26 -input#document-title-input:valid:focus, 27 27 #xwikicontent[contenteditable]:focus, 28 28 #xwikicontent[tabindex]:focus { 29 29 outline: 0;
- XWiki.UIExtensionClass[0]
-
- Executed Content
-
... ... @@ -39,16 +39,12 @@ 39 39 'colorTheme': $services.model.serialize($themeDoc.documentReference, 'default') 40 40 }) 41 41 #set ($jsParams = {'language': $xcontext.locale}) 42 - ## We have to explicitly enable the source mode for in-line edit because the latest version of the content editor 43 - ## could be installed on an older version of XWiki where the in-place editor didn't support the source mode (so the 44 - ## content editor cannot enable the source mode by default). 45 45 #set ($inplaceEditingConfig = { 46 46 'contentType': 'org.xwiki.rendering.syntax.SyntaxContent', 47 47 'editMode': $defaultEditMode, 48 - ' wysiwygEditor': $services.edit.syntaxContent.defaultWysiwygEditor.descriptor.id,45 + 'contentEditor': "xwiki-${services.edit.syntaxContent.defaultWysiwygEditor.descriptor.id}-inline", 49 49 'editButtonSelector': '#tmEdit > a', 50 50 'translateButtonSelector': '#tmTranslate > a', 51 - 'enableSourceMode': true, 52 52 'paths': { 53 53 'js': { 54 54 'xwiki-actionButtons': "#getSkinFileWithParams('js/xwiki/actionbuttons/actionButtons.js' $jsParams)",