diff --git a/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-ui/src/main/resources/RTFrontend/Ajax.xml b/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-ui/src/main/resources/RTFrontend/Ajax.xml
deleted file mode 100644
index 72bc2d6f8d3a813c200b9de7a1e46feb77415359..0000000000000000000000000000000000000000
--- a/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-ui/src/main/resources/RTFrontend/Ajax.xml
+++ /dev/null
@@ -1,131 +0,0 @@
-<?xml version="1.1" encoding="UTF-8"?>
-
-<!--
- * See the NOTICE file distributed with this work for additional
- * information regarding copyright ownership.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
--->
-
-<xwikidoc version="1.4" reference="RTFrontend.Ajax" locale="">
-  <web>RTFrontend</web>
-  <name>Ajax</name>
-  <language/>
-  <defaultLanguage/>
-  <translation>0</translation>
-  <creator>xwiki:XWiki.Admin</creator>
-  <parent>xwiki:XWiki.WebHome</parent>
-  <author>xwiki:XWiki.Admin</author>
-  <contentAuthor>xwiki:XWiki.Admin</contentAuthor>
-  <version>1.1</version>
-  <title>Realtime Ajax Merge</title>
-  <comment/>
-  <minorEdit>false</minorEdit>
-  <syntaxId>xwiki/2.1</syntaxId>
-  <hidden>true</hidden>
-  <content>{{velocity wiki=false}}
-#*
-  This script provides a serverside merge API which allows realtime sessions to peacefully coexist with simultaneous
-  non-realtime sessions. It expects the client to post its current realtime session content, the name of the relevant
-  document, and the version of its latest common ancestor (the last known savepoint).
-*#
-##
-#set ($result = {})
-#set ($result.error = false)
-#set ($CR = $util.decodeURI("%0D"))
-##
-#if ($request.version)
-  ##
-  ## the version the client expects to be its latest common ancestor
-  #set ($previousVersion = $request.get('version'))
-  ## the language of the document
-  #set ($language = $request.get('language'))
-  ## the name of the document
-  #set ($documentName = $request.get('document'))
-  ## the realtime editor's content
-  #set ($currentContent = $request.get('content'))
-  ##
-  #set ($nextDocument = $xwiki.getDocument($documentName).getTranslatedDocument($language))
-  #set ($nextContent = $nextDocument.content)
-  #set ($previousDocument = $nextDocument.getDocumentRevision($previousVersion))
-  #set ($previousContent = $previousDocument.content)
-  ##
-  ## Check if the translation in the requested language exists.
-  ## If the translation does not exist, i.e. "getTranslatedDocument" is the default document, it means it's a new translation.
-  ## In that case we have to set the contents to ""
-  #if ($language != "default" &amp;&amp; $nextDocument.locale != $language)
-    #set ($nextContent = '')
-    #set ($previousContent = '')
-  #end
-  ##
-  #if ("$!{request.convertHTML}" == '1')
-    #set ($xdom = $services.rendering.parse($currentContent, 'xhtml/1.0'))
-    #set ($currentContent = $services.rendering.render($xdom, $nextDocument.syntax.toIdString()))
-  #end
-  ##
-  #set ($result.latestVersion = $previousVersion)
-  #set ($result.currentVersion = $nextDocument.version)
-  #set ($result.previousVersionContent = $nextDocument.content) ##TODO : convert if needed
-  ##
-  ## We don't need to merge if the content has not been changed outside of the real-time session (previous == next) or
-  ## if the changes done outside of the real-time session are the same as those done within the real-time session
-  ## (current == next).
-  #if ($previousContent == $nextContent || $currentContent == $nextContent)
-    #set ($result.content = $currentContent)
-    #set ($result.merged = false)
-    #set ($result.conflicts = false)
-    #set ($result.saveRequired = $currentContent != $nextContent)
-  ## Otherwise we need to perform a 3-way merge.
-  #else
-    #set ($placeholder = " ${escapetool.getNewline()} ")
-    ##
-    #set ($previousArray = $previousContent.replace($CR, '').replace($escapetool.getNewline(), $placeholder).split(' '))
-    #set ($currentArray = $currentContent.replace($CR, '').replace($escapetool.getNewline(), $placeholder).split(' '))
-    #set ($nextArray = $nextContent.replace($CR, '').replace($escapetool.getNewline(), $placeholder).split(' '))
-    ##
-    ## $services.diff.merge() expects lists so we need to convert the arrays.
-    #set ($previousList = $previousArray.subList(0, $previousArray.size()))
-    #set ($currentList = $currentArray.subList(0, $currentArray.size()))
-    #set ($nextList = $nextArray.subList(0, $nextArray.size()))
-    #set ($mergeResult = $services.diff.merge($previousList, $nextList, $currentList, null))
-    ##
-    #set ($mergedContent = $stringtool.join($mergeResult.merged, ' ').replace($placeholder, $escapetool.getNewline()))
-    ##
-    #if ("$!{request.convertHTML}" == '1')
-      ## FIXME: This requires programming rights.
-      #set ($discard = $xcontext.setDoc($nextDocument.document))
-      #set ($source = {
-        'content': $mergedContent,
-        'syntax': $nextDocument.syntax,
-        'documentReference': $nextDocument.documentReference
-      })
-      ## FIXME: We shouldn't depend on a particular WYSIWYG editor. The code should work with the configured WYSIWYG
-      ## editor, whatever that may be.
-      #set ($mergedContent = $xwiki.getDocument('CKEditor.ContentSheet').getRenderedContent().replaceAll(
-        "([\t\r\n]|(\s{2,}))", ''))
-    #end
-    ##
-    #set ($result.content = $mergedContent)
-    #set ($result.merged = true)
-    #set ($result.conflicts = $mergeResult.log)
-    #set ($result.saveRequired = true)
-  #end
-#end
-#if ($xcontext.action == 'get')
-  #jsonResponse($result)
-#end
-{{/velocity}}</content>
-</xwikidoc>
diff --git a/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-ui/src/main/resources/RTFrontend/ConvertHTML.xml b/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-ui/src/main/resources/RTFrontend/ConvertHTML.xml
deleted file mode 100644
index a9065d7cb5da5225c6e582531f1d905fa90e24ae..0000000000000000000000000000000000000000
--- a/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-ui/src/main/resources/RTFrontend/ConvertHTML.xml
+++ /dev/null
@@ -1,64 +0,0 @@
-<?xml version="1.1" encoding="UTF-8"?>
-
-<!--
- * See the NOTICE file distributed with this work for additional
- * information regarding copyright ownership.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
--->
-
-<xwikidoc version="1.5" reference="RTFrontend.ConvertHTML" locale="">
-  <web>RTFrontend</web>
-  <name>ConvertHTML</name>
-  <language/>
-  <defaultLanguage/>
-  <translation>0</translation>
-  <creator>xwiki:XWiki.Admin</creator>
-  <parent>xwiki:XWiki.WebHome</parent>
-  <author>xwiki:XWiki.Admin</author>
-  <contentAuthor>xwiki:XWiki.Admin</contentAuthor>
-  <version>1.1</version>
-  <title>Realtime HTML Converter</title>
-  <comment/>
-  <minorEdit>false</minorEdit>
-  <syntaxId>xwiki/2.1</syntaxId>
-  <hidden>true</hidden>
-  <content>{{velocity wiki=false}}
-## TODO: We should pass directly the document reference.
-#set ($wiki = "$!request.getParameter('wiki')")
-#set ($space = "$!request.getParameter('space')")
-#set ($page = "$!request.getParameter('page')")
-#set ($documentReference = $services.model.createDocumentReference($wiki, $space, $page))
-#set ($document = $xwiki.getDocument($documentReference))
-## FIXME: This requires programming rights!
-#set ($discard = $xcontext.setDoc($document.document))
-## The $source variable is used inside the content sheet.
-#set ($source = {
-  'content': $request.text,
-  'syntax': $document.syntax,
-  'documentReference': $documentReference
-})
-#if ($xcontext.action == 'get')
-  ## Check that the CSRF token matches the user.
-  #if (!$services.csrf.isTokenValid($request.form_token))
-    $response.sendError(403, $services.localization.render('rtfFrontend.convertHtml.invalidCsrfToken'))
-  #else
-    ## FIXME: We shouldn't depend on CKEditor. This code should work independent of the configured WYSIWYG editor.
-    $xwiki.getDocument('CKEditor.ContentSheet').getRenderedContent()
-  #end
-#end
-{{/velocity}}</content>
-</xwikidoc>
diff --git a/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-ui/src/test/java/org/xwiki/realtime/ui/ConvertHTMLPageTest.java b/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-ui/src/test/java/org/xwiki/realtime/ui/ConvertHTMLPageTest.java
deleted file mode 100644
index dde6b3d7cf049d1d45280a0b39d2228373c82354..0000000000000000000000000000000000000000
--- a/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-ui/src/test/java/org/xwiki/realtime/ui/ConvertHTMLPageTest.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * See the NOTICE file distributed with this work for additional
- * information regarding copyright ownership.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
- */
-package org.xwiki.realtime.ui;
-
-import javax.inject.Provider;
-
-import org.jsoup.nodes.Document;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.xwiki.csrf.script.CSRFTokenScriptService;
-import org.xwiki.model.reference.DocumentReference;
-import org.xwiki.rendering.RenderingScriptServiceComponentList;
-import org.xwiki.rendering.internal.configuration.DefaultRenderingConfigurationComponentList;
-import org.xwiki.script.service.ScriptService;
-import org.xwiki.test.page.HTML50ComponentList;
-import org.xwiki.test.page.PageTest;
-import org.xwiki.test.page.XWikiSyntax21ComponentList;
-
-import com.xpn.xwiki.XWikiContext;
-import com.xpn.xwiki.user.api.XWikiRightService;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-@RenderingScriptServiceComponentList
-@DefaultRenderingConfigurationComponentList
-@HTML50ComponentList
-@XWikiSyntax21ComponentList
-class ConvertHTMLPageTest extends PageTest
-{
-
-    private static final String WIKI_NAME = "xwiki";
-
-    private static final String XWIKI_SPACE = "RTFrontend";
-
-    private static final DocumentReference RTF_FRONTEND_CONVERT_HTML =
-        new DocumentReference(WIKI_NAME, XWIKI_SPACE, "ConvertHTML");
-
-    private static final String CSRF_TOKEN = "a0a0a0a0";
-
-    private CSRFTokenScriptService tokenService;
-
-    @BeforeEach
-    void setUp() throws Exception
-    {
-        // Mock the Token Service to get a consistent CSRF token throughout the tests.
-        this.tokenService = this.oldcore.getMocker().registerMockComponent(ScriptService.class, "csrf",
-            CSRFTokenScriptService.class, true);
-        when(this.tokenService.isTokenValid(CSRF_TOKEN)).thenReturn(true);
-
-        this.xwiki.initializeMandatoryDocuments(this.context);
-
-        this.context = mock(XWikiContext.class);
-        Provider<XWikiContext> xcontextProvider =
-            this.componentManager.registerMockComponent(XWikiContext.TYPE_PROVIDER);
-        when(xcontextProvider.get()).thenReturn(this.context);
-        when(this.context.getRequest()).thenReturn(this.request);
-        when(this.context.getResponse()).thenReturn(this.response);
-        when(this.context.getWiki()).thenReturn(this.xwiki);
-
-        // Fake programming access level to display the complete page.
-        XWikiRightService rightService = this.oldcore.getMockRightService();
-        when(this.xwiki.getRightService()).thenReturn(rightService);
-        when(rightService.hasProgrammingRights(this.context)).thenReturn(true);
-        this.response = spy(this.response);
-        when(this.context.getResponse()).thenReturn(this.response);
-    }
-
-    @Test
-    void checkValidCSRFToken() throws Exception
-    {
-        when(this.context.getAction()).thenReturn("get");
-        this.request.put("text", "Hello");
-        this.request.put("form_token", CSRF_TOKEN);
-        Document result = renderHTMLPage(RTF_FRONTEND_CONVERT_HTML);
-
-        verify(this.response, never()).setStatus(anyInt());
-        verify(this.tokenService).isTokenValid(CSRF_TOKEN);
-        assertEquals("$xwiki.getDocument('CKEditor.ContentSheet').getRenderedContent()",
-            result.getElementsByTag("body").text());
-    }
-
-    @Test
-    void checkInvalidCSRFToken() throws Exception
-    {
-        String wrongToken = "wrong_token";
-        when(this.context.getAction()).thenReturn("get");
-        this.request.put("text", "Hello");
-        this.request.put("form_token", wrongToken);
-        Document result = renderHTMLPage(RTF_FRONTEND_CONVERT_HTML);
-
-        verify(this.response).sendError(403, "rtfFrontend.convertHtml.invalidCsrfToken");
-        verify(this.tokenService).isTokenValid(wrongToken);
-        assertEquals("", result.getElementsByTag("body").text());
-    }
-}
diff --git a/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-webjar/src/main/webjar/saver.js b/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-webjar/src/main/webjar/saver.js
index 82839c4137607c294a95927559e63055849125eb..338dceec9f8f25b8586f738aad30ed11826d82e5 100644
--- a/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-webjar/src/main/webjar/saver.js
+++ b/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-webjar/src/main/webjar/saver.js
@@ -95,89 +95,6 @@ define('xwiki-realtime-saver', [
     return false;
   },
 
-  /**
-   * Retrieves attributes about the local document for the purposes of AJAX merge (just data-xwiki-document and
-   * lastSaved.version).
-   */
-  getDocumentStatistics = function() {
-    var $html = $('html'),
-      fields = [
-        'wiki',
-        'document' // includes space and page
-      ],
-      result = {};
-
-    // We can't rely on people pushing the new lastSaved.version. If they quit before ISAVED other clients won't get the
-    // new version. This isn't such an issue, because they _will_ converge eventually.
-    result.version = lastSaved.version;
-
-    fields.forEach(function (field) {
-      result[field] = $html.data('xwiki-' + field);
-    });
-
-    result.language = doc.language;
-
-    return result;
-  },
-
-  ajaxMerge = function(content, cb) {
-    // version, document
-    var stats = getDocumentStatistics();
-
-    stats.content = content;
-    if (mainConfig.isHTML) {
-      stats.convertHTML = 1;
-    }
-
-    verbose('Posting with the following stats:');
-    verbose(stats);
-
-    $.ajax({
-      url: new XWiki.Document('Ajax', 'RTFrontend').getURL('get', 'xpage=plain&outputSyntax=plain'),
-      method: 'POST',
-      dataType: 'json',
-      success: function (data) {
-        try {
-          // Data is already an "application/json".
-          var merge = data;
-          var error = merge.conflicts && merge.conflicts.length && merge.conflicts[0].formattedMessage;
-          if (error) {
-            merge.error = error;
-            cb(error, merge);
-          } else {
-            // Let the callback handle textarea writes.
-            cb(null, merge);
-          }
-        } catch (err) {
-          var debugLog = {
-            state: 'ajaxMerge/parseError',
-            lastSavedVersion: lastSaved.version,
-            lastSavedContent: lastSaved.content,
-            cUser: mainConfig.userName,
-            mergeData: data,
-            error: err
-          };
-          ErrorBox.show('parse', JSON.stringify(debugLog));
-          warn(err);
-          cb(err, data);
-        }
-      },
-      data: stats,
-      error: function (err) {
-        var debugLog = {
-          state: 'ajaxMerger/velocityError',
-          lastSavedVersion: lastSaved.version,
-          lastSavedContent: lastSaved.content,
-          cContent: content,
-          cUser: mainConfig.userName,
-          err: err
-        };
-        ErrorBox.show('velocity', JSON.stringify(debugLog));
-        warn(err);
-      },
-    });
-  },
-
   bumpVersion = function(callback, versionData) {
     var success = function(doc) {
       debug('Triggering lastSaved refresh on remote clients.');
@@ -246,22 +163,6 @@ define('xwiki-realtime-saver', [
     });
   },
 
-  presentMergeDialog = function(question, labelDefault, choiceDefault, labelAlternative, choiceAlternative) {
-    var behave = {
-      onYes: choiceDefault,
-      onNo: choiceAlternative
-    };
-
-    var param = {
-      confirmationText: question,
-      yesButtonText: labelDefault,
-      noButtonText: labelAlternative,
-      showCancelButton: true
-    };
-
-    new XWiki.widgets.ConfirmationBox(behave, param);
-  },
-
   destroyDialog = Saver.destroyDialog = function(callback) {
     var $box = $('.xdialog-box.xdialog-box-confirmation'),
       $content = $box.find('.xdialog-content');
@@ -283,154 +184,6 @@ define('xwiki-realtime-saver', [
     lastSaved.wasEditedLocally = condition;
   },
 
-  resolveMergeConflictsPromise = function(merge, deferred) {
-    // There was a merge conflict we'll need to resolve.
-    warn(merge.error);
-
-    // Halt the autosave cycle to give the user time. Don't halt forever though, because you might disconnect and hang.
-    mergeDialogCurrentlyDisplayed = true;
-
-    presentMergeDialog(
-      Messages['mergeDialog.prompt'],
-
-      Messages['mergeDialog.keepRealtime'],
-
-      function() {
-        debug('User chose to use the realtime version!');
-        // Unset the merge dialog flag.
-        mergeDialogCurrentlyDisplayed = false;
-        deferred.resolve();
-      },
-
-      Messages['mergeDialog.keepRemote'],
-
-      function() {
-        debug('User chose to use the remote version!');
-        // Unset the merge dialog flag.
-        mergeDialogCurrentlyDisplayed = false;
-        var restURL = XWiki.currentDocument.getRestURL();
-        if (doc.language && !/\/pages\/(.+)\/translations\//.test(restURL)) {
-          restURL = restURL + doc.language;
-        }
-
-        $.getJSON(restURL).then(data => {
-          mainConfig.setTextValue(data.content, true, function() {
-            debug("Overwrote the realtime session's content with the latest saved state.");
-            bumpVersion(function() {
-              lastSaved.mergeMessage('mergeOverwrite', []);
-            });
-            deferred.resolve();
-          });
-        }).catch(error => {
-          mainConfig.safeCrash('keepremote');
-          warn("Encountered an error while fetching remote content.");
-          warn(error);
-          deferred.reject();
-        });
-      }
-    );
-  },
-
-  resolveMergeConflicts = function(merge) {
-    return new Promise((resolve, reject) => resolveMergeConflictsPromise(merge, {resolve, reject}));
-  },
-
-  mergedWithoutConflicts = function(merge, preMergeContent) {
-    return new Promise((resolve, reject) => {
-      // The content was merged and there were no errors / conflicts.
-      if (preMergeContent !== mainConfig.getTextValue()) {
-        // There have been changes since merging. Don't overwrite if there have been changes while merging
-        // See http://jira.xwiki.org/browse/RTWIKI-37
-        // Try again in one cycle.
-        reject();
-      } else {
-        // Walk the tree of hashes and if merge.previousVersionContent exists, then this merge is quite possibly faulty.
-        if (mainConfig.realtime.getDepthOfState(merge.previousVersionContent) !== -1) {
-          debug("The server merged a version which already existed in the history. " +
-            "Reversions shouldn't merge. Ignoring merge.");
-          debug("waseverstate=true");
-          resolve();
-        } else {
-          debug("The latest version content does not exist anywhere in our history.");
-          debug("Continuing...");
-          // There were no errors or local changes. Push to the textarea.
-          mainConfig.setTextValue(merge.content, false, resolve);
-        }
-      }
-    });
-  },
-
-  // callback takes signature (error, shouldSave)
-  mergeContinuation = function(merge, callback) {
-    // Our continuation has three cases:
-    if (isaveInterrupt()) {
-      // 1. ISAVE interrupt error
-      callback('ISAVED interrupt', null);
-    } else if (merge.saveRequired) {
-      // 2. saveRequired
-      callback(null, true);
-    } else {
-      // 3. saveNotRequired
-      callback(null, false);
-    }
-  },
-
-  mergeCallback = function(preMergeContent, andThen, error, merge) {
-    if (error) {
-      if (!merge || typeof merge === 'undefined') {
-        warn('The ajax merge API did not return an object. Something went wrong');
-        warn(error);
-        return;
-      } else if (error === merge.error) {
-        // There was a merge error. Continue and handle elsewhere.
-        warn(error);
-      } else {
-        // It was some other kind of error... parsing? Complain and return. This means the script failed.
-        warn(error);
-        return;
-      }
-    }
-
-    if (isaveInterrupt()) {
-      andThen('ISAVED interrupt', null);
-
-    } else if (merge.content === lastSaved.content) {
-      // Don't dead end, but indicate that you shouldn't save.
-      andThen("Merging didn't result in a change.", false);
-      setLocalEditFlag(false);
-
-    // http://jira.xwiki.org/browse/RTWIKI-34
-    // Give Messages when merging.
-    } else if (merge.merged) {
-      // TODO update version field with merge.currentVersion
-      console.log("Force updating version to: " + merge.currentVersion);
-      if (xwikiMeta.setVersion) {
-        xwikiMeta.setVersion(merge.currentVersion);
-      }
-      // A merge took place.
-      if (merge.error) {
-        resolveMergeConflicts(merge).then(mergeContinuation.bind(null, merge, andThen));
-      } else {
-        mergedWithoutConflicts(merge, preMergeContent).then(() => {
-          mergeContinuation(merge, andThen);
-        }).catch(() => {
-          andThen("The realtime content changed while we were performing our asynchronous merge.", false);
-        });
-      }
-
-    } else {
-      // No merge was necessary, but you might still have to save.
-      mergeContinuation(merge, andThen);
-    }
-  },
-
-  mergeRoutine = function(andThen) {
-    // Post your current version to the server to see if it must merge. Remember the current state so you can check if
-    // it has changed.
-    var preMergeContent = mainConfig.getTextValue();
-    ajaxMerge(preMergeContent, mergeCallback.bind(null, preMergeContent, andThen));
-  },
-
   onMessage = function(data) {
     // Set a flag so any concurrent processes know to abort.
     lastSaved.receivedISAVE = true;
@@ -524,7 +277,7 @@ define('xwiki-realtime-saver', [
     rtData = data;
 
     return false;
-  },
+  };
 
   /**
    * This contains some of the more complicated logic in this script. Clients check for remote changes on random
@@ -535,7 +288,6 @@ define('xwiki-realtime-saver', [
    * their local state to match. During this process, a series of checks are made to reduce the number of unnecessary
    * saves, as well as the number of unnecessary merges.
    */
-  mergeDialogCurrentlyDisplayed = false;
   Saver.create = function(config) {
     $.extend(mainConfig, config);
     mainConfig.formId = mainConfig.formId || 'edit';
@@ -545,91 +297,6 @@ define('xwiki-realtime-saver', [
     lastSaved.time = now();
 
     var onOpen = function(chan) {
-      // Originally implemented as part of 'saveRoutine', abstracted logic such that the merge/save algorithm can
-      // terminate with different callbacks for different use cases.
-      var saveFinalizer = function(e, shouldSave) {
-        var toSave = mainConfig.getTextValue();
-        if (toSave === null) {
-          e = "Unable to get the content of the document. Don't save.";
-        }
-        if (e) {
-          warn(e);
-        } else if (shouldSave) {
-          saveDocument(mainConfig.getSaveValue()).then(doc => {
-            // Cache this because bumpVersion will increment it.
-            var lastVersion = lastSaved.version;
-
-            // Update values in lastSaved.
-            updateLastSaved(toSave);
-
-            // Get document version.
-            bumpVersion(function(doc) {
-              if (doc.version === '1.1') {
-                debug('Created document version 1.1');
-              } else {
-                debug(`Version bumped from ${lastVersion} to ${doc.version}.`);
-              }
-              lastSaved.mergeMessage('saved', [doc.version]);
-            }, doc);
-          });
-        } else {
-          // local content matches that of the latest version
-          verbose("No save was necessary");
-          lastSaved.content = toSave;
-          // didn't save, don't need a callback
-          bumpVersion();
-        }
-      };
-
-      function saveRoutine(andThen, force) {
-        // If this is ever true in your save routine, complain and abort.
-        lastSaved.receivedISAVE = false;
-
-        const toSave = mainConfig.getTextValue();
-        if (toSave === null) {
-          warn("Unable to get the content of the document. Don't save.");
-          return;
-        }
-
-        if (lastSaved.content === toSave && !force ) {
-          verbose("No changes made since last save. Avoiding unnecessary commits.");
-          return;
-        }
-
-        if (mainConfig.mergeContent) {
-          mergeRoutine(andThen);
-        } else {
-          andThen(null, true);
-        }
-      }
-
-      let preventDefaultSave = true;
-      function saveButtonAction(continueEditing) {
-        debug("Saver.create.saveand" + (continueEditing ? 'continue' : 'view'));
-
-        saveRoutine(function(e) {
-          if (e) {
-            warn(e);
-          }
-
-          lastSaved.shouldRedirect = !continueEditing;
-          preventDefaultSave = false;
-          // We use the Save & Continue button to trigger the save because we want to perform some action after the
-          // content is saved and we handle the redirect to view mode ourselves.
-          $(`#${config.formId} [name="action_saveandcontinue"]`).click();
-        }, /* force: */ true);
-      }
-
-      // Special handling of save action.
-      $(document).off('xwiki:actions:beforeSave.realtime-saver')
-        .on('xwiki:actions:beforeSave.realtime-saver', function(event) {
-          if (preventDefaultSave) {
-            event.preventDefault();
-            saveButtonAction($(event.target).attr('name') === 'action_saveandcontinue');
-          }
-          // Reset the flag for the next save.
-          preventDefaultSave = true;
-        });
 
       // There's a very small chance that the preview button might cause problems, so let's just get rid of it.
       $('[name="action_preview"]').remove();
@@ -643,33 +310,28 @@ define('xwiki-realtime-saver', [
         updateLastSaved(toSave);
 
         doc.reload().then(doc => {
-          if (doc.isNew) {
-            // It didn't actually save?
-            ErrorBox.show('save');
-          } else {
-            lastSaved.onReceiveOwnIsave = function() {
-              // Once you get your isaved back, redirect.
-              debug("lastSaved.shouldRedirect " + lastSaved.shouldRedirect);
-              if (lastSaved.shouldRedirect) {
-                debug('Saver.create.saveandview.receivedOwnIsaved');
-                debug("redirecting!");
-                redirectToView();
-              } else {
-                debug('Saver.create.saveandcontinue.receivedOwnIsaved');
-              }
-              // Clean up after yourself..
-              lastSaved.onReceiveOwnIsave = null;
-            };
-            // Bump the version, fire your isaved.
-            bumpVersion(function(doc) {
-              if (doc.version === '1.1') {
-                debug('Created document version 1.1');
-              } else {
-                debug(`Version bumped from ${lastVersion} to ${doc.version}.`);
-              }
-              lastSaved.mergeMessage('saved', [doc.version]);
-            }, doc);
-          }
+          lastSaved.onReceiveOwnIsave = function() {
+            // Once you get your isaved back, redirect.
+            debug("lastSaved.shouldRedirect " + lastSaved.shouldRedirect);
+            if (lastSaved.shouldRedirect) {
+              debug('Saver.create.saveandview.receivedOwnIsaved');
+              debug("redirecting!");
+              redirectToView();
+            } else {
+              debug('Saver.create.saveandcontinue.receivedOwnIsaved');
+            }
+            // Clean up after yourself..
+            lastSaved.onReceiveOwnIsave = null;
+          };
+          // Bump the version, fire your isaved.
+          bumpVersion(function(doc) {
+            if (doc.version === '1.1') {
+              debug('Created document version 1.1');
+            } else {
+              debug(`Version bumped from ${lastVersion} to ${doc.version}.`);
+            }
+            lastSaved.mergeMessage('saved', [doc.version]);
+          }, doc);
         }).catch(error => {
           warn(error);
           ErrorBox.show('save');
@@ -695,37 +357,52 @@ define('xwiki-realtime-saver', [
          console.log(ev);
         }
       };
-      // FIXME: Find a way to remove the old listener without using Prototype.js API.
-      document.stopObserving('xwiki:document:saveFailed');
-      $(document).on('xwiki:document:saveFailed.realtime-saver', onSaveFailedHandler);
 
       // TimeOut
       var check = function() {
+        lastSaved.receivedISAVE = false;
+
+        const toSave = mainConfig.getTextValue();
+        if (toSave === null) {
+          warn("Unable to get the content of the document. Don't save.");
+          return;
+        }
+
+        if (lastSaved.content === toSave) {
+          verbose("No changes made since last save. Avoiding unnecessary commits.");
+          return;
+        }
+
         clearTimeout(mainConfig.autosaveTimeout);
         verbose("Saver.create.check");
         var periodDuration = Math.random() * SAVE_DOC_CHECK_CYCLE;
         mainConfig.autosaveTimeout = setTimeout(check, periodDuration);
 
         verbose(`Will attempt to save again in ${periodDuration} ms.`);
-
-        if (!lastSaved.wasEditedLocally) {
-          verbose("Skipping save routine because no changes have been made locally");
+        if (!lastSaved.wasEditedLocally || now() - lastSaved.time < SAVE_DOC_TIME) {
+          verbose("!lastSaved.wasEditedLocally || (Now - lastSaved.time) < SAVE_DOC_TIME");
           return;
-        } else {
-          verbose("There have been local changes!");
         }
-        if (now() - lastSaved.time < SAVE_DOC_TIME) {
-          verbose("(Now - lastSaved.time) < SAVE_DOC_TIME");
-          return;
-        }
-        // Avoid queuing up multiple merge dialogs.
-        if (mergeDialogCurrentlyDisplayed) {
-          return;
+
+        // The merge conflict modal is displayed after clicking on the save button when there is a merge conflict.
+        // Clicking the save button again would reopen the same modal and reset the fields the user did not submit yet.
+        if (!$('#previewDiffModal').is(':visible')) {
+          $(`#${config.formId} [name="action_saveandcontinue"]`).click();
         }
+      };
 
-        saveRoutine(saveFinalizer);
+      // Prevent the save buttons from reloading the page. Instead, reset the editor's content.
+      var overrideAjaxSaveAndContinue = function() {
+        var originalAjaxSaveAndContinue = $.extend({}, XWiki.actionButtons.AjaxSaveAndContinue.prototype);
+        $.extend(XWiki.actionButtons.AjaxSaveAndContinue.prototype, {
+          reloadEditor: function() {
+            // TODO: Handle the page title.
+            mainConfig.getTextAtCurrentRevision().then((data) => {mainConfig.setTextValue(data);});
+          }
+        });
       };
 
+      overrideAjaxSaveAndContinue();
       check();
     };
 
@@ -797,12 +474,6 @@ define('xwiki-realtime-saver', [
     }
     clearTimeout(mainConfig.autosaveTimeout);
     rtData = {};
-    // Remove the merge routine from the save buttons.
-    $(document).off([
-      'xwiki:actions:beforeSave.realtime-saver',
-      'xwiki:document:saved.realtime-saver',
-      'xwiki:document:saveFailed.realtime-saver'
-    ].join(' '));
   };
 
   Saver.setLastSavedContent = function(content) {
diff --git a/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wiki/xwiki-platform-realtime-wiki-webjar/src/main/webjar/wikiEditor.js b/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wiki/xwiki-platform-realtime-wiki-webjar/src/main/webjar/wikiEditor.js
index 494ffbba529dc310a63bca5da6d8ce83aa7d61d5..b5e04d89073989f9235311c2d0cd8d51fba78b58 100644
--- a/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wiki/xwiki-platform-realtime-wiki-webjar/src/main/webjar/wikiEditor.js
+++ b/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wiki/xwiki-platform-realtime-wiki-webjar/src/main/webjar/wikiEditor.js
@@ -245,6 +245,11 @@ define('xwiki-realtime-wikiEditor', [
           getTextValue: function() {
             return editor.getValue();
           },
+          getTextAtCurrentRevision: function() {
+            return $.get(XWiki.currentDocument.getRestURL('', $.param({media:'json'}))).then(data => {
+              return data.content;
+            });
+          },
           realtime: info.realtime,
           userList: info.userList,
           userName: editorConfig.userName,
diff --git a/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-test/xwiki-platform-realtime-wysiwyg-test-docker/pom.xml b/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-test/xwiki-platform-realtime-wysiwyg-test-docker/pom.xml
index 87bdcd73ce57fe1f16d816e0c84800a8449b305f..224cb049d3c18f7e1d4dbea79257ec33b180d31d 100644
--- a/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-test/xwiki-platform-realtime-wysiwyg-test-docker/pom.xml
+++ b/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-test/xwiki-platform-realtime-wysiwyg-test-docker/pom.xml
@@ -90,6 +90,13 @@
       <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
+    <!-- Used to handle the edit conflict modal.  -->
+    <dependency>
+      <groupId>org.xwiki.platform</groupId>
+      <artifactId>xwiki-platform-flamingo-skin-test-pageobjects</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
     <!-- Realtime synchronization is done through WebSockets. -->
     <dependency>
       <groupId>javax.websocket</groupId>
diff --git a/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-test/xwiki-platform-realtime-wysiwyg-test-docker/src/test/it/org/xwiki/realtime/wysiwyg/test/ui/RealtimeWYSIWYGEditorIT.java b/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-test/xwiki-platform-realtime-wysiwyg-test-docker/src/test/it/org/xwiki/realtime/wysiwyg/test/ui/RealtimeWYSIWYGEditorIT.java
index 51c5df3a7a7a0c50e27536e7ba563a82c501383e..ea3d7f47b29b755c7adbc3060db9ac6365e15bf5 100644
--- a/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-test/xwiki-platform-realtime-wysiwyg-test-docker/src/test/it/org/xwiki/realtime/wysiwyg/test/ui/RealtimeWYSIWYGEditorIT.java
+++ b/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-test/xwiki-platform-realtime-wysiwyg-test-docker/src/test/it/org/xwiki/realtime/wysiwyg/test/ui/RealtimeWYSIWYGEditorIT.java
@@ -34,6 +34,8 @@
 import org.xwiki.ckeditor.test.po.MacroDialogEditModal;
 import org.xwiki.ckeditor.test.po.image.ImageDialogEditModal;
 import org.xwiki.ckeditor.test.po.image.ImageDialogSelectModal;
+import org.xwiki.flamingo.skin.test.po.EditConflictModal;
+import org.xwiki.flamingo.skin.test.po.EditConflictModal.ConflictChoice;
 import org.xwiki.model.reference.AttachmentReference;
 import org.xwiki.model.reference.DocumentReference;
 import org.xwiki.realtime.wysiwyg.test.po.RealtimeCKEditor;
@@ -740,4 +742,124 @@ void editSameMacro(TestReference testReference, TestUtils setup)
         assertEquals("{{info cssClass=\"bar\" title=\"Some cool title\"}}\ntwo one\n{{/info}}\n\n ",
             WikiEditPage.gotoPage(testReference).getContent());
     }
+    
+    @Test
+    @Order(9)
+    void reloadEditorsMergeConflictManualSave(TestReference testReference, TestUtils setup)
+    {
+        //
+        // First Tab
+        //
+
+        // Start fresh.
+        setup.deletePage(testReference);
+
+        RealtimeWYSIWYGEditPage firstEditPage = RealtimeWYSIWYGEditPage.gotoPage(testReference);
+        RealtimeCKEditor firstEditor = firstEditPage.getContenEditor();
+        RealtimeRichTextAreaElement firstTextArea = firstEditor.getRichTextArea();
+        firstTextArea.sendKeys("First");
+        firstEditPage.clickSaveAndContinue();
+
+        //
+        // Second Tab
+        //
+
+        String secondTabHandle = setup.getDriver().switchTo().newWindow(WindowType.TAB).getWindowHandle();
+
+        RealtimeWYSIWYGEditPage secondEditPage = RealtimeWYSIWYGEditPage.gotoPage(testReference);
+        RealtimeCKEditor secondEditor = secondEditPage.getContenEditor();
+        RealtimeRichTextAreaElement secondTextArea = secondEditor.getRichTextArea();
+
+        secondTextArea.waitUntilContentContains("First");
+
+        //
+        // Third Tab
+        //
+
+        String thirdTabHandle = setup.getDriver().switchTo().newWindow(WindowType.TAB).getWindowHandle();
+
+        RealtimeWYSIWYGEditPage thirdEditPage = RealtimeWYSIWYGEditPage.gotoPage(testReference);
+        RealtimeCKEditor thirdEditor = thirdEditPage.getContenEditor();
+        RealtimeRichTextAreaElement thirdTextArea = thirdEditor.getRichTextArea();
+        thirdTextArea.waitUntilContentContains("First");
+
+        thirdEditPage.leaveRealtimeEditing();
+
+        thirdTextArea.sendKeys(Keys.END, " Second");
+        thirdEditPage.clickSaveAndContinue();
+
+        // First tab
+
+        setup.getDriver().switchTo().window(firstTabHandle);
+        firstTextArea.sendKeys(Keys.END, " First");
+        firstTextArea.waitUntilContentContains("First First");
+        firstEditPage.clickSaveAndContinue(false);
+
+        EditConflictModal editConflictModal = new EditConflictModal();
+        editConflictModal.makeChoiceAndSubmit(ConflictChoice.RELOAD, false);
+
+        firstTextArea.waitUntilContentContains("First Second");
+
+        // Second tab
+        setup.getDriver().switchTo().window(secondTabHandle);
+        secondTextArea.waitUntilContentContains("First Second");
+    }
+    
+    @Test
+    @Order(10)
+    void reloadEditorsSilentMergeConflictManualSave(TestReference testReference, TestUtils setup)
+    {
+        //
+        // First Tab
+        //
+
+        // Start fresh.
+        setup.deletePage(testReference);
+
+        RealtimeWYSIWYGEditPage firstEditPage = RealtimeWYSIWYGEditPage.gotoPage(testReference);
+        RealtimeCKEditor firstEditor = firstEditPage.getContenEditor();
+        RealtimeRichTextAreaElement firstTextArea = firstEditor.getRichTextArea();
+        firstTextArea.sendKeys("First");
+        firstEditPage.clickSaveAndContinue();
+
+        //
+        // Second Tab
+        //
+
+        String secondTabHandle = setup.getDriver().switchTo().newWindow(WindowType.TAB).getWindowHandle();
+
+        RealtimeWYSIWYGEditPage secondEditPage = RealtimeWYSIWYGEditPage.gotoPage(testReference);
+        RealtimeCKEditor secondEditor = secondEditPage.getContenEditor();
+        RealtimeRichTextAreaElement secondTextArea = secondEditor.getRichTextArea();
+
+        secondTextArea.waitUntilContentContains("First");
+
+        //
+        // Third Tab
+        //
+
+        String thirdTabHandle = setup.getDriver().switchTo().newWindow(WindowType.TAB).getWindowHandle();
+
+        RealtimeWYSIWYGEditPage thirdEditPage = RealtimeWYSIWYGEditPage.gotoPage(testReference);
+        RealtimeCKEditor thirdEditor = thirdEditPage.getContenEditor();
+        RealtimeRichTextAreaElement thirdTextArea = thirdEditor.getRichTextArea();
+        thirdTextArea.waitUntilContentContains("First");
+
+        thirdEditPage.leaveRealtimeEditing();
+
+        thirdTextArea.sendKeys(Keys.END, Keys.ENTER, "Second");
+        thirdEditPage.clickSaveAndContinue();
+
+        // First tab
+        setup.getDriver().switchTo().window(firstTabHandle);
+        firstEditPage.clickSaveAndContinue();
+
+        firstTextArea.waitUntilContentContains("First");
+        firstTextArea.waitUntilContentContains("Second");
+
+        // Second tab
+        setup.getDriver().switchTo().window(secondTabHandle);
+        secondTextArea.waitUntilContentContains("First");
+        secondTextArea.waitUntilContentContains("Second");
+    }
 }
diff --git a/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-test/xwiki-platform-realtime-wysiwyg-test-pageobjects/src/main/java/org/xwiki/realtime/wysiwyg/test/po/RealtimeWYSIWYGEditPage.java b/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-test/xwiki-platform-realtime-wysiwyg-test-pageobjects/src/main/java/org/xwiki/realtime/wysiwyg/test/po/RealtimeWYSIWYGEditPage.java
index 2b45c2b5081ba4430958d3751967d3252f1d0324..9b11287daeba1154f885f89841c8556e07e742ff 100644
--- a/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-test/xwiki-platform-realtime-wysiwyg-test-pageobjects/src/main/java/org/xwiki/realtime/wysiwyg/test/po/RealtimeWYSIWYGEditPage.java
+++ b/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-test/xwiki-platform-realtime-wysiwyg-test-pageobjects/src/main/java/org/xwiki/realtime/wysiwyg/test/po/RealtimeWYSIWYGEditPage.java
@@ -19,6 +19,7 @@
  */
 package org.xwiki.realtime.wysiwyg.test.po;
 
+import org.openqa.selenium.By;
 import org.openqa.selenium.WebElement;
 import org.openqa.selenium.support.FindBy;
 import org.xwiki.model.reference.EntityReference;
@@ -71,7 +72,7 @@ public void leaveRealtimeEditing()
     {
         if (isRealtimeEditing()) {
             this.allowRealtimeCheckbox.click();
-            // TODO: Handle the confirmation modal.
+            getDriver().findElement(By.cssSelector(".modal-popup .realtime-buttons .btn-primary")).click();
         }
     }
 
diff --git a/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-webjar/src/main/webjar/editor.js b/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-webjar/src/main/webjar/editor.js
index cae23f3be68c5d7c9b9d229611fcce725c80d2df..455d636d94e8b163507494e024b35331aada2f44 100644
--- a/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-webjar/src/main/webjar/editor.js
+++ b/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-webjar/src/main/webjar/editor.js
@@ -93,8 +93,10 @@ define('xwiki-realtime-wysiwyg-editor', [
      *
      * @param {Node[]} updatedNodes the DOM nodes that have been updated (added, modified directly or with removed
      *   descendants)
+     *
+     * @param {boolean} propagate true when the new content should be propagated to coeditors
      */
-    contentUpdated(updatedNodes) {
+    contentUpdated(updatedNodes, propagate) {
       try {
         this._initializeWidgets(updatedNodes);
       } catch (e) {
@@ -103,7 +105,7 @@ define('xwiki-realtime-wysiwyg-editor', [
 
       // Notify the content change (e.g. to update the empty line placeholders) without triggering our own change
       // handler (see #onChange()).
-      this._ckeditor.fire('change', {remote: true});
+      this._ckeditor.fire('change', {remote: !propagate});
     }
 
     /**
@@ -155,6 +157,16 @@ define('xwiki-realtime-wysiwyg-editor', [
       this._CKEDITOR.plugins.xwikiSelection.restoreSelection(this._ckeditor);
     }
 
+    /**
+     * Converts input data accepted by the editor to html that can be directly inserted in the editor's DOM.
+     *
+     * @param {string} data the data to be converted
+     * @returns {string} html representation of the input data that can be inserted in the editor's DOM.
+     */
+    convertDataToHtml(data) {
+      return this._ckeditor.dataProcessor.toHtml(data);
+    }
+
     _initializeWidgets(updatedNodes) {
       // Reset the focused and selected widgets, as well as the widget holding the focused editable because they may
       // have been invalidated by the DOM changes.
@@ -213,4 +225,4 @@ define('xwiki-realtime-wysiwyg-editor', [
   }
 
   return Editor;
-});
\ No newline at end of file
+});
diff --git a/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-webjar/src/main/webjar/patches.js b/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-webjar/src/main/webjar/patches.js
index 6836180a53f2ad30083665037b7f91ccd411fa1e..1bbf9948e6c95a029f6bad89bacb511f4e4c6d50 100644
--- a/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-webjar/src/main/webjar/patches.js
+++ b/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-webjar/src/main/webjar/patches.js
@@ -112,8 +112,10 @@ define('xwiki-realtime-wysiwyg-patches', [
      * 
      * @param {string} remoteFilteredHyperJSON the new normalized (filtered) content (usually the result of a remote
      *   change), serialized as HyperJSON
+     *
+     * @param {boolean} propagate true when the new content should be propagated to coeditors
      */
-    setHyperJSON(remoteFilteredHyperJSON) {
+    setHyperJSON(remoteFilteredHyperJSON, propagate) {
       let remoteHyperJSON;
       try {
         remoteHyperJSON = this._revertHyperJSONFilters(remoteFilteredHyperJSON);
@@ -137,32 +139,39 @@ define('xwiki-realtime-wysiwyg-patches', [
         return;
       }
 
-      this._updateContent(newContent);
+      this._updateContent(newContent, propagate);
     }
 
     /**
      * Update the editor content without affecting its caret / selection.
      * 
      * @param {string} html the new HTML content
+     * @param {boolean} propagate true when the new content should be propagated to coeditors
      */
-    setHTML(html) {
+    setHTML(html, propagate) {
+      const fixedHtml = this._editor.convertDataToHtml(html);
+
       let doc;
       try {
-        doc = new DOMParser().parseFromString(html, 'text/html');
+        doc = new DOMParser().parseFromString(fixedHtml, 'text/html');
       } catch (e) {
         console.error('Failed to parse the given HTML string: ' + html, e);
         return;
       }
 
-      this._updateContent(doc.body);
+      // We convert to HyperJSON and set the HyperJSON so that we can use the same filters
+      // as when receiving content from coeditors.
+      const hjson = Patches._stringifyNode(this._normalizeContent(doc.body), false);
+      this.setHyperJSON(hjson, propagate);
     }
 
     /**
      * Update the editor content without affecting its caret / selection.
      * 
      * @param {Node} newContent the new content to set, as a DOM node
+     * @param {boolean} propagate true when the new content should be propagated to coeditors
      */
-    _updateContent(newContent) {
+    _updateContent(newContent, propagate) {
       // Remember where the selection is, to be able to restore it in case the content update affects it.
       this._editor.saveSelection();
       const selection = this._editor.getSelection();
@@ -179,7 +188,7 @@ define('xwiki-realtime-wysiwyg-patches', [
         document: oldContent.ownerDocument
       });
 
-      this._editor.contentUpdated(this._diffDOM._updatedNodes);
+      this._editor.contentUpdated(this._diffDOM._updatedNodes, propagate);
 
       // Restore the selection if the editor had a selection (i.e. if the selection was inside the editing area) before
       // the content update and it was affected by the content update. Note that the selection restore focuses the
@@ -302,4 +311,4 @@ define('xwiki-realtime-wysiwyg-patches', [
   }
 
   return Patches;
-});
\ No newline at end of file
+});
diff --git a/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-webjar/src/main/webjar/wysiwygEditor.js b/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-webjar/src/main/webjar/wysiwygEditor.js
index 99296bdc40160514df9b4e84e87bb2b0c70a4b79..9a7dd67876219bb2995486c81fabb016f43d50e8 100644
--- a/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-webjar/src/main/webjar/wysiwygEditor.js
+++ b/xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-wysiwyg/xwiki-platform-realtime-wysiwyg-webjar/src/main/webjar/wysiwygEditor.js
@@ -191,39 +191,7 @@ define('xwiki-realtime-wysiwyg', [
           // Id of the wiki page form.
           formId: window.XWiki.editor === 'wysiwyg' ? 'edit' : 'inline',
           setTextValue: function(newText, toConvert, callback) {
-            function andThen(data) {
-              patchedEditor.setHTML(data);
-
-              // If available, transform the HTML comments for XWiki macros into macros before saving
-              // (<!--startmacro:{...}-->). We can do that by using the "xwiki-refresh" command provided the by
-              // CKEditor Integration application.
-              if (editor.plugins['xwiki-macro'] && findMacroComments(genericEditor.getContent()).length > 0) {
-                initializing = true;
-                editor.execCommand('xwiki-refresh');
-                afterRefresh.push(callback);
-              } else {
-                callback();
-                realtimeOptions.onLocal();
-              }
-            }
-            if (toConvert) {
-              const object = {
-                wiki: XWiki.currentWiki,
-                space: XWiki.currentSpace,
-                page: XWiki.currentPage,
-                convert: true,
-                text: newText
-              };
-              $.post(editorConfig.htmlConverterUrl, object).then(andThen).catch(() => {
-                const debugLog = {
-                  state: editorId + '/convertHTML',
-                  postData: object
-                };
-                module.onAbort(null, 'converthtml', JSON.stringify(debugLog));
-              });
-            } else {
-              andThen(newText);
-            }
+            patchedEditor.setHTML(newText, true);
           },
           getSaveValue: function() {
             return {
@@ -240,6 +208,13 @@ define('xwiki-realtime-wysiwyg', [
               return null;
             }
           },
+          getTextAtCurrentRevision: function(revision) {
+            return $.get(XWiki.currentDocument.getURL('get', $.param({xpage:'get',
+                                                                      outputSyntax:'annotatedhtml',
+                                                                      outputSyntaxVersion:'5.0',
+                                                                      transformations:'macro',
+                                                                      rev:revision})));
+          },
           realtime: info.realtime,
           userList: info.userList,
           userName: editorConfig.userName,