Open bkosborne opened 5 months ago
following. I just noticed this as well
I've made some progress on this by reading thru the source code for the Style plugin and the General HTML Support plugin. Style uses GHS to do its implementing. I believe I need to add "htmlCalloutAttributes" as an allowed attribute on my custom top level "callout" model. I think that's all I need to do to make my model compatible with GHS (though I may need to write my own upcast/downcast converters, not sure yet).
Then I need to make the widget compatible with the Styles plugin similar to what Table had to do here.
Will keep working at it...
Following -- I could use this functionality as well!
I've crawled out of the depths of the CKE5 source code and am victorious.
Here's what needs to happen to make a simple block widget, similar to the one in the tutorial, be compatible with the Styles plugin.
For context, I've created my own block widget plugin based of that tutorial that I call "callout". It's wrapped with an HTML "section" element. My goal was to allow a Style definition that targets the "section" element to be used on my widget, adding CSS classes to it.
Hopefully this helps others. It would be nice to have the block widget plugin example expanded to include integration with Styles.
When registering the schema for the top level "callout" model, I needed to indicate that it's a block and to allow the attribute "htmlSectionAttributes":
schema.register('callout', {
// Behaves like a self-contained object (e.g. an image).
isObject: true,
// This allows block styles to be applied to it.
isBlock: true,
// Allow in places where other blocks are allowed (e.g. directly in the root).
allowWhere: '$block',
// We need to allow the GHS attribute that the Style plugin uses when
// applying CSS classes from an applied section style.
allowAttributes: ['htmlSectionAttributes']
});
The "htmlSectionAttributes" attribute comes from the GHS (General HTML Support) plugin. This is an attribute that it uses to store things like custom "style" attribute values and a list of CSS classes. The Style plugin interacts with GHS to add and remove classes from this attribute.
It's very important for block widgets to have isBlock: true
as well, or the block-level "section" style definition won't ever be applicable to the widget and you won't be able to select it from the Styles drop-down.
By default, I guess the GHS plugin doesn't know how to take its attribute and downcast it to the view. I'm not really sure why, as the code that's required to make it work seems very generic and there's nothing in it that's specific to my plugin. But here we go:
conversion.for('downcast').add((dispatcher) => {
dispatcher.on('attribute:htmlSectionAttributes:callout', (evt, data, conversionApi) => {
// Tell the conversion API we're handling this attribute conversion
// so nothing else tries to do something with it.
if (!conversionApi.consumable.consume( data.item, evt.name )) {
return;
}
const { attributeOldValue, attributeNewValue } = data;
const viewCodeElement = conversionApi.mapper.toViewElement(data.item);
updateViewAttributes(
conversionApi.writer,
attributeOldValue,
attributeNewValue,
viewCodeElement
);
});
});
Looking at the existing plugin integrations written for the GHS plugin helped me here.
What's very odd is that I didn't also need to define an upcast converter from the view to the model. It just ... works? This is strange as other examples I looked at did define the upcast converter.
Okay, so now I've integrated my block widget with the GHS plugin, but I still need to integrate with the Style plugin. Without some extra code, section style definitions were not selectable from the drop-down.
I mostly looked at the existing code for integrating the Table and List plugins with the Style plugin. This was the important bit I needed in my plugin's main init()
function:
init() {
const editor = this.editor;
this._styleUtils = editor.plugins.get('StyleUtils');
this._htmlSupport = editor.plugins.get('GeneralHtmlSupport');
this.listenTo(this._styleUtils, 'isStyleEnabledForBlock', (evt, [definition, block]) => {
if (['section'].includes(definition.element) && block.name === 'callout') {
evt.return = true;
evt.stop();
}
}, { priority: 'high' });
// This doesn't seem strictly necessary as there's nothing in here specific
// to our model, but this was in the style integration code for list
// plugin, so might as well carry it over too.
this.listenTo(this._styleUtils, 'isStyleActiveForBlock', (evt, [definition, block]) => {
const attributeName = this._htmlSupport.getGhsAttributeNameForElement(definition.element);
const ghsAttributeValue = block.getAttribute(attributeName);
if (this._styleUtils.hasAllClasses(ghsAttributeValue, definition.classes)) {
evt.return = true;
evt.stop();
}
}, { priority: 'high' });
this.listenTo(this._styleUtils, 'getAffectedBlocks', (evt, [definition, block]) => {
if (['section'].includes(definition.element) && block.name === 'callout') {
evt.return = [block];
evt.stop();
}
}, { priority: 'high' });
}
This is what allows a style registered for the "section" HTML element to be used on my callout widget (which is wrapped with a "section").
Hi @bkosborne, and sorry for the delayed response, I was out for a couple of days.
Glad you worked out the solution. Creating an integration is a way to go, but the DX of the API is not there. I wasn't here when the Style was created, but AFAIK, it was thought as styling general HTML elements, and didn't implement ways to style specific ones. The table/list integration is there because those are the most complex features, which representation in the model differs a lot from the HTML.
Another discussion point is when styling via the dropdown is better than implementation of a toolbar on a widget that provides a bit more context-aware UI. But most likely, we should allow integrators to decide, and have both options.
I will change this to the feature improvement issue. Feedback is always welcome!
I've been struggling to figure out how a custom widget plugin I've created (which is more or less a clone of the block widget created in the tutorial) can become compatible with applying styles via the Style plugin.
Just like the tutorial, my view downcast outputs a
<section>
element as the widget root. I have a style definition to add a class to section elements. However, when I click the element in the editor, the styles dropdown is not active and I cannot select the element.It seems like I may need to create my own integration with style, like what was done for link and table - but browsing through the source code of those integrations I don't really know where to begin or how implementing similar code would work for me. I'm not sure why only a few plugins require custom integrations. What's allowing "paragraph" plugin to receive styles for example? And why is what I'm doing somehow more complicated?
It seems that the Style plugin is closely related to the GHS plugin - in that when a style is applied to a model of various elements, I notice that a model attribute like "htmlH2Attributes" or "htmlTableAttributes" is created on the outer element, but I can't find the code that's actually doing this.
I'm just feeling quite lost here. Any help is appreciated. Thank you.
Here's the code for my plugin that extends the schema and creates the downcast/upcast converters: