This gathers requirements from both heading and https://github.com/ckeditor/ckeditor5-font/issues/2#issuecomment-347838197 features.
It was proposed by @Reinmar in here.
The ViewElementConfigDefinition would server as an view configuration for some features that requires flexibility in parsing and should allow to greater customization of editor features like Heading and FontSize.
Full structure (whether values makes sense or not):
{
// Defines name of rendered element
name: 'h1',
// Optional classes
class: 'ck-heading-one',
// Optional styles:
style: 'font-family:serif',
alternatives: [
{ name: 'h2', /* styles, class, etc */ }
]
}
This would allow to:
Things to consider:
class and style rather then styles and classes - such forms are already used in other places AFAICS.data-header=true) needed?Ad. 2. Examples:
const singleClass = {
name: 'span',
class: 'foo'
}
const multipleClass = {
name: 'span',
class: [ 'foo', 'bar', 'baz' ]
}
const multipleStyles = {
name: 'span',
style: {
'font-size': '12px',
'font-weight': 'bolder'
}
}
This might grow heavy so alternative configuration would by just strings with defined format:
const multipleClass = {
name: 'span',
class: 'foo,bar,baz' // or as in HTML: 'foo bar baz'
}
const multipleStyles = {
name: 'span',
style: 'font-size:12px;font-weight: bolder'
}
As for second flavour we already have utility methods for parsing those:
https://github.com/ckeditor/ckeditor5-engine/blob/8eda4e5308f192cee58dd3d4b425f42769f66f17/src/view/element.js#L728
https://github.com/ckeditor/ckeditor5-engine/blob/8eda4e5308f192cee58dd3d4b425f42769f66f17/src/view/element.js#L804
About preliminary implementation in font size feature:
https://github.com/ckeditor/ckeditor5-font/blob/9b10a3fd90b8b2d8dd4cb059d18991fc9efd8ce8/src/fontsize/fontsizeediting.js#L42-L92
It kinda works but as for now is unfinished so do not review it ;).
How to define multiple classes/styles?
There's already getStyle() and setStyle() in view element, so styles could be defined as key->value pairs and classes as an array.
I'd go with class and style rather then styles and classes - such forms are already used in other places AFAICS.
Makes sense. I used singular nouns because they make more sense in configuration for that specific feature. But would be dealbreakers in general.
Are other attributes (data-header=true) needed?
May be. It only depends on peoples' use cases which we cannot imagine now. But we can say that there's a pretty big chance that other attribute names will be used.
I'm also thinking that perhaps a better name would be ViewElementDefinition. Also, we may consider whether the Element() constructor shouldn't accept it. Since conversion builders simply accept element to be used (it's then cloned quite like in a prototypal inheritance) that would already handle most of the job in M->V conversion. V->M conversion is a bigger problem (alternatives, matching and consuming), especially that we're still planning to review converters and builders.
I'd go with this as part of https://github.com/ckeditor/ckeditor5-font/issues/2 since I've already half of it done there. I will also introduce this in heading feature.
I'd go with this as part of ckeditor/ckeditor5-font#2 since I've already half of it done there. I will also introduce this in heading feature.
WDYM? Separate changes need to land in ckeditor5-engine, so it's a pretty much unrelated ticket.
OK so maybe it wasn't clear (as I thought it was). I'd like to go with this ticket now to implement this in header and then finish ckeditor5-font/2. As for now I don't see much work for this ticket alone besides docs. It might shed some light on utility methods when I'll make changes in those 2 features (font and heading). To have some idea's how this interface going to be used in other words.
As for PR's it rather be two unrelated PRs: one in engine and following in font feature.
I'm also thinking that perhaps a better name would be ViewElementDefinition. Also, we may consider whether the Element() constructor shouldn't accept it.
PS. The feature using ViewElementDefinition as its configuration needs to decide whether to use AttributeElement() or ContainerElement() which means that potential tool for creating these elements need to allow this (or simply that both constructors accept ViewElementDefinition). Right now I don't see other types of elements being used this way, but it may happen in the future.
It makes sens to have common syntax to create view/DOM elements in the simpler way. The one proposed above, with name, style (object), class (string or array) and attribute (object) is fine for me (agree that we need to keep if consisten and allways use singular or plurar).
However, I think that we should not go too far. alternatives is not a part of the view element definition, and should not be part of this ticket. If we will try to create too powerfull declarative API we will have to support more and more login as a declarative syntax.
alternatives will need to be added anyway. It wasn't meant to be a part of this ticket, but something (i.e. some converter builders) need to be able to handle these definitions.
If we will try to create too powerfull declarative API we will have to support more and more login as a declarative syntax.
We won't be able to use these options without support for alternatives. We can either drop configurability completely or implement them.
Since alternatives may not look nice in docs and might be misleading let's to with a view definition as:
// an array of matcher patterns
view: {
to: { name, class, style, attribute }
from: [
{ name, class, style, attribute },
{ name, class, style, attribute },
//...
]
}
// or a callback for most complex cases:
view: {
to: { name, class, style, attribute }
from: () => {}
}
// or when defined as one object it will assume view.to=== view.from:
view: {
name,
class,
style,
attribute
}
as converting M->V will always create the same result but when parsing content it might be required to convert different elements to same model.
Probably support for priorities makes sense also:
const headingConfig = {
options: [
{ model: 'paragraph', title: 'paragraph' },
{
model: 'heading1',
view: {
from: [
{ name: 'h1' },
{ name: 'p', attribute: { 'data-heading': 'h1' }, priority: 'high' }
],
to: {
name: 'h1'
}
},
title: 'User H1'
}
]
}
would allow to convert such HTML:
<h1>foobar</h1>
<p data-heading="h1">foobar</p>
<p>Other text</p>
As much as I don't like declarative APIs (in general), I have no better idea in this case. However I think that to will be a allways one of from values, so instead of view.to, and view.from we might have view (used to render element but also used to parse it) and posibility to add more types of DOM elements (or attributes) which will be parsed as this elements (accepts, parse, alternative, overrides, have no good idea now to call it). This parameter should be in the view matcher format, accept regexps and callbacks.
@pjasiun: after some quick thinking until better name appears I'll go with converts as separate attribute to view as this would convert specified view elements to this model.
const headingConfig = {
options: [
// 1: straight forward: any 'h1' element:
{
model: 'heading1',
view: 'h1'
},
// 2: a bit narrower: any 'h2' element with 'heading-foo' class
{
model: 'headingFoo',
view: {
name: 'h2',
class: 'heading-foo' // also as an array: [ 'foo', 'bar' ]
// would also accept: 'attribute', `style` objects
}
},
// 3: Full flavor with other elements parsed as 'headingBar':
{
model: 'headingBar',
view: 'h3',
converts: [
{ name: 'p', attribute: { 'data-heading': 'h3' } },
{ name: 'p', class: [ 'heading', 'heading-lvl-2' ] },
{ name: 'h4', style: { 'font-size': '23em' } }
]
}
]
}
as an alternative to option 3 we might define view in full-flavor as:
{
output: { name: 'h1' },
converts: [
{ name: 'h2' },
{ name: 'h3', attribute: { 'data-heading': 'lvl1' } }
]
}
ps.: converts or convert?
I'd go with accepts or even acceptsAlso. We do convert in both directions and this converts was supposed to only add more options for the V->M converter.
@pjasiun & @Reinmar what about option 3 (full flavour?)
{
view: {},
acceptsAlso: {}
}
or:
{
view: {
output: {},
acceptsAlso: {}
},
}
?
No opinion. Both seem good to me at the moment.
I'm for the first one. The second looks like there is input missing (output does not sounds like something what can be also an input).
@Reinmar & @pjasiun I've created some helper methods for conversion to use those configuration options. You can see that in linked PR.
Most helpful comment
I'd go with
acceptsor evenacceptsAlso. We do convert in both directions and thisconvertswas supposed to only add more options for the V->M converter.