From 77785808f70e913acec1effa77ebddc807c5c7d5 Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Tue, 27 Sep 2016 13:20:10 +0200 Subject: [PATCH] Improved text color support All colors with a hex representation are now supported, in addition CSS color names are converted into hex to unify the generated HTML. --- .../3rdParty/redactor2/plugins/WoltLabCode.js | 7 + .../redactor2/plugins/WoltLabColor.js | 48 ++-- .../Core/Ui/Redactor/RuntimeStyle.js | 42 ++++ .../node/HtmlInputNodeProcessor.class.php | 14 +- .../node/HtmlInputNodeWoltlabColor.class.php | 206 +++++------------- .../ColorMetacodeConverter.class.php | 4 +- 6 files changed, 142 insertions(+), 179 deletions(-) create mode 100644 wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/RuntimeStyle.js diff --git a/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabCode.js b/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabCode.js index 2a96ea4491..1c0bcd7ddf 100644 --- a/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabCode.js +++ b/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabCode.js @@ -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); } }; }; diff --git a/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabColor.js b/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabColor.js index 6d0d75fd5c..3fbb5e7776 100644 --- a/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabColor.js +++ b/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabColor.js @@ -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 index 0000000000..1b09ae1d60 --- /dev/null +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/RuntimeStyle.js @@ -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 + * @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 diff --git a/wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeProcessor.class.php b/wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeProcessor.class.php index 4b2364ff97..7e48c57bd0 100644 --- a/wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeProcessor.class.php +++ b/wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeProcessor.class.php @@ -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])); diff --git a/wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeWoltlabColor.class.php b/wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeWoltlabColor.class.php index a2d5b24de4..d00de2a81d 100644 --- a/wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeWoltlabColor.class.php +++ b/wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeWoltlabColor.class.php @@ -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); } } diff --git a/wcfsetup/install/files/lib/system/html/metacode/converter/ColorMetacodeConverter.class.php b/wcfsetup/install/files/lib/system/html/metacode/converter/ColorMetacodeConverter.class.php index 0ca82ece7f..6456da16a5 100644 --- a/wcfsetup/install/files/lib/system/html/metacode/converter/ColorMetacodeConverter.class.php +++ b/wcfsetup/install/files/lib/system/html/metacode/converter/ColorMetacodeConverter.class.php @@ -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; } -- 2.20.1