1010
1111namespace Umbraco . Cms . Core . PropertyEditors ;
1212
13- internal sealed class ColorPickerConfigurationEditor : ConfigurationEditor < ColorPickerConfiguration >
13+ internal sealed partial class ColorPickerConfigurationEditor : ConfigurationEditor < ColorPickerConfiguration >
1414{
15+ /// <summary>
16+ /// Initializes a new instance of the <see cref="ColorPickerConfigurationEditor"/> class.
17+ /// </summary>
1518 public ColorPickerConfigurationEditor ( IIOHelper ioHelper , IConfigurationEditorJsonSerializer configurationEditorJsonSerializer )
1619 : base ( ioHelper )
1720 {
1821 ConfigurationField items = Fields . First ( x => x . Key == "items" ) ;
1922 items . Validators . Add ( new ColorListValidator ( configurationEditorJsonSerializer ) ) ;
2023 }
2124
22- internal sealed class ColorListValidator : IValueValidator
25+ internal sealed partial class ColorListValidator : IValueValidator
2326 {
2427 private readonly IConfigurationEditorJsonSerializer _configurationEditorJsonSerializer ;
2528
29+ /// <summary>
30+ /// Initializes a new instance of the <see cref="ColorListValidator"/> class.
31+ /// </summary>
2632 public ColorListValidator ( IConfigurationEditorJsonSerializer configurationEditorJsonSerializer )
2733 => _configurationEditorJsonSerializer = configurationEditorJsonSerializer ;
2834
35+ /// <inheritdoc/>
2936 public IEnumerable < ValidationResult > Validate ( object ? value , string ? valueType , object ? dataTypeConfiguration , PropertyValidationContext validationContext )
3037 {
3138 var stringValue = value ? . ToString ( ) ;
@@ -46,17 +53,53 @@ public IEnumerable<ValidationResult> Validate(object? value, string? valueType,
4653
4754 if ( items is null )
4855 {
49- yield return new ValidationResult ( $ "The configuration value { stringValue } is not a valid color picker configuration", new [ ] { "items" } ) ;
56+ yield return new ValidationResult ( $ "The configuration value { stringValue } is not a valid color picker configuration", [ "items" ] ) ;
5057 yield break ;
5158 }
5259
60+ var seen = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;
61+ var duplicates = new List < string > ( ) ;
5362 foreach ( ColorPickerConfiguration . ColorPickerItem item in items )
5463 {
55- if ( Regex . IsMatch ( item . Value , "^([0-9a-f]{3}|[0-9a-f]{6})$" , RegexOptions . IgnoreCase ) == false )
64+ if ( ColorPattern ( ) . IsMatch ( item . Value ) == false )
5665 {
57- yield return new ValidationResult ( $ "The value { item . Value } is not a valid hex color", new [ ] { "items" } ) ;
66+ yield return new ValidationResult ( $ "The value { item . Value } is not a valid hex color", [ "items" ] ) ;
67+ continue ;
68+ }
69+
70+ var normalized = Normalize ( item . Value ) ;
71+ if ( seen . Add ( normalized ) is false )
72+ {
73+ duplicates . Add ( normalized ) ;
5874 }
5975 }
76+
77+ if ( duplicates . Count > 0 )
78+ {
79+ yield return new ValidationResult (
80+ $ "Duplicate color values are not allowed: { string . Join ( ", " , duplicates ) } ",
81+ [ "items" ] ) ;
82+ }
6083 }
84+
85+ private static string Normalize ( string ? value )
86+ {
87+ if ( string . IsNullOrWhiteSpace ( value ) )
88+ {
89+ return string . Empty ;
90+ }
91+
92+ var normalizedValue = value . Trim ( ) . ToLowerInvariant ( ) ;
93+
94+ if ( normalizedValue . Length == 3 )
95+ {
96+ normalizedValue = $ "{ normalizedValue [ 0 ] } { normalizedValue [ 0 ] } { normalizedValue [ 1 ] } { normalizedValue [ 1 ] } { normalizedValue [ 2 ] } { normalizedValue [ 2 ] } ";
97+ }
98+
99+ return normalizedValue ;
100+ }
101+
102+ [ GeneratedRegex ( "^([0-9a-f]{3}|[0-9a-f]{6})$" , RegexOptions . IgnoreCase , "en-GB" ) ]
103+ private static partial Regex ColorPattern ( ) ;
61104 }
62105}
0 commit comments