ckeditor / ckeditor5

Powerful rich text editor framework with a modular architecture, modern integrations, and features like collaborative editing.
https://ckeditor.com/ckeditor-5
Other
9.54k stars 3.7k forks source link

How to create a command that will change text color? #481

Closed djanosik closed 9 months ago

djanosik commented 7 years ago

First, thank you! CKE5 is what we all have been waiting for :) I would like to use it right now, but I don't actually understand how to build custom commands. Is it documented somewhere? Can you explain what is required to create a command that will change text color (or inline style in general)?

Reinmar commented 7 years ago

Hi Dusan! Thanks for the kind words :)

The documentation is (unfortunately) still work in progress. We first focused on generating the API docs as we had the content already (in the code) but we needed a system to present them. You can see nightly builds of them on https://ckeditor5.github.io/docs/nightly/ckeditor5/latest/api/index.html. They are not reviewed yet, but they are pretty complete (although, we definitely need a lot more examples).

As for guides on how to write commands and how to build features in general, there's nothing yet. But if you'd like to try I can give you some hints and based on the existing code you should be able to figure out the rest.

First of all, some very brief introduction to the architecture of the engine.

Then, the ckeditor5-basic-styles package. It contains two features – bold and italic. Text color will be pretty similar to them, so it'll be a good read.

So, how to start?

  1. Create a plugin. You can simply extends the Plugin class like the BoldEngine does (see https://github.com/ckeditor/ckeditor5-basic-styles/blob/master/src/boldengine.js).

    (Note: There are Bold and BoldEngine plugins in this specific package as the latter is an extension of the former one. The former one gives you bold feature's engine, the latter the feature's UI parts).

  2. In the model an inline style (like bold or text color) is not any more represented by an element, but by a text node's attribue. You need to allow your new attribute in the model's schema:

    editor.document.schema.allow( { name: '$inline', attributes: 'textColor', inside: '$block' } );

    This will allow textColor attribute to be set in all types of nodes which schema recognises as $inline (which is a generic type from which $text inherits).

    (Note: Schema is awaiting a complete refactor so this API will soon change. Don't ask.)

  3. Now, you need to tell the engine how to convert the textColor attribute in the model to an element in the view (because you need a <span style="color: ..."> after all). It should look something like this:

    // Build converter from model to view for data and editing pipelines.
    buildModelConverter().for( data.modelToView, editing.modelToView )
        .fromAttribute( 'textColor' )
        .toElement( data => {
            return new ViewAttributeElement( 'span', {
                style: `color: ${ data.item.getAttribute( 'textColor' ) }`
            } );
        } );
    
    // Build converter from view to model for data pipeline.
    buildViewConverter().for( data.viewToModel )
        .fromElement( 'span' )
        .consuming( { attribute: [ 'color' ] } )
        .toAttribute( viewElement => {
            return { key: 'textColor', value: viewElement.getStyle( 'color' ) }
        } );

    I wrote this from my memory and I'll check it with my colleagues tomorrow as we don't have such an example yet. So, before I verify that, it may not work at all :P.

  4. And last but not least, you need a command. You will need to register it like this:

    editor.commands.add( 'textColor', new TextColorCommand( editor ) );

    But of course you also need to implement the TextColorCommand class. The closest what we have right now in the code is the AttributeCommand (see its docs) used by the bold and italic features. You'll need to change it so it doesn't set the textColor attribute to true but to some string values like #FF0000. It also needs to accept this color as a param in execute(). And finally, once you'll have your editor up and running you'll just need to do:

    editor.execute( 'textColor', 'red' );

    And it should work.

This introduction is like 1/10th of what should be written in such a tutorial, but hopefully it will let you start hacking the editor.

Good luck :)

djanosik commented 7 years ago

@Reinmar Thank you! One more question. Is any event raised when a text with some text color is selected?

Reinmar commented 7 years ago

I assume you're asking about how to refresh your command? How to know when the selection is in a text with some color applied and when it's not?

The most reliable and the simplest way to know where to refresh a state of a command is to listen to Document#changesDone event (editor.document#changesDone). This event is fired every time any change is done to the model (including selection changes). Or to be more precise – by the Document#enqueueChanges() method, so that's why every code which changes the model needs to be wrapped with it.

(Side note: We're planning to change this API too.)

So, listening to that event is the easiest solution. And the Command class does it automatically. So, YourCommand#refresh() will be called automatically if anything has changed.

Going to the second part of the question – how to check if some text color is selected? First of all, we need to define what "text color is selected" means? Without going into the details why, for us, a style (like text color) is selected if the selection starts inside it or right after.

In the previous post I mentioned that text nodes have attributes. Guess what? Selection has attributes too :) Quick justification – how do we represent change in the model for Ctrl+B pressed on a collapsed selection? We set the 'bold' attribute on the selection itself.

What's more – document selection's (editor.document.selection) attributes are automatically refreshed based on selection changes. This means that the value (whether bold is applied here or not) of the bold command can be refreshed in this simple way:

this.value = editor.document.selection.hasAttribute( 'bold' );

See the code.

Side note: Most of state changes in CKEditor 5 can be observer. Selection attributes changes fire Selection#change:attribute event, so the short answer to your question is that you can listen to this event to know where selection attributes change:

const sel = editor.document.selection;

sel.on( 'change:attribute', () => {
    console.log( sel.getAttribute( 'textColor' ) );
} );
zhoufanglu commented 6 years ago

I used it as above, but it didn't work...

CKEditorBot commented 1 year ago

There has been no activity on this issue for the past year. We've marked it as stale and will close it in 30 days. We understand it may be relevant, so if you're interested in the solution, leave a comment or reaction under this issue.

CKEditorBot commented 10 months ago

There has been no activity on this issue for the past year. We've marked it as stale and will close it in 30 days. We understand it may still be relevant, so if you're interested in the solution, leave a comment or reaction under this issue.

CKEditorBot commented 9 months ago

We've closed your issue due to inactivity over the last year. We understand that the issue may still be relevant. If so, feel free to open a new one (and link this issue to it).