Skip to content

Commit

Permalink
Merge pull request #15774 from ckeditor/ck/15764
Browse files Browse the repository at this point in the history
Other (source-editing): Source editing will now throw when used with real-time collaboration. Added `sourceEditing.allowCollaborationFeatures` configuration flag that suppresses the error and allows to use both features together. Closes #15764.

MINOR BREAKING CHANGE (source-editing): Source editing will now throw when used with real-time collaboration as these features are not fully compatible and may lead to data loss. Now, you will have to explicitly enable source editing for real-time collaboration by setting `sourceEditing.allowCollaborationFeatures` configuration flag to `true`. If you have used both features, please read a new guide discussing the risks and add the flag to your configuration.
  • Loading branch information
scofalik committed Jan 29, 2024
2 parents f0af6d4 + 1082260 commit 7ed0b7c
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 43 deletions.
8 changes: 7 additions & 1 deletion packages/ckeditor5-engine/src/controller/datacontroller.ts
Expand Up @@ -406,7 +406,13 @@ export default class DataController extends EmitterMixin() {
* cleared after the new data is applied (all undo steps will be removed). If the batch type `isUndoable` flag is be set to `true`,
* the undo stack will be preserved instead and not cleared when new data is applied.
*/
public set( data: string | Record<string, string>, options: { batchType?: BatchType } = {} ): void {
public set(
data: string | Record<string, string>,
options: {
batchType?: BatchType;
[ key: string ]: unknown;
} = {}
): void {
let newData: Record<string, string> = {};

if ( typeof data === 'string' ) {
Expand Down
68 changes: 65 additions & 3 deletions packages/ckeditor5-source-editing/docs/features/source-editing.md
Expand Up @@ -21,11 +21,73 @@ You can also use one of the many CKEditor&nbsp;5 features available in the toolb
This demo presents a limited set of features. Visit the {@link examples/builds/full-featured-editor feature-rich editor example} to see more in action.
</info-box>

## Additional feature information
## Limitations and incompatibility

The source editing plugin is a low-level document editing interface, while all the buttons and dropdowns located in the toolbar are high-level ones.
<info-box error>
The source editing plugin is a low-level document editing method, which allows you to directly alter the document data source. This presents incompatibilities with some editor features which highly rely on the editor mechanisms and architecture.

Changes made to the document source will be applied to the editor's {@link framework/architecture/editing-engine data model} only if the editor understands (via loaded plugins) the given syntax. You will lose all changes that the editor features cannot understand. For example, if the editor does not have a {@link features/horizontal-line horizontal line} plugin loaded, the `<hr>` tag added in the document source will be removed upon exit from the source editing mode.
**Please carefully read this section, and check if these problems may apply to your editor configuration.**
</info-box>

### Real-time collaboration

Source editing used during real-time collaboration brings a severe risk of data loss in a way that may be difficult for a user to notice and understand.

After you switch to source editing, incoming changes performed by remote users are not reflected in the source code. When you switch back (saving the source code), **all changes done in the meantime by other users will be overwritten**.

Due to this risk, the features are not allowed to be used together by default. When both are added to the editor, it will throw an error. **You have to explicitly enable source editing mode for real-time collaboration, acknowledging this risk.**

To enable the features, set {@link module:source-editing/sourceeditingconfig~SourceEditingConfig#allowCollaborationFeatures `sourceEditing.allowCollaborationFeatures`} configuration flag to `true`.

### Comments and track changes

Comments and track changes features use markers to mark affected parts of the document.

In source editing mode, it will be easily possible for a user to modify these markers boundaries. The user will be able to, among other, change the range of a comment or tracked change, or remove them. This presents a potential problem related to users permissions.

The editor will show a warning in the browser's console when these plugins and source editing plugin are used together.

The warning will be silenced if you set {@link module:source-editing/sourceeditingconfig~SourceEditingConfig#allowCollaborationFeatures `sourceEditing.allowCollaborationFeatures`} configuration flag to `true`.

### Support for various HTML elements

Changes made to the document data source will be eventually saved only if the editor "understands" them, that is, only when one of the loaded plugins recognizes given syntax (HTML or Markdown). All changes that were not understood by the editor will be filtered out.

For example, if the editor does not have a {@link features/horizontal-line horizontal line} plugin loaded, the `<hr>` tag added in the document source will be removed upon exit from the source editing mode.

To avoid that, make sure that your editor configuration has all necessary plugins that handle various HTML tags.

In many cases, to enable advanced modifications through the source editing, you might need to enable {@link features/html-embed HTML embed} feature and {@link features/general-html-support General HTML support} feature, or write a plugin that will enable given HTML tag or attribute.

### HTML normalization

When the source data is read by the editor, it is converted to a normalized, high-level abstract data model structure, on which the editor operates. This structure differs from the HTML or Markdown code.

Note, that the same document "state" may be described using HTML in various ways.

For example, `<strong><em>Foo</em></strong>` and `<i><b>Foo</b></i>` both mean text "Foo" with bold and italic styling. Both will be represented the same when loaded to the internal editor data model.

When the editor data model is later converted back to the document source data, it will be normalized regardless what was the original input. So, `<i><b>Foo</b></i>` will eventually become `<strong><em>Foo</em></strong>`.

This limitation is a direct consequence of the core editor architecture and cannot be worked around. Although it is possible to change the editor final output, the input data will always be normalized to that output format.

### Impact on the editor UI

Editor features rely on high-level editor API which cannot be used when the source editing is active. Due to that, when you switch to the source editing mode, all toolbar buttons become disabled and all dialog windows are closed.

### Revision history

Saving the modified document source is internally executed through replacing the old data with the new one. As a consequence, it will be represented in revision history as a huge replace change (insertion + deletion).

The editor will show a warning in the browser's console when revision history and source editing are loaded together.

The warning will be silenced if you set {@link module:source-editing/sourceeditingconfig~SourceEditingConfig#allowCollaborationFeatures `sourceEditing.allowCollaborationFeatures`} configuration flag to `true`.

### Restricted editing

Restricted editing is not enforced in the source editing mode. This means that the user will be able to edit any part of the document, as well as remove the markers that mark the boundaries of restricted areas.

The editor will show a warning in the browser's console when restricted editing and source editing are loaded together.

## Markdown source view

Expand Down
12 changes: 11 additions & 1 deletion packages/ckeditor5-source-editing/src/augmentation.ts
Expand Up @@ -3,9 +3,19 @@
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

import type { SourceEditing } from './index.js';
import type { SourceEditing, SourceEditingConfig } from './index.js';

declare module '@ckeditor/ckeditor5-core' {
interface EditorConfig {

/**
* The configuration of the source editing feature.
*
* Read more in {@link module:source-editing/sourceeditingconfig~SourceEditingConfig}.
*/
sourceEditing?: SourceEditingConfig;
}

interface PluginsMap {
[ SourceEditing.pluginName ]: SourceEditing;
}
Expand Down
1 change: 1 addition & 0 deletions packages/ckeditor5-source-editing/src/index.ts
Expand Up @@ -8,5 +8,6 @@
*/

export { default as SourceEditing } from './sourceediting.js';
export type { SourceEditingConfig } from './sourceeditingconfig.js';

import './augmentation.js';
79 changes: 50 additions & 29 deletions packages/ckeditor5-source-editing/src/sourceediting.ts
Expand Up @@ -11,7 +11,7 @@

import { type Editor, Plugin, PendingActions } from 'ckeditor5/src/core.js';
import { ButtonView, type Dialog } from 'ckeditor5/src/ui.js';
import { createElement, ElementReplacer } from 'ckeditor5/src/utils.js';
import { CKEditorError, createElement, ElementReplacer } from 'ckeditor5/src/utils.js';
import { formatHtml } from './utils/formathtml.js';

import '../theme/sourceediting.css';
Expand Down Expand Up @@ -74,12 +74,16 @@ export default class SourceEditing extends Plugin {
this._elementReplacer = new ElementReplacer();
this._replacedRoots = new Map();
this._dataFromRoots = new Map();

editor.config.define( 'sourceEditing.allowCollaborationFeatures', false );
}

/**
* @inheritDoc
*/
public init(): void {
this._checkCompatibility();

const editor = this.editor;
const t = editor.t;

Expand Down Expand Up @@ -157,20 +161,61 @@ export default class SourceEditing extends Plugin {
}

/**
* @inheritDoc
* Updates the source data in all hidden editing roots.
*/
public afterInit(): void {
public updateEditorData(): void {
const editor = this.editor;
const data: Record<string, string> = {};

for ( const [ rootName, domSourceEditingElementWrapper ] of this._replacedRoots ) {
const oldData = this._dataFromRoots.get( rootName );
const newData = domSourceEditingElementWrapper.dataset.value!;

// Do not set the data unless some changes have been made in the meantime.
// This prevents empty undo steps after switching to the normal editor.
if ( oldData !== newData ) {
data[ rootName ] = newData;
this._dataFromRoots.set( rootName, newData );
}
}

if ( Object.keys( data ).length ) {
editor.data.set( data, { batchType: { isUndoable: true }, suppressErrorInCollaboration: true } );
}
}

private _checkCompatibility() {
const editor = this.editor;
const allowCollaboration = editor.config.get( 'sourceEditing.allowCollaborationFeatures' );

if ( !allowCollaboration && editor.plugins.has( 'RealTimeCollaborativeEditing' ) ) {
/**
* Source editing feature is not fully compatible with real-time collaboration,
* and using it may lead to data loss. Please read
* {@glink features/source-editing#limitations-and-incompatibility source editing feature guide} to learn more.
*
* If you understand the possible risk of data loss, you can enable the source editing
* by setting the
* {@link module:source-editing/sourceeditingconfig~SourceEditingConfig#allowCollaborationFeatures}
* configuration flag to `true`.
*
* @error source-editing-incompatible-with-real-time-collaboration
*/
throw new CKEditorError( 'source-editing-incompatible-with-real-time-collaboration', null );
}

const collaborationPluginNamesToWarn = [
'RealTimeCollaborativeEditing',
'CommentsEditing',
'TrackChangesEditing',
'RevisionHistory'
];

// Currently, the basic integration with Collaboration Features is to display a warning in the console.
if ( collaborationPluginNamesToWarn.some( pluginName => editor.plugins.has( pluginName ) ) ) {
//
// If `allowCollaboration` flag is set, do not show these warnings. If the flag is set, we assume that the integrator read
// appropriate section of the guide so there's no use to spam the console with warnings.
//
if ( !allowCollaboration && collaborationPluginNamesToWarn.some( pluginName => editor.plugins.has( pluginName ) ) ) {
console.warn(
'You initialized the editor with the source editing feature and at least one of the collaboration features. ' +
'Please be advised that the source editing feature may not work, and be careful when editing document source ' +
Expand All @@ -188,30 +233,6 @@ export default class SourceEditing extends Plugin {
}
}

/**
* Updates the source data in all hidden editing roots.
*/
public updateEditorData(): void {
const editor = this.editor;
const data: Record<string, string> = {};

for ( const [ rootName, domSourceEditingElementWrapper ] of this._replacedRoots ) {
const oldData = this._dataFromRoots.get( rootName );
const newData = domSourceEditingElementWrapper.dataset.value!;

// Do not set the data unless some changes have been made in the meantime.
// This prevents empty undo steps after switching to the normal editor.
if ( oldData !== newData ) {
data[ rootName ] = newData;
this._dataFromRoots.set( rootName, newData );
}
}

if ( Object.keys( data ).length ) {
editor.data.set( data, { batchType: { isUndoable: true } } );
}
}

/**
* Creates source editing wrappers that replace each editing root. Each wrapper contains the document source from the corresponding
* root.
Expand Down
37 changes: 37 additions & 0 deletions packages/ckeditor5-source-editing/src/sourceeditingconfig.ts
@@ -0,0 +1,37 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/**
* @module source-editing/sourceeditingconfig
*/

/**
* The configuration of the source editing feature.
*
* ```ts
* ClassicEditor
* .create( {
* sourceEditing: {
* allowCollaborationFeatures: true
* }
* } )
* .then( ... )
* .catch( ... );
* ```
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
*/
export interface SourceEditingConfig {

/**
* Set to `true` to enable source editing feature for real-time collaboration.
*
* Please note that source editing feature is not fully compatible with real-time collaboration and using it may lead to data loss.
* {@glink features/source-editing#limitations-and-incompatibility Read more}.
*
* @default false
*/
allowCollaborationFeatures?: boolean;
}

0 comments on commit 7ed0b7c

Please sign in to comment.