diff --git a/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-plugins/src/main/webjar/xwiki-realtime/ckeditorRealtimeAdapter.js b/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-plugins/src/main/webjar/xwiki-realtime/ckeditorRealtimeAdapter.js index cb04094fd251660038f457bf1259c2afe2478478..461d3d067370624876d984e59376fe14da59a6e2 100644 --- a/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-plugins/src/main/webjar/xwiki-realtime/ckeditorRealtimeAdapter.js +++ b/xwiki-platform-core/xwiki-platform-ckeditor/xwiki-platform-ckeditor-plugins/src/main/webjar/xwiki-realtime/ckeditorRealtimeAdapter.js @@ -235,7 +235,7 @@ define('xwiki-ckeditor-realtime-adapter', [ // name and data. We can only update an existing widget in a generic way if its type doesn't change. const widgetPlaceholder = widgetWrapper.ownerDocument.createElement('xwiki-widget-' + widget.name); widgetPlaceholder.classList.add('xwiki-widget'); - widgetPlaceholder.setAttribute('value', JSONSortify(widget.data)); + widgetPlaceholder.setAttribute('value', this._serializeWidgetData(widget)); widgetPlaceholder.dataset.widgetId = widget.id; widgetWrapper.replaceWith(widgetPlaceholder); @@ -266,7 +266,7 @@ define('xwiki-ckeditor-realtime-adapter', [ // data is an object and this makes the check always return false. The problem with this is that when the data // event is fired the scroll postion is updated, making it hard to scroll the content while remote changes are // applied (the scroll bar jumps). So we have to check ourselves if the data has changed. - if (widgetData !== JSONSortify(widget.data)) { + if (widgetData !== this._serializeWidgetData(widget)) { widgetData = JSON.parse(widgetData); widget.setData(widgetData); } @@ -314,6 +314,27 @@ define('xwiki-ckeditor-realtime-adapter', [ this._widgetCache = {}; } + _serializeWidgetData(widget) { + // Clone the data so that we can modify it without affecting the widget. + const data = JSON.parse((JSON.stringify(widget.data))); + if (widget.name === 'xwiki-macro') { + // Whether a macro widget is inline or block depends on the macro output, which is not synchronized and thus is + // missing from the new content that we want to apply to the editor. When the macro widgets from the new content + // are initialized the inline flag is set (in the absence of the macro outut) based on the siblings and the + // parent of the macro widget. But there are cases where the macro can be both inline and block in the same + // context (parent and siblings) so we don't know for sure if the original macro widget was inline or block + // (e.g. an info box can technically be both inline and block inside a table cell because a table cell accepts + // both inline and block level content). We had two options: + // * include information about whether a macro widget is inline or block in the content that we synchronize (so + // that the inline flag is properly set when the macro widgets from the new content are initialized) + // * ignore the inline flag when computing the changes between the old and the new content + // We chose the second option because it's simpler and because the user can't change only the inline flag + // without also changing either the macro parameters and content or the context where the macro is called. + delete data.inline; + } + return JSONSortify(data); + } + async _updateWidgets(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.