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.36k stars 3.68k forks source link

Replacement of the CKEditor 4's styles dropdown feature #5700

Closed Reinmar closed 2 years ago

Reinmar commented 4 years ago

What? (tl;dr)

CKEditor 4 offered a configurable styles feature:

image

You could define block or inline formatting options.

In the past, we were discussing a similar feature in https://github.com/ckeditor/ckeditor5/issues/648, but I'd like to keep that older ticket about what innovative we could do about styling in CKEditor 5. This ticket, on the other hand, should be about unblocking people who relied on the Styles feature from CKEditor 4.


Edited: 09.02.2022

Glossary

Rationale

I’d like to first go through some rationale behind the direction that we took when designing this feature.

We began the project with an extensive product research to understand how’s the Styles dropdown (SD) feature used in CKEditor 4 and whether that vision can (and should) be translated to CKEditor 5.

Our goal was to:

It’s important to note that SD was first introduced around 2009 and the vision behind it is blurred. This feature is understood slightly differently by everyone and its functionality is also not perfectly framed. It can be configured to completely replace CKE4’s Format feature but it’s shipped together with it (and placed next to it in the toolbar) in all the CKE4’s presets. At the same time, its default configuration (of the available styles) mixes presentational styles with semantic ones.

During the research we found that recreating the same functionality in CKE5 makes little sense as it would neither be possible technically nor make much sense from a product perspective. Additionally, due to an ambiguous nature of CKE4’s SD, the feature was used in countless and unpredictable scenarios, often running into its own limitations in corner cases.

Quick recap of CKE4’s SD

CKE4’s SD documentation: https://ckeditor.com/docs/ckeditor4/latest/features/styles.html

Modes

It allows configuring 4 groups of styles:

What’s important, the actual mode that the feature will choose is not controllable by the developer but it’s hardcoded in the feature based on the element name.

