ckeditor / ckeditor5-react

Official CKEditor 5 React component.
https://ckeditor.com/ckeditor-5
Other
424 stars 99 forks source link

Add an initialSuggestions prop to simplify save/load integration with Track Changes #508

Open DeltekDavid opened 3 months ago

DeltekDavid commented 3 months ago

It's currently too messy to load track changes. CKEditor component has an initialData prop, so why not initialSuggestions? The current integration instructions require either a globally-accessible variable (load/save) or an adapter that reads suggestions from the database on demand. The global variable is fine for demos, but we have a complex rendering tree with multiple editors, in which we load and convert the initial data as part of the parent component's rendering logic. For various reasons, we aren't going with the real-time adapter solution just yet. It doesn't work with our current model. Thanks

Edit: Alternately, an onInit config hook might do the trick, so we can populate the TC plugins' suggestions list as describe in the Save/Load integration docs. We tried doing it in the onReady callback, but that appears to be "too late" and we got an error:

CKEditorError: track-changes-user-not-found
Read more: https://ckeditor.com/docs/ckeditor5/latest/support/error-codes.html#error-track-changes-user-not-found
    at p._getAuthorFromId (index.js:24:1)
    at p._createSuggestion (index.js:24:1)
    at p._handleNewSuggestionMarker (index.js:24:1)
    at Document.<anonymous> (index.js:24:1)
    at Document.fire (emittermixin.ts:241:1)
    at Document._handleChangeBlock (document.ts:370:1)
    at Model._runPendingChanges (model.ts:1116:1)
    at Model.enqueueChange (model.ts:353:1)
    at DataController.init (datacontroller.ts:362:1)
    at DataController.<anonymous> (observablemixin.ts:277:1) {phase: 'initialization', willEditorRestart: false}

We've also populated the users in the onReady callback, before we populate the suggestions.

DeltekDavid commented 3 months ago

OK, I found a workaround for now. We simply added an initialSuggestions property to our EditorConfig. Then we can pass in the initialSuggestions this way, and they are available to our plugins' init() methods via editor.config! Phew. So our code looks much like the save/load example, but rather than load from some global named appData, we just load the suggestions like:

import { Plugin } from 'ckeditor5';

export default class TrackChangesIntegration extends Plugin {
    static get requires() {
        return ['TrackChanges', 'UsersIntegration'];
    }

    static get pluginName() {
        return 'TrackChangesIntegration';
    }

    init() {
        const trackChangesPlugin = this.editor.plugins.get('TrackChanges');

        // Load the initial suggestions from the config.
        // We have to do it this way since there's no prop to pass in for initial suggestions,
        // unlike initialData.
        const initialSuggestions = this.editor.config.get('initialSuggestions');
        if (!initialSuggestions?.length) {
            return;
        }

        for (const suggestion of initialSuggestions) {
            trackChangesPlugin.addSuggestion(suggestion);
        }
    }
}