How to create editable inline elements in CKEditor5 with actual nodes not text attributes

516 views Asked by At

We work with a backend that tags words like Persons or locations. I send text to the backend and it tags me words. I have to create tags in the ckeditor frontend.

  • So I have the task to create editable inline elements, means they should be addable to where text is addable.
  • The tag must be editable.
  • The tag should also be visible if its empty (should be a own tree node not text attribute).
  • The tag should have visual highlighting, like a background-color and border radius.
  • If the tag is empty, its type should be added via css' ::before pseudo class and content property.
  • It would be nice if you can toggle it's edtiable part. Since we also need readonly tags, their are developed as seperate elements so far.

My approach so far was using text attributes applying:

writer.createText('my tag', {tag: tagType})

I was basically creating tags like you would create bold text. I was applying css styles like background-color and border radius to make it look like a tag, but this approach came to it's limits. Also with this approach you cannot have editable and non-editable tags be the same ckeditor entity, since you cannot have non-editable text, I guess.

Then I found editableElement's in the view side. The Problem is you cannot have emtpy tags since empty text is nothing. You also cannot modify the "tag" at index 0 because then you are outside of the tag, see bold behavior for this. I mean I could somehow fix it all, but I would like the tags to be their own element on model side. so I have tried this approach:

// in editingDowncast conversion:
viewWriter.createEditableElement('div', {class: 'inline'})

// this is the whole code in the ui:
 
        this.editor.model.schema.register( 'test-tag', {
            allowChildren: '$text',
            allowWhere: '$text',
            allowAttributesOf: '$text',
            isObject: true        
        });
        // if it is isInline: true it behaves mostly like my approach with text attributes
        

        this.editor.conversion.for('editingDowncast').elementToElement({
            model: 'test-tag',
            view: (modelItem, conversionApi) => {

                let { writer: viewWriter } = conversionApi;
               

                const tagView = viewWriter.createEditableElement( 'div', {

                    class: 'inline'

                });

                return tagView;

            }
        })

basically EdtiableElement's work only with block elements, so I have tried to make them inline via css, setting their display property to inline-block. Here I have again the problem that when the element is empty you cannot access it anymore via cursor. So it will stay empty forever. Generelly it seems that it's behavior is kind of buggy because I guess you should not use it as inline. Basically I have many similiar issues like with the approach above.

I will keep implementing it with the first solution but I wanted to ask the community if there is any other way, maybe a less hacky way to create inline editable elements that are actual nodes in the model. Something like a span tag but on model side. Any Ideas?

1

There are 1 answers

0
blackWaveArcade On

As far as I can tell from looking at the open issues this isn't quite possible yet. I think the team will implement it but it seems pretty low on the list.

You should be able to do this though with a popup form connected to the inline widget.

So you would A) create the inline widget as per the placeholder example. https://ckeditor.com/docs/ckeditor5/latest/framework/tutorials/implementing-an-inline-widget.html

B) Add a click observer to the placeholder in the UI class

import ClickObserver from '@ckeditor/ckeditor5engine/src/view/observer/clickobserver';
const editor = this.editor;
const view = editor.editing.view;
const viewDocument = view.document;

view.addObserver( ClickObserver );
    
    editor.listenTo( viewDocument, 'click', ( evt, data ) => {
        const modelElement = editor.editing.mapper.toModelElement( data.target);
    
        if ( modelElement.name == 'placeholder' ) {
            console.log( 'Placeholder has been clicked.' );
          
        }
    } );

C) Follow the Get User Input with custom UI tutorial https://ckeditor.com/docs/ckeditor5/latest/framework/plugins/abbreviation-plugin/abbreviation-plugin-level-2.html

D) Edit the form items, buttons according to your needs.

E) Instead of launching the popup form from the toolbar button. Add the this._showUI(); in the click function above

if ( modelElement.name == 'placeholder' ) {
   console.log( 'Placeholder has been clicked.' );
   this._showUI();
}

F) Then you should be able to pass the placeholder text/attribute to the form and edit it that way.

I'm working on this myself. Will post a full solution when I'm happy with it.

Hope this helps.