This means that it’s impossible to create a style working in the “object mode” for paragraphs. If you want to allow marking all heading levels with a certain style, you need to define 6 styles (one per each h1-6 element) and all of them will be visible in the dropdown. This behavior leads to a [visual clutter](https://www.drupal.org/project/drupal/issues/3222797#comment-14195291)[](https://ckeditor.com/docs/ckeditor4/latest/examples/standardpreset.html).

Most likely, due to the block mode it was also impossible to implement a concept of style groups, reducing control over what the user is able to create in the editor and the UX for the user.

Focus on HTML

The feature’s configuration is based on HTML. It’s configured directly via HTML element names and attributes.

The SD’s configuration also affects ACF – i.e. defining a style makes that element and attributes allowed in the editor.

Attributes

The feature allows configuring styles that apply any attributes – from classes, through any custom attributes to inline styles. That addresses needs of both groups of integrators – ones aiming at a semantical content and ones focusing on content presentation.

Relation between SD and Formats

The SD and Formats feature cooperate quite well together.

Scenario:

  1. Open https://ckeditor.com/docs/ckeditor4/latest/examples/standardpreset.html
  2. Place the caret anywhere in the editor.
  3. Via SD: Apply “Italic title” (uses h3 + inline styles).
  4. Via Formats: Apply “Heading 1”.

Now, if Formats would blindly replace <h3> with <h1> two things could happen:

The cooperation between them is smart and I actually started wondering how was this achieved. How the Formats feature knows which attributes to remove. It’s probably achieved by coupling both features.

Anyways, this is quite neat and it actually doesn’t work that well in other editors. It also shows some of the things we need to take into consideration when working on this feature.

Relation between SD and other inline-style-based features

However, SD does not cooperate with other features that use inline styles that well.

E.g. applying a style via SD which sets <span style="font-size: 10px; color: red"> and then changing the font size via the font size features leaves us with a lone color: red.

Taken the complexity behind handling inline styles, it’s not a big surprise that it works this way.

HTML vs CKE5

The biggest challenge in designing this feature for CKE5 is SD4’s focus on HTML. This aspect makes it very non-native to CKE5 and hard to integrate with existing features.

For instance, the aforementioned scenario of how SD4 works with CKE4’s font size (it works this way most likely out of the box - without additional integration logic) would require a custom logic integrating SD5 with CKE5’s font size. Same for headings, table properties, etc.

Thus, we have to carefully design the scope of SD5 to avoid as many traps as possible.

Another design question is related to implementation strategy, but has strong impact on the product – what should be SD5’s relation to GHS?

In CKE5, the only feature tied to HTML is GHS. It’s also the only feature that maintains any sort of mapping between:

Taken strong ties of SD4 to HTML, it seems unavoidable to base SD5 on something native to CKE5. SD5 should be also configurable via HTML element names and attributes, so since GHS is the only map between CKE5 and HTML, we can’t avoid relying on it.

The question that remains is – how much we should rely on it?

I’m for the latter as this seems to better frame what SD5 will be and will benefit both features as they’ll share more logic.

Strategy for CKE5

SD is a surprisingly complex feature when confronted with CKE5’s reality. Thus, I propose a conservative approach aimed at reducing the complexity and chaos:

Key decisions

Stages

Specification


If you'd like to see this feature implemented, add a 👍 reaction to this post.

Reinmar commented 4 years ago

One thing to consider: People use our current heading dropdown to add more formats. We could go both ways:

What doesn't seem to have any sense is a feature like in CKE4 where you'd have both dropdowns at the same time (in one editor).

antoniolucasnobar commented 3 years ago

Hello,

I am trying to provide such feature extending the Heading feature. I am trying to keep it compatible heading configuration, so users could switch plugins without problems, and then, customize it. What I did so far:

added to properties to the root of the heading option config:

// allows to apply semantically some attributes to the node, so the user sees
//  it reflected in the toolbar and have the option to tweak the style.
// every property needs a plugin mapping to it, otherwise, it is lost 
modelAttributes: {
        //apply these properties to block level
    block: {
        alignment: 'justify',
        blockIndent: '5cm',
                // property provided with a custom plugin
                recuo: 3cm
    },
        // properties of text level
    text: { bold : true}
},

// if true, the style is copied over when user presses ENTER
// it creates a new node of the same type on ENTER
continueStyleOnEnter: true

The modelAttributes allows to config block styles, inline styles, or even a mix with the two types. This is handled by the HeadingCommand (code below). With this, you could define some properties editable by the user (using modelAttributes), and some properties not (using the view object in the configuration). I do not control if a configuration has both view and modelAttributes pointing the same property.

I tried to put modelAttributes to the model propety, but it requires a lot of changes on the plugin and the elementToElement() did not worked, as it expects model to be a string (maybe this could be changed? I do not know enough)

In headingediting, I changed the isHeading check in the afterInit method to include the new continueStyleOnEnter property:

const isHeading = options.some( option => positionParent.is( option.model ) 
                                       && !option.continueStyleOnEnter );

in HeadingCommand, I had changed the execute method, to apply the modelAttributes into the model.

execute( options ) {
    const model = this.editor.model;
    const document = model.document;
    const modelElement = options.value;

    model.change( writer => {
    const selection = document.selection;
    const blocks = Array.from( selection.getSelectedBlocks() )
        .filter( block => {
            return checkCanBecomeHeading( block, modelElement, model.schema );
        } );

    const configOptions = editor.config.get( 'heading.options' );
    const modelOption = configOptions.find( option => option.model === modelElement);

    for ( const block of blocks ) {
        if ( !block.is( modelElement ) ) {
               //clear the old attributes to apply the new styles 
            writer.clearAttributes(block)
            writer.rename( block, modelElement );
            const attributes = modelOption.modelAttributes;
            if (attributes) {
                writer.setAttributes(attributes.block , block );
                const children = Array.from(block.getChildren());
                for (const child of children) {
                                //clear the old attributes to apply the ones in the new style
                                writer.clearAttributes(child);
                    writer.setAttributes( attributes.text , child );
                }
            }
        }
    }

    } );
}

I clear the attributes of each node before apply the ones defined in the configuration. I looked in Google docs and it works the same way when you select a style/heading.

I still did not think in external configuration, but this could be a fetch to a URL the user defines in configuration (I understood it works this way in ckeditor4).

Is this a good way to do it?

I am still learning about the ckeditor world, but I have deadlines and features to deliver to my users, so with the time I have, I am trying my best to make something not too weird that coudl, maybe, help other users of the editor.

PS.: If you guys do no want the result of this work in the ckeditor repo, could I release the result as a separate plugin? I ask, because, as of now, 98% of the code is the heading plugin (which I would make it very clear, of course).

Thanks in advance for your kind attention and congrats on this great product.

JPustkuchen commented 3 years ago

Hi and thank you for your discussion!

I think the style dropdown is very important, especially if you'd like to only allow preconfigured styles (via classes). In CMS that's typically the case as you don't want end-users to set inline styles with font-size, font-color etc. as they will destroy the design.

Anyway, they should be able to for example change the style of a list or a heading or ... by selecting a style.

The CKEditor 4 solution was sub-optimal in some points:

  1. it combined elements with classes. Selecting a style changed the dom element, which isn't always desirable (instead it would be better to only allow certain styles for certain elements defined + wildcards)
  2. The UX wasn't good. It should allow combining multiple classes / styles if they are compatble (based on the filter rules mentioned above) and show a checkbox for the applied styles (based on classes). Furthermore it should allow to group the styles.

I know this is not a simple task, but I think that would be important to discuss.

So in short:

mmichaelis commented 3 years ago

Some more thoughts: Just as @JPustkuchen mentioned, we require this feature in a CMS, which in its data layer allows setting classes on most elements (similar to HTML).

Having this, we especially need the suggested matching algorithm. Thus, if you are in a paragraph in a table cell, it should be possible adding a class to the table cell or even table.

Obviously, this clashes with the table plugin: It also provides styling options for cells and table (missing rows there?) which are directly applied as style attribute.

And it clashes with the text-alignment feature. You may already re-map this to using classNames (which we require).

For now, for example text-alignment feature works nicely with headings feature, when they both apply classes in view.

Kwaadpepper commented 2 years ago

Need this feature, just applying span with class or inline style could be great ! I think this is really different from the "heading feature".

wimleers commented 2 years ago

FYI: the Drupal issue that is blocked on this: https://www.drupal.org/project/drupal/issues/3222797

Reinmar commented 2 years ago

FYI: I posted a plan for this feature in the original post of this thread: https://github.com/ckeditor/ckeditor5/issues/5700#issue-516228316

larowlan commented 2 years ago

Plus one for the plan, sounds great, very thorough 👏

lauriii commented 2 years ago

Only one of the styles for element can be applied at the same time.

Isn’t that a change from CKEditor 4? If it is, is there a particular reason for introducing that as a new restriction?

wimleers commented 2 years ago

@lauriii

Isn’t that a change from CKEditor 4? If it is, is there a particular reason for introducing that as a new restriction?

I don't think that's an actual change. Even in \Drupal\ckeditor\Plugin\CKEditorPlugin\StylesCombo::settingsForm() for CKEditor 4's StylesCombo, we had the following form item description:

A list of classes that will be provided in the "Styles" dropdown. Enter one or more classes on each line in the format: element.classA.classB|Label. Example: h1.title|Title. Advanced example: h1.fancy.title|Fancy title.<br />These styles should be available in your theme\'s CSS file.

Which means that a single style can have multiple classes assigned. I didn't even realize you would be able to apply (and hence _accumulate) multiple styles. I just confirmed that this indeed works in CKEditor 4 by going to https://ckeditor.com/docs/ckeditor4/latest/examples/styles.html. But, this was not possible in Drupal's CKEditor 4 integration anyway because it only allowed specifying a tag plus >=1 classes. StylesCombo apparently allowed "accumulating" styles if they affected different attributes/wrapped existing inline content in additional tags.


@Reinmar

Or should it be basically be seen as a UI for GHS (set GHS’s attributes on the model)?

By this, I think you mean an alternative UI to the SourceEditing UI: more limited yet easier to use. Is that a correct interpretation? If so: 👏, makes perfect sense! → The "Reuse of GHS" section later in your plan confirms this 👍

Widget styling is out of the picture for now.

This was also not supported in CKEditor 4, and the key widgets are still images and media (in Drupal at least). So: no regression concern. 👍

If we’ll decide to merge SD into ckeditor5-html-support […]

From my POV, SD seems like an optional additional plugin that depends on ckeditor5-html-support. But I guess the latter is just a package, and not a plugin, so it's fine? We'd get htmlSupport.Style in addition to htmlSupport.GeneralHtmlSupport, and Style would depend on GeneralHtmlSupport I think? 😊

items sounds like a […] definitions would sound better […] OTOH config.heading.items […] OTOH config.image.styles.options […]

Heh … naming is hard! 🤓 😄 But fortunately this is a trivial one to fix. You can make all consistent and just add a BC layer to continue reading the old inconsistently named configuration.

I agree definitions is best, but honestly … no strong feelings about this 😅

But the feature actually may have multiple styles active at once (one for <p>, one for <td> and so on). Which one to show? The closest one?

I think the dropdown in unopened state should then show a special Multiple styles label. Upon opening it, you'll see the active styles highlighted. (I've seen this in many places, but don't recall where 🙈 )

We should try displaying previews of the defined styles in the styles dropdown panel

This is IMHO a nice-to-have — could be a follow-up.


This looks like a super solid plan! 👏 Can't wait to try it 🤓 😄

JPustkuchen commented 2 years ago

Only one of the styles for element can be applied at the same time. Is a problematic limitation. From the implementation side, I absolutely understand why supporting multiple styles simultaneously is a problem, especially if the administrator defining the styles doesn't think about it carefully.

Sadly, from the user (end-user and site owner) perspective, it's a huge limitation as you often need to combine styles (by classes) and it's crazy to multiply them to get all combinations for exclusive selection. That was one of my points in this comment: https://github.com/ckeditor/ckeditor5/issues/5700#issuecomment-912650707

So from daily project use, while I think everything else is really a GREAT plan, I think that limitation should really be reconsidered, as I frequently see users trying to combine for example a font layout variant (by class, e.g. "large text") with a text style (by class, e.g. "highlighted").

The funny part is, that it seems to separate into exclusive categories that were former set by inline styles like

Thinking even more about this, I think the nature of this is to be able to combine text layout with text style.

That lead me to the idea, that from the user and UX perspective it might be well understandable and a good choice that styles are exclusive (at least as long as they are not declared to be compatible which might be a later addition). But for the combination of text layout and text style we could find a clever solution I'll propose in the following:

Problem: Text writers need defined flexibility in setting texts in different layouts (font sizes & font faces) and styles (color, background-color, border, ...). In early days, WYISWYG editors provided separate selections for this (font size, font face, font color, background-color, ...) which were unique and thereby combinable. Today we typically don't want to give text editors that much freedom and no inline styles, to provide a better UX and consistent (CI / CD) text styles. Therefor it became best practice to use classes instead.

HTML / CSS combine layout and style in CSS language, defined in classes. In HTML it's an antipattern to use inline styles (e.g. font-size, color, background-color, ...), that can be combined as each attribute itsself is unique (no 2x font size at the same element). At class level, these details are abstracted away and we can't detect that easy anymore, which properties were used and are combinable.

In webdesign we separate between layout (greyboxing) which defines general properties and positions of an element, but without style (screendesign). Layout and style combined provide the full picture.

HTML only partially solves this separation semantically (only fully by web components). A large part is left to CSS, where things get mixed up, at least in WYSIWYG.

Proposed solution: Separation between layout and style could be reintroduced. To prevent us from the requirement of combining styles that should not be combined in styles multiselect and still allow to combine layout and style selection for the users.

The missing piece could be to allow to define sublayouts in the 'ck-heading-dropdown': grafik (sorry this screenshot is missing the styles dropdown, of course that would also exist for the style selection!)

Allowing an exclusive optional choice of paragraph types, like "large paragraph", "abstract", "subline", which only defines the layout, not the style, could still be combined with a style from the style select. Both by exclusive classes. Of course the person defining the layouts vs. styles has to know his tool...

In my eyes and for our typical use-cases that would solve several problems we're having with the exclusive style selection in dropdown suggested.

Solvable combinations for example:

Technically, these sublayouts would also just set / remove a class. We'de introduce a separation option on that level, which already exists on HTML element type, for example selecting a paragraph vs. a quote. But now it allows to separate paragraphs layouts.

Just one idea... perhaps there are even better ones? What do you think?

LukeLeber commented 2 years ago

I think this is definitely a step in the right direction, but I have to agree that limiting end-users to selecting a single style per element is not ideal and can lead to huge UI clutter, and a poor UX for design system implementors / users.

Imagine a design system that separates heading sizes, colors, and other attributes into distinct well defined classes.

Pseudo-design-system:

/* color modifiers */
.heading--red {
  color: red;
}
.heading--blue {
  color: blue;
}

/* size modifiers */
.heading--large {
  font-size: 2rem;
  line-height: 2.8;
}
.heading--small {
  font-size: 1.5rem;
  line-height: 2.4;
}

/* flushness modifier (to correct for line-height rounding) */
.heading--flush {
  margin-top: -0.15rem;
}

One would normally expect to mix and match these classes as the situation dictates:

<!-- A small, red, flush heading, semantically level 2 -->
<h2 class="heading heading--small heading--red heading--flush">I'm small, red, and flush!</h2>

<!-- A large, blue heading, semantically level 3 -->
<h3 class="heading heading--large heading--blue">I'm large and blue!</h3>

By not being able to mix and match classing, we'd be left with 8+ style dropdown options to accomplish just these three simple modifiers. In a real-world design system, the actual UX would be exponentially worse.

Please forgive my crude depiction, but this would make for an ideal user experience in my opinion:

image

...with a potential user defined configuration:

{
  elements: ['h2', 'h3', 'h4', 'h5'],   // <-- Apply to all of these elements
  styles: {
    'Flush?': {
      options: {
        'heading--flush': 'Flush',
      }
    },
    'Size': {
      options: {
        'heading--xl': 'Extra large',
        'heading--l': 'Large',
        'heading--m': 'Medium',
      }
    }
    'Color': {
      options: {
        'heading--blue': 'Blue',
        'heading--red': 'Red'
      }
    },
  }
}

Any configured styles would ideally be merged together on a per-element basis so that each element has a full knowledge of which configurable options are available for it.

Thanks for your consideration!

Reinmar commented 2 years ago

The eagle has landed :partying_face:

The very first, very MVP version of styles dropdown was just merged to master and will most likely be a part of the upcoming release.

During the implementation phase, we've run into lovely complexity on how to make this feature cooperate with GHS. That stopped us for a while and burned time that we hoped to spend on functionality of this feature. Therefore, it's a real MVP and we'll have to keep working it. In other words – no fancy stuff so far ;)

For the coming 1-2 releases the feature will be on an "experimental" level. It has no documentation yet and might require going through e.g. our manual test to figure out how to configure it. It should be relatively stable too, although, we learned that there are many edge cases in the editing part of it.

For the time being, a teaser:

Thank you @dawidurbanski @oleq @niegowski for pushing hard on the last stretch :clap: :superhero:

larowlan commented 2 years ago

🤩

godai78 commented 2 years ago

It almost has documentation. What is lacking, is the configuration section and proper demo content (and defining styles in the demo editor, which I can't do right now without the configuration section :D )

thomasfrobieter commented 2 years ago

That looks really, really awesome! Are those styles combinable ("multiselect") like discussed above? And if they are combinable, what exactly, one block and one text style or unlimited for both?

dawidurbanski commented 2 years ago

@thomasfrobieter They are combinable without any limitations. You can even apply multiple "block" styles to a block element (like heading), and on top of that you can apply multiple "text" styles into any text inside this heading as well.

The only big limitation for now is that we don't support applying styles to widgets (like tables).

wimleers commented 2 years ago

The only big limitation for now is that we don't support applying styles to widgets (like tables).

Does this mean images are also not supported yet?

wimleers commented 2 years ago

But … most importantly … echo'ing @larowlan 's response:

🤩

um505 commented 2 years ago

Is't possible somehow to include it to JS file in build/ckeditor.js from "Online Builder"?

NavnathKumbhar commented 2 years ago

So, these styles are customizable by user?

godai78 commented 2 years ago

@um505 - we're working on it.
@NavnathKumbhar - yes, but by an integrator, on the level of setting up.

NavnathKumbhar commented 2 years ago

@NavnathKumbhar - yes, but by an integrator, on the level of setting up.

OK, Thank you. But what do you mean by "on the level of setting up"? Can end-user (our customer) add their custom styles?

Our customer would like to edit the text with some custom styles, for example: • Text Style1 – (Font Size: 12, Font-Family: Arial, Font-Color: Yellow) • Text Style2 – (Font Size: 10, Font-Family: Sans Serif, Font-Color: Black, Background Color: Yellow ) • Etc…

Is this possible with this new UI? Will these custom styles be appear in the list?

godai78 commented 2 years ago

What I mean is that the client can, but it is done during CKEditor 5 setup. You set up style definitions in the config for CKE5, and then the corresponding CSS definitions per document.

You can't define styles on the fly during writing.

There is a styles dropdown guide in the works waiting to be merged which will be released soon as the development team greenlights the feature as fully stable. Not sure when that will happen, though.

gregherrell commented 2 years ago

What I mean is that the client can, but it is done during CKEditor 5 setup. You set up style definitions in the config for CKE5, and then the corresponding CSS definitions per document.

You can't define styles on the fly during writing.

There is a styles dropdown guide in the works waiting to be merged which will be released soon as the development team greenlights the feature as fully stable. Not sure when that will happen, though.

I would like clarification as I am still not clear. Will there be a url like the current styleSet config element in CKEditor 4? This has always allowed us to dynamically add CSS classes by generating dynamic content for our CMS.

Currently, we cannot upgrade to CKEditor 5 because this is missing. If it is not going to be brought into version 5 we will have to look into another editor (which we do not want to do!).

godai78 commented 2 years ago

We have a newly released guide about the Styles feature, that may shed some light on this case: https://ckeditor.com/docs/ckeditor5/latest/features/style.html

The feature itself is stable after the recent release (35.0.1) and should work as intended. It is also compatible with collaboration features far as I can tell - in most configurations.

larowlan commented 2 years ago

Is there any advice on how to use this dropdown with the tables plugin? I can't seem to make the styles in the dropdown enabled when on a table

godai78 commented 2 years ago

I'm afraid I don't know this. However, the styles seem to be working inside tables. Some of the styles in the demo in the guide are block styles, that can only be applied to elements such as headers, horizontal lines etc. and hence they may not be available for table contents.

2022-08-11 06 47 51

larowlan commented 2 years ago

Yep that seems to be what I found too, it works inside tables, but can't be used to apply a style to the whole table. Thanks for the quick reply!

thomasfrobieter commented 1 year ago

I am not sure if its a Drupal or a CKeditor limitation, in CKE5, is it still required to define the element + class(es)?

So theres currently no way to define a style for all block elements or all inline elements (or simply all elements)?

Sorry, I've read the issue, but this is still not 100% clear to me..

Witoso commented 1 year ago

Hey @thomasfrobieter!

So theres currently no way to define a style for all block elements or all inline elements (or simply all elements)?

That's correct, the styles dropdown currently doesn't support all elements but it is something we plan to work on and enhance in the near future. Feel free to observe #11574.