Improved text color support
authorAlexander Ebert <ebert@woltlab.com>
Tue, 27 Sep 2016 11:20:10 +0000 (13:20 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Tue, 27 Sep 2016 11:20:10 +0000 (13:20 +0200)
All colors with a hex representation are now supported, in addition CSS
color names are converted into hex to unify the generated HTML.

wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabCode.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabColor.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/RuntimeStyle.js [new file with mode: 0644]
wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeProcessor.class.php
wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeWoltlabColor.class.php
wcfsetup/install/files/lib/system/html/metacode/converter/ColorMetacodeConverter.class.php

index 2a96ea44911977795c5ace290af4e430c7fb49bb..1c0bcd7ddf140650ccf1c40e35e2a7a68c1d3ccb 100644 (file)
@@ -6,6 +6,13 @@ $.Redactor.prototype.WoltLabCode = function() {
                        require(['WoltLabSuite/Core/Ui/Redactor/Code'], (function (UiRedactorCode) {
                                new UiRedactorCode(this);
                        }).bind(this));
+                       
+                       var mpStart = this.code.start;
+                       this.code.start = (function (html) {
+                               mpStart.call(this, html);
+                               
+                               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'codeStart_' + this.$element[0].id);
+                       }).bind(this);
                }
        };
 };
index 6d0d75fd5cd266635038af49729bbd7f71c40311..3fbb5e777614362dcf1d873ca5164976be860e31 100644 (file)
@@ -1,17 +1,17 @@
 $.Redactor.prototype.WoltLabColor = function() {
        "use strict";
        
+       // these are hex values, but the '#' was left out for convenience
+       var _defaultColors = [
+               '000000', '800000', '8B4513', '2F4F4F', '008080', '000080', '4B0082', '696969',
+               'B22222', 'A52A2A', 'DAA520', '006400', '40E0D0', '0000CD', '800080', '808080',
+               'FF0000', 'FF8C00', 'FFD700', '008000', '00FFFF', '0000FF', 'EE82EE', 'A9A9A9',
+               'FFA07A', 'FFA500', 'FFFF00', '00FF00', 'AFEEEE', 'ADD8E6', 'DDA0DD', 'D3D3D3',
+               'FFF0F5', 'FAEBD7', 'FFFFE0', 'F0FFF0', 'F0FFFF', 'F0F8FF', 'E6E6FA', 'FFFFFF'
+       ];
+       
        return {
                init: function() {
-                       // these are hex values, but the '#' was left out for convenience
-                       var colors = [
-                               '000000', '800000', '8B4513', '2F4F4F', '008080', '000080', '4B0082', '696969',
-                               'B22222', 'A52A2A', 'DAA520', '006400', '40E0D0', '0000CD', '800080', '808080',
-                               'FF0000', 'FF8C00', 'FFD700', '008000', '00FFFF', '0000FF', 'EE82EE', 'A9A9A9',
-                               'FFA07A', 'FFA500', 'FFFF00', '00FF00', 'AFEEEE', 'ADD8E6', 'DDA0DD', 'D3D3D3',
-                               'FFF0F5', 'FAEBD7', 'FFFFE0', 'F0FFF0', 'F0FFFF', 'F0F8FF', 'E6E6FA', 'FFFFFF'
-                       ];
-                       
                        var callback = this.WoltLabColor.setColor.bind(this), color;
                        var dropdown = {
                                'removeColor': {
@@ -19,8 +19,8 @@ $.Redactor.prototype.WoltLabColor = function() {
                                        func: this.WoltLabColor.removeColor.bind(this)
                                }
                        };
-                       for (var i = 0, length = colors.length; i < length; i++) {
-                               color = colors[i];
+                       for (var i = 0, length = _defaultColors.length; i < length; i++) {
+                               color = _defaultColors[i];
                                
                                dropdown['color_' + color] = {
                                        title: '#' + color,
@@ -41,13 +41,13 @@ $.Redactor.prototype.WoltLabColor = function() {
                        
                        WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'convertTags_' + this.$element[0].id, function (data) {
                                elBySelAll('woltlab-color', data.div, function (element) {
-                                       if (element.className.match(/^woltlab-color-([0-9A-F]{6})$/)) {
-                                               if (colors.indexOf(RegExp.$1) !== -1) {
-                                                       data.addToStorage(element, ['class']);
-                                               }
-                                       }
+                                       data.addToStorage(element, ['class']);
                                });
                        });
+                       
+                       this.WoltLabColor.registerColors();
+                       
+                       WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'codeStart_' + this.$element[0].id, this.WoltLabColor.registerColors.bind(this));
                },
                
                setColor: function(key) {
@@ -74,6 +74,22 @@ $.Redactor.prototype.WoltLabColor = function() {
                                
                                this.buffer.set();
                        }).bind(this));
+               },
+               
+               registerColors: function () {
+                       require(['WoltLabSuite/Core/Ui/Redactor/RuntimeStyle'], (function (UiRedactorRuntimeStyle) {
+                               elBySelAll('woltlab-color', this.$editor[0], (function (color) {
+                                       if (color.className.match(/woltlab-color-([a-z0-9]{3}(?:[a-z0-9]{3})?)/i)) {
+                                               var code = RegExp.$1;
+                                               if (code.length === 3) code = code.substr(0, 1) + code.substr(0, 1) + code.substr(1, 1) + code.substr(1, 1) + code.substr(2, 1) + code.substr(2, 1);
+                                               code = code.toUpperCase();
+                                               
+                                               if (_defaultColors.indexOf(code) === -1) {
+                                                       UiRedactorRuntimeStyle.add('woltlab-color-' + code, 'color: #' + code + ';');
+                                               }
+                                       }
+                               }).bind(this));
+                       }).bind(this));
                }
        };
 };
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/RuntimeStyle.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/RuntimeStyle.js
new file mode 100644 (file)
index 0000000..1b09ae1
--- /dev/null
@@ -0,0 +1,42 @@
+/**
+ * Provides an easy API to register CSS styles on runtime.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2016 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Ui/Redactor/RuntimeStyle
+ */
+define([], function () {
+       "use strict";
+       
+       var _knownClasses = [];
+       var _style = null;
+       
+       /**
+        * @exports     WoltLabSuite/Core/Ui/Redactor/RuntimeStyle
+        */
+       return {
+               /**
+                * Adds a new style rule for provided class name.
+                * 
+                * @param       {string}        className       CSS class name without a leading dot
+                * @param       {string}        definitions     CSS definitions
+                */
+               add: function (className, definitions) {
+                       if (_knownClasses.indexOf(className) !== -1) {
+                               return;
+                       }
+                       
+                       if (_style === null) {
+                               _style = elCreate('style');
+                               _style.appendChild(document.createTextNode(''));
+                               elData(_style, 'created-by', 'WoltLabSuite/Core/Ui/Redactor/RuntimeStyle');
+                               document.head.appendChild(_style);
+                       }
+                       
+                       //noinspection JSUnresolvedVariable
+                       _style.sheet.insertRule('.' + className + ' { ' + definitions + ' }', _style.sheet.cssRules.length);
+                       _knownClasses.push(className);
+               }
+       };
+});
\ No newline at end of file
index 4b2364ff97fafdd5da5cf9543290a0c4b2652306..7e48c57bd0075541af881837461d5943c36de5bd 100644 (file)
@@ -31,15 +31,7 @@ class HtmlInputNodeProcessor extends AbstractHtmlNodeProcessor {
                ],
                'p' => ['text-center', 'text-justify', 'text-right'],
                'td' => ['text-center', 'text-justify', 'text-right'],
-               'woltlab-color' => [
-                       'woltlab-color-000000', 'woltlab-color-000080', 'woltlab-color-0000CD', 'woltlab-color-0000FF', 'woltlab-color-006400', 'woltlab-color-008000',
-                       'woltlab-color-008080', 'woltlab-color-00FF00', 'woltlab-color-00FFFF', 'woltlab-color-2F4F4F', 'woltlab-color-40E0D0', 'woltlab-color-4B0082',
-                       'woltlab-color-696969', 'woltlab-color-800000', 'woltlab-color-800080', 'woltlab-color-808080', 'woltlab-color-8B4513', 'woltlab-color-A52A2A',
-                       'woltlab-color-A9A9A9', 'woltlab-color-ADD8E6', 'woltlab-color-AFEEEE', 'woltlab-color-B22222', 'woltlab-color-D3D3D3', 'woltlab-color-DAA520',
-                       'woltlab-color-DDA0DD', 'woltlab-color-E6E6FA', 'woltlab-color-EE82EE', 'woltlab-color-F0F8FF', 'woltlab-color-F0FFF0', 'woltlab-color-F0FFFF',
-                       'woltlab-color-FAEBD7', 'woltlab-color-FF0000', 'woltlab-color-FF8C00', 'woltlab-color-FFA07A', 'woltlab-color-FFA500', 'woltlab-color-FFD700',
-                       'woltlab-color-FFF0F5', 'woltlab-color-FFFF00', 'woltlab-color-FFFFE0', 'woltlab-color-FFFFFF'
-               ],
+               'woltlab-color' => '*',
                'woltlab-font' => [
                        'woltlab-font-arial', 'woltlab-font-comicSansMs', 'woltlab-font-courierNew', 'woltlab-font-georgia', 'woltlab-font-lucidaSansUnicode',
                        'woltlab-font-tahoma', 'woltlab-font-timesNewRoman', 'woltlab-font-trebuchetMs', 'woltlab-font-verdana'
@@ -94,6 +86,10 @@ class HtmlInputNodeProcessor extends AbstractHtmlNodeProcessor {
                foreach ($this->getXPath()->query('//*[@class]') as $element) {
                        $nodeName = $element->nodeName;
                        if (isset(self::$allowedClassNames[$nodeName])) {
+                               if (self::$allowedClassNames[$nodeName] === '*') {
+                                       continue;
+                               }
+                               
                                $classNames = explode(' ', $element->getAttribute('class'));
                                $classNames = array_filter($classNames, function ($className) use ($nodeName) {
                                        return ($className && in_array($className, self::$allowedClassNames[$nodeName]));
index a2d5b24de4d93eab78944bb1b70d7d7fcc11a99f..d00de2a81d6a892e85c5e9ebef544a63c68c6493 100644 (file)
@@ -19,16 +19,44 @@ class HtmlInputNodeWoltlabColor extends AbstractHtmlInputNode {
         */
        protected $tagName = 'woltlab-color';
        
-       public static $validColors = [
-               '000000', '000080', '0000CD', '0000FF', '006400', '008000', '008080', '00FF00',
-               '00FFFF', '2F4F4F', '40E0D0', '4B0082', '696969', '800000', '800080', '808080',
-               '8B4513', 'A52A2A', 'A9A9A9', 'ADD8E6', 'AFEEEE', 'B22222', 'D3D3D3', 'DAA520',
-               'DDA0DD', 'E6E6FA', 'EE82EE', 'F0F8FF', 'F0FFF0', 'F0FFFF', 'FAEBD7', 'FF0000',
-               'FF8C00', 'FFA07A', 'FFA500', 'FFD700', 'FFF0F5', 'FFFF00', 'FFFFE0', 'FFFFFF'
+       /**
+        * list of color names in CSS
+        * @var string[]
+        */
+       protected $colorNames = [
+               'aliceblue' => 'F0F8FF', 'antiquewhite' => 'FAEBD7', 'aqua' => '00FFFF', 'aquamarine' => '7FFFD4', 'azure' => 'F0FFFF',
+               'beige' => 'F5F5DC', 'bisque' => 'FFE4C4', 'black' => '000000', 'blanchedalmond' => 'FFEBCD', 'blue' => '0000FF',
+               'blueviolet' => '8A2BE2', 'brown' => 'A52A2A', 'burlywood' => 'DEB887', 'cadetblue' => '5F9EA0', 'chartreuse' => '7FFF00',
+               'chocolate' => 'D2691E', 'coral' => 'FF7F50', 'cornflowerblue' => '6495ED', 'cornsilk' => 'FFF8DC', 'crimson' => 'DC143C',
+               'cyan' => '00FFFF', 'darkblue' => '00008B', 'darkcyan' => '008B8B', 'darkgoldenrod' => 'B8860B', 'darkgray' => 'A9A9A9',
+               'darkgrey' => 'A9A9A9', 'darkgreen' => '006400', 'darkkhaki' => 'BDB76B', 'darkmagenta' => '8B008B', 'darkolivegreen' => '556B2F',
+               'darkorange' => 'FF8C00', 'darkorchid' => '9932CC', 'darkred' => '8B0000', 'darksalmon' => 'E9967A', 'darkseagreen' => '8FBC8F',
+               'darkslateblue' => '483D8B', 'darkslategray' => '2F4F4F', 'darkslategrey' => '2F4F4F', 'darkturquoise' => '00CED1',
+               'darkviolet' => '9400D3', 'deeppink' => 'FF1493', 'deepskyblue' => '00BFFF', 'dimgray' => '696969', 'dimgrey' => '696969',
+               'dodgerblue' => '1E90FF', 'firebrick' => 'B22222', 'floralwhite' => 'FFFAF0', 'forestgreen' => '228B22', 'fuchsia' => 'FF00FF',
+               'gainsboro' => 'DCDCDC', 'ghostwhite' => 'F8F8FF', 'gold' => 'FFD700', 'goldenrod' => 'DAA520', 'gray' => '808080',
+               'grey' => '808080', 'green' => '008000', 'greenyellow' => 'ADFF2F', 'honeydew' => 'F0FFF0', 'hotpink' => 'FF69B4',
+               'indianred' => 'CD5C5C', 'indigo' => '4B0082', 'ivory' => 'FFFFF0', 'khaki' => 'F0E68C', 'lavender' => 'E6E6FA',
+               'lavenderblush' => 'FFF0F5', 'lawngreen' => '7CFC00', 'lemonchiffon' => 'FFFACD', 'lightblue' => 'ADD8E6',
+               'lightcoral' => 'F08080', 'lightcyan' => 'E0FFFF', 'lightgoldenrodyellow' => 'FAFAD2', 'lightgray' => 'D3D3D3',
+               'lightgrey' => 'D3D3D3', 'lightgreen' => '90EE90', 'lightpink' => 'FFB6C1', 'lightsalmon' => 'FFA07A', 'lightseagreen' => '20B2AA',
+               'lightskyblue' => '87CEFA', 'lightslategray' => '778899', 'lightslategrey' => '778899', 'lightsteelblue' => 'B0C4DE',
+               'lightyellow' => 'FFFFE0', 'lime' => '00FF00', 'limegreen' => '32CD32', 'linen' => 'FAF0E6', 'magenta' => 'FF00FF',
+               'maroon' => '800000', 'mediumaquamarine' => '66CDAA', 'mediumblue' => '0000CD', 'mediumorchid' => 'BA55D3',
+               'mediumpurple' => '9370D8', 'mediumseagreen' => '3CB371', 'mediumslateblue' => '7B68EE', 'mediumspringgreen' => '00FA9A',
+               'mediumturquoise' => '48D1CC', 'mediumvioletred' => 'C71585', 'midnightblue' => '191970', 'mintcream' => 'F5FFFA',
+               'mistyrose' => 'FFE4E1', 'moccasin' => 'FFE4B5', 'navajowhite' => 'FFDEAD', 'navy' => '000080', 'oldlace' => 'FDF5E6',
+               'olive' => '808000', 'olivedrab' => '6B8E23', 'orange' => 'FFA500', 'orangered' => 'FF4500', 'orchid' => 'DA70D6',
+               'palegoldenrod' => 'EEE8AA', 'palegreen' => '98FB98', 'paleturquoise' => 'AFEEEE', 'palevioletred' => 'D87093',
+               'papayawhip' => 'FFEFD5', 'peachpuff' => 'FFDAB9', 'peru' => 'CD853F', 'pink' => 'FFC0CB', 'plum' => 'DDA0DD',
+               'powderblue' => 'B0E0E6', 'purple' => '800080', 'red' => 'FF0000', 'rosybrown' => 'BC8F8F', 'royalblue' => '4169E1',
+               'saddlebrown' => '8B4513', 'salmon' => 'FA8072', 'sandybrown' => 'F4A460', 'seagreen' => '2E8B57', 'seashell' => 'FFF5EE',
+               'sienna' => 'A0522D', 'silver' => 'C0C0C0', 'skyblue' => '87CEEB', 'slateblue' => '6A5ACD', 'slategray' => '708090',
+               'slategrey' => '708090', 'snow' => 'FFFAFA', 'springgreen' => '00FF7F', 'steelblue' => '4682B4', 'tan' => 'D2B48C',
+               'teal' => '008080', 'thistle' => 'D8BFD8', 'tomato' => 'FF6347', 'turquoise' => '40E0D0', 'violet' => 'EE82EE',
+               'wheat' => 'F5DEB3', 'white' => 'FFFFFF', 'whitesmoke' => 'F5F5F5', 'yellow' => 'FFFF00', 'yellowgreen' => '9ACD32'
        ];
        
-       protected static $colorsToLab = [];
-       
        /**
         * @inheritDoc
         */
@@ -50,155 +78,29 @@ class HtmlInputNodeWoltlabColor extends AbstractHtmlInputNode {
        public function process(array $elements, AbstractHtmlNodeProcessor $htmlNodeProcessor) {
                /** @var \DOMElement $element */
                foreach ($elements as $element) {
-                       if (preg_match('~\bwoltlab-color-([a-z0-9]{6})\b~i', $element->getAttribute('class'), $matches)) {
-                               $color = strtoupper($matches[1]);
-                               if (!in_array($color, self::$validColors)) {
-                                       $color = $this->getClosestColor($color);
+                       $className = $element->getAttribute('class');
+                       $colorCode = '';
+                       if (preg_match('~^woltlab-color-([a-z0-9]{3}(?:[a-z0-9]{3})?)$~i', $className, $matches)) {
+                               if (strlen($matches[1]) === 3) {
+                                       $colorCode = $matches[1]{0}.$matches[1]{0} . $matches[1]{1}.$matches[1]{1} . $matches[1]{2}.$matches[1]{2};
+                               }
+                               else {
+                                       $colorCode = $matches[1];
                                }
-                               
-                               $element->setAttribute('class', 'woltlab-color-'.$color);
-                       }
-                       else {
-                               DOMUtil::removeNode($element, true);
                        }
-               }
-       }
-       
-       protected function getClosestColor($hex) {
-               if (empty(self::$colorsToLab)) {
-                       // build static lookup table
-                       foreach (self::$validColors as $color) {
-                               self::$colorsToLab[$color] = $this->hexToLab($color);
+                       else if (preg_match('~^woltlab-color-([a-z]+)$~i', $className, $matches)) {
+                               $colorName = strtolower($matches[1]);
+                               if (isset($this->colorNames[$colorName])) {
+                                       $colorCode = $this->colorNames[$colorName];
+                               }
                        }
-               }
-               
-               $lab1 = $this->hexToLab($hex);
-               
-               $diff = 0;
-               $newColor = '';
-               
-               foreach (self::$colorsToLab as $color => $lab2) {
-                       $newDiff = $this->deltaE_cie1994($lab1, $lab2);
                        
-                       if ($newColor === '' || $diff > $newDiff) {
-                               $diff = $newDiff;
-                               $newColor = $color;
+                       if (empty($colorCode)) {
+                               DOMUtil::removeNode($element, true);
+                       }
+                       else {
+                               $element->setAttribute('class', 'woltlab-color-' . strtoupper($colorCode));
                        }
                }
-               
-               return $newColor;
-       }
-       
-       protected function hexToLab($hex) {
-               return $this->xyzToLab(
-                       $this->rgbToXyz(
-                               $this->hexToRgb($hex)
-                       )
-               );
-       }
-       
-       protected function hexToRgb($hex) {
-               // [r, g, b]
-               return [
-                       hexdec($hex{0}.$hex{1}),
-                       hexdec($hex{2}.$hex{3}),
-                       hexdec($hex{4}.$hex{5})
-               ];
-       }
-       
-       protected function rgbToXyz($rgb) {
-               // convert into values between 0 and 1
-               $red = $rgb[0] / 255;
-               $green = $rgb[1] / 255;
-               $blue = $rgb[2] / 255;
-               
-               if ($red > 0.04045) {
-                       $red = ($red + 0.055) / 1.055;
-                       $red = pow($red, 2.4);
-               }
-               else {
-                       $red = $red / 12.92;
-               }
-               
-               if ($green > 0.04045) {
-                       $green = ($green + 0.055) / 1.055;
-                       $green = pow($green, 2.4);
-               }
-               else {
-                       $green = $green / 12.92;
-               }
-               
-               if ($blue > 0.04045) {
-                       $blue = ($blue + 0.055) / 1.055;
-                       $blue = pow($blue, 2.4);
-               }
-               else {
-                       $blue = $blue / 12.92;
-               }
-               
-               $red *= 100;
-               $green *= 100;
-               $blue *= 100;
-               
-               // [x, y, z]
-               return [
-                       $red * 0.4124 + $green * 0.3576 + $blue * 0.1805,
-                       $red * 0.2126 + $green * 0.7152 + $blue * 0.0722,
-                       $red * 0.0193 + $green * 0.1192 + $blue * 0.9505
-               ];
-       }
-       
-       protected function xyzToLab($xyz) {
-               $x = $xyz[0] / 95.047;
-               $y = $xyz[1] / 100;
-               $z = $xyz[2] / 108.883;
-               
-               if ($x > 0.008856) {
-                       $x = pow($x, 1 / 3);
-               }
-               else {
-                       $x = 7.787 * $x + 16 / 116;
-               }
-               
-               if ($y > 0.008856) {
-                       $y = pow($y, 1 / 3);
-               }
-               else {
-                       $y = (7.787 * $y) + (16 / 116);
-               }
-               
-               if ($z > 0.008856) {
-                       $z = pow($z, 1 / 3);
-               }
-               else {
-                       $z = 7.787 * $z + 16 / 116;
-               }
-               
-               // [l, a, b]
-               return [
-                       116 * $y - 16,
-                       500 * ($x - $y),
-                       200 * ($y - $z)
-               ];
-       }
-       
-       protected function deltaE_cie1994($lab1, $lab2) {
-               // Delta E (CIE 1994) difference of two colors
-               // http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE94.html
-               $c1 = sqrt($lab1[1] * $lab1[1] + $lab1[2] * $lab1[2]);
-               $c2 = sqrt($lab2[1] * $lab2[1] + $lab2[2] * $lab2[2]);
-               
-               $dc = $c1 - $c2;
-               $dl = $lab1[0] - $lab2[0];
-               $da = $lab1[1] - $lab2[1];
-               $db = $lab1[2] - $lab2[2];
-               $dh = ($da * $da) + ($db * $db) - ($dc * $dc);
-               $dh = ($dh < 0) ? 0 : sqrt($dh);
-               
-               $first = $dl;
-               $second = $dc / (1 + 0.045 * $c1);
-               $third = $dh / (1 + 0.015 * $c1);
-               
-               return sqrt($first * $first + $second * $second + $third * $third);
        }
 }
index 0ca82ece7fbd4196860fa08e5d78ff67b20943f7..6456da16a59ca20dea543a9dae1961adff5d6217 100644 (file)
@@ -30,8 +30,8 @@ class ColorMetacodeConverter extends AbstractMetacodeConverter {
                        return false;
                }
                
-               // validates if code is a valid (short) HEX color code
-               if (!preg_match('~^\#[A-F0-9]{3}(?:[A-F0-9]{3})?$~i', $attributes[0])) {
+               // validates if code is a valid (short) HEX color code or color name
+               if (!preg_match('~^\#[A-F0-9]{3}(?:[A-F0-9]{3})?$~i', $attributes[0]) && !preg_match('~^[a-z]$~i', $attributes[0])) {
                        return false;
                }