From 14e82adbc75f0f395ed20737ee2e313e5fbf4d32 Mon Sep 17 00:00:00 2001 From: Oana-Lavinia Florean Date: Tue, 28 Mar 2023 22:11:17 +0300 Subject: [PATCH] Make collabora save and exit actions more intuitive #12 * add 2 new buttons in the editor menu: save and exit + close editor and use the postMesage API to handle the events --- .../collabora/internal/rest/DefaultWopi.java | 3 + .../main/resources/Collabora/Code/Main.xml | 161 +++++++++++++++--- .../resources/Collabora/Code/Translations.xml | 6 + .../src/main/resources/Collabora/Code/UI.xml | 20 +++ 4 files changed, 171 insertions(+), 19 deletions(-) diff --git a/application-collabora-default/src/main/java/com/xwiki/collabora/internal/rest/DefaultWopi.java b/application-collabora-default/src/main/java/com/xwiki/collabora/internal/rest/DefaultWopi.java index d43dca7..3f7d878 100644 --- a/application-collabora-default/src/main/java/com/xwiki/collabora/internal/rest/DefaultWopi.java +++ b/application-collabora-default/src/main/java/com/xwiki/collabora/internal/rest/DefaultWopi.java @@ -33,6 +33,7 @@ import org.apache.commons.lang3.exception.ExceptionUtils; import org.json.JSONObject; +import org.restlet.Request; import org.slf4j.Logger; import org.xwiki.component.annotation.Component; import org.xwiki.model.reference.AttachmentReference; @@ -101,6 +102,8 @@ public Response get(String fileId, String token, String userCanWrite) throws XWi message.put("Size", String.valueOf(attachment.getLongSize())); message.put("UserCanWrite", userCanWrite); message.put(LAST_MODIFIED_TIME, dateFormat.format(attachment.getDate())); + // Needed for using the PostMessage API. + message.put("PostMessageOrigin", Request.getCurrent().getHostRef().toString()); return Response.status(Response.Status.OK).entity(message.toString()).type(MediaType.APPLICATION_JSON) .build(); diff --git a/application-collabora-ui/src/main/resources/Collabora/Code/Main.xml b/application-collabora-ui/src/main/resources/Collabora/Code/Main.xml index da6a7c3..db8d498 100644 --- a/application-collabora-ui/src/main/resources/Collabora/Code/Main.xml +++ b/application-collabora-ui/src/main/resources/Collabora/Code/Main.xml @@ -37,28 +37,50 @@ xwiki/2.1 true {{velocity output="false"}} -#macro (renderCollaboraContent $mode) - ## Display logo and some information about the currently edited file. Show Cancel action on the right. - ## Save action is included in Collabora editor. - <div class="actionMenu"> - <div> - <a href="${xwiki.getURL('Main.WebHome')}"><img src="$xwiki.getSkinFile('logo.svg')"></a> - </div> - <div> - <strong>$currentAction</strong> - <em>$escapetool.xml($request.filename)</em> - $escapetool.xml($services.localization.render('collabora.editor.onPage')) - <a href="${fileDoc.URL}#Attachments" title="$escapetool.xml($services.localization.render('cancel'))"> - $escapetool.xml($fileDoc.displayTitle) - </a> +#macro (unsavedChangesModal) + <div class="modal fade" id="editUnsavedChanges" tabindex="-1" role="dialog" aria-hidden="true" + aria-label="$escapetool.xml($services.localization.render('collabora.editor.unsaved.label'))"> + <div class="modal-dialog modal-sm"> + <div class="modal-content"> + <div class="modal-body"> + $escapetool.xml($services.localization.render('collabora.editor.unsaved.info')) </div> - <div> - <a href="${fileDoc.URL}#Attachments" class="btn btn-default" - title="$escapetool.xml($services.localization.render('cancel'))"> - $escapetool.xml($services.localization.render('cancel')) - </a> + <div class="modal-footer"> + <input type="submit" class="btn btn-danger" + value="$escapetool.xml($services.localization.render('collabora.editor.unsaved.submit'))"> + <input type="button" class="btn btn-default" data-dismiss="modal" + value="$escapetool.xml($services.localization.render('collabora.editor.unsaved.close'))"> </div> + </div> + </div> + </div> +#end +#macro (renderCollaboraContent $mode) + ## Display logo and some information about the currently edited file. Save, Save and exit and Close actions are + ## included in Collabora editor. + <div class="actionMenu"> + <div> + <a href="${xwiki.getURL('Main.WebHome')}"><img src="$xwiki.getSkinFile('logo.svg')"></a> + </div> + <div> + <strong>$currentAction</strong> + <em>$escapetool.xml($request.filename)</em> + $escapetool.xml($services.localization.render('collabora.editor.onPage')) + <a id='fileHomePage' href="${fileDoc.URL}#Attachments" + title="$escapetool.xml($services.localization.render('cancel'))"> + $escapetool.xml($fileDoc.displayTitle) + </a> </div> + </div> + #set ($saveAndExitIconURL = $xwiki.getDocument('Collabora.Code.UI').getAttachmentURL('saveAndExit.png')) + #set ($closeIconURL = $xwiki.getDocument('Collabora.Code.UI').getAttachmentURL('close.png')) + #set ($saveAndExitMessage = $escapetool.xml($services.localization.render('collabora.editor.saveAndExit'))) + #set ($closeMessage = $escapetool.xml($services.localization.render('collabora.editor.close'))) + ## TODO: The translations are added for now as a data attribute. This should be changed once the application starts + ## depending on a XWiki version >= 13.8 to include XWIKI-18973: Simplify the way JavaScript code loads translation + ## messages. + <span id="collaboraConfig" data-save-exit-icon="$saveAndExitIconURL" data-close-icon="$closeIconURL" + data-save-exit-message="$saveAndExitMessage" data-close-message="$closeMessage"></span> ## Information needed by collabora to be able to edit the current file. #set ($fileId = $escapetool.xml($services.model.serialize($attachment.getReference(), 'default'))) #set ($errorMessage = $escapetool.xml($services.localization.render('collabora.editor.error'))) @@ -70,6 +92,7 @@ </form> ## Where the document will be displayed. <iframe id="collaboraViewer" name="collaboraViewer"></iframe> + #unsavedChangesModal() #end {{/velocity}} @@ -197,6 +220,99 @@ require(['jquery'], function($) { const collaboraPath ='/rest/collabora/files/'; + const iframeWindow = document.getElementById('collaboraViewer').contentWindow; + var isDocModified = false; + + var getSaveAndExitButtonMessage = function() { + return JSON.stringify({ + 'MessageId': 'Insert_Button', + 'SendTime': Date.now(), + 'Values': { + 'id': 'saveAndExitEditor', + 'imgurl': window.location.origin + $("#collaboraConfig").data('saveExitIcon'), + 'hint': $("#collaboraConfig").data('saveExitMessage'), + 'mobile': true, + 'label': $("#collaboraConfig").data('saveExitMessage') + } + }); + }; + + var getCloseButtonMessage = function() { + return JSON.stringify({ + 'MessageId': 'Insert_Button', + 'SendTime': Date.now(), + 'Values': { + 'id': 'closeEditor', + 'imgurl': window.location.origin + $("#collaboraConfig").data('closeIcon'), + 'hint': $("#collaboraConfig").data('closeMessage'), + 'mobile': true, + 'label': $("#collaboraConfig").data('closeMessage') + } + }); + }; + + var getSaveDocumentMessage = function() { + return JSON.stringify({ + "MessageId" : "Action_Save", + "SendTime": Date.now(), + "Values" : { + "DontTerminateEdit" : true, + "DontSaveIfUnmodified": true, + "Notify" : true + } + }); + } + + /** + * If there are unsaved changes, ask the user if these should be discarded. Otherwise, go to the file home page. + */ + var onCloseEditor = function() { + if (isDocModified) { + $('#editUnsavedChanges').modal('show'); + } else { + window.location.href = $('#fileHomePage').attr('href'); + } + }; + + var onDocumentLoaded = function() { + // Notify the server that postMessage is going to be used. + iframeWindow.postMessage(JSON.stringify({ 'MessageId': 'Host_PostmessageReady' }), '*'); + // Add the saveAndExit and close custom buttons. + iframeWindow.postMessage(getSaveAndExitButtonMessage(), '*'); + iframeWindow.postMessage(getCloseButtonMessage(), '*'); + }; + + var receiveMessage = function(event) { + // Skip messages that are not from this iframe. + if (event.source !== iframeWindow) { + return; + } + var msg = JSON.parse(event.data); + if (!msg) { + return; + } + + if (msg.MessageId == 'App_LoadingStatus' && msg.Values && msg.Values.Status == 'Document_Loaded') { + onDocumentLoaded(); + } else if (msg.MessageId == 'Doc_ModifiedStatus') { + isDocModified = msg.Values && msg.Values.Modified; + } else if (msg.MessageId == 'Clicked_Button') { + if (msg.Values && msg.Values.Id == 'saveAndExitEditor') { + // Save the file and let the editor close action be handleded after this is finished, on Action_Save_Resp. + iframeWindow.postMessage(getSaveDocumentMessage(), '*'); + } else if (msg.Values && msg.Values.Id == 'closeEditor') { + onCloseEditor(); + } + } else if (msg.MessageId == 'Action_Save_Resp') { + // This event is send when Action_Save or Action_Save_As are triggered from our code, and UI_Save when using the + // editor's default save button. Right now, we only fire Action_Save on the custom saveAndExit button. So after + // this save is succesfull, a redirect to the file home page is required. + if (msg.Values && msg.Values.success) { + window.location.href = $('#fileHomePage').attr('href'); + } + } + } + $(function() { const fileId = $("#collaboraServer").data('fileId'); const userCanWrite = $("#collaboraServer").data('mode') === 'edit'; @@ -219,6 +335,13 @@ }).fail(function(jqxhr, textStatus, error) { new XWiki.widgets.Notification(errorMessage + error, 'error'); }); + + // Listen to messages send by Collabora server. + window.addEventListener("message", receiveMessage, false); + }); + + $(document).on('click', '#editUnsavedChanges input.btn-danger', function(e) { + window.location.href = $('#fileHomePage').attr('href'); }); $(window).unload(function() { diff --git a/application-collabora-ui/src/main/resources/Collabora/Code/Translations.xml b/application-collabora-ui/src/main/resources/Collabora/Code/Translations.xml index 2e51bfd..b242b13 100644 --- a/application-collabora-ui/src/main/resources/Collabora/Code/Translations.xml +++ b/application-collabora-ui/src/main/resources/Collabora/Code/Translations.xml @@ -62,6 +62,12 @@ collabora.attachment.view.title=View using Collabora ## Editor collabora.editor.onPage=on page collabora.editor.error=Error while loading this file. Cause: +collabora.editor.close=Close editor +collabora.editor.saveAndExit=Save and exit +collabora.editor.unsaved.close=Keep editing +collabora.editor.unsaved.info=There are unsaved changes. Are you sure you want to exit without saving? +collabora.editor.unsaved.label=Unsaved changes +collabora.editor.unsaved.submit=Close without saving collabora.validation.attachmentMissing=Document or attachment not found! collabora.validation.documentMissing=Document parameter required! collabora.validation.filenameMissing=Filename parameter required! diff --git a/application-collabora-ui/src/main/resources/Collabora/Code/UI.xml b/application-collabora-ui/src/main/resources/Collabora/Code/UI.xml index db705d2..71be801 100644 --- a/application-collabora-ui/src/main/resources/Collabora/Code/UI.xml +++ b/application-collabora-ui/src/main/resources/Collabora/Code/UI.xml @@ -37,6 +37,16 @@ xwiki/2.1 true + + close.png + image/png + UTF-8 + xwiki:XWiki.Admin + 1.1 + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYBAMAAAASWSDLAAAAMFBMVEUAAADkLx/iMCDiMCDjMSDkMCDiLx/iMCDiLx/iMCDiMB/////iMB/iLx/iMCDiMR8QM5r+AAAAEHRSTlMAYJ/P/19Q35CP4P/woO/Q9/WoOgAAAKVJREFUeJxjYMALhEycVWHsdBcgKIOwOVzAYAKY0wLheIDYbC5QkADkcLl473FxOb3FpQDIEXHx5j7is3uLixuQc8XFb8Oe07ufuPgCOV9cXF7v3r3PxcUZyAFq9dsNlHBxgXB8du8+AuEAlZ3evQFooD/YAJ/d+15zHwEbADR69xM/qNFAS4FGvd7isgDdOSgOhXmhAeKhLBB7Gcyrsl/8L+IPGABYMkPB47UBjQAAAABJRU5ErkJggg== + 310 + collabora-symbol.svg image/svg+xml @@ -47,6 +57,16 @@ PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMTJweCIgaGVpZ2h0PSIxMnB4IiB2aWV3Qm94PSIwIDAgMTIgMTIiIHZlcnNpb249IjEuMSI+CjxnIGlkPSJzdXJmYWNlMSI+CjxwYXRoIHN0eWxlPSIgc3Ryb2tlOm5vbmU7ZmlsbC1ydWxlOm5vbnplcm87ZmlsbDpyZ2IoMTAwJSwxMDAlLDEwMCUpO2ZpbGwtb3BhY2l0eToxOyIgZD0iTSAwIDAgTCAxMS45ODQzNzUgMCBMIDExLjk4NDM3NSAxMS45ODQzNzUgTCAwIDExLjk4NDM3NSBaIE0gMCAwICIvPgo8cGF0aCBzdHlsZT0iIHN0cm9rZTpub25lO2ZpbGwtcnVsZTpub256ZXJvO2ZpbGw6cmdiKDEwMCUsMTAwJSwxMDAlKTtmaWxsLW9wYWNpdHk6MTsiIGQ9Ik0gMy40NDUzMTIgMy43MzQzNzUgTCAzLjU4OTg0NCAzLjU4OTg0NCBMIDUuOTkyMTg4IDUuOTkyMTg4IEwgMy41ODk4NDQgOC4zOTQ1MzEgTCAzLjQ0NTMxMiA4LjI1IEwgNS43MDMxMjUgNS45OTIxODggWiBNIDMuNDQ1MzEyIDMuNzM0Mzc1ICIvPgo8cGF0aCBzdHlsZT0iIHN0cm9rZTpub25lO2ZpbGwtcnVsZTpub256ZXJvO2ZpbGw6cmdiKDc4LjQzMTM3MyUsNzguNDMxMzczJSw3OC40MzEzNzMlKTtmaWxsLW9wYWNpdHk6MTsiIGQ9Ik0gMy40NDUzMTIgMy43MzQzNzUgTCAzLjQ0NTMxMiA4LjI1IEwgNS43MDMxMjUgNS45OTIxODggWiBNIDMuNDQ1MzEyIDMuNzM0Mzc1ICIvPgo8cGF0aCBzdHlsZT0iIHN0cm9rZTpub25lO2ZpbGwtcnVsZTpub256ZXJvO2ZpbGw6cmdiKDMxLjM3MjU0OSUsMjguNjI3NDUxJSw2MCUpO2ZpbGwtb3BhY2l0eToxOyIgZD0iTSA1LjQ5MjE4OCAxLjY4NzUgTCAzLjU4OTg0NCAzLjU4OTg0NCBMIDUuOTkyMTg4IDUuOTkyMTg4IEwgMy41ODk4NDQgOC4zOTQ1MzEgTCA1LjQ5MjE4OCAxMC4yOTY4NzUgTCA5Ljc5Njg3NSA1Ljk5MjE4OCBaIE0gNS40OTIxODggMS42ODc1ICIvPgo8cGF0aCBzdHlsZT0iIHN0cm9rZTpub25lO2ZpbGwtcnVsZTpub256ZXJvO2ZpbGw6cmdiKDIxLjk2MDc4NCUsMTQuNTA5ODA0JSw0Ny44NDMxMzclKTtmaWxsLW9wYWNpdHk6MTsiIGQ9Ik0gNS40MDIzNDQgNi41ODIwMzEgTCAzLjgxMjUgOC4xNjc5NjkgTCA2LjE2MDE1NiA3LjY1MjM0NCBaIE0gNS40MDIzNDQgNi41ODIwMzEgIi8+CjwvZz4KPC9zdmc+Cg== 1225 + + saveAndExit.png + image/png + UTF-8 + xwiki:XWiki.Admin + 1.1 + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAmVBMVEUAAAA6Ojg6Ojg6Ojh5d3T6+vr5+Pj129vvvbvusrCoNi6famZ7dnOPODHYMiTjMSDZQDWPODK3XVjjMiHlMCGlZ2LjMSDlMh/aPzT////jMSCgamanNi7tsbCFODNBOjjmbGfjNCXjMCA7OzlXV1Tz1dTlMCBISEg6Ojnwvr3jMyJFRUXWMiTWMSSYNzH/fwDnMiLkMx/lMiI9Pt+zAAAAM3RSTlMAxf/D/////////////////////3n//lH//97/////////3cv//08V0v94Fv//1QRCe40eLfJ5AAAArElEQVR4nMXS1w6CMBQGYKzUiR6xrrqwgnsAvv/D2Z62WCR64Y1/Agn/F9LpeT+kRiqpIxDfJ9SJ+v4LNJqtdqcbvMCk1weVQWjBZMjAZFSCMQMtDCZTB4IZMM5lr15zBxbYcXwADKiRl6AF+wLkVFdgBMrw8Q+yVj19H0NCtAFRzGorIU40kJ0AYdexl3A4JhroSdiVn3FPLrHdq/CK9e1eOf80yx95ln67Ik+P+BKgdL3gCAAAAABJRU5ErkJggg== + 457 + Collabora.Code.UI 0