Skip to content

Commit

Permalink
Introduced the disableTwoWayDataBinding flag (#468)
Browse files Browse the repository at this point in the history
Other: Introduced the `disableTwoWayDataBinding` flag which disabled the two-way binding between the editor and React state. Closes #457.
  • Loading branch information
DawidKossowski authored Apr 18, 2024
1 parent e4db82d commit fbb9c91
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 105 deletions.
2 changes: 1 addition & 1 deletion demo-multiroot-react-18/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
},
"dependencies": {
"@ckeditor/ckeditor5-react": "file:..",
"@ckeditor/ckeditor5-build-multi-root": "^40.1.0"
"@ckeditor/ckeditor5-build-multi-root": "^41.3.1"
},
"devDependencies": {
"@types/react": "^18.0.28",
Expand Down
50 changes: 25 additions & 25 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,36 +23,36 @@
"devDependencies": {
"@babel/core": "^7.10.5",
"@babel/preset-react": "^7.10.4",
"@ckeditor/ckeditor5-autoformat": "^40.1.0",
"@ckeditor/ckeditor5-basic-styles": "^40.1.0",
"@ckeditor/ckeditor5-block-quote": "^40.1.0",
"@ckeditor/ckeditor5-build-classic": "^40.1.0",
"@ckeditor/ckeditor5-build-multi-root": "^40.1.0",
"@ckeditor/ckeditor5-cloud-services": "^40.1.0",
"@ckeditor/ckeditor5-autoformat": "^41.3.1",
"@ckeditor/ckeditor5-basic-styles": "^41.3.1",
"@ckeditor/ckeditor5-block-quote": "^41.3.1",
"@ckeditor/ckeditor5-build-classic": "^41.3.1",
"@ckeditor/ckeditor5-build-multi-root": "^41.3.1",
"@ckeditor/ckeditor5-cloud-services": "^41.3.1",
"@ckeditor/ckeditor5-dev-bump-year": "^38.0.0",
"@ckeditor/ckeditor5-dev-ci": "^38.0.0",
"@ckeditor/ckeditor5-dev-release-tools": "^38.0.0",
"@ckeditor/ckeditor5-dev-utils": "^38.0.0",
"@ckeditor/ckeditor5-editor-classic": "^40.1.0",
"@ckeditor/ckeditor5-editor-multi-root": "^40.1.0",
"@ckeditor/ckeditor5-essentials": "^40.1.0",
"@ckeditor/ckeditor5-heading": "^40.1.0",
"@ckeditor/ckeditor5-image": "^40.1.0",
"@ckeditor/ckeditor5-indent": "^40.1.0",
"@ckeditor/ckeditor5-link": "^40.1.0",
"@ckeditor/ckeditor5-list": "^40.1.0",
"@ckeditor/ckeditor5-media-embed": "^40.1.0",
"@ckeditor/ckeditor5-paste-from-office": "^40.1.0",
"@ckeditor/ckeditor5-table": "^40.1.0",
"@ckeditor/ckeditor5-utils": "^40.1.0",
"@ckeditor/ckeditor5-watchdog": "^40.1.0",
"@ckeditor/ckeditor5-editor-classic": "^41.3.1",
"@ckeditor/ckeditor5-editor-multi-root": "^41.3.1",
"@ckeditor/ckeditor5-essentials": "^41.3.1",
"@ckeditor/ckeditor5-heading": "^41.3.1",
"@ckeditor/ckeditor5-image": "^41.3.1",
"@ckeditor/ckeditor5-indent": "^41.3.1",
"@ckeditor/ckeditor5-link": "^41.3.1",
"@ckeditor/ckeditor5-list": "^41.3.1",
"@ckeditor/ckeditor5-media-embed": "^41.3.1",
"@ckeditor/ckeditor5-paste-from-office": "^41.3.1",
"@ckeditor/ckeditor5-table": "^41.3.1",
"@ckeditor/ckeditor5-utils": "^41.3.1",
"@ckeditor/ckeditor5-watchdog": "^41.3.1",
"@testing-library/react-hooks": "^8.0.1",
"@types/react": "^16.14.35",
"@types/react-dom": "^16.9.18",
"babel-loader": "^8.1.0",
"babel-plugin-istanbul": "^6.1.0",
"chai": "^4.2.0",
"ckeditor5": "^40.1.0",
"ckeditor5": "^41.3.1",
"coveralls": "^3.1.1",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
Expand Down Expand Up @@ -88,11 +88,11 @@
"semver": "^7.0.0"
},
"peerDependencies": {
"@ckeditor/ckeditor5-core": ">=40.1.0",
"@ckeditor/ckeditor5-editor-multi-root": ">=40.1.0",
"@ckeditor/ckeditor5-engine": ">=40.1.0",
"@ckeditor/ckeditor5-utils": ">=40.1.0",
"@ckeditor/ckeditor5-watchdog": ">=40.1.0",
"@ckeditor/ckeditor5-core": ">=41.3.1",
"@ckeditor/ckeditor5-editor-multi-root": ">=41.3.1",
"@ckeditor/ckeditor5-engine": ">=41.3.1",
"@ckeditor/ckeditor5-utils": ">=41.3.1",
"@ckeditor/ckeditor5-watchdog": ">=41.3.1",
"react": "^16.13.1 || ^17.0.0 || ^18.0.0"
},
"engines": {
Expand Down
161 changes: 97 additions & 64 deletions src/useMultiRootEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const REACT_INTEGRATION_READ_ONLY_LOCK_ID = 'Lock from React integration (@ckedi

/* eslint-disable @typescript-eslint/no-use-before-define */
const useMultiRootEditor = ( props: MultiRootHookProps ): MultiRootHookReturns => {
let watchdog: EditorWatchdog | EditorWatchdogAdapter<MultiRootEditor> | null = null;
const watchdog = useRef<EditorWatchdog | EditorWatchdogAdapter<MultiRootEditor> | null>( null );

const editorDestructionInProgress = useRef<Promise<void> | null>( null );

Expand All @@ -42,8 +42,6 @@ const useMultiRootEditor = ( props: MultiRootHookProps ): MultiRootHookReturns =

const shouldUpdateEditor = useRef<boolean>( true );

const toolbarElement = <div dangerouslySetInnerHTML={{ __html: editor ? editor.ui.view.toolbar.element!.outerHTML : '' }}></div>;

useEffect( () => {
const initEditor = async () => {
// When the component has been remounted it is crucial to wait for removing the old editor
Expand Down Expand Up @@ -113,54 +111,56 @@ const useMultiRootEditor = ( props: MultiRootHookProps ): MultiRootHookReturns =
const onChangeData = ( editor: MultiRootEditor, event: EventInfo ): void => {
const modelDocument = editor!.model.document;

const newData: Record<string, string> = {};
const newAttributes: Record<string, Record<string, unknown>> = {};
if ( !props.disableTwoWayDataBinding ) {
const newData: Record<string, string> = {};
const newAttributes: Record<string, Record<string, unknown>> = {};

modelDocument.differ.getChanges()
.forEach( change => {
let root: RootElement;
modelDocument.differ.getChanges()
.forEach( change => {
let root: RootElement;

if ( change.type == 'insert' || change.type == 'remove' ) {
root = change.position.root as RootElement;
} else {
// Must be `attribute` diff item.
root = change.range.root as RootElement;
}
if ( change.type == 'insert' || change.type == 'remove' ) {
root = change.position.root as RootElement;
} else {
// Must be `attribute` diff item.
root = change.range.root as RootElement;
}

// Getting data from a not attached root will trigger a warning.
// There is another callback for handling detached roots.
if ( !root.isAttached() ) {
return;
}
// Getting data from a not attached root will trigger a warning.
// There is another callback for handling detached roots.
if ( !root.isAttached() ) {
return;
}

const { rootName } = root;
const { rootName } = root;

newData[ rootName ] = editor!.getData( { rootName } );
} );
newData[ rootName ] = editor!.getData( { rootName } );
} );

modelDocument.differ.getChangedRoots()
.forEach( changedRoot => {
// Ignore added and removed roots. They are handled by a different function.
// Only register if roots attributes changed.
if ( changedRoot.state ) {
if ( newData[ changedRoot.name ] !== undefined ) {
delete newData[ changedRoot.name ];
}
modelDocument.differ.getChangedRoots()
.forEach( changedRoot => {
// Ignore added and removed roots. They are handled by a different function.
// Only register if roots attributes changed.
if ( changedRoot.state ) {
if ( newData[ changedRoot.name ] !== undefined ) {
delete newData[ changedRoot.name ];
}

return;
}
return;
}

const rootName = changedRoot.name;
const rootName = changedRoot.name;

newAttributes[ rootName ] = editor!.getRootAttributes( rootName );
} );
newAttributes[ rootName ] = editor!.getRootAttributes( rootName );
} );

if ( Object.keys( newData ).length ) {
setData( previousData => ( { ...previousData, ...newData } ) );
}
if ( Object.keys( newData ).length ) {
setData( previousData => ( { ...previousData, ...newData } ) );
}

if ( Object.keys( newAttributes ).length ) {
setAttributes( previousAttributes => ( { ...previousAttributes, ...newAttributes } ) );
if ( Object.keys( newAttributes ).length ) {
setAttributes( previousAttributes => ( { ...previousAttributes, ...newAttributes } ) );
}
}

/* istanbul ignore else */
Expand All @@ -177,13 +177,15 @@ const useMultiRootEditor = ( props: MultiRootHookProps ): MultiRootHookReturns =

const reactElement = _createEditableElement( editor, rootName );

setData( previousData =>
( { ...previousData, [ rootName ]: editor!.getData( { rootName } ) } )
);
if ( !props.disableTwoWayDataBinding ) {
setData( previousData =>
( { ...previousData, [ rootName ]: editor!.getData( { rootName } ) } )
);

setAttributes( previousAttributes =>
( { ...previousAttributes, [ rootName ]: editor!.getRootAttributes( rootName ) } )
);
setAttributes( previousAttributes =>
( { ...previousAttributes, [ rootName ]: editor!.getRootAttributes( rootName ) } )
);
}

setElements( previousElements => [ ...previousElements, reactElement ] );
};
Expand All @@ -196,17 +198,19 @@ const useMultiRootEditor = ( props: MultiRootHookProps ): MultiRootHookReturns =

setElements( previousElements => previousElements.filter( element => element.props.id !== rootName ) );

setData( previousData => {
const { [ rootName! ]: _, ...newData } = previousData;
if ( !props.disableTwoWayDataBinding ) {
setData( previousData => {
const { [ rootName! ]: _, ...newData } = previousData;

return { ...newData };
} );
return { ...newData };
} );

setAttributes( previousAttributes => {
const { [ rootName! ]: _, ...newAttributes } = previousAttributes;
setAttributes( previousAttributes => {
const { [ rootName! ]: _, ...newAttributes } = previousAttributes;

return { ...newAttributes };
} );
return { ...newAttributes };
} );
}

editor!.detachEditable( root );
};
Expand Down Expand Up @@ -300,9 +304,9 @@ const useMultiRootEditor = ( props: MultiRootHookProps ): MultiRootHookReturns =
// the `ContextWatchdog#state` would have a correct value. See `EditorWatchdogAdapter#destroy()` for more information.
/* istanbul ignore next */
setTimeout( async () => {
if ( watchdog ) {
await watchdog.destroy();
watchdog = null;
if ( watchdog.current ) {
await watchdog.current.destroy();
watchdog.current = null;

return resolve();
}
Expand All @@ -329,25 +333,27 @@ const useMultiRootEditor = ( props: MultiRootHookProps ): MultiRootHookReturns =
}

/* istanbul ignore next */
if ( watchdog ) {
if ( watchdog.current ) {
return;
}

if ( context instanceof ContextWatchdog ) {
watchdog = new EditorWatchdogAdapter( context );
watchdog.current = new EditorWatchdogAdapter( context );
} else {
watchdog = new EditorWatchdog( props.editor, props.watchdogConfig );
watchdog.current = new EditorWatchdog( props.editor, props.watchdogConfig );
}

watchdog.setCreator( ( data, config ) => _createEditor( data as Record<string, string>, config ) );
const watchdogInstance = watchdog.current;

watchdog.on( 'error', ( _, { error, causesRestart } ) => {
watchdogInstance.setCreator( ( data, config ) => _createEditor( data as Record<string, string>, config ) );

watchdogInstance.on( 'error', ( _, { error, causesRestart } ) => {
const onError = props.onError || console.error;

onError( error, { phase: 'runtime', willEditorRestart: causesRestart } );
} );

await watchdog
await watchdogInstance
.create( data as any, _getConfig() )
.catch( error => {
const onError = props.onError || console.error;
Expand Down Expand Up @@ -475,12 +481,38 @@ const useMultiRootEditor = ( props: MultiRootHookProps ): MultiRootHookReturns =
);

return {
editor, editableElements: elements, toolbarElement,
editor, editableElements: elements, toolbarElement: <EditorToolbarWrapper editor={editor} />,
data, setData: _externalSetData,
attributes, setAttributes: _externalSetAttributes
};
};

const EditorToolbarWrapper = ( { editor }: any ) => {
const toolbarRef = useRef<HTMLDivElement>( null );

useEffect( () => {
const toolbarContainer = toolbarRef.current;

if ( !editor || !toolbarContainer ) {
return undefined;
}

const element = editor.ui.view.toolbar.element!;

if ( toolbarContainer ) {
toolbarContainer.appendChild( element! );
}

return () => {
if ( toolbarContainer ) {
toolbarContainer.removeChild( element! );
}
};
}, [ editor && editor.id ] );

return <div ref={toolbarRef}></div>;
};

export default useMultiRootEditor;

interface ErrorDetails {
Expand All @@ -496,6 +528,7 @@ export type MultiRootHookProps = {
editor: typeof MultiRootEditor;
watchdogConfig?: WatchdogConfig;
disableWatchdog?: boolean;
disableTwoWayDataBinding?: boolean;

onReady?: ( editor: MultiRootEditor ) => void;
onError?: ( error: Error, details: ErrorDetails ) => void;
Expand Down
Loading

0 comments on commit fbb9c91

Please sign in to comment.