Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify color handling #4718

Closed
wants to merge 29 commits into from
Closed

Unify color handling #4718

wants to merge 29 commits into from

Conversation

Comandeer
Copy link
Member

@Comandeer Comandeer commented May 18, 2021

What is the purpose of this pull request?

Task

Does your PR contain necessary tests?

All patches that change the editor code must include tests. You can always read more
on PR testing,
how to set the testing environment and
how to create tests
in the official CKEditor documentation.

This PR contains

  • Unit tests
  • Manual tests

Did you follow the CKEditor 4 code style guide?

Your code should follow the guidelines from the CKEditor 4 code style guide which helps keep the entire codebase consistent.

  • PR is consistent with the code style guide

What is the proposed changelog entry for this pull request?

Fixed Issues:

* [#4333](https://github.com/ckeditor/ckeditor4/issues/4333): Fixed: [Color Button](https://ckeditor.com/cke4/addon/colorbutton) history does not render correctly colors passed in non-hex format.

API Changes:

* [#4726](https://github.com/ckeditor/ckeditor4/issues/4726): Added `options` parameter to both [`CKEDITOR.tools.color#getHex()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_color.html#method-getHex) and [`CKEDITOR.tools.color#getHexWithAlpha()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_color.html#method-getHexWithAlpha) methods with `options.shorten` property to allow getting short hex from the given color.

What changes did you make?

I've used CKEDITOR.tools.color wherever it was possible. Some places still need some love and refactor, but I'm not sure if it's worth it.

I've also introduced the ability to get short hex from CKEDITOR.tools.color#getHex() and CKEDITOR.tools.color#getHexWithAlpha() via a new parameter, shorten. However I'm not sure if it wouldn't be better to introduce options parameter. It would be more flexible 🤔

I've also changed the way in which colors in Color History are rendered.

Which issues does your PR resolve?

Closes #4592.
Closes #4726.
Closes #4333.

@Comandeer Comandeer self-assigned this May 20, 2021
@Comandeer Comandeer marked this pull request as ready for review May 25, 2021 15:57
@Comandeer Comandeer removed their assignment May 25, 2021
@sculpt0r sculpt0r self-assigned this May 26, 2021
@sculpt0r sculpt0r self-requested a review May 26, 2021 09:37
Copy link
Contributor

@sculpt0r sculpt0r left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the root issue is fixed now (#4333) 👍 👍

However I'm not sure if it wouldn't be better to introduce options parameter. It would be more flexible

Well, it sounds like a good idea. When you look at some functions, that didn't use an object as an option. They end with lots of code just to provide compatibility later. For example:

ckeditor4/core/editor.js

Lines 1125 to 1135 in 8e5575f

setData: function( data, options, internal ) {
var fireSnapshot = true,
// Backward compatibility.
callback = options,
eventData;
if ( options && typeof options == 'object' ) {
internal = options.internal;
callback = options.callback;
fireSnapshot = !options.noSnapshot;
}

It looks for now that there won't be any more thing to add, but... you never know that - until you have to add another one :)

I've also changed the way in which colors in Color History are rendered.

I found a little inconsistency here. When you explicitly type lowercase 6-HEX - it's lowercase in history. If you type it uppercase - it's uppercased.

Also, If we are allowing 6-HEX in history, why we even want them to be shortened? It looks like that creates some additional code that could be skipped? I know, we are allowing 3-HEX-es, but for sake of unify we could decide on one format to be displayed. Therefore, even 3-HEX will be converted to 6-HEX.

Overall I see, that a few '#' and substr disappears 👍

}

// Replace 3-character hexadecimal notation with a 6-character hexadecimal notation (#1008).
return CKEDITOR.tools.normalizeHex( '#' + CKEDITOR.tools.convertRgbToHex( color || '' ) ).replace( /#/g, '' );
// It also covers other cases, like colors with alpha channel (#4351).
return CKEDITOR.tools.normalizeHex( colorInstance.getHex() || '' );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is already marked as deprecated. We already have some ambiguous normalizations around. The one is here in ColorBox and another in tools. So I'm starting to wonder - what is the normalization process because it leads to very different results. It is related to parsing values from CSS attributes. So maybe we should separate the so-called normalization and extracting value from the CSS attribute?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue is that removing this normalization will basically nuke tests… CKEDITOR.tools.color returns uppercase hexes and whole tests assume they are lowercase. So I'm not sure if it's worth it to refactor all tests.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or not, nothing happened 🤔 Strange, because it suggests that there's yet another normalization on the way…

@Comandeer Comandeer self-assigned this May 31, 2021
@Comandeer
Copy link
Member Author

Also, If we are allowing 6-HEX in history, why we even want them to be shortened? It looks like that creates some additional code that could be skipped? I know, we are allowing 3-HEX-es, but for sake of unify we could decide on one format to be displayed. Therefore, even 3-HEX will be converted to 6-HEX.

The issue is that user could want to use the original format of the color (see http://localhost:1030/tests/plugins/colorbutton/config#tests%2Fplugins%2Fcolorbutton%2Fconfig%20test%20config.colorButton\_foreStyle%20with%20color%20attribute), not the normalized one. And the current flow de facto disables the ability to force normalization for history entries (it would require creating separate ColorBox class for history).

And that's why I'm so unhappy with the whole refactor: the code is still messy. And with changes from this PR it becomes even messier…

Copy link
Contributor

@sculpt0r sculpt0r left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a few requests for color class extensions and few other thoughts about passing the entire value. Also, I've made some diagram which helps me with the code flow (https://miro.com/app/board/o9J_lApbiDQ=/)

If my comments would appear to be invalid due to back incompatibility, I think we will be finishing with this only with a few detailed comments I also left. 🤞 .

Indeed there is a lot going on. It takes a while before I catch up how it works.

The last thing: I think we should look at colordialog - there is also some magic with color validation and RegExps...

@@ -308,7 +308,7 @@
}
}, null, colorData );
} else {
setColor( color && '#' + color, colorName, history );
setColor( color && color, colorName, history );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
setColor( color && color, colorName, history );
setColor( color, colorName, history );

@@ -269,9 +269,9 @@
finalColor = '';
}
if ( type == 'fore' ) {
colorData.automaticTextColor = '#' + ColorBox.normalizeColor( automaticColor );
colorData.automaticTextColor = ColorBox.normalizeColor( automaticColor );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also adjust line with automaticColor = '#ffffff'; to be used with tools.color?

Then we can use explicit `panelBlock.element.findOne( '#' + colorBoxId ).setStyle( 'background-color', automaticColor.getHex() );


return CKEDITOR.tools.normalizeHex( colorInstance.getHex() || '' ).replace( /#/g, '' );
var colorInstance = ( color instanceof CKEDITOR.tools.color ) ? color : new CKEDITOR.tools.color( color, '' ),
isTransparent = Number( colorInstance._.alpha ) === 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we already store alpha as a number in the color instance? But, what is more important, I would rather add colorInstance.isFullyTransparent() method to the color class without taking out the inner implementation of alpha outside. We shouldn't reach for private members just to avoid problems if we decide to change something inside the color class.

* @returns {String} Returns color in hex format, but without the hash at the beginning, e.g. `ff0000` for red.
* In case of transparent colors, it returns `'transparent'` string.
*/
normalizeColor: function( color ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a problem with naming here. Since it is private - nothing should break on the client's side if we rename it? As I wrote before normalization is a kind of a wide concept. In tools, we have normalizeHex which means to convert styleText into 6-HEX. Here, normalizeColor means: return transparent or 6-HEX or even default value which could be anything that comes with the color instance.

And we have some intention when we call this method. We want to receive some value that would be applicable as a CSS attribute. And we use it to compare two colors that come from various sources.

So maybe convertToStyleValue() or sth similar?

Copy link
Member Author

@Comandeer Comandeer Jun 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about changing name. It's not used only for getting color suitable for [style] attribute, e.g.

color = ColorBox.normalizeColor( color );

In one case it's even used for the reversed operation – getting color from CSS →

return color in list ? list[ color ].substr( 1 ) : ColorBox.normalizeColor( span.getComputedStyle( cssProperty ) ).toUpperCase();


this.setHtml();
},

statics: {
getColorName: function( editor, color ) {
var names = ColorBox.colorNames( editor ),
hex = color.getHex().replace( /^#/g, '' ),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we need HEX without # sign, maybe it's good time to add another option (e.g. named withHash or sth similar) to our getHex( optionObj ). The same goes for the below line. It should be easier to read that we want

getHex( { withHash: false } )

than decoding replace while we are reading this logic. I know, this is not so complicated, but still - requires at least a moment to find out what is the intention here.

return colorInstance.getHex() || '';
},

getAppliableColor: function( color ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is used only inside setHtml, why we need this to be static?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought that would be more coherent with existing normalizeColor. But there isn't any real need for that, it can be moved as a private function into setHtml.

},

getAppliableColor: function( color ) {
if ( !color._.isValidColor ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are reaching for a private members of the color instance - which is not good practice. I try to break color-code / color-obj flow (from the getApplicableColor point of view): https://miro.com/app/board/o9J_lApbiDQ=/

I'm not sure how much of this should be affected by backward compatibility, but it looks like we are spinning around with our own value transformations. The ideal way: we are take anything from colordialog and convert it to a color object. The same for input from config.

If color from colordialog is ivalid -> then we could have an empty default value (as default value returned by color class in case of invalid color). If we want to store original input provided by the user, we can always store it as data attribute using color.getInitialValue().

color.getHex( { shorten: initialValue.length === 3 } ).toLowerCase().substr( 1 ),
isShortHexLike = isHexLike && initialValue.length === 3;

return isHexLike ? color.getHex( { shorten: isShortHexLike } ) : initialValue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably could return to the user getInitialValue() without any checks. The User always gets back what was provided. And just in case someone misspells RGB value, it can be corrected instead of typing it again from scratch. But we probably should add some indicator to the colordialog in case of invalid value.

@@ -532,16 +553,19 @@
},

setHtml: function() {
var appliableColor = ColorBox.getAppliableColor( this.color ),
colorStr = appliableColor ? this.color.getInitialValue() : '';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

colorStr name seems to be low informative. Could you name it something like validatedOriginalColor or similar?

} );
var normalizedColor = ColorBox.normalizeColor( colorCode ),
index = CKEDITOR.tools.getIndex( this.boxes, function( box ) {
return ColorBox.normalizeColor( box.color ) === normalizedColor;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two instance creations of ColorBox have color property passed as an object. We should create compare method inside color class that tells whenever two colors are equals.

@Comandeer Comandeer self-assigned this Jun 4, 2021
@Comandeer
Copy link
Member Author

Rebased onto latest major.

@f1ames
Copy link
Contributor

f1ames commented Jun 9, 2021

After some talks with @Comandeer and @sculpt0r we have decided to freeze this PR for now. Any reasonable changes would require refactoring big part of the existing solution which would be really time consuming and due to other priorities we can't proceed with this ATM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pr:frozen ❄ The pull request on which work was suspended due to various reasons.
Projects
None yet
3 participants