From 10ce4eeefa34898920ec787dc900814839be8c8c Mon Sep 17 00:00:00 2001 From: tophf Date: Thu, 16 Nov 2017 11:45:48 +0300 Subject: [PATCH 001/161] prefs.subscribe: allow multiple listeners for one key --- js/prefs.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/js/prefs.js b/js/prefs.js index 59735db2..20903389 100644 --- a/js/prefs.js +++ b/js/prefs.js @@ -136,9 +136,13 @@ var prefs = new function Prefs() { } } if (hasChanged) { - const listener = onChange.specific.get(key); - if (listener) { - listener(key, value); + const specific = onChange.specific.get(key); + if (typeof specific === 'function') { + specific(key, value); + } else if (specific instanceof Set) { + for (const listener of specific.values()) { + listener(key, value); + } } for (const listener of onChange.any.values()) { listener(key, value); @@ -164,7 +168,14 @@ var prefs = new function Prefs() { // listener: function (key, value) if (keys) { for (const key of keys) { - onChange.specific.set(key, listener); + const existing = onChange.specific.get(key); + if (!existing) { + onChange.specific.set(key, listener); + } else if (existing instanceof Set) { + existing.add(listener); + } else { + onChange.specific.set(key, new Set([existing, listener])); + } } } else { onChange.any.add(listener); From c50b251ae46ce16a5d764894ab8c748751096987 Mon Sep 17 00:00:00 2001 From: tophf Date: Tue, 14 Nov 2017 16:06:29 +0300 Subject: [PATCH 002/161] add original colorpicker plugin --- vendor-overwrites/colorpicker/LICENSE | 21 + vendor-overwrites/colorpicker/colorpicker.css | 310 ++++ vendor-overwrites/colorpicker/colorpicker.js | 1369 +++++++++++++++++ vendor-overwrites/colorpicker/colorview.js | 434 ++++++ 4 files changed, 2134 insertions(+) create mode 100644 vendor-overwrites/colorpicker/LICENSE create mode 100644 vendor-overwrites/colorpicker/colorpicker.css create mode 100644 vendor-overwrites/colorpicker/colorpicker.js create mode 100644 vendor-overwrites/colorpicker/colorview.js diff --git a/vendor-overwrites/colorpicker/LICENSE b/vendor-overwrites/colorpicker/LICENSE new file mode 100644 index 00000000..1ea38875 --- /dev/null +++ b/vendor-overwrites/colorpicker/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 jinho park (cyberuls@gmail.com, easylogic) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor-overwrites/colorpicker/colorpicker.css b/vendor-overwrites/colorpicker/colorpicker.css new file mode 100644 index 00000000..f80b45c2 --- /dev/null +++ b/vendor-overwrites/colorpicker/colorpicker.css @@ -0,0 +1,310 @@ + +/* codemirror colorview */ + +.codemirror-colorview { + border : 1px solid #8e8e8e; + position: relative; + display : inline-block; + box-sizing : border-box; + margin : 0px 2px; + width : 10px; + height : 10px; + cursor: pointer; + background-image : url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAJElEQVQYV2NctWrVfwYkEBYWxojMZ6SDAmT7QGx0K1EcRBsFAADeG/3M/HteAAAAAElFTkSuQmCC"); + background-repeat: repeat; +} + +.codemirror-colorview .codemirror-colorview-background { + content: ""; + position: absolute; + left:0px; + right:0px; + bottom:0px; + top:0px; +} + +.codemirror-colorview:hover { + border-color: #494949; +} + + +/* codemirror-colorpicker */ + +.codemirror-colorpicker { + position: relative; + width: 226px; + z-index: 1000; +} +.codemirror-colorpicker > .color { + position: relative; + height: 120px; + overflow: hidden; + cursor: pointer; +} +.codemirror-colorpicker > .color > .saturation { + position: relative; + width: 100%; + height: 100%; +} +.codemirror-colorpicker > .color > .saturation > .value { + position: relative; + width: 100%; + height: 100%; +} +.codemirror-colorpicker > .color > .saturation > .value > .drag-pointer { + position: absolute; + width: 10px; + height: 10px; + -webkit-border-radius: 50%; + -moz-border-radius: 50%; + border-radius: 50%; + left: -5px; + top: -5px; +} +.codemirror-colorpicker > .control { + position: relative; + padding: 18px 0px 14px 0px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} +.codemirror-colorpicker > .control > .color, +.codemirror-colorpicker > .control > .empty { + position: absolute; + left: 11px; + top: 24px; + width: 30px; + height: 30px; + -webkit-border-radius: 50%; + -moz-border-radius: 50%; + border-radius: 50%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.codemirror-colorpicker > .control > .hue { + position: relative; + padding: 6px 12px; + margin: 0px 0px 0px 45px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + cursor: pointer; +} +.codemirror-colorpicker > .control > .hue > .hue-container { + position: relative; + width: 100%; + height: 10px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +.codemirror-colorpicker > .control > .opacity { + position: relative; + padding: 3px 12px; + margin: 0px 0px 0px 45px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + cursor: pointer; +} +.codemirror-colorpicker > .control > .opacity > .opacity-container { + position: relative; + width: 100%; + height: 10px; + z-index: 2; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} + +.codemirror-colorpicker > .control .drag-bar, +.codemirror-colorpicker > .control .drag-bar2 { + position: absolute; + cursor: pointer; + top: 50% !important; + margin-top: -7px !important; + left: -3px; + width: 12px; + height: 12px; + -webkit-border-radius: 50px; + -moz-border-radius: 50px; + border-radius: 50px; +} +.codemirror-colorpicker > .information { + position: relative; + -webkit-box-sizing: padding-box; + -moz-box-sizing: padding-box; + box-sizing: padding-box; +} + +.codemirror-colorpicker > .information.hex > .information-item.hex { + display: flex; +} + +.codemirror-colorpicker > .information.rgb > .information-item.rgb { + display: flex; +} + +.codemirror-colorpicker > .information.hsl > .information-item.hsl { + display: flex; +} + +.codemirror-colorpicker > .information > .information-item { + display:none; + position: relative; + padding: 0px 5px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + margin-right:40px; +} + +.codemirror-colorpicker > .information > .information-change { + position:absolute; + display:block; + width:40px; + top:0px; + right:0px; + bottom:0px; +} + +.codemirror-colorpicker > .information > .information-change > .format-change-button { + width:100%; + height:100%; + background:transparent; + border:0px; + cursor:pointer; + outline:none; +} + +.codemirror-colorpicker > .information > .information-item > .input-field { + display:block; + flex:1; + padding: 5px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.codemirror-colorpicker > .information > .information-item > .input-field > input { + text-align: center; + width:100%; + padding:3px 5px; + font-size:11px; + color: #333; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + -o-user-select: text; + user-select: text; + border: 1px solid #cbcbcb; +} + +.codemirror-colorpicker > .information > .information-item > .input-field > .title { + text-align:center; + font-size:12px; + color:#a9a9a9; +} + +.codemirror-colorpicker > .information > input { + position: absolute; + font-size: 10px; + height: 20px; + bottom: 20px; + padding: 0 0 0 2px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + -o-user-select: text; + user-select: text; +} + + +.codemirror-colorpicker { + border: 1px solid #ececec; + background-color: #fff; + -webkit-box-shadow: 0 0px 10px 0px rgba(0, 0, 0, 0.12); + -moz-box-shadow: 0 0px 10px 0px rgba(0, 0, 0, 0.12); + box-shadow: 0 0px 10px 0px rgba(0, 0, 0, 0.12); +} +.codemirror-colorpicker > .color > .saturation { + background-color: rgba(204, 154, 129, 0); + background-image: -moz-linear-gradient(left, #FFF, rgba(204, 154, 129, 0)); + background-image: -webkit-gradient(linear, 0 0, 100% 0, from(#FFF), to(rgba(204, 154, 129, 0))); + background-image: -webkit-linear-gradient(left, #FFF, rgba(204, 154, 129, 0)); + background-image: -o-linear-gradient(left, #FFF, rgba(204, 154, 129, 0)); + background-image: linear-gradient(to right, #FFF, rgba(204, 154, 129, 0)); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#00cc9a81', GradientType=1); +} +.codemirror-colorpicker > .color > .saturation > .value { + background-image: -webkit-gradient(linear, 0 100%, 0 0, from(#000000), to(rgba(204, 154, 129, 0))); + background-image: -webkit-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0)); + background-image: -moz-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0)); + background-image: -o-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0)); + background-image: -ms-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0)); + background-image: linear-gradient(to top, #000000, rgba(204, 154, 129, 0)); +} +.codemirror-colorpicker > .color > .saturation > .value > .drag-pointer { + border: 1px solid #fff; + -webkit-box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.05); + -moz-box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.05); + box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.05); +} +.codemirror-colorpicker > .control > .hue > .hue-container { + background: -moz-linear-gradient(left, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); + background: -ms-linear-gradient(left, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); + background: -o-linear-gradient(left, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); + background: -webkit-gradient(linear, left top, right top, from(#ff0000), color-stop(0.17, #ffff00), color-stop(0.33, #00ff00), color-stop(0.5, #00ffff), color-stop(0.67, #0000ff), color-stop(0.83, #ff00ff), to(#ff0000)); + background: -webkit-linear-gradient(left, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); + background: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); +} +.codemirror-colorpicker > .control > .opacity > .opacity-container > .color-bar { + position:absolute; + display:block; + content:""; + left:0px; + right:0px; + bottom:0px; + top:0px; + background: -moz-linear-gradient(left, rgba(232, 232, 232, 0), rgba(232, 232, 232, 1)); + background: -ms-linear-gradient(left, rgba(232, 232, 232, 0), rgba(232, 232, 232, 1)); + background: -o-linear-gradient(left, rgba(232, 232, 232, 0), rgba(232, 232, 232, 1)); + background: -webkit-gradient(linear, left top, right top, from(rgba(232, 232, 232, 0)), to(rgba(232, 232, 232, 1))); + background: -webkit-linear-gradient(left, rgba(232, 232, 232, 0), rgba(232, 232, 232, 1)); + background: linear-gradient(to right, rgba(232, 232, 232, 0) , rgba(232, 232, 232, 1) ); +} +.codemirror-colorpicker > .control > .opacity > .opacity-container { + background-image : url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAJElEQVQYV2NctWrVfwYkEBYWxojMZ6SDAmT7QGx0K1EcRBsFAADeG/3M/HteAAAAAElFTkSuQmCC"); + background-repeat: repeat; +} + +.codemirror-colorpicker > .control > .empty { + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAJElEQVQYV2NctWrVfwYkEBYWxojMZ6SDAmT7QGx0K1EcRBsFAADeG/3M/HteAAAAAElFTkSuQmCC") repeat; +} +.codemirror-colorpicker > .control .drag-bar, +.codemirror-colorpicker > .control .drag-bar2 { + border: 1px solid rgba(0, 0, 0, 0.5); + -webkit-box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.1); + -moz-box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.1); + box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.1); + background-color: #FFFFFF; +} +.codemirror-colorpicker > .information { + /*border-top: 1px solid #e8e8e8;*/ +} +.codemirror-colorpicker > .information > .title { + color: #a3a3a3; +} +.codemirror-colorpicker > .information > .input { + color: #333; +} \ No newline at end of file diff --git a/vendor-overwrites/colorpicker/colorpicker.js b/vendor-overwrites/colorpicker/colorpicker.js new file mode 100644 index 00000000..25fc8741 --- /dev/null +++ b/vendor-overwrites/colorpicker/colorpicker.js @@ -0,0 +1,1369 @@ +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["codemirror" ], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + + CodeMirror.defineExtension("colorpicker", function () { + + var cm = this; + + var color = { + + trim : function (str) { + return str.replace(/^\s+|\s+$/g, ''); + }, + + /** + * @method format + * + * convert color to format string + * + * // hex + * color.format({ r : 255, g : 255, b : 255 }, 'hex') // #FFFFFF + * + * // rgb + * color.format({ r : 255, g : 255, b : 255 }, 'rgb') // rgba(255, 255, 255, 0.5); + * + * // rgba + * color.format({ r : 255, g : 255, b : 255, a : 0.5 }, 'rgb') // rgba(255, 255, 255, 0.5); + * + * @param {Object} obj obj has r, g, b and a attributes + * @param {"hex"/"rgb"} type format string type + * @returns {*} + */ + format : function(obj, type) { + if (type == 'hex') { + var r = obj.r.toString(16); + if (obj.r < 16) r = "0" + r; + + var g = obj.g.toString(16); + if (obj.g < 16) g = "0" + g; + + var b = obj.b.toString(16); + if (obj.b < 16) b = "0" + b; + + return "#" + [r, g, b].join(""); + } else if (type == 'rgb') { + if (typeof obj.a == 'undefined') { + return "rgb(" + [obj.r, obj.g, obj.b].join(",") + ")"; + } else { + return "rgba(" + [obj.r, obj.g, obj.b, obj.a].join(",") + ")"; + } + } else if (type == 'hsl') { + if (typeof obj.a == 'undefined') { + return "hsl(" + [obj.h, obj.s + '%', obj.l + '%'].join(",") + ")"; + } else { + return "hsla(" + [obj.h, obj.s + '%', obj.l + '%', obj.a].join(",") + ")"; + } + } + + return obj; + }, + + /** + * @method rgb + * + * parse string to rgb color + * + * color.rgb("#FF0000") === { r : 255, g : 0, b : 0 } + * + * color.rgb("rgb(255, 0, 0)") == { r : 255, g : 0, b : } + * + * @param {String} str color string + * @returns {Object} rgb object + */ + parse : function (str) { + if (typeof str == 'string') { + if (str.indexOf("rgb(") > -1) { + var arr = str.replace("rgb(", "").replace(")","").split(","); + + for(var i = 0, len = arr.length; i < len; i++) { + arr[i] = parseInt(color.trim(arr[i]), 10); + } + + return { type : 'rgb', r : arr[0], g : arr[1], b : arr[2], a : 1 }; + } else if (str.indexOf("rgba(") > -1) { + var arr = str.replace("rgba(", "").replace(")", "").split(","); + + for (var i = 0, len = arr.length; i < len; i++) { + + if (len - 1 == i) { + arr[i] = parseFloat(color.trim(arr[i])); + } else { + arr[i] = parseInt(color.trim(arr[i]), 10); + } + } + + return {type: 'rgb', r: arr[0], g: arr[1], b: arr[2], a: arr[3]}; + } else if (str.indexOf("hsl(") > -1) { + var arr = str.replace("hsl(", "").replace(")","").split(","); + + for(var i = 0, len = arr.length; i < len; i++) { + arr[i] = parseInt(color.trim(arr[i]), 10); + } + + var obj = { type : 'hsl', h : arr[0], s : arr[1], l : arr[2], a : 1 }; + + var temp = color.HSLtoRGB(obj.h, obj.s, obj.l); + + obj.r = temp.r; + obj.g = temp.g; + obj.b = temp.b; + + return obj; + } else if (str.indexOf("hsla(") > -1) { + var arr = str.replace("hsla(", "").replace(")", "").split(","); + + for (var i = 0, len = arr.length; i < len; i++) { + + if (len - 1 == i) { + arr[i] = parseFloat(color.trim(arr[i])); + } else { + arr[i] = parseInt(color.trim(arr[i]), 10); + } + } + + var obj = {type: 'hsl', h: arr[0], s: arr[1], l: arr[2], a: arr[3]}; + + var temp = color.HSLtoRGB(obj.h, obj.s, obj.l); + + obj.r = temp.r; + obj.g = temp.g; + obj.b = temp.b; + + return obj; + } else if (str.indexOf("#") == 0) { + + str = str.replace("#", ""); + + var arr = []; + if (str.length == 3) { + for(var i = 0, len = str.length; i < len; i++) { + var char = str.substr(i, 1); + arr.push(parseInt(char+char, 16)); + } + } else { + for(var i = 0, len = str.length; i < len; i+=2) { + arr.push(parseInt(str.substr(i, 2), 16)); + } + } + + return { type : 'hex', r : arr[0], g : arr[1], b : arr[2], a : 1 }; + } + } + + return str; + + }, + + /** + * @method HSVtoRGB + * + * convert hsv to rgb + * + * color.HSVtoRGB(0,0,1) === #FFFFF === { r : 255, g : 0, b : 0 } + * + * @param {Number} H hue color number (min : 0, max : 360) + * @param {Number} S Saturation number (min : 0, max : 1) + * @param {Number} V Value number (min : 0, max : 1 ) + * @returns {Object} + */ + HSVtoRGB : function (H, S, V) { + + if (H == 360) { + H = 0; + } + + var C = S * V; + var X = C * (1 - Math.abs((H/60) % 2 -1) ); + var m = V - C; + + var temp = []; + + if (0 <= H && H < 60) { temp = [C, X, 0]; } + else if (60 <= H && H < 120) { temp = [X, C, 0]; } + else if (120 <= H && H < 180) { temp = [0, C, X]; } + else if (180 <= H && H < 240) { temp = [0, X, C]; } + else if (240 <= H && H < 300) { temp = [X, 0, C]; } + else if (300 <= H && H < 360) { temp = [C, 0, X]; } + + return { + r : Math.ceil((temp[0] + m) * 255), + g : Math.ceil((temp[1] + m) * 255), + b : Math.ceil((temp[2] + m) * 255) + }; + }, + + /** + * @method RGBtoHSV + * + * convert rgb to hsv + * + * color.RGBtoHSV(0, 0, 255) === { h : 240, s : 1, v : 1 } === '#FFFF00' + * + * @param {Number} R red color value + * @param {Number} G green color value + * @param {Number} B blue color value + * @return {Object} hsv color code + */ + RGBtoHSV : function (R, G, B) { + + var R1 = R / 255; + var G1 = G / 255; + var B1 = B / 255; + + var MaxC = Math.max(R1, G1, B1); + var MinC = Math.min(R1, G1, B1); + + var DeltaC = MaxC - MinC; + + var H = 0; + + if (DeltaC == 0) { H = 0; } + else if (MaxC == R1) { + H = 60 * (( (G1 - B1) / DeltaC) % 6); + } else if (MaxC == G1) { + H = 60 * (( (B1 - R1) / DeltaC) + 2); + } else if (MaxC == B1) { + H = 60 * (( (R1 - G1) / DeltaC) + 4); + } + + if (H < 0) { + H = 360 + H; + } + + var S = 0; + + if (MaxC == 0) S = 0; + else S = DeltaC / MaxC; + + var V = MaxC; + + return { h : H, s : S, v : V }; + }, + + RGBtoHSL : function (r, g, b) { + r /= 255, g /= 255, b /= 255; + var max = Math.max(r, g, b), min = Math.min(r, g, b); + var h, s, l = (max + min) / 2; + + if(max == min){ + h = s = 0; // achromatic + }else{ + var d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch(max){ + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + h /= 6; + } + + return { h : Math.round(h * 360) , s : Math.round(s * 100), l : Math.round(l * 100)}; + }, + + HUEtoRGB : function (p, q, t) { + if(t < 0) t += 1; + if(t > 1) t -= 1; + if(t < 1/6) return p + (q - p) * 6 * t; + if(t < 1/2) return q; + if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return p; + }, + + HSLtoRGB : function (h, s, l) { + var r, g, b; + + h /= 360; + s /= 100; + l /= 100; + + if(s == 0){ + r = g = b = l; // achromatic + }else{ + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = this.HUEtoRGB(p, q, h + 1/3); + g = this.HUEtoRGB(p, q, h); + b = this.HUEtoRGB(p, q, h - 1/3); + } + + return { r : r * 255, g : g * 255, b : b * 255 }; + } + }; + + var hue_color = [ + { rgb : '#ff0000', start : .0 }, + { rgb : '#ffff00', start : .17 }, + { rgb : '#00ff00', start : .33 }, + { rgb : '#00ffff', start : .50 }, + { rgb : '#0000ff', start : .67 }, + { rgb : '#ff00ff', start : .83 }, + { rgb : '#ff0000', start : 1 } + ]; + + var $body, $root, $hue, $color, $value, $saturation, $drag_pointer, $drag_bar, + $control, $controlPattern, $controlColor, $hueContainer, $opacity, $opacityContainer, $opacityColorBar, $formatChangeButton, + $opacity_drag_bar, $information, $informationChange; + + var currentA, currentH, currentS, currentV; + var $hexCode; + var $rgb_r, $rgb_g, $rgb_b, $rgb_a; + var $hsl_h, $hsl_s, $hsl_l, $hsl_a; + var cssPrefix = getCssValuePrefix(); + + var colorpickerCallback = function () {}; + var counter = 0; + var cached = {}; + var isColorPickerShow = false; + var isShortCut = false; + var hideDelay = 2000; + + function dom(tag, className, attr) { + + if (typeof tag != 'string') { + this.el = tag; + } else { + + var el = document.createElement(tag); + + this.uniqId = counter++; + + el.className = className; + + attr = attr || {}; + + for(var k in attr) { + el.setAttribute(k, attr[k]); + } + + this.el = el; + } + } + + dom.prototype.closest = function (cls) { + + var temp = this; + var checkCls = false; + + while(!(checkCls = temp.hasClass(cls))) { + if (temp.el.parentNode) { + temp = new dom(temp.el.parentNode); + } else { + return null; + } + } + + if (checkCls) { + return temp; + } + + return null; + } + + dom.prototype.removeClass = function (cls) { + this.el.className = color.trim((" " + this.el.className + " ").replace(' ' + cls + ' ', ' ')); + } + + dom.prototype.hasClass = function (cls) { + if (!this.el.className) + { + return false; + } else { + var newClass = ' ' + this.el.className + ' '; + return newClass.indexOf(' ' + cls + ' ') > -1; + } + } + + dom.prototype.addClass = function (cls) { + if (!this.hasClass(cls)) { + this.el.className = this.el.className + " " + cls; + } + + } + + dom.prototype.html = function (html) { + this.el.innerHTML = html; + + return this; + } + + dom.prototype.empty = function () { + return this.html(''); + } + + dom.prototype.append = function (el) { + + if (typeof el == 'string') { + this.el.appendChild(document.createTextNode(el)); + } else { + this.el.appendChild(el.el || el); + } + + return this; + } + + dom.prototype.appendTo = function (target) { + var t = target.el ? target.el : target; + + t.appendChild(this.el); + + return this; + } + + dom.prototype.remove = function () { + if (this.el.parentNode) { + this.el.parentNode.removeChild(this.el); + } + + return this; + } + + dom.prototype.text = function () { + return this.el.textContent; + } + + dom.prototype.css = function (key, value) { + if (arguments.length == 2) { + this.el.style[key] = value; + } else if (arguments.length == 1) { + + if (typeof key == 'string') { + return getComputedStyle(this.el)[key]; + } else { + var keys = key || {}; + for(var k in keys) { + this.el.style[k] = keys[k]; + } + } + + } + + return this; + } + + dom.prototype.offset = function () { + var rect = this.el.getBoundingClientRect(); + + return { + top: rect.top + document.body.scrollTop, + left: rect.left + document.body.scrollLeft + }; + } + + dom.prototype.position = function () { + return { + top: parseFloat(this.el.style.top), + left: parseFloat(this.el.style.left) + }; + } + + dom.prototype.width = function () { + return this.el.offsetWidth; + } + + dom.prototype.height = function () { + return this.el.offsetHeight; + } + + dom.prototype.dataKey = function (key) { + return this.uniqId + '.' + key; + } + + dom.prototype.data = function (key, value) { + if (arguments.length == 2) { + cached[this.dataKey(key)] = value; + } else if (arguments.length == 1) { + return cached[this.dataKey(key)]; + } else { + var keys = Object.keys(cached); + + var uniqId = this.uniqId + "."; + return keys.filter(function (key) { + if (key.indexOf(uniqId) == 0) { + return true; + } + + return false; + }).map(function (value) { + return cached[value]; + }) + } + + return this; + } + + dom.prototype.val = function (value) { + if (arguments.length == 0) { + return this.el.value; + } else if (arguments.length == 1) { + this.el.value = value; + } + + return this; + } + + dom.prototype.int = function () { + return parseInt(this.val(), 10); + } + + dom.prototype.show = function () { + return this.css('display', 'block'); + } + + dom.prototype.hide = function () { + return this.css('display', 'none'); + } + + function setRGBInput(r, g, b) { + $rgb_r.val(r); + $rgb_g.val(g); + $rgb_b.val(b); + $rgb_a.val(currentA); + } + + function setHSLInput(h, s, l) { + $hsl_h.val(h); + $hsl_s.val(s + '%'); + $hsl_l.val(l + '%'); + $hsl_a.val(currentA); + } + + function getHexFormat() { + return color.format({ + r : $rgb_r.int(), + g : $rgb_g.int(), + b : $rgb_b.int() + }, 'hex'); + } + + function convertRGB() { + return color.HSVtoRGB(currentH, currentS, currentV); + } + + function convertHEX() { + return color.format(convertRGB(), 'hex'); + } + + function convertHSL() { + var rgb = color.HSVtoRGB(currentH, currentS, currentV); + return color.RGBtoHSL(rgb.r, rgb.g, rgb.b); + } + + function getFormattedColor (format) { + format = format || 'hex'; + + if (format == 'rgb') { + var rgb = convertRGB(); + rgb.a = currentA == 1 ? undefined : currentA; + return color.format(rgb, 'rgb'); + } else if (format == 'hsl') { + var hsl = convertHSL(); + hsl.a = currentA == 1 ? undefined : currentA; + return color.format(hsl, 'hsl'); + } else { + var rgb = convertRGB(); + return color.format(rgb, 'hex'); + } + } + + function setControlColor (color) { + $controlColor.css('background-color', color); + } + + function setInputColor() { + + var format = $information.data('format') || 'hex'; + + var rgb = null; + if (format == 'hex') { + $hexCode.val(convertHEX()); + } else if (format == 'rgb') { + var rgb = convertRGB(); + setRGBInput(rgb.r, rgb.g, rgb.b); + } else if (format == 'hsl') { + var hsl = convertHSL(); + setHSLInput(hsl.h, hsl.s, hsl.l); + } + + // set background + setControlColor(getFormattedColor('rgb')); + + var rgb = convertRGB(); + var colorString = color.format(rgb, 'rgb'); + setOpacityColorBar(colorString); + + if (typeof colorpickerCallback == 'function') { + + if (!isNaN(currentA)) { + colorpickerCallback(getFormattedColor(format)); + } + + } + } + + function setMainColor(e) { + e.preventDefault(); + var pos = $root.position(); // position for screen + var w = $color.width(); + var h = $color.height(); + + var x = e.clientX - pos.left; + var y = e.clientY - pos.top; + + if (x < 0) x = 0; + else if (x > w) x = w; + + if (y < 0) y = 0; + else if (y > h) y = h; + + $drag_pointer.css({ + left: (x - 5) + 'px', + top: (y - 5) + 'px' + }); + + $drag_pointer.data('pos', { x: x, y : y}); + + caculateHSV() + setInputColor(); + } + + function scale (startColor, endColor, t) { + var obj = { + r : parseInt(startColor.r + (endColor.r - startColor.r) * t, 10) , + g : parseInt(startColor.g + (endColor.g - startColor.g) * t, 10), + b : parseInt(startColor.b + (endColor.b - startColor.b) * t, 10) + }; + + return color.format(obj, 'hex'); + + } + + function checkHueColor(p) { + var startColor, endColor; + + for(var i = 0; i < hue_color.length;i++) { + if (hue_color[i].start >= p) { + startColor = hue_color[i-1]; + endColor = hue_color[i]; + break; + } + } + + if (startColor && endColor) { + return scale(startColor, endColor, (p - startColor.start)/(endColor.start - startColor.start)); + } + + return hue_color[0].rgb; + } + + function setBackgroundColor (color) { + $color.css("background-color", color); + } + + function setCurrentH (h) { + currentH = h; + } + + function setHueColor(e) { + var min = $hueContainer.offset().left; + var max = min + $hueContainer.width(); + var current = e ? pos(e).clientX : min + (max - min) * (currentH / 360); + + var dist; + if (current < min) { + dist = 0; + } else if (current > max) { + dist = 100; + } else { + dist = (current - min) / (max - min) * 100; + } + + var x = ($hueContainer.width() * (dist/100)); + + $drag_bar.css({ + left: (x -Math.ceil($drag_bar.width()/2)) + 'px' + }); + + $drag_bar.data('pos', { x : x}); + + var hueColor = checkHueColor(dist/100); + + setBackgroundColor(hueColor); + setCurrentH((dist/100) * 360); + setInputColor(); + } + + function getCssValuePrefix() + { + var rtrnVal = '';//default to standard syntax + var prefixes = ['', '-o-', '-ms-', '-moz-', '-webkit-']; + + // Create a temporary DOM object for testing + var dom = document.createElement('div'); + + for (var i = 0; i < prefixes.length; i++) + { + // Attempt to set the style + dom.style.background = prefixes[i] + 'linear-gradient(#000000, #ffffff)'; + + // Detect if the style was successfully set + if (dom.style.background) + { + rtrnVal = prefixes[i]; + } + } + + dom = null; + delete dom; + + return rtrnVal; + } + + function setOpacityColorBar(hueColor) { + var rgb = color.parse(hueColor); + + rgb.a = 0; + var start = color.format(rgb, 'rgb'); + + rgb.a = 1; + var end = color.format(rgb, 'rgb'); + + var prefix = cssPrefix; + $opacityColorBar.css('background', 'linear-gradient(to right, ' + start + ', ' + end + ')'); + } + + function setOpacity(e) { + var min = $opacityContainer.offset().left; + var max = min + $opacityContainer.width(); + var current = pos(e).clientX; + var dist; + + if (current < min) { + dist = 0; + } else if (current > max) { + dist = 100; + } else { + dist = (current - min) / (max - min) * 100; + } + + var x = ($opacityContainer.width() * (dist/100)); + + $opacity_drag_bar.css({ + left: (x -Math.ceil($opacity_drag_bar.width()/2)) + 'px' + }); + + $opacity_drag_bar.data('pos', { x : x }); + + caculateOpacity(); + currentFormat(); + setInputColor(); + } + + function caculateOpacity() { + var opacityPos = $opacity_drag_bar.data('pos') || { x : 0 }; + var a = Math.round((opacityPos.x / $opacityContainer.width()) * 100) / 100; + + currentA = isNaN(a) ? 1 : a; + } + + function caculateHSV() { + var pos = $drag_pointer.data('pos') || { x : 0, y : 0 }; + var huePos = $drag_bar.data('pos') || { x : 0 }; + + var width = $color.width(); + var height = $color.height(); + + var h = (huePos.x / $hueContainer.width()) * 360; + var s = (pos.x / width); + var v = ((height - pos.y) / height); + + if (width == 0) { + h = 0; + s = 0; + v = 0; + } + + currentH = h; + currentS = s; + currentV = v; + } + + function pos(e) { + if (e.touches && e.touches[0]) { + return e.touches[0]; + } + + return e; + } + + function checkNumberKey(e) { + var code = e.which, + isExcept = false; + + if(code == 37 || code == 39 || code == 8 || code == 46 || code == 9) + isExcept = true; + + if(!isExcept && (code < 48 || code > 57)) + return false; + + return true; + } + + function setRGBtoHexColor(e) { + var r = $rgb_r.val(), + g = $rgb_g.val(), + b = $rgb_b.val(); + + if(r == "" || g == "" || b == "") return; + + if(parseInt(r) > 255) $rgb_r.val(255); + else $rgb_r.val(parseInt(r)); + + if(parseInt(g) > 255) $rgb_g.val(255); + else $rgb_g.val(parseInt(g)); + + if(parseInt(b) > 255) $rgb_b.val(255); + else $rgb_b.val(parseInt(b)); + + initColor(getHexFormat()); + } + + function setColorUI() { + var x = $color.width() * currentS, y = $color.height() * ( 1 - currentV ); + + $drag_pointer.css({ + left : (x - 5) + "px", + top : (y - 5) + "px" + }); + + $drag_pointer.data('pos', { x : x, y : y }); + + var hueX = $hueContainer.width() * (currentH / 360); + + $drag_bar.css({ + left : (hueX - 7.5) + 'px' + }); + + $drag_bar.data('pos', { x : hueX }); + + var opacityX = $opacityContainer.width() * (currentA || 0); + + $opacity_drag_bar.css({ + left : (opacityX - 7.5) + 'px' + }); + + $opacity_drag_bar.data('pos', { x : opacityX }); + } + + function setCurrentHSV (h, s, v, a) { + currentA = a; + currentH = h; + currentS = s; + currentV = v; + } + + function setCurrentFormat (format) { + $information.data('format', format); + initFormat(); + } + + + + function initColor(newColor) { + var c = newColor || "#FF0000", colorObj = color.parse(c); + + setCurrentFormat(colorObj.type); + setBackgroundColor(c); + + var hsv = color.RGBtoHSV(colorObj.r, colorObj.g, colorObj.b); + + setCurrentHSV(hsv.h, hsv.s, hsv.v, colorObj.a); + setColorUI(); + setHueColor(); + setInputColor(); + } + + function addEvent (dom, eventName, callback) { + dom.addEventListener(eventName, callback); + } + + function removeEvent(dom, eventName, callback) { + dom.removeEventListener(eventName, callback); + } + + function EventColorMouseDown(e) { + $color.data('isDown', true); + setMainColor(e); + } + + function EventColorMouseUp(e) { + $color.data('isDown', false); + } + + function EventDragBarMouseDown (e) { + e.preventDefault(); + $hue.data('isDown', true); + } + + function EventOpacityDragBarMouseDown(e) { + e.preventDefault(); + $opacity.data('isDown', true); + } + + function EventHueMouseDown (e) { + $hue.data('isDown', true); + setHueColor(e); + } + + function EventOpacityMouseDown (e) { + $opacity.data('isDown', true); + setOpacity(e); + } + + function EventHexCodeKeyDown(e) { + if(e.which < 65 || e.which > 70) { + return checkNumberKey(e); + } + } + + function EventHexCodeKeyUp (e) { + var code = $hexCode.val(); + + if(code.charAt(0) == '#' && code.length == 7) { + initColor(code); + } + } + + function EventFormatChangeClick(e) { + nextFormat(); + } + + function initEvent() { + addEvent($color.el, 'mousedown', EventColorMouseDown); + addEvent($color.el, 'mouseup', EventColorMouseUp); + addEvent($drag_bar.el, 'mousedown', EventDragBarMouseDown); + addEvent($opacity_drag_bar.el, 'mousedown', EventOpacityDragBarMouseDown); + addEvent($hueContainer.el, 'mousedown', EventHueMouseDown); + addEvent($opacityContainer.el, 'mousedown', EventOpacityMouseDown); + addEvent($hexCode.el, 'keydown', EventHexCodeKeyDown); + addEvent($hexCode.el, 'keyup', EventHexCodeKeyUp); + + addEvent($rgb_r.el, 'keydown', checkNumberKey); + addEvent($rgb_r.el, 'keyup', setRGBtoHexColor); + addEvent($rgb_g.el, 'keydown', checkNumberKey); + addEvent($rgb_g.el, 'keyup', setRGBtoHexColor); + addEvent($rgb_b.el, 'keydown', checkNumberKey); + addEvent($rgb_b.el, 'keyup', setRGBtoHexColor); + + addEvent(document, 'mouseup', EventDocumentMouseUp); + addEvent(document, 'mousemove', EventDocumentMouseMove); + + addEvent($formatChangeButton.el, 'click', EventFormatChangeClick) + } + + function checkColorPickerClass(el) { + var hasColorView = new dom(el).closest('codemirror-colorview'); + var hasColorPicker = new dom(el).closest('codemirror-colorpicker'); + var hasCodeMirror = new dom(el).closest('CodeMirror'); + var IsInHtml = el.nodeName == 'HTML'; + + return !!(hasColorPicker || hasColorView || hasCodeMirror); + } + + function checkInHtml (el) { + var IsInHtml = el.nodeName == 'HTML'; + + return IsInHtml; + } + + function EventDocumentMouseUp (e) { + $color.data('isDown', false); + $hue.data('isDown', false); + $opacity.data('isDown', false); + + // when color picker clicked in outside + if (checkInHtml(e.target)) { + //setHideDelay(hideDelay); + } else if (checkColorPickerClass(e.target) == false ) { + hide(); + } + + } + + function EventDocumentMouseMove(e) { + if ($color.data('isDown')) { + setMainColor(e); + } + + if ($hue.data('isDown')) { + setHueColor(e); + } + + if ($opacity.data('isDown')) { + setOpacity(e); + } + } + + function destroy() { + removeEvent($color.el, 'mousedown', EventColorMouseDown); + removeEvent($color.el, 'mouseup', EventColorMouseUp); + removeEvent($drag_bar.el, 'mousedown', EventDragBarMouseDown); + removeEvent($opacity_drag_bar.el, 'mousedown', EventOpacityDragBarMouseDown); + removeEvent($hueContainer.el, 'mousedown', EventHueMouseDown); + removeEvent($opacityContainer.el, 'mousedown', EventOpacityMouseDown); + removeEvent($hexCode.el, 'keydown', EventHexCodeKeyDown); + removeEvent($hexCode.el, 'keyup', EventHexCodeKeyUp); + removeEvent($rgb_r.el, 'keydown', checkNumberKey); + removeEvent($rgb_r.el, 'keyup', setRGBtoHexColor); + removeEvent($rgb_g.el, 'keydown', checkNumberKey); + removeEvent($rgb_g.el, 'keyup', setRGBtoHexColor); + removeEvent($rgb_b.el, 'keydown', checkNumberKey); + removeEvent($rgb_b.el, 'keyup', setRGBtoHexColor); + removeEvent(document, 'mouseup', EventDocumentMouseUp); + removeEvent(document, 'mousemove', EventDocumentMouseMove); + removeEvent($formatChangeButton.el, 'click', EventFormatChangeClick); + + // remove color picker callback + colorpickerCallback = undefined; + } + + function currentFormat () { + var current_format = $information.data('format') || 'hex'; + if (currentA < 1 && current_format == 'hex' ) { + var next_format = 'rgb'; + $information.removeClass(current_format); + $information.addClass(next_format); + $information.data('format', next_format); + + setInputColor(); + } + } + + function initFormat () { + var current_format = $information.data('format') || 'hex'; + + $information.removeClass('hex'); + $information.removeClass('rgb'); + $information.removeClass('hsl'); + $information.addClass(current_format); + } + + function nextFormat() { + var current_format = $information.data('format') || 'hex'; + + var next_format = 'hex'; + if (current_format == 'hex') { + next_format = 'rgb'; + } else if (current_format == 'rgb') { + next_format = 'hsl'; + } else if (current_format == 'hsl') { + if (currentA == 1) { + next_format = 'hex'; + } else { + next_format = 'rgb'; + } + } + + $information.removeClass(current_format); + $information.addClass(next_format); + $information.data('format', next_format); + + setInputColor(); + } + + function makeInputField(type) { + var item = new dom('div', 'information-item '+ type); + + if (type == 'hex') { + var field = new dom('div', 'input-field hex'); + + $hexCode = new dom('input', 'input', { type : 'text' }); + + field.append($hexCode); + field.append(new dom('div', 'title').html('HEX')); + + item.append(field); + + } else if (type == 'rgb') { + var field = new dom('div', 'input-field rgb-r'); + $rgb_r = new dom('input', 'input', { type : 'text' }); + + field.append($rgb_r); + field.append(new dom('div', 'title').html('R')); + + item.append(field); + + field = new dom('div', 'input-field rgb-g'); + $rgb_g = new dom('input', 'input', { type : 'text' }); + + field.append($rgb_g); + field.append(new dom('div', 'title').html('G')); + + item.append(field); + + field = new dom('div', 'input-field rgb-b'); + $rgb_b = new dom('input', 'input', { type : 'text' }); + + field.append($rgb_b); + field.append(new dom('div', 'title').html('B')); + + item.append(field); + + // rgba + field = new dom('div', 'input-field rgb-a'); + $rgb_a = new dom('input', 'input', { type : 'text' }); + + field.append($rgb_a); + field.append(new dom('div', 'title').html('A')); + + item.append(field); + + } else if (type == 'hsl') { + var field = new dom('div', 'input-field hsl-h'); + $hsl_h = new dom('input', 'input', { type : 'text' }); + + field.append($hsl_h); + field.append(new dom('div', 'title').html('H')); + + item.append(field); + + field = new dom('div', 'input-field hsl-s'); + $hsl_s = new dom('input', 'input', { type : 'text' }); + + field.append($hsl_s); + field.append(new dom('div', 'title').html('S')); + + item.append(field); + + field = new dom('div', 'input-field hsl-l'); + $hsl_l = new dom('input', 'input', { type : 'text' }); + + field.append($hsl_l); + field.append(new dom('div', 'title').html('L')); + + item.append(field); + + // rgba + field = new dom('div', 'input-field hsl-a'); + $hsl_a = new dom('input', 'input', { type : 'text' }); + + field.append($hsl_a); + field.append(new dom('div', 'title').html('A')); + + item.append(field); + } + + return item; + } + + function init() { + $body = new dom(document.body); + + $root = new dom('div', 'codemirror-colorpicker'); + $color = new dom('div', 'color'); + $drag_pointer = new dom('div', 'drag-pointer' ); + $value = new dom( 'div', 'value' ); + $saturation = new dom('div', 'saturation' ); + + $control = new dom('div', 'control' ); + $controlPattern = new dom('div', 'empty' ); + $controlColor = new dom('div', 'color' ); + $hue = new dom('div', 'hue' ); + $hueContainer = new dom('div', 'hue-container' ); + $drag_bar = new dom('div', 'drag-bar' ); + $opacity = new dom('div', 'opacity' ); + $opacityContainer = new dom('div', 'opacity-container' ); + $opacityColorBar = new dom('div', 'color-bar' ); + + $opacity_drag_bar = new dom('div', 'drag-bar2' ); + + $information = new dom('div', 'information hex' ); + + $informationChange = new dom('div', 'information-change'); + + $formatChangeButton = new dom('button', 'format-change-button', { type : 'button'}).html('↔'); + $informationChange.append($formatChangeButton); + + + $information.append(makeInputField('hex')); + $information.append(makeInputField('rgb')); + $information.append(makeInputField('hsl')); + $information.append($informationChange); + + + $value.append($drag_pointer); + $saturation.append($value); + $color.append($saturation); + + $hueContainer.append($drag_bar); + $hue.append($hueContainer); + + $opacityContainer.append($opacityColorBar); + $opacityContainer.append($opacity_drag_bar); + $opacity.append($opacityContainer); + + $control.append($hue); + $control.append($opacity); + $control.append($controlPattern); + $control.append($controlColor); + + $root.append($color); + $root.append($control); + $root.append($information); + + initHueColors(); + //initEvent(); + initColor(); + }; + + function initHueColors () { + for(var i = 0, len = hue_color.length; i < len; i++) { + var hue = hue_color[i]; + + var obj = color.parse(hue.rgb); + + hue.r = obj.r; + hue.g = obj.g; + hue.b = obj.b; + } + } + + /** + * public methods + */ + function setColor(value) { + if(typeof(value) == "object") { + if(!value.r || !value.g || !value.b) + return; + + initColor(color.format(value, "hex")); + } else if(typeof(value) == "string") { + if(value.charAt(0) != "#") + return; + + initColor(value); + } + } + + function getColor(type) { + caculateHSV(); + var rgb = convertRGB(); + + if (type) { + return color.format(rgb, type); + } + + return rgb; + } + + function definePostion (opt) { + + var width = $root.width(); + var height = $root.height(); + + // set left position for color picker + var elementScreenLeft = opt.left - $body.el.scrollLeft ; + if (width + elementScreenLeft > window.innerWidth) { + elementScreenLeft -= (width + elementScreenLeft) - window.innerWidth; + } + if (elementScreenLeft < 0) { elementScreenLeft = 0; } + + // set top position for color picker + var elementScreenTop = opt.top - $body.el.scrollTop ; + if (height + elementScreenTop > window.innerHeight) { + elementScreenTop -= (height + elementScreenTop) - window.innerHeight; + } + if (elementScreenTop < 0) { elementScreenTop = 0; } + + // set position + $root.css({ + left : elementScreenLeft + 'px', + top : elementScreenTop + 'px' + }); + } + + function show (opt, color, callback) { + destroy(); + initEvent(); + $root.appendTo(document.body); + + $root.css({ + position: 'fixed', // color picker has fixed position + left : '-10000px', + top : '-10000px' + }); + + $root.show(); + + definePostion(opt); + + isColorPickerShow = true; + + isShortCut = opt.isShortCut || false; + + initColor(color); + + // define colorpicker callback + colorpickerCallback = function (colorString) { + callback(colorString); + } + + // define hide delay + hideDelay = opt.hideDelay || 2000; + if (hideDelay > 0) { + setHideDelay(hideDelay); + } + + } + + + var timerCloseColorPicker; + function setHideDelay (delayTime) { + delayTime = delayTime || 0; + removeEvent($root.el, 'mouseenter'); + removeEvent($root.el, 'mouseleave'); + + addEvent($root.el, 'mouseenter', function () { + clearTimeout(timerCloseColorPicker); + }); + + addEvent($root.el, 'mouseleave', function () { + clearTimeout(timerCloseColorPicker); + timerCloseColorPicker = setTimeout(hide, delayTime); + }); + + clearTimeout(timerCloseColorPicker); + timerCloseColorPicker = setTimeout(hide, delayTime); + } + + function hide () { + if (isColorPickerShow) { + destroy(); + $root.hide(); + $root.remove(); + isColorPickerShow = false; + } + + } + + init(); + + return { + isShortCut : function () { + return isShortCut; + }, + $root: $root, + show: show, + hide: hide, + setColor: setColor, + getColor: getColor + } + }) + +}); diff --git a/vendor-overwrites/colorpicker/colorview.js b/vendor-overwrites/colorpicker/colorview.js new file mode 100644 index 00000000..b53fec3e --- /dev/null +++ b/vendor-overwrites/colorpicker/colorview.js @@ -0,0 +1,434 @@ +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + // color names + var color_names={aliceblue:"rgb(240, 248, 255)",antiquewhite:"rgb(250, 235, 215)",aqua:"rgb(0, 255, 255)",aquamarine:"rgb(127, 255, 212)",azure:"rgb(240, 255, 255)",beige:"rgb(245, 245, 220)",bisque:"rgb(255, 228, 196)",black:"rgb(0, 0, 0)",blanchedalmond:"rgb(255, 235, 205)",blue:"rgb(0, 0, 255)",blueviolet:"rgb(138, 43, 226)",brown:"rgb(165, 42, 42)",burlywood:"rgb(222, 184, 135)",cadetblue:"rgb(95, 158, 160)",chartreuse:"rgb(127, 255, 0)",chocolate:"rgb(210, 105, 30)",coral:"rgb(255, 127, 80)",cornflowerblue:"rgb(100, 149, 237)",cornsilk:"rgb(255, 248, 220)",crimson:"rgb(237, 20, 61)",cyan:"rgb(0, 255, 255)",darkblue:"rgb(0, 0, 139)",darkcyan:"rgb(0, 139, 139)",darkgoldenrod:"rgb(184, 134, 11)",darkgray:"rgb(169, 169, 169)",darkgrey:"rgb(169, 169, 169)",darkgreen:"rgb(0, 100, 0)",darkkhaki:"rgb(189, 183, 107)",darkmagenta:"rgb(139, 0, 139)",darkolivegreen:"rgb(85, 107, 47)",darkorange:"rgb(255, 140, 0)",darkorchid:"rgb(153, 50, 204)",darkred:"rgb(139, 0, 0)",darksalmon:"rgb(233, 150, 122)",darkseagreen:"rgb(143, 188, 143)",darkslateblue:"rgb(72, 61, 139)",darkslategray:"rgb(47, 79, 79)",darkslategrey:"rgb(47, 79, 79)",darkturquoise:"rgb(0, 206, 209)",darkviolet:"rgb(148, 0, 211)",deeppink:"rgb(255, 20, 147)",deepskyblue:"rgb(0, 191, 255)",dimgray:"rgb(105, 105, 105)",dimgrey:"rgb(105, 105, 105)",dodgerblue:"rgb(30, 144, 255)",firebrick:"rgb(178, 34, 34)",floralwhite:"rgb(255, 250, 240)",forestgreen:"rgb(34, 139, 34)",fuchsia:"rgb(255, 0, 255)",gainsboro:"rgb(220, 220, 220)",ghostwhite:"rgb(248, 248, 255)",gold:"rgb(255, 215, 0)",goldenrod:"rgb(218, 165, 32)",gray:"rgb(128, 128, 128)",grey:"rgb(128, 128, 128)",green:"rgb(0, 128, 0)",greenyellow:"rgb(173, 255, 47)",honeydew:"rgb(240, 255, 240)",hotpink:"rgb(255, 105, 180)",indianred:"rgb(205, 92, 92)",indigo:"rgb(75, 0, 130)",ivory:"rgb(255, 255, 240)",khaki:"rgb(240, 230, 140)",lavender:"rgb(230, 230, 250)",lavenderblush:"rgb(255, 240, 245)",lawngreen:"rgb(124, 252, 0)",lemonchiffon:"rgb(255, 250, 205)",lightblue:"rgb(173, 216, 230)",lightcoral:"rgb(240, 128, 128)",lightcyan:"rgb(224, 255, 255)",lightgoldenrodyellow:"rgb(250, 250, 210)",lightgreen:"rgb(144, 238, 144)",lightgray:"rgb(211, 211, 211)",lightgrey:"rgb(211, 211, 211)",lightpink:"rgb(255, 182, 193)",lightsalmon:"rgb(255, 160, 122)",lightseagreen:"rgb(32, 178, 170)",lightskyblue:"rgb(135, 206, 250)",lightslategray:"rgb(119, 136, 153)",lightslategrey:"rgb(119, 136, 153)",lightsteelblue:"rgb(176, 196, 222)",lightyellow:"rgb(255, 255, 224)",lime:"rgb(0, 255, 0)",limegreen:"rgb(50, 205, 50)",linen:"rgb(250, 240, 230)",magenta:"rgb(255, 0, 255)",maroon:"rgb(128, 0, 0)",mediumaquamarine:"rgb(102, 205, 170)",mediumblue:"rgb(0, 0, 205)",mediumorchid:"rgb(186, 85, 211)",mediumpurple:"rgb(147, 112, 219)",mediumseagreen:"rgb(60, 179, 113)",mediumslateblue:"rgb(123, 104, 238)",mediumspringgreen:"rgb(0, 250, 154)",mediumturquoise:"rgb(72, 209, 204)",mediumvioletred:"rgb(199, 21, 133)",midnightblue:"rgb(25, 25, 112)",mintcream:"rgb(245, 255, 250)",mistyrose:"rgb(255, 228, 225)",moccasin:"rgb(255, 228, 181)",navajowhite:"rgb(255, 222, 173)",navy:"rgb(0, 0, 128)",oldlace:"rgb(253, 245, 230)",olive:"rgb(128, 128, 0)",olivedrab:"rgb(107, 142, 35)",orange:"rgb(255, 165, 0)",orangered:"rgb(255, 69, 0)",orchid:"rgb(218, 112, 214)",palegoldenrod:"rgb(238, 232, 170)",palegreen:"rgb(152, 251, 152)",paleturquoise:"rgb(175, 238, 238)",palevioletred:"rgb(219, 112, 147)",papayawhip:"rgb(255, 239, 213)",peachpuff:"rgb(255, 218, 185)",peru:"rgb(205, 133, 63)",pink:"rgb(255, 192, 203)",plum:"rgb(221, 160, 221)",powderblue:"rgb(176, 224, 230)",purple:"rgb(128, 0, 128)",rebeccapurple:"rgb(102, 51, 153)",red:"rgb(255, 0, 0)",rosybrown:"rgb(188, 143, 143)",royalblue:"rgb(65, 105, 225)",saddlebrown:"rgb(139, 69, 19)",salmon:"rgb(250, 128, 114)",sandybrown:"rgb(244, 164, 96)",seagreen:"rgb(46, 139, 87)",seashell:"rgb(255, 245, 238)",sienna:"rgb(160, 82, 45)",silver:"rgb(192, 192, 192)",skyblue:"rgb(135, 206, 235)",slateblue:"rgb(106, 90, 205)",slategray:"rgb(112, 128, 144)",slategrey:"rgb(112, 128, 144)",snow:"rgb(255, 250, 250)",springgreen:"rgb(0, 255, 127)",steelblue:"rgb(70, 130, 180)",tan:"rgb(210, 180, 140)",teal:"rgb(0, 128, 128)",thistle:"rgb(216, 191, 216)",tomato:"rgb(255, 99, 71)",turquoise:"rgb(64, 224, 208)",violet:"rgb(238, 130, 238)",wheat:"rgb(245, 222, 179)",white:"rgb(255, 255, 255)",whitesmoke:"rgb(245, 245, 245)",yellow:"rgb(255, 255, 0)",yellowgreen:"rgb(154, 205, 50)",transparent:"rgba(0, 0, 0, 0)"}; + + var colorpicker_class = 'codemirror-colorview'; + var colorpicker_background_class = 'codemirror-colorview-background'; + + // Excluded tokens do not show color views.. + var excluded_token = ['comment']; + + CodeMirror.defineOption("colorpicker", false, function (cm, val, old) { + + if (old && old != CodeMirror.Init) { + + if (cm.state.colorpicker) + { + cm.state.colorpicker.destroy(); + cm.state.colorpicker = null; + + } + // remove event listener + } + + if (val) + { + cm.state.colorpicker = new codemirror_colorpicker(cm, val); + } + }); + + function onChange(cm, evt) { + if (evt.origin == 'setValue') { // if content is changed by setValue method, it initialize markers + cm.state.colorpicker.close_color_picker(); + cm.state.colorpicker.init_color_update(); + cm.state.colorpicker.style_color_update(); + } else { + cm.state.colorpicker.style_color_update(cm.getCursor().line); + } + + } + + function onUpdate(cm, evt) { + if (!cm.state.colorpicker.isUpdate) { + cm.state.colorpicker.isUpdate = true; + cm.state.colorpicker.close_color_picker(); + cm.state.colorpicker.init_color_update(); + cm.state.colorpicker.style_color_update(); + } + } + + function onRefresh(cm, evt) { + onChange(cm, { origin : 'setValue'}); + } + + function onKeyup(cm, evt) { + cm.state.colorpicker.keyup(evt); + } + + function onMousedown(cm, evt) { + if (cm.state.colorpicker.is_edit_mode()) + { + cm.state.colorpicker.check_mousedown(evt); + } + } + + function onPaste (cm, evt) { + onChange(cm, { origin : 'setValue'}); + } + + function onScroll (cm) { + cm.state.colorpicker.close_color_picker(); + } + + function debounce (callback, delay) { + + var t = undefined; + + return function (cm, e) { + if (t) { + clearTimeout(t); + } + + t = setTimeout(function () { + callback(cm, e); + }, delay || 300); + } + } + + function has_class(el, cls) { + if (!el || !el.className) { + return false; + } else { + var newClass = ' ' + el.className + ' '; + return newClass.indexOf(' ' + cls + ' ') > -1; + } + } + + function codemirror_colorpicker (cm, opt) { + var self = this; + + if (typeof opt == 'boolean') + { + opt = { mode : 'view' }; + } else { + opt = Object.assign({ mode: 'view' }, opt || {}); + } + + this.opt = opt; + this.cm = cm; + this.markers = {}; + + // set excluded token + excluded_token = this.opt.excluded_token || excluded_token; + + if (this.cm.colorpicker) { + this.colorpicker = this.cm.colorpicker(); + } else if (this.opt.colorpicker) { + this.colorpicker = this.opt.colorpicker; + } + + this.init_event(); + + } + + codemirror_colorpicker.prototype.init_event = function () { + + this.cm.on('mousedown', onMousedown); + this.cm.on('keyup', onKeyup); + this.cm.on('change', onChange); + this.cm.on('update', onUpdate); + this.cm.on('refresh', onRefresh); + + // create paste callback + this.onPasteCallback = (function (cm, callback) { + return function (evt) { + callback.call(this, cm, evt); + } + })(this.cm, onPaste); + + this.cm.getWrapperElement().addEventListener('paste', this.onPasteCallback); + + if (this.is_edit_mode()) + { + this.cm.on('scroll', debounce(onScroll, 50)); + } + + } + + codemirror_colorpicker.prototype.is_edit_mode = function () { + return this.opt.mode == 'edit'; + } + + codemirror_colorpicker.prototype.is_view_mode = function () { + return this.opt.mode == 'view'; + } + + codemirror_colorpicker.prototype.destroy = function () { + this.cm.off('mousedown', onMousedown); + this.cm.off('keyup', onKeyup); + this.cm.off('change', onChange) + this.cm.getWrapperElement().removeEventListener('paste', this.onPasteCallback); + + if (this.is_edit_mode()) + { + this.cm.off('scroll'); + } + } + + codemirror_colorpicker.prototype.hasClass = function (el, className) { + if (!el.className) + { + return false; + } else { + var newClass = ' ' + el.className + ' '; + return newClass.indexOf(' ' + className + ' ') > -1; + } + } + + codemirror_colorpicker.prototype.check_mousedown = function (evt) { + if (this.hasClass(evt.target, colorpicker_background_class) ) + { + this.open_color_picker(evt.target.parentNode); + } else { + this.close_color_picker(); + } + } + + codemirror_colorpicker.prototype.popup_color_picker = function (defalutColor) { + var cursor = this.cm.getCursor(); + var self = this; + var colorMarker = { + lineNo : cursor.line, + ch : cursor.ch, + color: defalutColor || '#FFFFFF', + isShortCut : true + }; + + Object.keys(this.markers).forEach(function(key) { + var searchKey = "#" + key; + if (searchKey.indexOf( "#" + colorMarker.lineNo + ":") > -1) { + var marker = self.markers[key]; + + if (marker.ch <= colorMarker.ch && colorMarker.ch <= marker.ch + marker.color.length) { + // when cursor has marker + colorMarker.ch = marker.ch; + colorMarker.color = marker.color; + colorMarker.nameColor = marker.nameColor; + } + + } + }); + + this.open_color_picker(colorMarker); + } + + codemirror_colorpicker.prototype.open_color_picker = function (el) { + var lineNo = el.lineNo; + var ch = el.ch; + var nameColor = el.nameColor; + var color = el.color; + + + if (this.colorpicker) { + var self = this; + var prevColor = color; + var pos = this.cm.charCoords({line : lineNo, ch : ch }); + this.colorpicker.show({ + left : pos.left, + top : pos.bottom, + isShortCut : el.isShortCut || false, + hideDelay : self.opt.hideDelay || 2000 + }, nameColor || color, function (newColor) { + self.cm.replaceRange(newColor, { line : lineNo, ch : ch } , { line : lineNo, ch : ch + prevColor.length }, '*colorpicker'); + prevColor = newColor; + }); + + } + + } + + codemirror_colorpicker.prototype.close_color_picker = function (el) { + if (this.colorpicker) + { + this.colorpicker.hide(); + } + } + + codemirror_colorpicker.prototype.key = function (lineNo, ch) { + return [lineNo, ch].join(":"); + } + + + codemirror_colorpicker.prototype.keyup = function (evt) { + + if (this.colorpicker ) { + if (evt.key == 'Escape') { + this.colorpicker.hide(); + } else if (this.colorpicker.isShortCut() == false) { + this.colorpicker.hide(); + } + } + } + + codemirror_colorpicker.prototype.init_color_update = function () { + this.markers = {}; // initialize marker list + } + + codemirror_colorpicker.prototype.style_color_update = function (lineHandle) { + + if (lineHandle) { + this.match(lineHandle); + } else { + var max = this.cm.lineCount(); + + for(var lineNo = 0; lineNo < max; lineNo++) { + this.match(lineNo); + } + } + + } + + codemirror_colorpicker.prototype.empty_marker = function (lineNo, lineHandle) { + var list = lineHandle.markedSpans || []; + + for(var i = 0, len = list.length; i < len; i++) { + var key = this.key(lineNo, list[i].from); + + if (key && has_class(list[i].marker.replacedWith, colorpicker_class)) { + delete this.markers[key]; + list[i].marker.clear(); + } + + } + } + + codemirror_colorpicker.prototype.color_regexp = /(#(?:[\da-f]{3}){1,2}|rgb\((?:\s*\d{1,3},\s*){2}\d{1,3}\s*\)|rgba\((?:\s*\d{1,3},\s*){3}\d*\.?\d+\s*\)|hsl\(\s*\d{1,3}(?:,\s*\d{1,3}%){2}\s*\)|hsla\(\s*\d{1,3}(?:,\s*\d{1,3}%){2},\s*\d*\.?\d+\s*\)|([\w_\-]+))/gi; + + codemirror_colorpicker.prototype.match_result = function (lineHandle) { + return lineHandle.text.match(this.color_regexp); + } + + codemirror_colorpicker.prototype.match = function (lineNo) { + var lineHandle = this.cm.getLineHandle(lineNo); + + this.empty_marker(lineNo, lineHandle); + + var result = this.match_result(lineHandle); + if (result) + { + var obj = { next : 0 }; + for(var i = 0, len = result.length; i < len; i++) { + + if (result[i].indexOf('#') > -1 || result[i].indexOf('rgb') > -1 || result[i].indexOf('hsl') > -1) { + this.render(obj, lineNo, lineHandle, result[i]); + } else { + var nameColor = color_names[result[i]]; + if (nameColor) { + this.render(obj, lineNo, lineHandle, result[i], nameColor); + } + } + } + } + } + + codemirror_colorpicker.prototype.make_element = function () { + var el = document.createElement('div'); + + el.className = colorpicker_class; + + if (this.is_edit_mode()) + { + el.title ="open color picker"; + } else { + el.title =""; + } + + el.back_element = this.make_background_element(); + el.appendChild(el.back_element); + + return el; + } + + codemirror_colorpicker.prototype.make_background_element = function () { + var el = document.createElement('div'); + + el.className = colorpicker_background_class; + + return el; + } + + codemirror_colorpicker.prototype.set_state = function (lineNo, start, color, nameColor) { + var marker = this.create_marker(lineNo, start); + + + marker.lineNo = lineNo; + marker.ch = start; + marker.color = color; + marker.nameColor = nameColor; + + return marker; + } + + codemirror_colorpicker.prototype.create_marker = function (lineNo, start) { + + var key = this.key(lineNo,start); + + if (!this.markers[key]) { + this.markers[key] = this.make_element(); + } + + + return this.markers[key]; + + } + + codemirror_colorpicker.prototype.has_marker = function (lineNo, start) { + var key = this.key(lineNo,start); + return !!(this.markers[key]) + } + + codemirror_colorpicker.prototype.update_element = function (el, color) { + el.back_element.style.backgroundColor = color; + } + + codemirror_colorpicker.prototype.set_mark = function (line, ch, el) { + this.cm.setBookmark({ line : line, ch : ch}, { widget : el, handleMouseEvents : true} ); + + } + + codemirror_colorpicker.prototype.is_excluded_token = function (line, ch) { + var token = this.cm.getTokenAt({line : line, ch : ch}); + var count = 0; + for(var i = 0, len = excluded_token.length; i < len; i++) { + if (token.type === excluded_token[i]) { + count++; + break; + } + } + + return count > 0; // true is that it has a excluded token + } + + codemirror_colorpicker.prototype.render = function (cursor, lineNo, lineHandle, color, nameColor) { + var start = lineHandle.text.indexOf(color, cursor.next); + + if (this.is_excluded_token(lineNo, start) === true) { + // excluded token do not show. + return; + } + + cursor.next = start + color.length; + + if (this.has_marker(lineNo, start)) + { + this.update_element(this.create_marker(lineNo, start), nameColor || color); + this.set_state(lineNo, start, color, nameColor); + return; + } + + var el = this.create_marker(lineNo, start); + + this.update_element(el, nameColor || color); + this.set_state(lineNo, start, color, nameColor || color); + this.set_mark(lineNo, start, el); + } +}); From 271aa793557f06f9afb62ad35e64b948b50bb4fc Mon Sep 17 00:00:00 2001 From: tophf Date: Mon, 20 Nov 2017 15:06:27 +0300 Subject: [PATCH 003/161] pull locales from transifex --- _locales/ar/messages.json | 2 +- _locales/bg/messages.json | 2 +- _locales/de/messages.json | 235 +++++++++++++++++++++++++++++++---- _locales/el/messages.json | 2 +- _locales/en/messages.json | 2 +- _locales/es/messages.json | 201 ++++++++++++++++++++++++++++++ _locales/et/messages.json | 97 ++++++++++++++- _locales/fi/messages.json | 2 +- _locales/fr/messages.json | 2 +- _locales/hu/messages.json | 142 ++++++++++++++++++++- _locales/it/messages.json | 2 +- _locales/ja/messages.json | 207 +++++++++++++++++++++++++++++- _locales/nl/messages.json | 2 +- _locales/pl/messages.json | 205 +++++++++++++++++++++++++++++- _locales/pt_BR/messages.json | 2 +- _locales/pt_PT/messages.json | 2 +- _locales/ru/messages.json | 203 +++++++++++++++++++++++++++++- _locales/sv/messages.json | 2 +- _locales/tr/messages.json | 2 +- _locales/zh/messages.json | 2 +- _locales/zh_CN/messages.json | 2 +- _locales/zh_TW/messages.json | 203 +++++++++++++++++++++++++++++- 22 files changed, 1477 insertions(+), 44 deletions(-) diff --git a/_locales/ar/messages.json b/_locales/ar/messages.json index 56ca799a..1eda8a03 100644 --- a/_locales/ar/messages.json +++ b/_locales/ar/messages.json @@ -8,7 +8,7 @@ "description": "Label for the button to enable a style" }, "styleMissingName": { - "message": "أدخل اسمًا.", + "message": "أدخل اسمًا", "description": "Error displayed when user saves without providing a name" }, "appliesDomainOption": { diff --git a/_locales/bg/messages.json b/_locales/bg/messages.json index 6dfa70e3..6af23aa4 100644 --- a/_locales/bg/messages.json +++ b/_locales/bg/messages.json @@ -48,7 +48,7 @@ "description": "Label for the button to enable a style" }, "styleMissingName": { - "message": "Въведете име.", + "message": "Въведете име", "description": "Error displayed when user saves without providing a name" }, "genericHistoryLabel": { diff --git a/_locales/de/messages.json b/_locales/de/messages.json index e436ca3c..4a7a0e31 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -31,6 +31,14 @@ "message": "Exportieren", "description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" }, + "installButton": { + "message": "Installieren", + "description": "Label for install button" + }, + "styleMetaErrorCheckbox": { + "message": "Ungültige @var Checkbox: Wert muss 0 oder 1 sein", + "description": "Error displayed when the value of @var checkbox is invalid" + }, "linterJSONError": { "message": "Ungültiges JSON Format", "description": "Setting linter config with invalid JSON" @@ -40,7 +48,7 @@ "description": "" }, "updateCheckHistory": { - "message": "Verlauf der Aktualisierungs-Überprüfungen", + "message": "Verlauf der Updatesuche", "description": "" }, "cm_tabSize": { @@ -52,7 +60,7 @@ "description": "Label for the button to enable a style" }, "styleMissingName": { - "message": "Bitte einen Namen eingeben.", + "message": "Bitte einen Namen eingeben", "description": "Error displayed when user saves without providing a name" }, "genericHistoryLabel": { @@ -68,7 +76,7 @@ "description": "Option to make the style apply to the entered string as a domain" }, "checkForUpdate": { - "message": "Nach Aktualisierung suchen", + "message": "Nach Update suchen", "description": "Label for the button to check a single style for an update" }, "styleNotAppliedRegexpProblemTooltip": { @@ -83,6 +91,10 @@ "message": "Dunkle Browser-Themes", "description": "" }, + "styleFromMozillaFormatError": { + "message": "Import vom Mozilla Format fehlgeschlagen", + "description": "Label for the import error" + }, "importAppendLabel": { "message": "Zum Style anfügen", "description": "Label for the button to import a style and append to the existing sections" @@ -92,7 +104,7 @@ "description": "" }, "updateAllCheckSucceededNoUpdate": { - "message": "Keine Aktualisierung gefunden.", + "message": "Keine Updates gefunden.", "description": "Text that displays when an update all check completed and no updates are available" }, "importReportLegendAdded": { @@ -119,6 +131,10 @@ "message": "Ausgegraut", "description": "Label for the checkbox that toggles grayed out mode of applies-to favicons in the new UI on manage page" }, + "versionInvalidOlder": { + "message": "Die Version des Styles ist älter als die des bereits installierten.", + "description": "Displayed when the version of style is older than the installed one" + }, "confirmYes": { "message": "Ja", "description": "'Yes' button in a confirm dialog" @@ -167,10 +183,18 @@ "message": "Löschen", "description": "" }, + "confirmDefault": { + "message": "Voreinstellung verwenden", + "description": "'Set to default' button in a confirm dialog" + }, "confirmCancel": { "message": "Abbrechen", "description": "" }, + "cm_autoCloseBracketsTooltip": { + "message": "Wenn ( [ { ' \" geschrieben werden, automatisch schließende ) ] } ' \" setzen", + "description": "Label for the checkbox in the style editor." + }, "retrieveBckp": { "message": "Styles Importieren", "description": "" @@ -196,7 +220,7 @@ "description": "Option to make the style apply to the entered string as a regular expression" }, "optionsAdvancedExposeIframesNote": { - "message": "Aktiviert die iFrame-spezifische CSS Auszeichnung wie 'html[stylus-iframe] h1 { display:none; }'", + "message": "Aktiviert die iFrame-spezifische CSS Auszeichnung wie \"html[stylus-iframe] h1 { display:none; }\"", "description": "" }, "importReportLegendUpdatedCode": { @@ -228,6 +252,15 @@ "message": "Nur lokale Styles", "description": "Checkbox to show only locally created styles i.e. non-updatable" }, + "styleMetaErrorPreprocessor": { + "message": "Nicht unterstützter @preprocessor: $preprocessor$", + "description": "Error displayed when the value of @preprocessor is not supported", + "placeholders": { + "preprocessor": { + "content": "$1" + } + } + }, "linterIssuesHelp": { "message": "Folgende Probleme wurden von $link$ gefunden:", "description": "Help popup message for the selected CSS linter issues block on the style edit page", @@ -237,18 +270,26 @@ } } }, + "parseUsercssError": { + "message": "Usercss parsen fehlgeschlagen:", + "description": "The error message to show when stylus failed to parse usercss" + }, "searchStyles": { "message": "Inhalte durchsuchen", "description": "Label for the search filter textbox on the Manage styles page" }, "optionsUpdateImportNote": { - "message": "Nach dem Importieren von Styles aus einer alten Version oder von Stylish ist eine einmalige manuelle Überprüfung der Aktualisierungen (Updates) in der Verwaltung nötig. Dies stellt sicher, dass alle Styles auf dem aktuellsten Stand sind.", + "message": "Nach dem Importieren von Styles aus einer alten Version oder von Stylish ist eine einmalige manuelle Updatesuche in der Verwaltung nötig. Dies stellt sicher, dass alle Styles auf dem aktuellsten Stand sind.", "description": "" }, "checkAllUpdatesForce": { "message": "Nochmals überprüfen, ich habe keine Styles bearbeitet!", "description": "Label for the button to apply all detected updates" }, + "liveReloadLabel": { + "message": "Echtzeitaktualisierung", + "description": "The label of live-reload feature" + }, "unreachableFileHint": { "message": "Stylus kann nur auf das file:// Protokoll in der URL zugreifen, wenn dies in den Einstellungen der Erweiterung unter chrome://extensions festgelegt wurde.", "description": "Note in the toolbar popup for file:// URLs" @@ -262,7 +303,7 @@ "description": "Label for the checkbox controlling toolbar badge text." }, "manageFavicons": { - "message": "Favicons in der 'Gilt für' Spalte anzeigen", + "message": "Favicons in der \"Gilt für\" Spalte anzeigen", "description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page" }, "menuShowBadge": { @@ -313,6 +354,10 @@ "message": "Regulärer Ausdruck ist ungültig.", "description": "Validation message for a bad regexp in a style" }, + "license": { + "message": "Lizenz", + "description": "Label for the license" + }, "optionsHeading": { "message": "Optionen", "description": "Heading for options section on manage page." @@ -335,7 +380,7 @@ "description": "Label for the style maanger opener in the browser action context menu." }, "styleUpdate": { - "message": "Möchtest Du '$stylename$' wirklich aktualisieren?", + "message": "Möchtest Du \"$stylename$\" wirklich aktualisieren?", "description": "Confirmation when updating a style", "placeholders": { "stylename": { @@ -355,6 +400,10 @@ "message": "Ungültige übersprungen", "description": "Text after the number of styles skipped due to being invalid (not a Stylus/Stylish backup file probably) in the report shown after importing styles" }, + "optionsAdvancedNewStyleAsUsercss": { + "message": "Schreibe neuen Style als usercss", + "description": "" + }, "genericResetLabel": { "message": "Zurücksetzen", "description": "Used in various parts of UI to indicate that something may be reset to its original state" @@ -384,6 +433,10 @@ "message": "Verwende die /re/ Syntax zur Suche als Regulärer Ausdruck", "description": "Label after the search input field in the editor shown on Ctrl-F" }, + "popupBordersTooltip": { + "message": "Nützlich für dunkle Themes im neuen Chrome, da dort die Seitenränder nicht mehr gefärbt werden", + "description": "" + }, "updateCheckManualUpdateHint": { "message": "Eine erzwungene Aktualisierung wird die lokalen Änderungen überschreiben.", "description": "Additional text displayed when an update check skipped updating the style to avoid losing local modifications" @@ -396,6 +449,10 @@ "message": "Verwerfe den aktuellen Style-Inhalt und überschreibe ihn mit dem importierten", "description": "Label for the button to import and overwrite current style" }, + "installUpdateFromLabel": { + "message": "Nach Updates suchen", + "description": "Label for the checkbox to save current URL for update check" + }, "cm_resizeGripHint": { "message": "Doppelklick, um Höhe zu maximieren / wiederherzustellen", "description": "Tooltip for the resize grip in style editor" @@ -421,7 +478,7 @@ "description": "Label for the button to remove an 'applies' entry" }, "updatesCurrentlyInstalled": { - "message": "Installierte Aktualisierungen:", + "message": "Installierte Updates:", "description": "Text that displays when an update is installed on options page. Followed by the number of currently installed updates." }, "styleToMozillaFormatTitle": { @@ -470,11 +527,11 @@ "description": "Label for the button to make a style apply only to specific sites" }, "installUpdate": { - "message": "Aktualisierung installieren", + "message": "Update installieren", "description": "Label for the button to install an update for a single style" }, "optionsCheckUpdate": { - "message": "Alle verfügbaren Aktualisierungen Installieren", + "message": "Alle verfügbaren Updates Installieren", "description": "" }, "filteredStyles": { @@ -525,6 +582,10 @@ "message": "Alle Styles deaktivieren", "description": "Label for the checkbox that turns all enabled styles off." }, + "appliesLineWidgetLabel": { + "message": "Zeige \"Gilt für\" Info", + "description": "Label for the checkbox to display applies-to information in the single editor" + }, "updateCheckSkippedMaybeLocallyEdited": { "message": "Dieser Style scheint lokal bearbeitet worden zu sein.", "description": "Text that displays when an update check skipped updating the style to avoid losing possible local modifications" @@ -546,7 +607,7 @@ "description": "Displayed in style manager when unable to connect to the background page" }, "checkingForUpdate": { - "message": "Suche nach Aktualisierungen...", + "message": "Suche nach Updates...", "description": "Text to display when checking a style for an update" }, "styleRegexpTestFull": { @@ -554,7 +615,7 @@ "description": "RegExp test report: label for the fully matching expressions" }, "manageMaxTargets": { - "message": "Anzahl der 'Gilt für' Elemente", + "message": "Anzahl der \"Gilt für\" Elemente", "description": "Label for the numeric input box to limit max number of applies-to targets in the new UI on manage page" }, "popupManageTooltip": { @@ -565,20 +626,43 @@ "message": "Stylus nutzt hierzu den externen Dienst https://www.google.com/s2/favicons", "description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page" }, + "styleInstallOverwrite": { + "message": "\"$stylename$\" ist bereits installiert. Überschreiben?\nVersion: $oldVersion$ -> $newVersion$", + "description": "Confirmation when re-installing a style", + "placeholders": { + "stylename": { + "content": "$1" + }, + "newVersion": { + "content": "$3" + }, + "oldVersion": { + "content": "$2" + } + } + }, "updateCheckSkippedLocallyEdited": { "message": "Dieser Style wurde lokal bearbeitet.", "description": "Text that displays when an update check skipped updating the style to avoid losing local modifications" }, "linterRulesLink": { - "message": "Vollständige Liste der Regeln anzeigen", + "message": "Vollständige Liste der Regeln anzeigen für", "description": "Stylelint or CSSLint rules label added immediately before a link" }, + "styleUpdateDiscardChanges": { + "message": "Der Style wurde außerhalb des Editors verändert. Style neu laden?", + "description": "Confirmation to update the style in the editor" + }, "optionsResetButton": { "message": "Optionen zurücksetzen", "description": "" }, + "externalUsercssDocument": { + "message": "Dokumentation für Usercss", + "description": "Label for the external link to usercss documentation" + }, "optionsAdvancedContextDelete": { - "message": "'Löschen' im Editor-Kontextmenü hinzufügen", + "message": "\"Löschen\" im Editor-Kontextmenü hinzufügen", "description": "" }, "linterConfigPopupTitle": { @@ -590,6 +674,10 @@ } } }, + "configureStyle": { + "message": "Konfigurieren", + "description": "Label for the button to configure userstyle" + }, "importReportLegendUpdatedBoth": { "message": "Aktualisierte Meta Infos und Codes", "description": "Text after the number of styles updated entirely in the report shown after importing styles" @@ -603,7 +691,7 @@ "description": "RegExp test button label in the editor shown when applies-to list has a regexp value" }, "appliesHelp": { - "message": "Lege mit den Einstellungen von 'Gilt für' fest, für welche URLs der Code in diesem Bereich gelten soll.", + "message": "Lege mit den Einstellungen von \"Gilt für\" fest, für welche URLs der Code in diesem Bereich gelten soll.", "description": "Help text for 'applies to' section" }, "editStyleHeading": { @@ -618,6 +706,28 @@ "message": "Autovervollständigen bei Eingabe", "description": "Label for the checkbox in the style editor." }, + "linterCSSLintIncompatible": { + "message": "CSSLint unterstützt $preprocessorname$ nicht", + "description": "The label to display when the preprocessor isn't compatible with CSSLint", + "placeholders": { + "preprocessorname": { + "content": "$1" + } + } + }, + "styleMetaErrorSelectValueMismatch": { + "message": "Ungültiges @select: Wert existiert nicht in der Liste", + "description": "Error displayed when the value of @select is invalid" + }, + "styleMetaErrorColor": { + "message": "$color$ist kein gültiger Farbcode", + "description": "Error displayed when the value of @var color is invalid", + "placeholders": { + "color": { + "content": "$1" + } + } + }, "manageOnlyDisabled": { "message": "Nur deaktivierte Styles", "description": "Checkbox to show only disabled styles" @@ -631,21 +741,29 @@ "description": "Style editor's 'highglight' drop-down list option: highlight the occurrences of currently selected text" }, "updateAllCheckSucceededSomeEdited": { - "message": "Einige Styles wurden nicht überprüft, um dem Verlust von lokalen Bearbeitungen vorzubeugen. Die Aktualisierungen können entweder durch einzelne manuelle Überprüfung oder durch eine erneute Ausführung der Aktualisierung und anschließendem Update für alle Styles erzwungen werden (Lokale Bearbeitungen werden dann überschrieben).", + "message": "Einige Styles wurden nicht überprüft, um dem Verlust von lokalen Bearbeitungen vorzubeugen. Die Aktualisierungen können entweder durch einzelne manuelle Überprüfung oder durch eine erneute Ausführung der Updatesuche (und anschließendem Update) für alle Styles erzwungen werden. Lokale Bearbeitungen werden dann überschrieben.", "description": "Text that displays when an update all check completed and no updates are available" }, "stylusUnavailableForURL": { "message": "Stylus funktioniert nicht auf Seiten wie diesen.", "description": "Note in the toolbar pop-up when on a URL Stylus can't affect" }, + "popupBorders": { + "message": "Weiße Rahmen an den Seiten hinzufügen", + "description": "" + }, "manageOnlyUpdates": { - "message": "Nur mit Aktualisierungen oder Problemen", + "message": "Nur mit Updates oder Problemen", "description": "Checkbox to show only styles that have updates after check-all-styles-for-updates was performed" }, "addStyleTitle": { "message": "Style hinzufügen", "description": "Title of the page for adding styles" }, + "externalLink": { + "message": "Externer Link", + "description": "Label for external links" + }, "importReplaceLabel": { "message": "Style überschreiben", "description": "Label for the button to import and overwrite current style" @@ -662,6 +780,10 @@ "message": "Erweitert", "description": "" }, + "alphaChannel": { + "message": "Deckkraft", + "description": "Label of color's opacity" + }, "importAppendTooltip": { "message": "Füge den importierten Style an den aktuellen", "description": "Tooltip for the button to import a style and append to the existing sections" @@ -707,7 +829,7 @@ "description": "Tooltip for the checkbox to show only locally created styles i.e. non-updatable" }, "checkAllUpdates": { - "message": "Nach Aktualisierungen suchen", + "message": "Nach Updates suchen", "description": "Label for the button to check all styles for updates" }, "openOptionsManage": { @@ -718,6 +840,15 @@ "message": "Badge auf dem Toolbar-Icon", "description": "" }, + "installUpdateFrom": { + "message": "Style erhält momentan Updates von $url$", + "description": "Label to describe where the style gets update", + "placeholders": { + "url": { + "content": "$1" + } + } + }, "importReportLegendIdentical": { "message": "Identische übersprungen", "description": "Text after the number of styles skipped due to being identical to the already installed ones in the report shown after importing styles" @@ -726,6 +857,14 @@ "message": "Popup-Breite (in Pixeln)", "description": "" }, + "cm_autoCloseBrackets": { + "message": "Klammern automatisch schließen", + "description": "Label for the checkbox in the style editor." + }, + "installButtonReinstall": { + "message": "Neuinstallieren", + "description": "Label for reinstall button" + }, "linterInvalidConfigError": { "message": "Nicht gespeichert aufgrund folgender ungültiger Einstellungen:", "description": "Invalid linter config will show a message followed by a list of invalid entries" @@ -734,6 +873,19 @@ "message": "Nein", "description": "'No' button in a confirm dialog" }, + "styleMissingMeta": { + "message": "Erforderliche Metadaten fehlen: @$key$", + "description": "Error displayed when a mandatory metadata is missing", + "placeholders": { + "key": { + "content": "$1" + } + } + }, + "appliesLineWidgetWarning": { + "message": "Funktioniert nicht mit minified CSS", + "description": "A warning that applies-to information won't show properly with minified CSS" + }, "undo": { "message": "Rückgängig", "description": "Button label" @@ -742,6 +894,14 @@ "message": "Tastaturbelegung", "description": "Label for the drop-down list controlling the keymap for the style editor." }, + "externalSupport": { + "message": "zur Supportseite", + "description": "Label for the external link to style's support site" + }, + "confirmSave": { + "message": "Speichern", + "description": "'Save' button in a confirm dialog" + }, "manageNewUI": { "message": "Neues Verwaltungs Design-Layout.", "description": "Label for the checkbox that toggles the new UI on manage page" @@ -762,14 +922,27 @@ "message": "Ersetzen durch", "description": "Label before the replace-with input field in the editor shown on Ctrl-H etc." }, + "liveReloadError": { + "message": "Bei der Echtzeitaktualisierung der Datei ist ein Fehler aufgetreten", + "description": "The label of live-reload error" + }, "deleteStyleLabel": { "message": "Löschen", "description": "Label for the button to delete a style" }, "updateCheckManualUpdateForce": { - "message": "Aktualisierungen installieren (Lokale Bearbeitungen werden überschrieben)", + "message": "Updates installieren (Lokale Bearbeitungen werden überschrieben)", "description": "Additional text displayed when an update check skipped updating the style to avoid losing local modifications" }, + "styleInstallFailed": { + "message": "Installation des Userstyles fehlgeschlagen!\n$error$", + "description": "Warning when installation failed", + "placeholders": { + "error": { + "content": "$1" + } + } + }, "optionsAdvancedExposeIframes": { "message": "Ermögliche Iframes via HTML[stylus-iframe]", "description": "" @@ -779,9 +952,13 @@ "description": "Label for the button to go to the add style page" }, "optionsUpdateIntervalNote": { - "message": "Zum Deaktivieren der automatischen Aktualisierungs-Überprüfung den Wert auf 0 setzen.", + "message": "Zum Deaktivieren der automatischen Updateüberprüfung den Wert auf 0 setzen.", "description": "" }, + "installButtonUpdate": { + "message": "Aktualisieren", + "description": "Label for update button" + }, "backupButtons": { "message": "Datensicherung", "description": "Heading for backup" @@ -794,10 +971,22 @@ "message": "Bearbeiten", "description": "Label for the button to go to the edit style page" }, + "installButtonInstalled": { + "message": "Installiert", + "description": "Text displayed when the style is successfully installed" + }, + "author": { + "message": "Autor", + "description": "Label for the style author" + }, "popupOpenEditInWindow": { "message": "Editor in neuem Fenster öffnen", "description": "Label for the checkbox controlling 'edit' action behavior in the popup." }, + "appliesRemoveError": { + "message": "Kann letzten \"Gilt für\" Eintrag nicht entfernen", + "description": "Error displayed when the last 'applies' is going to be removed" + }, "backupMessage": { "message": "Wähle eine Datei aus oder ziehe die Datei auf diese Seite. (Drag and Drop)", "description": "Message for backup" @@ -817,5 +1006,9 @@ "description": { "message": "Gestalte das Web neu mit Stylus, dem Style Manager. Stylus ermöglicht dir ganz einfach Themes und Designs für viele populäre Websites zu installieren.", "description": "Extension description" + }, + "confirmClose": { + "message": "Schließen", + "description": "'Close' button in a confirm dialog" } } \ No newline at end of file diff --git a/_locales/el/messages.json b/_locales/el/messages.json index a6e0de1a..473ab9ea 100644 --- a/_locales/el/messages.json +++ b/_locales/el/messages.json @@ -12,7 +12,7 @@ "description": "Label for the button to enable a style" }, "styleMissingName": { - "message": "Εισάγετε ένα όνομα.", + "message": "Εισάγετε ένα όνομα", "description": "Error displayed when user saves without providing a name" }, "appliesDomainOption": { diff --git a/_locales/en/messages.json b/_locales/en/messages.json index fb0236c7..83d22b05 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -813,7 +813,7 @@ } }, "styleMissingName": { - "message": "Enter a name.", + "message": "Enter a name", "description": "Error displayed when user saves without providing a name" }, "styleSaveLabel": { diff --git a/_locales/es/messages.json b/_locales/es/messages.json index d9ee12a6..5b6d7cf7 100644 --- a/_locales/es/messages.json +++ b/_locales/es/messages.json @@ -31,6 +31,14 @@ "message": "Exportar", "description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" }, + "installButton": { + "message": "Instalar", + "description": "Label for install button" + }, + "styleMetaErrorCheckbox": { + "message": "Casilla @var no válida: El valor debe estar ser 0 o 1", + "description": "Error displayed when the value of @var checkbox is invalid" + }, "linterJSONError": { "message": "Formato JSON no válido", "description": "Setting linter config with invalid JSON" @@ -83,6 +91,10 @@ "message": "Temas de navegador oscuros", "description": "" }, + "styleFromMozillaFormatError": { + "message": "No se pudo importar desde el formato Mozilla", + "description": "Label for the import error" + }, "importAppendLabel": { "message": "Agregar al estilo", "description": "Label for the button to import a style and append to the existing sections" @@ -123,6 +135,10 @@ "message": "Atenuado", "description": "Label for the checkbox that toggles grayed out mode of applies-to favicons in the new UI on manage page" }, + "versionInvalidOlder": { + "message": "Esta versión es posterior al estilo instalado.", + "description": "Displayed when the version of style is older than the installed one" + }, "confirmYes": { "message": "Sí", "description": "'Yes' button in a confirm dialog" @@ -175,10 +191,18 @@ "message": "Eliminar", "description": "" }, + "confirmDefault": { + "message": "Usar predeterminado", + "description": "'Set to default' button in a confirm dialog" + }, "confirmCancel": { "message": "Cancelar", "description": "" }, + "cm_autoCloseBracketsTooltip": { + "message": "Añade automáticamente un equivalente de cierre al escribir uno de apertura de ()[]{}''\"\"", + "description": "Label for the checkbox in the style editor." + }, "retrieveBckp": { "message": "Importar estilos", "description": "" @@ -236,6 +260,15 @@ "message": "Sólo estilos creados localmente", "description": "Checkbox to show only locally created styles i.e. non-updatable" }, + "styleMetaErrorPreprocessor": { + "message": "@preprocessor no soportado: $preprocessor$", + "description": "Error displayed when the value of @preprocessor is not supported", + "placeholders": { + "preprocessor": { + "content": "$1" + } + } + }, "linterIssuesHelp": { "message": "Estos problemas fueron encontrados por $link$:", "description": "Help popup message for the selected CSS linter issues block on the style edit page", @@ -245,6 +278,10 @@ } } }, + "parseUsercssError": { + "message": "Stylus no pudo interpretar usercss:", + "description": "The error message to show when stylus failed to parse usercss" + }, "searchStyles": { "message": "Buscar contenidos", "description": "Label for the search filter textbox on the Manage styles page" @@ -257,6 +294,10 @@ "message": "Comprobar de nuevo, ¡no he editado ningún estilo!", "description": "Label for the button to apply all detected updates" }, + "liveReloadLabel": { + "message": "Recargar sobre la marcha", + "description": "The label of live-reload feature" + }, "unreachableFileHint": { "message": "Stylus puede acceder a URL file:// solo si activa la casilla correspondiente para la extensión Stylus en la página chrome://extensions.", "description": "Note in the toolbar popup for file:// URLs" @@ -329,6 +370,10 @@ "message": "La expresión regular proporcionada no es válida.", "description": "Validation message for a bad regexp in a style" }, + "license": { + "message": "Licencia", + "description": "Label for the license" + }, "optionsHeading": { "message": "Opciones", "description": "Heading for options section on manage page." @@ -371,6 +416,10 @@ "message": "no válidos omitidos", "description": "Text after the number of styles skipped due to being invalid (not a Stylus/Stylish backup file probably) in the report shown after importing styles" }, + "optionsAdvancedNewStyleAsUsercss": { + "message": "Escribir nuevo estilo como usercss", + "description": "" + }, "genericResetLabel": { "message": "Restablecer", "description": "Used in various parts of UI to indicate that something may be reset to its original state" @@ -400,6 +449,10 @@ "message": "Use la sintaxis /re/ para búsquedas con expresiones regulares", "description": "Label after the search input field in the editor shown on Ctrl-F" }, + "popupBordersTooltip": { + "message": "Útil para temas oscuros en el nuevo Chrome pues ya no pinta los bordes laterales", + "description": "" + }, "updateCheckManualUpdateHint": { "message": "Forzar una actualización sobrescribirá cualquier edición local.", "description": "Additional text displayed when an update check skipped updating the style to avoid losing local modifications" @@ -412,6 +465,10 @@ "message": "Descarta contenidos del estilo actual y los sobrescribe con el estilo importado", "description": "Label for the button to import and overwrite current style" }, + "installUpdateFromLabel": { + "message": "Buscar actualizaciones", + "description": "Label for the checkbox to save current URL for update check" + }, "cm_resizeGripHint": { "message": "Doble-clic para maximizar/restaurar la altura", "description": "Tooltip for the resize grip in style editor" @@ -545,6 +602,10 @@ "message": "Desactivar todos los estilos", "description": "Label for the checkbox that turns all enabled styles off." }, + "appliesLineWidgetLabel": { + "message": "Muestra la información 'Se aplica a'", + "description": "Label for the checkbox to display applies-to information in the single editor" + }, "updateCheckSkippedMaybeLocallyEdited": { "message": "Este estilo podría haberse editado localmente.", "description": "Text that displays when an update check skipped updating the style to avoid losing possible local modifications" @@ -585,6 +646,21 @@ "message": "Stylus usa un servicio externo https://www.google.com/s2/favicons", "description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page" }, + "styleInstallOverwrite": { + "message": "'$stylename$' ya está instalado ¿Sobrescribirlo?\nVersión: $oldVersion$ -> $newVersion$", + "description": "Confirmation when re-installing a style", + "placeholders": { + "stylename": { + "content": "$1" + }, + "newVersion": { + "content": "$3" + }, + "oldVersion": { + "content": "$2" + } + } + }, "updateCheckSkippedLocallyEdited": { "message": "Este estilo se editó localmente.", "description": "Text that displays when an update check skipped updating the style to avoid losing local modifications" @@ -593,6 +669,10 @@ "message": "Vea una lista completa de reglas", "description": "Stylelint or CSSLint rules label added immediately before a link" }, + "styleUpdateDiscardChanges": { + "message": "El estilo se cambió fuera del editor. ¿Desea recagar el estilo?", + "description": "Confirmation to update the style in the editor" + }, "optionsResetButton": { "message": "Restablecer opciones", "description": "" @@ -601,6 +681,10 @@ "message": "Código", "description": "Label for the code for a section" }, + "externalUsercssDocument": { + "message": "Documentación para Usercss", + "description": "Label for the external link to usercss documentation" + }, "optionsAdvancedContextDelete": { "message": "Añadir 'Eliminar' al menú contextual del editor", "description": "" @@ -614,6 +698,10 @@ } } }, + "configureStyle": { + "message": "Configurar", + "description": "Label for the button to configure userstyle" + }, "importReportLegendUpdatedBoth": { "message": "actualizarón tanto meta información como código", "description": "Text after the number of styles updated entirely in the report shown after importing styles" @@ -642,6 +730,32 @@ "message": "Autocompletar al escribir", "description": "Label for the checkbox in the style editor." }, + "linterCSSLintIncompatible": { + "message": "CSSLint no soporta el 'preprocessor' $preprocessorname$", + "description": "The label to display when the preprocessor isn't compatible with CSSLint", + "placeholders": { + "preprocessorname": { + "content": "$1" + } + } + }, + "styleMetaErrorSelectValueMismatch": { + "message": "@select no válido: El valor no existe en la lista", + "description": "Error displayed when the value of @select is invalid" + }, + "styleMetaErrorColor": { + "message": "$color$ no es un color válido", + "description": "Error displayed when the value of @var color is invalid", + "placeholders": { + "color": { + "content": "$1" + } + } + }, + "externalFeedback": { + "message": "Comentarios", + "description": "Label for the external link to send feedback for the style" + }, "manageOnlyDisabled": { "message": "Sólo estilos deshabilitados", "description": "Checkbox to show only disabled styles" @@ -662,6 +776,10 @@ "message": "Stylus no funciona en páginas como esta.", "description": "Note in the toolbar pop-up when on a URL Stylus can't affect" }, + "popupBorders": { + "message": "Añadir bordes blancos en los laterales", + "description": "" + }, "manageOnlyUpdates": { "message": "Sólo con actualizaciones o problemas", "description": "Checkbox to show only styles that have updates after check-all-styles-for-updates was performed" @@ -670,6 +788,14 @@ "message": "Añadir estilo", "description": "Title of the page for adding styles" }, + "externalLink": { + "message": "Enlace externo", + "description": "Label for external links" + }, + "externalHomepage": { + "message": "Página principal", + "description": "Label for the external link to style's homepage" + }, "importReplaceLabel": { "message": "Sobrescribir estilo", "description": "Label for the button to import and overwrite current style" @@ -686,6 +812,10 @@ "message": "Avanzadas", "description": "" }, + "alphaChannel": { + "message": "Opacidad", + "description": "Label of color's opacity" + }, "importAppendTooltip": { "message": "Agrega el estilo importado al estilo actual", "description": "Tooltip for the button to import a style and append to the existing sections" @@ -742,6 +872,15 @@ "message": "Distintivo en el icono de barra de herramientas", "description": "" }, + "installUpdateFrom": { + "message": "Actualmente el estilo se actualiza desde $url$", + "description": "Label to describe where the style gets update", + "placeholders": { + "url": { + "content": "$1" + } + } + }, "importReportLegendIdentical": { "message": "idénticos omitidos", "description": "Text after the number of styles skipped due to being identical to the already installed ones in the report shown after importing styles" @@ -750,10 +889,31 @@ "message": "Anchura del diálogo emergente (en píxeles)", "description": "" }, + "cm_autoCloseBrackets": { + "message": "Cerrar automáticamente corchetes y comillas", + "description": "Label for the checkbox in the style editor." + }, + "installButtonReinstall": { + "message": "Reinstalar", + "description": "Label for reinstall button" + }, "linterInvalidConfigError": { "message": "No se guardó debido a estos ajustes de configuración no válidos:", "description": "Invalid linter config will show a message followed by a list of invalid entries" }, + "styleMissingMeta": { + "message": "Metadatos @$key$ ausentes", + "description": "Error displayed when a mandatory metadata is missing", + "placeholders": { + "key": { + "content": "$1" + } + } + }, + "appliesLineWidgetWarning": { + "message": "No funciona con CSS minificado", + "description": "A warning that applies-to information won't show properly with minified CSS" + }, "undo": { "message": "Deshacer", "description": "Button label" @@ -762,6 +922,14 @@ "message": "Mapa de teclado", "description": "Label for the drop-down list controlling the keymap for the style editor." }, + "externalSupport": { + "message": "Asistencia", + "description": "Label for the external link to style's support site" + }, + "confirmSave": { + "message": "Guardar", + "description": "'Save' button in a confirm dialog" + }, "manageNewUI": { "message": "Nuevo diseño de interfaz de gestión", "description": "Label for the checkbox that toggles the new UI on manage page" @@ -782,6 +950,10 @@ "message": "Reemplazar con", "description": "Label before the replace-with input field in the editor shown on Ctrl-H etc." }, + "liveReloadError": { + "message": "Ocurrió un error al vigilar el fichero", + "description": "The label of live-reload error" + }, "deleteStyleLabel": { "message": "Eliminar", "description": "Label for the button to delete a style" @@ -790,6 +962,15 @@ "message": "Instalar actualización (se sobrescribirán las ediciones locales)", "description": "Additional text displayed when an update check skipped updating the style to avoid losing local modifications" }, + "styleInstallFailed": { + "message": "¡No se pudo instalar el estilo de usuario!\n$error$", + "description": "Warning when installation failed", + "placeholders": { + "error": { + "content": "$1" + } + } + }, "optionsAdvancedExposeIframes": { "message": "Exponer marcos integrados (iframes) vía HTML[stylus-iframe]", "description": "" @@ -802,6 +983,10 @@ "message": "Para desactivar las búsquedas automáticas de actualizaciones, establezca el intervalo a 0", "description": "" }, + "installButtonUpdate": { + "message": "Actualizar", + "description": "Label for update button" + }, "backupButtons": { "message": "Copia de seguridad", "description": "Heading for backup" @@ -814,6 +999,14 @@ "message": "Editar", "description": "Label for the button to go to the edit style page" }, + "installButtonInstalled": { + "message": "Instalado", + "description": "Text displayed when the style is successfully installed" + }, + "author": { + "message": "Autor", + "description": "Label for the style author" + }, "cm_theme": { "message": "Temas", "description": "Label for the style editor's CSS theme." @@ -822,6 +1015,10 @@ "message": "Abrir editor en una nueva ventana", "description": "Label for the checkbox controlling 'edit' action behavior in the popup." }, + "appliesRemoveError": { + "message": "No se puede eliminar la última entrada 'se aplica a'", + "description": "Error displayed when the last 'applies' is going to be removed" + }, "backupMessage": { "message": "Seleccione un fichero o arrástrelo y suéltelo en esta página.", "description": "Message for backup" @@ -841,5 +1038,9 @@ "description": { "message": "Rediseñe la web con Stylus, un administrador de estilos de usuario. Stylus le permite instalar fácilmente temas y coberturas para muchos sitios populares.", "description": "Extension description" + }, + "confirmClose": { + "message": "Cerrar", + "description": "'Close' button in a confirm dialog" } } \ No newline at end of file diff --git a/_locales/et/messages.json b/_locales/et/messages.json index 60e10258..5bc3eb6e 100644 --- a/_locales/et/messages.json +++ b/_locales/et/messages.json @@ -31,6 +31,14 @@ "message": "Ekspordi", "description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" }, + "installButton": { + "message": "Installi", + "description": "Label for install button" + }, + "styleMetaErrorCheckbox": { + "message": "Vigane @var märkeruut: väärtus peab olema 0 või 1", + "description": "Error displayed when the value of @var checkbox is invalid" + }, "linterJSONError": { "message": "Vigane JSON-formaat", "description": "Setting linter config with invalid JSON" @@ -52,7 +60,7 @@ "description": "Label for the button to enable a style" }, "styleMissingName": { - "message": "Sisesta nimi.", + "message": "Sisesta nimi", "description": "Error displayed when user saves without providing a name" }, "genericHistoryLabel": { @@ -119,6 +127,10 @@ "message": "Tee halliks", "description": "Label for the checkbox that toggles grayed out mode of applies-to favicons in the new UI on manage page" }, + "versionInvalidOlder": { + "message": "Versioon on installitud stiilist vanem.", + "description": "Displayed when the version of style is older than the installed one" + }, "confirmYes": { "message": "Jah", "description": "'Yes' button in a confirm dialog" @@ -171,6 +183,10 @@ "message": "Kustuta", "description": "" }, + "confirmDefault": { + "message": "Kasuta vaikesätet", + "description": "'Set to default' button in a confirm dialog" + }, "confirmCancel": { "message": "Tühista", "description": "" @@ -325,6 +341,10 @@ "message": "Regulaaravaldis on sobimatu.", "description": "Validation message for a bad regexp in a style" }, + "license": { + "message": "Litsents", + "description": "Label for the license" + }, "optionsHeading": { "message": "Valikud", "description": "Heading for options section on manage page." @@ -408,6 +428,10 @@ "message": "Tühista praeguse stiili sisu ja kirjuta see üle imporditud stiiliga", "description": "Label for the button to import and overwrite current style" }, + "installUpdateFromLabel": { + "message": "Kontrolli uuendusi", + "description": "Label for the checkbox to save current URL for update check" + }, "cm_resizeGripHint": { "message": "Topeltklõpsa, et maksimeerida/taastada kõrgus", "description": "Tooltip for the resize grip in style editor" @@ -541,6 +565,10 @@ "message": "Lülita kõik stiilid välja", "description": "Label for the checkbox that turns all enabled styles off." }, + "appliesLineWidgetLabel": { + "message": "Näita 'Rakendub' teavet", + "description": "Label for the checkbox to display applies-to information in the single editor" + }, "updateCheckSkippedMaybeLocallyEdited": { "message": "Seda stiili võib olla kohalikult muudetud.", "description": "Text that displays when an update check skipped updating the style to avoid losing possible local modifications" @@ -589,6 +617,10 @@ "message": "Vaata reeglite täielikku loendit", "description": "Stylelint or CSSLint rules label added immediately before a link" }, + "styleUpdateDiscardChanges": { + "message": "Stiili on redaktoriväliselt muudetud. Kas soovid stiili uuesti laadida?", + "description": "Confirmation to update the style in the editor" + }, "optionsResetButton": { "message": "Lähtesta valikud", "description": "" @@ -638,6 +670,19 @@ "message": "Automaattäide kirjutamisel", "description": "Label for the checkbox in the style editor." }, + "styleMetaErrorColor": { + "message": "$color$ ei ole sobiv värv", + "description": "Error displayed when the value of @var color is invalid", + "placeholders": { + "color": { + "content": "$1" + } + } + }, + "externalFeedback": { + "message": "Tagasiside", + "description": "Label for the external link to send feedback for the style" + }, "manageOnlyDisabled": { "message": "Ainult keelatud stiilid", "description": "Checkbox to show only disabled styles" @@ -666,6 +711,14 @@ "message": "Lisa stiil", "description": "Title of the page for adding styles" }, + "externalLink": { + "message": "Väline link", + "description": "Label for external links" + }, + "externalHomepage": { + "message": "Koduleht", + "description": "Label for the external link to style's homepage" + }, "importReplaceLabel": { "message": "Kirjuta stiil üle", "description": "Label for the button to import and overwrite current style" @@ -682,6 +735,10 @@ "message": "Täpsem", "description": "" }, + "alphaChannel": { + "message": "Läbipaistmatus", + "description": "Label of color's opacity" + }, "importAppendTooltip": { "message": "Lisa imporditud stiil praegusele stiilile", "description": "Tooltip for the button to import a style and append to the existing sections" @@ -738,6 +795,15 @@ "message": "Number tööriistaribaikoonil", "description": "" }, + "installUpdateFrom": { + "message": "Praegu uuendatakse stiili aadressilt $url$", + "description": "Label to describe where the style gets update", + "placeholders": { + "url": { + "content": "$1" + } + } + }, "importReportLegendIdentical": { "message": "identset vahele jäetud", "description": "Text after the number of styles skipped due to being identical to the already installed ones in the report shown after importing styles" @@ -762,6 +828,14 @@ "message": "Klahvimäärangud", "description": "Label for the drop-down list controlling the keymap for the style editor." }, + "externalSupport": { + "message": "Kasutajatugi", + "description": "Label for the external link to style's support site" + }, + "confirmSave": { + "message": "Salvesta", + "description": "'Save' button in a confirm dialog" + }, "manageNewUI": { "message": "Uus haldusliidese välimus", "description": "Label for the checkbox that toggles the new UI on manage page" @@ -790,6 +864,15 @@ "message": "Installi uuendus (kohalikud muutused kirjutatakse üle)", "description": "Additional text displayed when an update check skipped updating the style to avoid losing local modifications" }, + "styleInstallFailed": { + "message": "Kasutaja stiili installimine ebaõnnestus!\n$error$", + "description": "Warning when installation failed", + "placeholders": { + "error": { + "content": "$1" + } + } + }, "optionsAdvancedExposeIframes": { "message": "Paljasta iframe-id HTML [stylus-iframe] kaudu", "description": "" @@ -802,6 +885,10 @@ "message": "Et keelata automaatsed kasutajastiilide uuenduste kontrollid, sea intervalliks 0", "description": "" }, + "installButtonUpdate": { + "message": "Uuenda", + "description": "Label for update button" + }, "backupButtons": { "message": "Varunda", "description": "Heading for backup" @@ -814,6 +901,10 @@ "message": "Muuda", "description": "Label for the button to go to the edit style page" }, + "author": { + "message": "Autor", + "description": "Label for the style author" + }, "cm_theme": { "message": "Teema", "description": "Label for the style editor's CSS theme." @@ -841,5 +932,9 @@ "description": { "message": "Disaini veeb ümber kasutajastiilide halduri Stylus'iga. Stylus võimaldab sul lihtsalt installida teemasid ja välimusi mitmetele populaarsetele saitidele.", "description": "Extension description" + }, + "confirmClose": { + "message": "Sulge", + "description": "'Close' button in a confirm dialog" } } \ No newline at end of file diff --git a/_locales/fi/messages.json b/_locales/fi/messages.json index 3a751e01..2b09680c 100644 --- a/_locales/fi/messages.json +++ b/_locales/fi/messages.json @@ -8,7 +8,7 @@ "description": "Label for the button to enable a style" }, "styleMissingName": { - "message": "Syötä nimi.", + "message": "Syötä nimi", "description": "Error displayed when user saves without providing a name" }, "appliesDomainOption": { diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index 892244cf..a0d3ec2b 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -20,7 +20,7 @@ "description": "Label for the button to enable a style" }, "styleMissingName": { - "message": "Veuillez saisir un nom.", + "message": "Veuillez saisir un nom", "description": "Error displayed when user saves without providing a name" }, "appliesDomainOption": { diff --git a/_locales/hu/messages.json b/_locales/hu/messages.json index 9cba268a..9a971b99 100644 --- a/_locales/hu/messages.json +++ b/_locales/hu/messages.json @@ -7,6 +7,10 @@ "message": "alapértelmezett", "description": "Default CodeMirror CSS theme option on the edit style page" }, + "styleRegexpTestTitle": { + "message": "Illeszkedő megnyitott fülek megjelenítése (kattints az URL-re az arra a fülre való ugráshoz)", + "description": "RegExp test report: title of the report" + }, "bckpInstStyles": { "message": "Stílusok exportálása", "description": "" @@ -32,7 +36,7 @@ "description": "Label for the button to enable a style" }, "styleMissingName": { - "message": "Írj be egy nevet!", + "message": "Írj be egy nevet", "description": "Error displayed when user saves without providing a name" }, "appliesDomainOption": { @@ -43,6 +47,10 @@ "message": "Frissítések ellenőrzése", "description": "Label for the button to check a single style for an update" }, + "styleRegexpInvalidExplanation": { + "message": "Egyes „regexp()” szabályokat nem lehetett lefordítani.", + "description": "" + }, "importAppendLabel": { "message": "Hozzáadás stílushoz", "description": "Label for the button to import a style and append to the existing sections" @@ -59,6 +67,10 @@ "message": "Mozilla formátumú kód beillesztése", "description": "Prompt in the dialog displayed after clicking 'Import from Mozilla format' button" }, + "dragDropMessage": { + "message": "Ejtsd a biztonsági másolat fájlt bárhova erre az oldalra az importáláshoz!", + "description": "Drag'n'drop message" + }, "helpAlt": { "message": "Segítség", "description": "Alternate text for help buttons" @@ -67,6 +79,10 @@ "message": "Keresés", "description": "Label before the search input field in the editor shown on Ctrl-F" }, + "manageFaviconsGray": { + "message": "Szürke mód", + "description": "Label for the checkbox that toggles grayed out mode of applies-to favicons in the new UI on manage page" + }, "confirmYes": { "message": "Igen", "description": "'Yes' button in a confirm dialog" @@ -99,6 +115,10 @@ "message": "Mentés", "description": "Label for save button for style editing" }, + "confirmDelete": { + "message": "Törlés", + "description": "" + }, "confirmCancel": { "message": "Mégsem", "description": "" @@ -127,6 +147,14 @@ "message": "Reguláris kifejezésekre (regexp) illeszkedő URL-ek", "description": "Option to make the style apply to the entered string as a regular expression" }, + "optionsAdvancedExposeIframesNote": { + "message": "Engedélyezi az olyan iframe-specifikus CSS írását, mint a „html[stylus-iframe] h1 { display:none }”", + "description": "" + }, + "importReportLegendUpdatedCode": { + "message": "frissített kód", + "description": "Text after the number of styles with updated code (meta info is unchanged) in the report shown after importing styles" + }, "styleInstall": { "message": "Telepíted a(z) „$stylename$” nevű stílust a Stylusba?", "description": "Confirmation when installing a style", @@ -152,6 +180,10 @@ "message": "Tartalom keresése", "description": "Label for the search filter textbox on the Manage styles page" }, + "unreachableFileHint": { + "message": "A Stylus csak akkor képes hozzáférni a file:// URL-ekhez, ha engedélyezed az erre vonatkozó beállítást a Stylus kiegészítőre a chrome://extensions oldalon.", + "description": "Note in the toolbar popup for file:// URLs" + }, "disableStyleLabel": { "message": "Letiltás", "description": "Label for the button to disable a style" @@ -160,6 +192,10 @@ "message": "A jelenlegi oldalon aktív stílusok száma", "description": "Label for the checkbox controlling toolbar badge text." }, + "manageFavicons": { + "message": "Faviconok az alkalmazási oszlopban", + "description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page" + }, "menuShowBadge": { "message": "Aktív stílusok számlálójának mutatása", "description": "Label (must be very short) for the checkbox in the toolbar button context menu controlling toolbar badge text." @@ -180,6 +216,10 @@ "message": "Importálás", "description": "Label for the button to import a style ('edit' page) or all styles ('manage' page)" }, + "shortcuts": { + "message": "Gyorsbillentyűk", + "description": "Go to shortcut configuration" + }, "updateCheckFailServerUnreachable": { "message": "Sikertelen frissítés: nem érhető el a szerver.", "description": "Text that displays when an update check failed because the update server is unreachable" @@ -192,10 +232,22 @@ "message": "Minden módosítás alkalmazása", "description": "Label for the button to apply all detected updates" }, + "optionsReset": { + "message": "Beállítások visszaállítása alapértelmezett értékekre.", + "description": "" + }, + "optionsCustomizeUpdate": { + "message": "Frissítések", + "description": "" + }, "deleteStyleConfirm": { "message": "Biztos, hogy törölni akarod ezt a stílust?", "description": "Confirmation before deleting a style" }, + "optionsCustomizePopup": { + "message": "Felugró", + "description": "" + }, "styleBadRegexp": { "message": "Érvénytelen regexp.", "description": "Validation message for a bad regexp in a style" @@ -251,6 +303,14 @@ "message": "Használd a /re/ szintaxist a regexp kereséshez", "description": "Label after the search input field in the editor shown on Ctrl-F" }, + "updateCheckManualUpdateHint": { + "message": "Egy frissítés erőltetése minden helyi szerkesztést felül fog írni.", + "description": "Additional text displayed when an update check skipped updating the style to avoid losing local modifications" + }, + "toggleStyle": { + "message": "Stílus be-/kikapcsolása", + "description": "Label for the checkbox to enable/disable a style" + }, "importReplaceTooltip": { "message": "A jelenlegi stílus tartalmának elvetése és annak felülírása az importált stílussal", "description": "Label for the button to import and overwrite current style" @@ -275,6 +335,10 @@ "message": "Eltávolítás", "description": "Label for the button to remove an 'applies' entry" }, + "updatesCurrentlyInstalled": { + "message": "Frissítve:", + "description": "Text that displays when an update is installed on options page. Followed by the number of currently installed updates." + }, "styleToMozillaFormatTitle": { "message": "Mozilla formátumú stílus", "description": "Title of the popup with the style code in Mozilla format, shown after pressing the Export button on Edit style page" @@ -287,6 +351,10 @@ "message": "Csere", "description": "Label before the replace input field in the editor shown on Ctrl-H" }, + "styleRegexpTestNone": { + "message": "Nincs illeszkedő fül", + "description": "RegExp test report: label for expressions that didn't match any tabs" + }, "appliesLabel": { "message": "Amire érvényesül", "description": "Label for 'applies to' fields on the edit/add screen" @@ -324,6 +392,10 @@ "message": "Mozilla formátum", "description": "Heading for the section with buttons to import/export Mozilla format of the style" }, + "styleRegexpPartialExplanation": { + "message": "Ez a stílus olyan részlegesen illeszkedő reguláris kifejezéseket használ, melyek sértik aCSS4@dokumentumspecifikációt, mely szerint egy teljes URL-illeszkedésre van szükség. Az érintett CSS-szekciók nem kerültek alkalmazásra az oldalon. Ez a stílus valószínűleg a Stylish Chrome-kiegészítőben lett létrehozva, amely helytelenül ellenőrzi a „regexp()” szabályokat az első verziótól fogva (ismert hiba).", + "description": "" + }, "sectionRemove": { "message": "Szekció eltávolítása", "description": "Label for the button to remove a section" @@ -332,6 +404,10 @@ "message": "Az összes stílus kikapcsolása", "description": "Label for the checkbox that turns all enabled styles off." }, + "updateCheckSkippedMaybeLocallyEdited": { + "message": "Lehet, hogy ezt a stílust helyileg szerkesztették", + "description": "Text that displays when an update check skipped updating the style to avoid losing possible local modifications" + }, "undoGlobal": { "message": "VIsszavonás mindegyik szekcióban", "description": "CSS-beautify global Undo button label" @@ -344,6 +420,26 @@ "message": "Ellenőrzés...", "description": "Text to display when checking a style for an update" }, + "styleRegexpTestFull": { + "message": "Illeszkedő fülek", + "description": "RegExp test report: label for the fully matching expressions" + }, + "manageMaxTargets": { + "message": "Megjelenítendő célok száma", + "description": "Label for the numeric input box to limit max number of applies-to targets in the new UI on manage page" + }, + "manageFaviconsHelp": { + "message": "A Stylus egy külső szolgáltatást használ (https://www.google.com/s2/favicons)", + "description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page" + }, + "updateCheckSkippedLocallyEdited": { + "message": "Ez a stílus helyileg lett szerkesztve.", + "description": "Text that displays when an update check skipped updating the style to avoid losing local modifications" + }, + "optionsResetButton": { + "message": "Beállítások visszaállítása alapra", + "description": "" + }, "sectionCode": { "message": "Kód", "description": "Label for the code for a section" @@ -360,6 +456,18 @@ "message": "Stílus szerkesztése", "description": "Title of the page for editing styles" }, + "stylusUnavailableForURLdetails": { + "message": "Biztonsági okokból a böngésző megtiltja, hogy a kiegészítők változtatásokat tegyenek a beépített oldalain (pl. chrome://verzió, a Chrome 61 alapértelmezett „új lap” oldala, about:addons és így tovább) valamint más kiterjesztések oldalain. Ezen kívül mindegyik böngésző korlátozza a saját kiegészítőgalériájának elérését (pl. Chrome webáruház vagy a Mozilla kiegészítők oldala)", + "description": "Sub-note in the toolbar pop-up when on a URL Stylus can't affect" + }, + "updateAllCheckSucceededSomeEdited": { + "message": "Egyes frissíthető stílusok nem lettek ellenőrizve, nehogy elvesszenek a helyi változtatások. A frissítéseket lehet erőltetni egyéni ellenőrzéssel külön-külön vagy az összes stílus ismételt ellenőrzésével (ez felülírja a helyi módosításokat).", + "description": "Text that displays when an update all check completed and no updates are available" + }, + "stylusUnavailableForURL": { + "message": "A Stylus nem működik az ilyen oldalakon.", + "description": "Note in the toolbar pop-up when on a URL Stylus can't affect" + }, "addStyleTitle": { "message": "Stílus hozzáadása", "description": "Title of the page for adding styles" @@ -372,6 +480,10 @@ "message": "Hiba történt a Stylus adatbázisának használatakor. Szeretnéd meglátogatni a lehetséges megoldásokat tartalmazó weboldalt?", "description": "Prompt when a DB error is encountered" }, + "optionsAdvanced": { + "message": "Haladó", + "description": "" + }, "importAppendTooltip": { "message": "Az importált stílus hozzáadása a jelenlegi stílushoz", "description": "Tooltip for the button to import a style and append to the existing sections" @@ -392,6 +504,10 @@ "message": "Az összes cseréje", "description": "Label before the replace input field in the editor shown on 'replaceAll' hotkey" }, + "importReportUnchanged": { + "message": "Semmi sem változott.", + "description": "Message in the report shown after importing styles" + }, "optionsActions": { "message": "Műveletek", "description": "" @@ -400,6 +516,10 @@ "message": "Sorra (vagy sor:oszlopra) ugrás", "description": "Go to line or line:column on Ctrl-G in style code editor" }, + "manageOnlyLocalTooltip": { + "message": "(a stílusok nem egy userstyles.org oldalon lettek telepítve)", + "description": "Tooltip for the checkbox to show only locally created styles i.e. non-updatable" + }, "checkAllUpdates": { "message": "Az összes stílus frissítésének ellenőrzése", "description": "Label for the button to check all styles for updates" @@ -408,6 +528,10 @@ "message": "A beállítások felülete", "description": "Go to Options UI" }, + "importReportLegendIdentical": { + "message": "kihagyott egyezések", + "description": "Text after the number of styles skipped due to being identical to the already installed ones in the report shown after importing styles" + }, "optionsPopupWidth": { "message": "Felugró ablak szélessége (pixelben)", "description": "" @@ -424,6 +548,18 @@ "message": "Billentyűműveletek", "description": "Label for the drop-down list controlling the keymap for the style editor." }, + "manageNewUI": { + "message": "Az új kezelési felületkiosztás", + "description": "Label for the checkbox that toggles the new UI on manage page" + }, + "importReportUndoneTitle": { + "message": "Az importálás vissza lett vonva", + "description": "Title of the message box shown after undoing the import of styles" + }, + "genericDisabledLabel": { + "message": "Letiltott", + "description": "Used in various lists/options to indicate that something is disabled" + }, "cm_indentWithTabs": { "message": "Tabulátorok használata intelligens behúzásra", "description": "Label for the checkbox controlling tabs with smart indentation option for the style editor." @@ -436,6 +572,10 @@ "message": "Törlés", "description": "Label for the button to delete a style" }, + "optionsAdvancedExposeIframes": { + "message": "iframe-ek kitevése HTML[stylus-iframe]-en keresztül", + "description": "" + }, "addStyleLabel": { "message": "Új stílus írása", "description": "Label for the button to go to the add style page" diff --git a/_locales/it/messages.json b/_locales/it/messages.json index 50b41173..48d316ee 100644 --- a/_locales/it/messages.json +++ b/_locales/it/messages.json @@ -36,7 +36,7 @@ "description": "Label for the button to enable a style" }, "styleMissingName": { - "message": "Inserisci un nome.", + "message": "Inserisci un nome", "description": "Error displayed when user saves without providing a name" }, "genericHistoryLabel": { diff --git a/_locales/ja/messages.json b/_locales/ja/messages.json index 405f9835..20bda3f9 100644 --- a/_locales/ja/messages.json +++ b/_locales/ja/messages.json @@ -31,6 +31,14 @@ "message": "エクスポート", "description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" }, + "installButton": { + "message": "インストール", + "description": "Label for install button" + }, + "styleMetaErrorCheckbox": { + "message": "不正な @var チェックボックス: 値は0か1である必要があります", + "description": "Error displayed when the value of @var checkbox is invalid" + }, "linterJSONError": { "message": "不正なJSONフォーマットです", "description": "Setting linter config with invalid JSON" @@ -52,7 +60,7 @@ "description": "Label for the button to enable a style" }, "styleMissingName": { - "message": "名前を入力してください。", + "message": "名前を入力してください", "description": "Error displayed when user saves without providing a name" }, "genericHistoryLabel": { @@ -83,6 +91,10 @@ "message": "暗いブラウザのテーマ", "description": "" }, + "styleFromMozillaFormatError": { + "message": "Mozilla形式のインポートに失敗しました", + "description": "Label for the import error" + }, "importAppendLabel": { "message": "スタイルに追加", "description": "Label for the button to import a style and append to the existing sections" @@ -119,6 +131,10 @@ "message": "グレー表示", "description": "Label for the checkbox that toggles grayed out mode of applies-to favicons in the new UI on manage page" }, + "versionInvalidOlder": { + "message": "インストール済みのスタイルより古いバージョンです。", + "description": "Displayed when the version of style is older than the installed one" + }, "confirmYes": { "message": "はい", "description": "'Yes' button in a confirm dialog" @@ -171,10 +187,18 @@ "message": "削除", "description": "" }, + "confirmDefault": { + "message": "デフォルトを使用", + "description": "'Set to default' button in a confirm dialog" + }, "confirmCancel": { "message": "キャンセル", "description": "" }, + "cm_autoCloseBracketsTooltip": { + "message": "()[]{}''\"\" の開き括弧/引用符の入力時に、自動的に対応する閉じ括弧/引用符を追加します", + "description": "Label for the checkbox in the style editor." + }, "retrieveBckp": { "message": "スタイルをインポート", "description": "" @@ -200,7 +224,7 @@ "description": "Option to make the style apply to the entered string as a regular expression" }, "optionsAdvancedExposeIframesNote": { - "message": "'html[stylus-iframe] h1 { display:none }' のようにiframe用のCSSを書くことができるようになります。", + "message": "「html[stylus-iframe] h1 { display:none }」のようにiframe用のCSSを書くことができるようになります。", "description": "" }, "importReportLegendUpdatedCode": { @@ -232,6 +256,15 @@ "message": "ローカル作成スタイルのみ", "description": "Checkbox to show only locally created styles i.e. non-updatable" }, + "styleMetaErrorPreprocessor": { + "message": "未サポートの @preprocessor: $preprocessor$", + "description": "Error displayed when the value of @preprocessor is not supported", + "placeholders": { + "preprocessor": { + "content": "$1" + } + } + }, "linterIssuesHelp": { "message": "$link$ によってこれらの問題点が見つかりました :", "description": "Help popup message for the selected CSS linter issues block on the style edit page", @@ -241,6 +274,10 @@ } } }, + "parseUsercssError": { + "message": "Stylusはusercssの解析に失敗しました: ", + "description": "The error message to show when stylus failed to parse usercss" + }, "searchStyles": { "message": "コンテンツの検索", "description": "Label for the search filter textbox on the Manage styles page" @@ -253,6 +290,10 @@ "message": "全スタイルを再チェックします。私はスタイルを編集していません!", "description": "Label for the button to apply all detected updates" }, + "liveReloadLabel": { + "message": "自動リロード", + "description": "The label of live-reload feature" + }, "unreachableFileHint": { "message": "chrome://extensions ページでStylus拡張機能の当該チェックボックスを有効にした場合にのみ、Stylusは file:// URLにアクセスできます。", "description": "Note in the toolbar popup for file:// URLs" @@ -325,6 +366,10 @@ "message": "正規表現は無効です。", "description": "Validation message for a bad regexp in a style" }, + "license": { + "message": "ライセンス", + "description": "Label for the license" + }, "optionsHeading": { "message": "オプション", "description": "Heading for options section on manage page." @@ -367,6 +412,10 @@ "message": "件の無効なスタイルをスキップしました", "description": "Text after the number of styles skipped due to being invalid (not a Stylus/Stylish backup file probably) in the report shown after importing styles" }, + "optionsAdvancedNewStyleAsUsercss": { + "message": "新しいスタイルを usercss として作成します", + "description": "" + }, "genericResetLabel": { "message": "リセット", "description": "Used in various parts of UI to indicate that something may be reset to its original state" @@ -396,6 +445,10 @@ "message": "正規表現検索に /re/ シンタックスを使用する", "description": "Label after the search input field in the editor shown on Ctrl-F" }, + "popupBordersTooltip": { + "message": "新しいChromeが横の境界線を塗りつぶさなくなったため、暗いテーマで有効です", + "description": "" + }, "updateCheckManualUpdateHint": { "message": "更新を強制すると、すべてのローカルでの編集内容が上書きされます。", "description": "Additional text displayed when an update check skipped updating the style to avoid losing local modifications" @@ -408,6 +461,10 @@ "message": "現在のスタイルの内容を破棄し、インポートされたスタイルで上書きする", "description": "Label for the button to import and overwrite current style" }, + "installUpdateFromLabel": { + "message": "更新をチェック", + "description": "Label for the checkbox to save current URL for update check" + }, "cm_resizeGripHint": { "message": "ダブルクリックで高さを最大化/元に戻す", "description": "Tooltip for the resize grip in style editor" @@ -425,7 +482,7 @@ "description": "Text displayed when no styles are installed for the current site" }, "appliesDisplayTruncatedSuffix": { - "message": "サイトを追加", + "message": "さらに表示", "description": "Text added to appliesDisplay when there are more sites for the style than are displayed" }, "appliesRemove": { @@ -537,6 +594,10 @@ "message": "すべてのスタイルをオフにする", "description": "Label for the checkbox that turns all enabled styles off." }, + "appliesLineWidgetLabel": { + "message": "「適用先」の情報を表示", + "description": "Label for the checkbox to display applies-to information in the single editor" + }, "updateCheckSkippedMaybeLocallyEdited": { "message": "このスタイルはローカルで編集されている可能性があります。", "description": "Text that displays when an update check skipped updating the style to avoid losing possible local modifications" @@ -577,6 +638,21 @@ "message": "Stylusは外部サービスを使用します https://www.google.com/s2/favicons", "description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page" }, + "styleInstallOverwrite": { + "message": "「$stylename$」はすでにインストール済みです。上書きしますか?\nバージョン: $oldVersion$ -> $newVersion$", + "description": "Confirmation when re-installing a style", + "placeholders": { + "stylename": { + "content": "$1" + }, + "newVersion": { + "content": "$3" + }, + "oldVersion": { + "content": "$2" + } + } + }, "updateCheckSkippedLocallyEdited": { "message": "このスタイルはローカルで編集されました。", "description": "Text that displays when an update check skipped updating the style to avoid losing local modifications" @@ -585,6 +661,10 @@ "message": "ルールの完全なリストを見る", "description": "Stylelint or CSSLint rules label added immediately before a link" }, + "styleUpdateDiscardChanges": { + "message": "このスタイルはエディタの外部で変更されました。このスタイルをリロードしますか?", + "description": "Confirmation to update the style in the editor" + }, "optionsResetButton": { "message": "オプションをリセット", "description": "" @@ -593,6 +673,10 @@ "message": "コード", "description": "Label for the code for a section" }, + "externalUsercssDocument": { + "message": "Usercssの文書", + "description": "Label for the external link to usercss documentation" + }, "optionsAdvancedContextDelete": { "message": "エディタのコンテキストメニューに「削除」を追加します", "description": "" @@ -606,6 +690,10 @@ } } }, + "configureStyle": { + "message": "設定", + "description": "Label for the button to configure userstyle" + }, "importReportLegendUpdatedBoth": { "message": "件のメタ情報とコードを更新しました", "description": "Text after the number of styles updated entirely in the report shown after importing styles" @@ -634,6 +722,32 @@ "message": "入力の自動補完", "description": "Label for the checkbox in the style editor." }, + "linterCSSLintIncompatible": { + "message": "CSSLintは「$preprocessorname$」プリプロセッサをサポートしていません", + "description": "The label to display when the preprocessor isn't compatible with CSSLint", + "placeholders": { + "preprocessorname": { + "content": "$1" + } + } + }, + "styleMetaErrorSelectValueMismatch": { + "message": "不正な @select: 値がリストに存在しません", + "description": "Error displayed when the value of @select is invalid" + }, + "styleMetaErrorColor": { + "message": "$color$ は有効な色ではありません", + "description": "Error displayed when the value of @var color is invalid", + "placeholders": { + "color": { + "content": "$1" + } + } + }, + "externalFeedback": { + "message": "フィードバック", + "description": "Label for the external link to send feedback for the style" + }, "manageOnlyDisabled": { "message": "無効なスタイルのみ", "description": "Checkbox to show only disabled styles" @@ -654,6 +768,10 @@ "message": "このようなページではStylusは動作しません。", "description": "Note in the toolbar pop-up when on a URL Stylus can't affect" }, + "popupBorders": { + "message": "横に白い境界線を追加する", + "description": "" + }, "manageOnlyUpdates": { "message": "更新または問題があるスタイルのみ", "description": "Checkbox to show only styles that have updates after check-all-styles-for-updates was performed" @@ -662,6 +780,14 @@ "message": "スタイルを追加", "description": "Title of the page for adding styles" }, + "externalLink": { + "message": "外部リンク", + "description": "Label for external links" + }, + "externalHomepage": { + "message": "ホームページ", + "description": "Label for the external link to style's homepage" + }, "importReplaceLabel": { "message": "スタイルを上書き", "description": "Label for the button to import and overwrite current style" @@ -678,6 +804,10 @@ "message": "上級者向け", "description": "" }, + "alphaChannel": { + "message": "透明度", + "description": "Label of color's opacity" + }, "importAppendTooltip": { "message": "インポートされたスタイルを現在のスタイルに追加する", "description": "Tooltip for the button to import a style and append to the existing sections" @@ -734,6 +864,15 @@ "message": "ツールバーアイコンのバッジ", "description": "" }, + "installUpdateFrom": { + "message": "現在、このスタイルは「$url$」から更新されます", + "description": "Label to describe where the style gets update", + "placeholders": { + "url": { + "content": "$1" + } + } + }, "importReportLegendIdentical": { "message": "件の同一のスタイルをスキップしました", "description": "Text after the number of styles skipped due to being identical to the already installed ones in the report shown after importing styles" @@ -742,6 +881,14 @@ "message": "ポップアップの幅(ピクセル単位)", "description": "" }, + "cm_autoCloseBrackets": { + "message": "括弧と引用符を自動的に閉じる", + "description": "Label for the checkbox in the style editor." + }, + "installButtonReinstall": { + "message": "再インストール", + "description": "Label for reinstall button" + }, "linterInvalidConfigError": { "message": "これらの不正な設定のために保存されませんでした :", "description": "Invalid linter config will show a message followed by a list of invalid entries" @@ -750,6 +897,19 @@ "message": "いいえ", "description": "'No' button in a confirm dialog" }, + "styleMissingMeta": { + "message": "メタデータがありません @$key$", + "description": "Error displayed when a mandatory metadata is missing", + "placeholders": { + "key": { + "content": "$1" + } + } + }, + "appliesLineWidgetWarning": { + "message": "圧縮されたCSSでは機能しません", + "description": "A warning that applies-to information won't show properly with minified CSS" + }, "undo": { "message": "元に戻す", "description": "Button label" @@ -758,6 +918,14 @@ "message": "キーマップ", "description": "Label for the drop-down list controlling the keymap for the style editor." }, + "externalSupport": { + "message": "サポート", + "description": "Label for the external link to style's support site" + }, + "confirmSave": { + "message": "保存", + "description": "'Save' button in a confirm dialog" + }, "manageNewUI": { "message": "新しい管理UIレイアウト", "description": "Label for the checkbox that toggles the new UI on manage page" @@ -778,6 +946,10 @@ "message": "次に置換", "description": "Label before the replace-with input field in the editor shown on Ctrl-H etc." }, + "liveReloadError": { + "message": "ファイルの監視中にエラーが発生しました", + "description": "The label of live-reload error" + }, "deleteStyleLabel": { "message": "削除", "description": "Label for the button to delete a style" @@ -786,6 +958,15 @@ "message": "更新をインストール(ローカルでの編集内容は上書きされます)", "description": "Additional text displayed when an update check skipped updating the style to avoid losing local modifications" }, + "styleInstallFailed": { + "message": "ユーザースタイルのインストールに失敗しました!\n$error$", + "description": "Warning when installation failed", + "placeholders": { + "error": { + "content": "$1" + } + } + }, "optionsAdvancedExposeIframes": { "message": "HTML[stylus-iframe] によってiframeへのアクセスを可能にします", "description": "" @@ -798,6 +979,10 @@ "message": "ユーザースタイルの自動更新チェックを無効にするには、間隔を0に設定します。", "description": "" }, + "installButtonUpdate": { + "message": "更新", + "description": "Label for update button" + }, "backupButtons": { "message": "バックアップ", "description": "Heading for backup" @@ -810,6 +995,14 @@ "message": "編集", "description": "Label for the button to go to the edit style page" }, + "installButtonInstalled": { + "message": "インストール済み", + "description": "Text displayed when the style is successfully installed" + }, + "author": { + "message": "作者", + "description": "Label for the style author" + }, "cm_theme": { "message": "テーマ", "description": "Label for the style editor's CSS theme." @@ -818,6 +1011,10 @@ "message": "新しいウィンドウでエディタを開く", "description": "Label for the checkbox controlling 'edit' action behavior in the popup." }, + "appliesRemoveError": { + "message": "「適用先」の最後の項目を削除することはできません", + "description": "Error displayed when the last 'applies' is going to be removed" + }, "backupMessage": { "message": "ファイルを選択するか、このページにドラッグ&ドロップします。", "description": "Message for backup" @@ -837,5 +1034,9 @@ "description": { "message": "Stylus でウェブのデザインを変更しましょう。これは、ユーザースタイルを管理するツールです。Stylus を利用すると、多くの人気サイト向けのテーマやスキンを簡単にインストールできます。", "description": "Extension description" + }, + "confirmClose": { + "message": "閉じる", + "description": "'Close' button in a confirm dialog" } } \ No newline at end of file diff --git a/_locales/nl/messages.json b/_locales/nl/messages.json index c71d4d7d..a9ce5425 100644 --- a/_locales/nl/messages.json +++ b/_locales/nl/messages.json @@ -24,7 +24,7 @@ "description": "Label for the button to enable a style" }, "styleMissingName": { - "message": "Vul een naam in.", + "message": "Vul een naam in", "description": "Error displayed when user saves without providing a name" }, "appliesDomainOption": { diff --git a/_locales/pl/messages.json b/_locales/pl/messages.json index 98cb0b6b..b220e980 100644 --- a/_locales/pl/messages.json +++ b/_locales/pl/messages.json @@ -31,6 +31,14 @@ "message": "Eksportuj", "description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" }, + "installButton": { + "message": "Zainstaluj", + "description": "Label for install button" + }, + "styleMetaErrorCheckbox": { + "message": "Nieprawidłowe pole @var: wartość musi wynosić 0 lub 1", + "description": "Error displayed when the value of @var checkbox is invalid" + }, "linterJSONError": { "message": "Nieprawidłowy format JSON", "description": "Setting linter config with invalid JSON" @@ -44,7 +52,7 @@ "description": "" }, "cm_tabSize": { - "message": "Rozmiar tabulatora", + "message": "Szerokość tabulacji", "description": "Label for the text box controlling tab size option for the style editor." }, "enableStyleLabel": { @@ -52,7 +60,7 @@ "description": "Label for the button to enable a style" }, "styleMissingName": { - "message": "Wprowadź nazwę.", + "message": "Wprowadź nazwę", "description": "Error displayed when user saves without providing a name" }, "genericHistoryLabel": { @@ -83,6 +91,10 @@ "message": "Ciemne motywy przeglądarki", "description": "" }, + "styleFromMozillaFormatError": { + "message": "Nie udało się zaimportować z formatu Mozilla", + "description": "Label for the import error" + }, "importAppendLabel": { "message": "Dołącz do stylu", "description": "Label for the button to import a style and append to the existing sections" @@ -119,6 +131,10 @@ "message": "Wyszarzone", "description": "Label for the checkbox that toggles grayed out mode of applies-to favicons in the new UI on manage page" }, + "versionInvalidOlder": { + "message": "Wersja jest starsza niż zainstalowany styl.", + "description": "Displayed when the version of style is older than the installed one" + }, "confirmYes": { "message": "Tak", "description": "'Yes' button in a confirm dialog" @@ -171,10 +187,18 @@ "message": "Usuń", "description": "" }, + "confirmDefault": { + "message": "Użyj domyślnych", + "description": "'Set to default' button in a confirm dialog" + }, "confirmCancel": { "message": "Anuluj", "description": "" }, + "cm_autoCloseBracketsTooltip": { + "message": "Automatycznie dodawaj zamknięcie pary podczas pisania jednego z otwarcia ()[]{}''\"\"", + "description": "Label for the checkbox in the style editor." + }, "retrieveBckp": { "message": "Importuj style", "description": "" @@ -232,6 +256,15 @@ "message": "Tylko style stworzone lokalnie", "description": "Checkbox to show only locally created styles i.e. non-updatable" }, + "styleMetaErrorPreprocessor": { + "message": "Nieobsługiwany @preprocessor: $preprocessor$", + "description": "Error displayed when the value of @preprocessor is not supported", + "placeholders": { + "preprocessor": { + "content": "$1" + } + } + }, "linterIssuesHelp": { "message": "Te problemy zostały znalezione przez $link$:", "description": "Help popup message for the selected CSS linter issues block on the style edit page", @@ -241,6 +274,10 @@ } } }, + "parseUsercssError": { + "message": "Stylusowi nie udało się sparsować usercss:", + "description": "The error message to show when stylus failed to parse usercss" + }, "searchStyles": { "message": "Szukaj treści", "description": "Label for the search filter textbox on the Manage styles page" @@ -253,6 +290,10 @@ "message": "Sprawdź ponownie, nie edytowałem żadnych stylów!", "description": "Label for the button to apply all detected updates" }, + "liveReloadLabel": { + "message": "Przeładuj na żywo", + "description": "The label of live-reload feature" + }, "unreachableFileHint": { "message": "Stylus może uzyskać dostęp do adresów URL file:// tylko po włączeniu odpowiedniego pola wyboru rozszerzenia Stylus na stronie chrome://extensions.", "description": "Note in the toolbar popup for file:// URLs" @@ -325,6 +366,10 @@ "message": "Regexp jest nieprawidłowe.", "description": "Validation message for a bad regexp in a style" }, + "license": { + "message": "Licencja", + "description": "Label for the license" + }, "optionsHeading": { "message": "Opcje", "description": "Heading for options section on manage page." @@ -367,6 +412,10 @@ "message": "nieprawidłowe pominięte", "description": "Text after the number of styles skipped due to being invalid (not a Stylus/Stylish backup file probably) in the report shown after importing styles" }, + "optionsAdvancedNewStyleAsUsercss": { + "message": "Napisz nowy styl jako usercss", + "description": "" + }, "genericResetLabel": { "message": "Resetuj", "description": "Used in various parts of UI to indicate that something may be reset to its original state" @@ -396,6 +445,10 @@ "message": "Użyj składni /re/ dla wyszukiwania regexp", "description": "Label after the search input field in the editor shown on Ctrl-F" }, + "popupBordersTooltip": { + "message": "Przydaje się do ciemnych motywów w nowym Chrome, ponieważ nie maluje już bocznych krawędzi", + "description": "" + }, "updateCheckManualUpdateHint": { "message": "Wymuszenie aktualizacji spowoduje nadpisanie wszelkich lokalnych zmian.", "description": "Additional text displayed when an update check skipped updating the style to avoid losing local modifications" @@ -408,6 +461,10 @@ "message": "Odrzuć zawartość bieżącego stylu i nadpisz go przy użyciu importowanego stylu", "description": "Label for the button to import and overwrite current style" }, + "installUpdateFromLabel": { + "message": "Sprawdź aktualizacje", + "description": "Label for the checkbox to save current URL for update check" + }, "cm_resizeGripHint": { "message": "Kliknij dwukrotnie, aby zmaksymalizować lub przywrócić wysokość", "description": "Tooltip for the resize grip in style editor" @@ -541,6 +598,10 @@ "message": "Wyłącz wszystkie style", "description": "Label for the checkbox that turns all enabled styles off." }, + "appliesLineWidgetLabel": { + "message": "Wyświetl informacje 'Dotyczy'", + "description": "Label for the checkbox to display applies-to information in the single editor" + }, "updateCheckSkippedMaybeLocallyEdited": { "message": "Ten styl mógł być edytowany lokalnie.", "description": "Text that displays when an update check skipped updating the style to avoid losing possible local modifications" @@ -581,6 +642,21 @@ "message": "Stylus korzysta z usługi zewnętrznej https://www.google.com/s2/favicons", "description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page" }, + "styleInstallOverwrite": { + "message": "'$stylename$' jest już zainstalowany. Zastąpić?\nWersja: $oldVersion$ -> $newVersion$", + "description": "Confirmation when re-installing a style", + "placeholders": { + "stylename": { + "content": "$1" + }, + "newVersion": { + "content": "$3" + }, + "oldVersion": { + "content": "$2" + } + } + }, "updateCheckSkippedLocallyEdited": { "message": "Ten styl był edytowany lokalnie.", "description": "Text that displays when an update check skipped updating the style to avoid losing local modifications" @@ -589,6 +665,10 @@ "message": "Zobacz pełną listę reguł", "description": "Stylelint or CSSLint rules label added immediately before a link" }, + "styleUpdateDiscardChanges": { + "message": "Styl zmieniono poza edytorem. Czy chcesz przeładować styl?", + "description": "Confirmation to update the style in the editor" + }, "optionsResetButton": { "message": "Resetuj opcje", "description": "" @@ -597,6 +677,10 @@ "message": "Kod", "description": "Label for the code for a section" }, + "externalUsercssDocument": { + "message": "Dokumentacja Usercss", + "description": "Label for the external link to usercss documentation" + }, "optionsAdvancedContextDelete": { "message": "Dodaj 'Usuń' do menu kontekstowego edytora", "description": "" @@ -610,6 +694,10 @@ } } }, + "configureStyle": { + "message": "Skonfiguruj", + "description": "Label for the button to configure userstyle" + }, "importReportLegendUpdatedBoth": { "message": "zaktualizowano meta info i kod", "description": "Text after the number of styles updated entirely in the report shown after importing styles" @@ -638,6 +726,32 @@ "message": "Autouzupełnianie podczas pisania", "description": "Label for the checkbox in the style editor." }, + "linterCSSLintIncompatible": { + "message": "CSSLint nie obsługuje preprocesora $preprocessorname$", + "description": "The label to display when the preprocessor isn't compatible with CSSLint", + "placeholders": { + "preprocessorname": { + "content": "$1" + } + } + }, + "styleMetaErrorSelectValueMismatch": { + "message": "Nieprawidłowy @select: wartość nie istnieje na liście", + "description": "Error displayed when the value of @select is invalid" + }, + "styleMetaErrorColor": { + "message": "$color$ nie jest prawidłowym kolorem", + "description": "Error displayed when the value of @var color is invalid", + "placeholders": { + "color": { + "content": "$1" + } + } + }, + "externalFeedback": { + "message": "Informacje zwrotne", + "description": "Label for the external link to send feedback for the style" + }, "manageOnlyDisabled": { "message": "Tylko wyłączone style", "description": "Checkbox to show only disabled styles" @@ -662,6 +776,10 @@ "message": "Stylus nie działa na takich stronach.", "description": "Note in the toolbar pop-up when on a URL Stylus can't affect" }, + "popupBorders": { + "message": "Dodaj białe obramowania po bokach", + "description": "" + }, "manageOnlyUpdates": { "message": "Tylko z aktualizacjami lub problemami", "description": "Checkbox to show only styles that have updates after check-all-styles-for-updates was performed" @@ -670,6 +788,14 @@ "message": "Dodaj styl", "description": "Title of the page for adding styles" }, + "externalLink": { + "message": "Link zewnętrzny", + "description": "Label for external links" + }, + "externalHomepage": { + "message": "Strona główna", + "description": "Label for the external link to style's homepage" + }, "importReplaceLabel": { "message": "Nadpisz styl", "description": "Label for the button to import and overwrite current style" @@ -686,6 +812,10 @@ "message": "Zaawansowane", "description": "" }, + "alphaChannel": { + "message": "Nieprzezroczystość", + "description": "Label of color's opacity" + }, "importAppendTooltip": { "message": "Dołącz importowany styl do bieżącego stylu", "description": "Tooltip for the button to import a style and append to the existing sections" @@ -746,6 +876,15 @@ "message": "Emblemat na ikonie paska narzędzi", "description": "" }, + "installUpdateFrom": { + "message": "Obecnie styl jest aktualizowany z $url$", + "description": "Label to describe where the style gets update", + "placeholders": { + "url": { + "content": "$1" + } + } + }, "importReportLegendIdentical": { "message": "identyczne pominięte", "description": "Text after the number of styles skipped due to being identical to the already installed ones in the report shown after importing styles" @@ -754,6 +893,14 @@ "message": "Szerokość okna (w pikselach)", "description": "" }, + "cm_autoCloseBrackets": { + "message": "Automatyczne zamykanie nawiasów i cytatów", + "description": "Label for the checkbox in the style editor." + }, + "installButtonReinstall": { + "message": "Przeinstaluj", + "description": "Label for reinstall button" + }, "linterInvalidConfigError": { "message": "Nie zapisano z powodu tych nieprawidłowych ustawień konfiguracji:", "description": "Invalid linter config will show a message followed by a list of invalid entries" @@ -762,6 +909,19 @@ "message": "Nie", "description": "'No' button in a confirm dialog" }, + "styleMissingMeta": { + "message": "Brakujące metadane @$key$", + "description": "Error displayed when a mandatory metadata is missing", + "placeholders": { + "key": { + "content": "$1" + } + } + }, + "appliesLineWidgetWarning": { + "message": "Nie działa ze zminifikowanym CSS", + "description": "A warning that applies-to information won't show properly with minified CSS" + }, "undo": { "message": "Cofnij", "description": "Button label" @@ -770,6 +930,14 @@ "message": "Mapa klawiszy", "description": "Label for the drop-down list controlling the keymap for the style editor." }, + "externalSupport": { + "message": "Wsparcie", + "description": "Label for the external link to style's support site" + }, + "confirmSave": { + "message": "Zapisz", + "description": "'Save' button in a confirm dialog" + }, "manageNewUI": { "message": "Nowy układ interfejsu zarządzania", "description": "Label for the checkbox that toggles the new UI on manage page" @@ -790,6 +958,10 @@ "message": "Zamień na", "description": "Label before the replace-with input field in the editor shown on Ctrl-H etc." }, + "liveReloadError": { + "message": "Wystąpił błąd podczas oglądania pliku", + "description": "The label of live-reload error" + }, "deleteStyleLabel": { "message": "Usuń", "description": "Label for the button to delete a style" @@ -798,6 +970,15 @@ "message": "Zainstaluj aktualizację (lokalne zmiany zostaną nadpisane)", "description": "Additional text displayed when an update check skipped updating the style to avoid losing local modifications" }, + "styleInstallFailed": { + "message": "Nie udało się zainstalować stylu!\n$error$", + "description": "Warning when installation failed", + "placeholders": { + "error": { + "content": "$1" + } + } + }, "optionsAdvancedExposeIframes": { "message": "Odsłoń ramki pływające za pomocą HTML[stylus-iframe]", "description": "" @@ -810,6 +991,10 @@ "message": "Aby wyłączyć automatyczne sprawdzanie aktualizacji stylów użytkownika, ustaw interwał na 0", "description": "" }, + "installButtonUpdate": { + "message": "Zaktualizuj", + "description": "Label for update button" + }, "backupButtons": { "message": "Kopia zapasowa", "description": "Heading for backup" @@ -822,6 +1007,14 @@ "message": "Edytuj", "description": "Label for the button to go to the edit style page" }, + "installButtonInstalled": { + "message": "Zainstalowany", + "description": "Text displayed when the style is successfully installed" + }, + "author": { + "message": "Autor", + "description": "Label for the style author" + }, "cm_theme": { "message": "Motyw", "description": "Label for the style editor's CSS theme." @@ -830,6 +1023,10 @@ "message": "Otwórz edytor w nowym oknie", "description": "Label for the checkbox controlling 'edit' action behavior in the popup." }, + "appliesRemoveError": { + "message": "Nie można usunąć ostatniego wpisu 'dotyczy'", + "description": "Error displayed when the last 'applies' is going to be removed" + }, "backupMessage": { "message": "Wybierz plik lub przeciągnij i upuść go na tę stronę.", "description": "Message for backup" @@ -849,5 +1046,9 @@ "description": { "message": "Przeprojektuj sieć za pomocą Stylusa – menedżera stylów użytkownika. Stylus umożliwia łatwe instalowanie motywów i skórek dla wielu popularnych stron.", "description": "Extension description" + }, + "confirmClose": { + "message": "Zamknij", + "description": "'Close' button in a confirm dialog" } } \ No newline at end of file diff --git a/_locales/pt_BR/messages.json b/_locales/pt_BR/messages.json index ce98da46..1fd49753 100644 --- a/_locales/pt_BR/messages.json +++ b/_locales/pt_BR/messages.json @@ -8,7 +8,7 @@ "description": "Label for the button to enable a style" }, "styleMissingName": { - "message": "Insira um nome.", + "message": "Insira um nome", "description": "Error displayed when user saves without providing a name" }, "appliesDomainOption": { diff --git a/_locales/pt_PT/messages.json b/_locales/pt_PT/messages.json index d470e6e9..f1b288d8 100644 --- a/_locales/pt_PT/messages.json +++ b/_locales/pt_PT/messages.json @@ -44,7 +44,7 @@ "description": "Label for the button to enable a style" }, "styleMissingName": { - "message": "Insira um nome.", + "message": "Insira um nome", "description": "Error displayed when user saves without providing a name" }, "genericHistoryLabel": { diff --git a/_locales/ru/messages.json b/_locales/ru/messages.json index 557cc6e6..f20bc767 100644 --- a/_locales/ru/messages.json +++ b/_locales/ru/messages.json @@ -31,6 +31,14 @@ "message": "Экспорт", "description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" }, + "installButton": { + "message": "Установить", + "description": "Label for install button" + }, + "styleMetaErrorCheckbox": { + "message": "Ошибочный @var checkbox: значение должно быть 0 или 1", + "description": "Error displayed when the value of @var checkbox is invalid" + }, "linterJSONError": { "message": "Ошибка формата JSON", "description": "Setting linter config with invalid JSON" @@ -52,7 +60,7 @@ "description": "Label for the button to enable a style" }, "styleMissingName": { - "message": "Введите название.", + "message": "Введите название", "description": "Error displayed when user saves without providing a name" }, "genericHistoryLabel": { @@ -83,6 +91,10 @@ "message": "Тёмный интерфейс браузера", "description": "" }, + "styleFromMozillaFormatError": { + "message": "Ошибка импорта формата Mozilla", + "description": "Label for the import error" + }, "importAppendLabel": { "message": "Добавить к стилю", "description": "Label for the button to import a style and append to the existing sections" @@ -123,6 +135,10 @@ "message": "Обесцвечивать", "description": "Label for the checkbox that toggles grayed out mode of applies-to favicons in the new UI on manage page" }, + "versionInvalidOlder": { + "message": "Версия меньше установленной.", + "description": "Displayed when the version of style is older than the installed one" + }, "confirmYes": { "message": "Да", "description": "'Yes' button in a confirm dialog" @@ -175,10 +191,18 @@ "message": "Удалить", "description": "" }, + "confirmDefault": { + "message": "По умолчанию", + "description": "'Set to default' button in a confirm dialog" + }, "confirmCancel": { "message": "Отмена", "description": "" }, + "cm_autoCloseBracketsTooltip": { + "message": "Автоматически вставлять закрывающий символ при наборе открывающего из ()[]{}''\"\"", + "description": "Label for the checkbox in the style editor." + }, "retrieveBckp": { "message": "Импорт стилей", "description": "" @@ -236,6 +260,15 @@ "message": "Только локально созданные стили", "description": "Checkbox to show only locally created styles i.e. non-updatable" }, + "styleMetaErrorPreprocessor": { + "message": "Ошибочный @preprocessor: $preprocessor$", + "description": "Error displayed when the value of @preprocessor is not supported", + "placeholders": { + "preprocessor": { + "content": "$1" + } + } + }, "linterIssuesHelp": { "message": "Проблемы, найденные правилами $link$:", "description": "Help popup message for the selected CSS linter issues block on the style edit page", @@ -245,6 +278,10 @@ } } }, + "parseUsercssError": { + "message": "Stylus не смог распарсить usercss:", + "description": "The error message to show when stylus failed to parse usercss" + }, "searchStyles": { "message": "Искать по содержимому", "description": "Label for the search filter textbox on the Manage styles page" @@ -257,6 +294,10 @@ "message": "Я не менял стили, проверить заново!", "description": "Label for the button to apply all detected updates" }, + "liveReloadLabel": { + "message": "Автозагрузка изменений", + "description": "The label of live-reload feature" + }, "unreachableFileHint": { "message": "Доступ к адресам file:// возможен только если включить соответствующую опцию для Stylus на странице chrome://extensions.", "description": "Note in the toolbar popup for file:// URLs" @@ -329,6 +370,10 @@ "message": "Ошибка в регулярном выражении.", "description": "Validation message for a bad regexp in a style" }, + "license": { + "message": "Лицензия", + "description": "Label for the license" + }, "optionsHeading": { "message": "Настройки", "description": "Heading for options section on manage page." @@ -371,6 +416,10 @@ "message": "некорректных пропущено", "description": "Text after the number of styles skipped due to being invalid (not a Stylus/Stylish backup file probably) in the report shown after importing styles" }, + "optionsAdvancedNewStyleAsUsercss": { + "message": "Создавать стили в формате usercss", + "description": "" + }, "genericResetLabel": { "message": "Сброс", "description": "Used in various parts of UI to indicate that something may be reset to its original state" @@ -400,6 +449,10 @@ "message": "Используйте нотацию /re/ для поиска регулярными выражениями", "description": "Label after the search input field in the editor shown on Ctrl-F" }, + "popupBordersTooltip": { + "message": "Полезно для темных тем оформления т.к. новый Chrome не рисует боковые бордюры", + "description": "" + }, "updateCheckManualUpdateHint": { "message": "Форсирование обновления удалит локальные изменения.", "description": "Additional text displayed when an update check skipped updating the style to avoid losing local modifications" @@ -412,6 +465,10 @@ "message": "Удалить содержимое редактируемого стиля и заменить его на импортируемый стиль", "description": "Label for the button to import and overwrite current style" }, + "installUpdateFromLabel": { + "message": "Проверить обновления", + "description": "Label for the checkbox to save current URL for update check" + }, "cm_resizeGripHint": { "message": "Двойной клик = переключить максимальный размер", "description": "Tooltip for the resize grip in style editor" @@ -545,6 +602,10 @@ "message": "Выключить все стили", "description": "Label for the checkbox that turns all enabled styles off." }, + "appliesLineWidgetLabel": { + "message": "Показать целевые сайты секций", + "description": "Label for the checkbox to display applies-to information in the single editor" + }, "updateCheckSkippedMaybeLocallyEdited": { "message": "Возможно был изменен локально.", "description": "Text that displays when an update check skipped updating the style to avoid losing possible local modifications" @@ -585,6 +646,21 @@ "message": "Используется сторонний сервис https://www.google.com/s2/favicons", "description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page" }, + "styleInstallOverwrite": { + "message": "'$stylename$' уже установлен. Обновить?\nВерсии: $oldVersion$ -> $newVersion$", + "description": "Confirmation when re-installing a style", + "placeholders": { + "stylename": { + "content": "$1" + }, + "newVersion": { + "content": "$3" + }, + "oldVersion": { + "content": "$2" + } + } + }, "updateCheckSkippedLocallyEdited": { "message": "Стиль был изменен локально.", "description": "Text that displays when an update check skipped updating the style to avoid losing local modifications" @@ -593,6 +669,10 @@ "message": "Открыть полный список правил", "description": "Stylelint or CSSLint rules label added immediately before a link" }, + "styleUpdateDiscardChanges": { + "message": "Стиль был изменен вне редактора. Перезагрузить?", + "description": "Confirmation to update the style in the editor" + }, "optionsResetButton": { "message": "Сброс настроек", "description": "" @@ -601,6 +681,10 @@ "message": "Код", "description": "Label for the code for a section" }, + "externalUsercssDocument": { + "message": "Документация по usercss", + "description": "Label for the external link to usercss documentation" + }, "optionsAdvancedContextDelete": { "message": "Показывать команду \"Удалить\" в контекстном меню редактора", "description": "" @@ -614,6 +698,10 @@ } } }, + "configureStyle": { + "message": "Настроить", + "description": "Label for the button to configure userstyle" + }, "importReportLegendUpdatedBoth": { "message": "обновлены мета-данные и код", "description": "Text after the number of styles updated entirely in the report shown after importing styles" @@ -642,6 +730,32 @@ "message": "Подсказки при наборе кода", "description": "Label for the checkbox in the style editor." }, + "linterCSSLintIncompatible": { + "message": "CSSLint не поддерживает препроцессор $preprocessorname$", + "description": "The label to display when the preprocessor isn't compatible with CSSLint", + "placeholders": { + "preprocessorname": { + "content": "$1" + } + } + }, + "styleMetaErrorSelectValueMismatch": { + "message": "Ошибочный @select: значение не в списке", + "description": "Error displayed when the value of @select is invalid" + }, + "styleMetaErrorColor": { + "message": "$color$ - цвет не распознан", + "description": "Error displayed when the value of @var color is invalid", + "placeholders": { + "color": { + "content": "$1" + } + } + }, + "externalFeedback": { + "message": "Отзывы", + "description": "Label for the external link to send feedback for the style" + }, "manageOnlyDisabled": { "message": "Только неактивные стили", "description": "Checkbox to show only disabled styles" @@ -662,6 +776,10 @@ "message": "Такие адреса не поддерживаются.", "description": "Note in the toolbar pop-up when on a URL Stylus can't affect" }, + "popupBorders": { + "message": "Добавить белый бордюр по бокам", + "description": "" + }, "manageOnlyUpdates": { "message": "Только обновляемые и проблемные", "description": "Checkbox to show only styles that have updates after check-all-styles-for-updates was performed" @@ -670,6 +788,14 @@ "message": "Добавление стиля", "description": "Title of the page for adding styles" }, + "externalLink": { + "message": "Внешняя ссылка", + "description": "Label for external links" + }, + "externalHomepage": { + "message": "Домашняя страницы", + "description": "Label for the external link to style's homepage" + }, "importReplaceLabel": { "message": "Заменить стиль", "description": "Label for the button to import and overwrite current style" @@ -686,6 +812,10 @@ "message": "Другое", "description": "" }, + "alphaChannel": { + "message": "Прозрачность", + "description": "Label of color's opacity" + }, "importAppendTooltip": { "message": "Добавить импортируемый стиль к редактируемому", "description": "Tooltip for the button to import a style and append to the existing sections" @@ -746,6 +876,15 @@ "message": "Бейдж на пиктограмме в тулбаре", "description": "" }, + "installUpdateFrom": { + "message": "Источник обновления стиля: $url$", + "description": "Label to describe where the style gets update", + "placeholders": { + "url": { + "content": "$1" + } + } + }, "importReportLegendIdentical": { "message": "идентичные пропущены", "description": "Text after the number of styles skipped due to being identical to the already installed ones in the report shown after importing styles" @@ -754,6 +893,14 @@ "message": "Ширина всплывающего окна (в пикселах)", "description": "" }, + "cm_autoCloseBrackets": { + "message": "Закрывать скобки/кавычки при наборе", + "description": "Label for the checkbox in the style editor." + }, + "installButtonReinstall": { + "message": "Переустановить", + "description": "Label for reinstall button" + }, "linterInvalidConfigError": { "message": "Не сохранено из-за неправильных настроек ниже:", "description": "Invalid linter config will show a message followed by a list of invalid entries" @@ -762,6 +909,19 @@ "message": "Нет", "description": "'No' button in a confirm dialog" }, + "styleMissingMeta": { + "message": "Отсутствуют метаданные @$key$", + "description": "Error displayed when a mandatory metadata is missing", + "placeholders": { + "key": { + "content": "$1" + } + } + }, + "appliesLineWidgetWarning": { + "message": "Не работает с минифицированным CSS", + "description": "A warning that applies-to information won't show properly with minified CSS" + }, "undo": { "message": "Отменить", "description": "Button label" @@ -770,6 +930,14 @@ "message": "Раскладка", "description": "Label for the drop-down list controlling the keymap for the style editor." }, + "externalSupport": { + "message": "Поддержка", + "description": "Label for the external link to style's support site" + }, + "confirmSave": { + "message": "Сохранить", + "description": "'Save' button in a confirm dialog" + }, "manageNewUI": { "message": "Новый интерфейс", "description": "Label for the checkbox that toggles the new UI on manage page" @@ -790,6 +958,10 @@ "message": "Заменить на", "description": "Label before the replace-with input field in the editor shown on Ctrl-H etc." }, + "liveReloadError": { + "message": "Ошибка слежения за файлом", + "description": "The label of live-reload error" + }, "deleteStyleLabel": { "message": "Удалить", "description": "Label for the button to delete a style" @@ -798,6 +970,15 @@ "message": "Установить обновление (локальные изменения будут утеряны)", "description": "Additional text displayed when an update check skipped updating the style to avoid losing local modifications" }, + "styleInstallFailed": { + "message": "Ошибка установки стиля!\n$error$", + "description": "Warning when installation failed", + "placeholders": { + "error": { + "content": "$1" + } + } + }, "optionsAdvancedExposeIframes": { "message": "Выявлять iframe путем добавления HTML[stylus-iframe]", "description": "" @@ -810,6 +991,10 @@ "message": "Для отключения автоматического обновления стилей введите 0 (ноль)", "description": "" }, + "installButtonUpdate": { + "message": "Обновить", + "description": "Label for update button" + }, "backupButtons": { "message": "Резервное копирование", "description": "Heading for backup" @@ -822,6 +1007,14 @@ "message": "Изменить", "description": "Label for the button to go to the edit style page" }, + "installButtonInstalled": { + "message": "Установлен", + "description": "Text displayed when the style is successfully installed" + }, + "author": { + "message": "Автор", + "description": "Label for the style author" + }, "cm_theme": { "message": "Тема", "description": "Label for the style editor's CSS theme." @@ -830,6 +1023,10 @@ "message": "Открывать редактор в новом окне", "description": "Label for the checkbox controlling 'edit' action behavior in the popup." }, + "appliesRemoveError": { + "message": "Нельзя удалить последний элемент", + "description": "Error displayed when the last 'applies' is going to be removed" + }, "backupMessage": { "message": "Нажмите Импорт чтобы выбрать файл или просто перетащите его на эту страницу", "description": "Message for backup" @@ -849,5 +1046,9 @@ "description": { "message": "Настраивайте стили веб-сайтов с помощью менеджера стилей Stylus. Он позволяет легко установить темы и изменить внешний вид сайтов Google, Facebook, YouTube, Orkut и множества других веб-страниц.", "description": "Extension description" + }, + "confirmClose": { + "message": "Закрыть", + "description": "'Close' button in a confirm dialog" } } \ No newline at end of file diff --git a/_locales/sv/messages.json b/_locales/sv/messages.json index b42e3c2c..e432893d 100644 --- a/_locales/sv/messages.json +++ b/_locales/sv/messages.json @@ -12,7 +12,7 @@ "description": "Label for the button to enable a style" }, "styleMissingName": { - "message": "Ange ett namn.", + "message": "Ange ett namn", "description": "Error displayed when user saves without providing a name" }, "appliesDomainOption": { diff --git a/_locales/tr/messages.json b/_locales/tr/messages.json index b9b565d1..51d9907e 100644 --- a/_locales/tr/messages.json +++ b/_locales/tr/messages.json @@ -8,7 +8,7 @@ "description": "Label for the button to enable a style" }, "styleMissingName": { - "message": "Bir ad girin.", + "message": "Bir ad girin", "description": "Error displayed when user saves without providing a name" }, "appliesDomainOption": { diff --git a/_locales/zh/messages.json b/_locales/zh/messages.json index dee83899..ea30b3b7 100644 --- a/_locales/zh/messages.json +++ b/_locales/zh/messages.json @@ -8,7 +8,7 @@ "description": "Label for the button to enable a style" }, "styleMissingName": { - "message": "请输入名称.", + "message": "请输入名称", "description": "Error displayed when user saves without providing a name" }, "appliesDomainOption": { diff --git a/_locales/zh_CN/messages.json b/_locales/zh_CN/messages.json index 9e7d9c25..72841ce5 100644 --- a/_locales/zh_CN/messages.json +++ b/_locales/zh_CN/messages.json @@ -48,7 +48,7 @@ "description": "Label for the button to enable a style" }, "styleMissingName": { - "message": "输入名称。", + "message": "输入名称", "description": "Error displayed when user saves without providing a name" }, "genericHistoryLabel": { diff --git a/_locales/zh_TW/messages.json b/_locales/zh_TW/messages.json index 33a40331..9a1ad2ad 100644 --- a/_locales/zh_TW/messages.json +++ b/_locales/zh_TW/messages.json @@ -31,6 +31,14 @@ "message": "導出", "description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" }, + "installButton": { + "message": "安裝", + "description": "Label for install button" + }, + "styleMetaErrorCheckbox": { + "message": "無效的 @var 勾選框:值必須為 0 或 1", + "description": "Error displayed when the value of @var checkbox is invalid" + }, "linterJSONError": { "message": "無效的 JSON 格式", "description": "Setting linter config with invalid JSON" @@ -52,7 +60,7 @@ "description": "Label for the button to enable a style" }, "styleMissingName": { - "message": "請輸入名稱。", + "message": "請輸入名稱", "description": "Error displayed when user saves without providing a name" }, "genericHistoryLabel": { @@ -83,6 +91,10 @@ "message": "暗色瀏覽器主題", "description": "" }, + "styleFromMozillaFormatError": { + "message": "從 Mozilla 格式匯入失敗", + "description": "Label for the import error" + }, "importAppendLabel": { "message": "追加到樣式", "description": "Label for the button to import a style and append to the existing sections" @@ -123,6 +135,10 @@ "message": "灰階淡出", "description": "Label for the checkbox that toggles grayed out mode of applies-to favicons in the new UI on manage page" }, + "versionInvalidOlder": { + "message": "版本舊於已安裝的樣式。", + "description": "Displayed when the version of style is older than the installed one" + }, "confirmYes": { "message": "是", "description": "'Yes' button in a confirm dialog" @@ -175,10 +191,18 @@ "message": "刪除", "description": "" }, + "confirmDefault": { + "message": "使用預設值", + "description": "'Set to default' button in a confirm dialog" + }, "confirmCancel": { "message": "取消", "description": "" }, + "cm_autoCloseBracketsTooltip": { + "message": "當輸入開放的 ()[]{}''\"\" 時自動新增另一邊的標點符號", + "description": "Label for the checkbox in the style editor." + }, "retrieveBckp": { "message": "匯入樣式", "description": "" @@ -236,6 +260,15 @@ "message": "僅本機建立的樣式", "description": "Checkbox to show only locally created styles i.e. non-updatable" }, + "styleMetaErrorPreprocessor": { + "message": "不支援的 @preprocessor:$preprocessor$", + "description": "Error displayed when the value of @preprocessor is not supported", + "placeholders": { + "preprocessor": { + "content": "$1" + } + } + }, "linterIssuesHelp": { "message": "這些問題被 $link$ 找到了:", "description": "Help popup message for the selected CSS linter issues block on the style edit page", @@ -245,6 +278,10 @@ } } }, + "parseUsercssError": { + "message": "Stylus 解析 usercss 失敗:", + "description": "The error message to show when stylus failed to parse usercss" + }, "searchStyles": { "message": "搜索內容", "description": "Label for the search filter textbox on the Manage styles page" @@ -257,6 +294,10 @@ "message": "再次檢查,我沒有編輯任何樣式!", "description": "Label for the button to apply all detected updates" }, + "liveReloadLabel": { + "message": "即時重新整理", + "description": "The label of live-reload feature" + }, "unreachableFileHint": { "message": "Stylus 僅在您於 chrome://extensions 啟用了 Stylus 擴充套件中對應的勾選框時才能存取 file:// 的 URL。", "description": "Note in the toolbar popup for file:// URLs" @@ -329,6 +370,10 @@ "message": "正規表示式無效。", "description": "Validation message for a bad regexp in a style" }, + "license": { + "message": "授權條款", + "description": "Label for the license" + }, "optionsHeading": { "message": "選項", "description": "Heading for options section on manage page." @@ -371,6 +416,10 @@ "message": "已跳過無效的", "description": "Text after the number of styles skipped due to being invalid (not a Stylus/Stylish backup file probably) in the report shown after importing styles" }, + "optionsAdvancedNewStyleAsUsercss": { + "message": "以 usercss 編寫新樣式", + "description": "" + }, "genericResetLabel": { "message": "重設", "description": "Used in various parts of UI to indicate that something may be reset to its original state" @@ -400,6 +449,10 @@ "message": "使用/re/句法正則表達式搜索", "description": "Label after the search input field in the editor shown on Ctrl-F" }, + "popupBordersTooltip": { + "message": "對新 Chrome 中的暗色主題很有用,因為其不再繪製邊框", + "description": "" + }, "updateCheckManualUpdateHint": { "message": "強制更新覆蓋任何本機編輯。", "description": "Additional text displayed when an update check skipped updating the style to avoid losing local modifications" @@ -412,6 +465,10 @@ "message": "棄用當前樣式內容并用導入樣式覆蓋", "description": "Label for the button to import and overwrite current style" }, + "installUpdateFromLabel": { + "message": "檢查更新", + "description": "Label for the checkbox to save current URL for update check" + }, "cm_resizeGripHint": { "message": "雙擊以最大化/復原高度", "description": "Tooltip for the resize grip in style editor" @@ -545,6 +602,10 @@ "message": "禁用所有樣式", "description": "Label for the checkbox that turns all enabled styles off." }, + "appliesLineWidgetLabel": { + "message": "顯示「套用至」資訊", + "description": "Label for the checkbox to display applies-to information in the single editor" + }, "updateCheckSkippedMaybeLocallyEdited": { "message": "這個樣式可能在本機被編輯過。", "description": "Text that displays when an update check skipped updating the style to avoid losing possible local modifications" @@ -585,6 +646,21 @@ "message": "Stylus 使用外部服務 https://www.google.com/s2/favicons", "description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page" }, + "styleInstallOverwrite": { + "message": "「$stylename$」已安裝。要覆寫嗎?\n版本:$oldVersion$→$newVersion$", + "description": "Confirmation when re-installing a style", + "placeholders": { + "stylename": { + "content": "$1" + }, + "newVersion": { + "content": "$3" + }, + "oldVersion": { + "content": "$2" + } + } + }, "updateCheckSkippedLocallyEdited": { "message": "這個樣式已在本機編輯。", "description": "Text that displays when an update check skipped updating the style to avoid losing local modifications" @@ -593,6 +669,10 @@ "message": "見完整的規則清單", "description": "Stylelint or CSSLint rules label added immediately before a link" }, + "styleUpdateDiscardChanges": { + "message": "樣式已在編輯器外變更。您想要重新載入樣式嗎?", + "description": "Confirmation to update the style in the editor" + }, "optionsResetButton": { "message": "重設選項", "description": "" @@ -601,6 +681,10 @@ "message": "代碼", "description": "Label for the code for a section" }, + "externalUsercssDocument": { + "message": "Usercss 的文件", + "description": "Label for the external link to usercss documentation" + }, "optionsAdvancedContextDelete": { "message": "在編輯器的右鍵選單中加入「刪除」", "description": "" @@ -614,6 +698,10 @@ } } }, + "configureStyle": { + "message": "設定", + "description": "Label for the button to configure userstyle" + }, "importReportLegendUpdatedBoth": { "message": "後設資訊與程式碼均已更新", "description": "Text after the number of styles updated entirely in the report shown after importing styles" @@ -642,6 +730,32 @@ "message": "在輸入時自動完成", "description": "Label for the checkbox in the style editor." }, + "linterCSSLintIncompatible": { + "message": "CSSLint 不支援 $preprocessorname$ 預處理器", + "description": "The label to display when the preprocessor isn't compatible with CSSLint", + "placeholders": { + "preprocessorname": { + "content": "$1" + } + } + }, + "styleMetaErrorSelectValueMismatch": { + "message": "無效的 @select:值不存在於清單中", + "description": "Error displayed when the value of @select is invalid" + }, + "styleMetaErrorColor": { + "message": "$color$是無效的顏色", + "description": "Error displayed when the value of @var color is invalid", + "placeholders": { + "color": { + "content": "$1" + } + } + }, + "externalFeedback": { + "message": "回饋", + "description": "Label for the external link to send feedback for the style" + }, "manageOnlyDisabled": { "message": "僅已停用的樣式", "description": "Checkbox to show only disabled styles" @@ -666,6 +780,10 @@ "message": "Stylus 不能在諸如此類的網頁上生效。", "description": "Note in the toolbar pop-up when on a URL Stylus can't affect" }, + "popupBorders": { + "message": "在邊邊新增白色邊框", + "description": "" + }, "manageOnlyUpdates": { "message": "僅有更新或是有問題的", "description": "Checkbox to show only styles that have updates after check-all-styles-for-updates was performed" @@ -674,6 +792,14 @@ "message": "添加樣式", "description": "Title of the page for adding styles" }, + "externalLink": { + "message": "外部連結", + "description": "Label for external links" + }, + "externalHomepage": { + "message": "首頁", + "description": "Label for the external link to style's homepage" + }, "importReplaceLabel": { "message": "覆蓋樣式", "description": "Label for the button to import and overwrite current style" @@ -690,6 +816,10 @@ "message": "進階", "description": "" }, + "alphaChannel": { + "message": "不透明度", + "description": "Label of color's opacity" + }, "importAppendTooltip": { "message": "追加導入的樣式到當前樣式", "description": "Tooltip for the button to import a style and append to the existing sections" @@ -746,6 +876,15 @@ "message": "在工具列圖示上的徽章", "description": "" }, + "installUpdateFrom": { + "message": "目前從 $url$ 更新樣式", + "description": "Label to describe where the style gets update", + "placeholders": { + "url": { + "content": "$1" + } + } + }, "importReportLegendIdentical": { "message": "已跳過相同的", "description": "Text after the number of styles skipped due to being identical to the already installed ones in the report shown after importing styles" @@ -754,6 +893,14 @@ "message": "彈出視窗寬度(以像素計)", "description": "" }, + "cm_autoCloseBrackets": { + "message": "自動關閉括號與引號", + "description": "Label for the checkbox in the style editor." + }, + "installButtonReinstall": { + "message": "重新安裝", + "description": "Label for reinstall button" + }, "linterInvalidConfigError": { "message": "因為無效的設定所以未儲存:", "description": "Invalid linter config will show a message followed by a list of invalid entries" @@ -762,6 +909,19 @@ "message": "否", "description": "'No' button in a confirm dialog" }, + "styleMissingMeta": { + "message": "遺失詮釋資料 @ $key$", + "description": "Error displayed when a mandatory metadata is missing", + "placeholders": { + "key": { + "content": "$1" + } + } + }, + "appliesLineWidgetWarning": { + "message": "無法與最小化的 CSS 一起運作", + "description": "A warning that applies-to information won't show properly with minified CSS" + }, "undo": { "message": "撤銷", "description": "Button label" @@ -770,6 +930,14 @@ "message": "鍵盤映射", "description": "Label for the drop-down list controlling the keymap for the style editor." }, + "externalSupport": { + "message": "支援", + "description": "Label for the external link to style's support site" + }, + "confirmSave": { + "message": "儲存", + "description": "'Save' button in a confirm dialog" + }, "manageNewUI": { "message": "新的管理介面佈局", "description": "Label for the checkbox that toggles the new UI on manage page" @@ -790,6 +958,10 @@ "message": "替換為", "description": "Label before the replace-with input field in the editor shown on Ctrl-H etc." }, + "liveReloadError": { + "message": "觀看檔案時發生錯誤", + "description": "The label of live-reload error" + }, "deleteStyleLabel": { "message": "删除", "description": "Label for the button to delete a style" @@ -798,6 +970,15 @@ "message": "安裝更新(本機編輯將會被覆寫)", "description": "Additional text displayed when an update check skipped updating the style to avoid losing local modifications" }, + "styleInstallFailed": { + "message": "安裝使用者樣式失敗!\n$error$", + "description": "Warning when installation failed", + "placeholders": { + "error": { + "content": "$1" + } + } + }, "optionsAdvancedExposeIframes": { "message": "透過 HTML[stylus-iframe] 公開 iframes", "description": "" @@ -810,6 +991,10 @@ "message": "要停用自動化使用者樣式更新檢查,設定間隔為 0", "description": "" }, + "installButtonUpdate": { + "message": "更新", + "description": "Label for update button" + }, "backupButtons": { "message": "備份", "description": "Heading for backup" @@ -822,6 +1007,14 @@ "message": "編輯", "description": "Label for the button to go to the edit style page" }, + "installButtonInstalled": { + "message": "已安裝", + "description": "Text displayed when the style is successfully installed" + }, + "author": { + "message": "作者", + "description": "Label for the style author" + }, "cm_theme": { "message": "主題", "description": "Label for the style editor's CSS theme." @@ -830,6 +1023,10 @@ "message": "在新視窗開啟編輯器", "description": "Label for the checkbox controlling 'edit' action behavior in the popup." }, + "appliesRemoveError": { + "message": "無法移除最後的「套用到」項目", + "description": "Error displayed when the last 'applies' is going to be removed" + }, "backupMessage": { "message": "選取檔案並拖曳到此頁面。", "description": "Message for backup" @@ -849,5 +1046,9 @@ "description": { "message": "用Stylus(一個用戶樣式管理器)重塑網頁。 Stylus 讓你能為诸多主流網站輕鬆的安裝主題和皮膚。", "description": "Extension description" + }, + "confirmClose": { + "message": "關閉", + "description": "'Close' button in a confirm dialog" } } \ No newline at end of file From 37a62b0407985276b2dc826b3811eeadb1efbbb8 Mon Sep 17 00:00:00 2001 From: tophf Date: Wed, 15 Nov 2017 15:59:24 +0300 Subject: [PATCH 004/161] fix/rewrite/integrate colorpicker * full rewrite of colorview.js to make it 10-100 times faster (render on demand via extendMode) * full rewrite of colorpicker.js to simplify CSS * automatic light/dark theme based on current color of the editor * fixes, tweaks, speedups * color spot will always be on the left of its text i.e. no line break on wrapping * support #RRGGBBAA and #RGBA hex colors * support "transparent" as rgba(0, 0, 0, 0) * HEX/hex toggle * fix HSLA regexp * Esc/Enter key to close * innerHTML -> textContent * toggle the feature correctly * fade out before autohiding * always show alpha 1 like devtools does * set cursor:pointer only on the clickable part of the sliders * bigger color format switcher with a tooltip * autofocus input on open, disable spellcheck * try not to obscure the source color spot & text * restore focus without scrolling --- .eslintignore | 3 +- _locales/en/messages.json | 12 + edit.html | 4 + edit/edit.js | 44 +- js/prefs.js | 5 + vendor-overwrites/colorpicker/colorpicker.css | 466 ++-- vendor-overwrites/colorpicker/colorpicker.js | 2230 +++++++---------- vendor-overwrites/colorpicker/colorview.js | 837 ++++--- 8 files changed, 1632 insertions(+), 1969 deletions(-) diff --git a/.eslintignore b/.eslintignore index a710e413..f1fc323f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,3 @@ vendor/ -vendor-overwrites/ +vendor-overwrites/* +!vendor-overwrites/colorpicker diff --git a/_locales/en/messages.json b/_locales/en/messages.json index fb0236c7..d7f0fa2a 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -127,6 +127,10 @@ "message": "Autocomplete on typing", "description": "Label for the checkbox in the style editor." }, + "cm_colorpicker": { + "message": "Colorpickers for CSS colors", + "description": "Label for the checkbox controlling colorpicker option for the style editor." + }, "cm_indentWithTabs": { "message": "Use tabs with smart indentation", "description": "Label for the checkbox controlling tabs with smart indentation option for the style editor." @@ -171,6 +175,14 @@ "message": "Theme", "description": "Label for the style editor's CSS theme." }, + "colorpickerSwitchFormatTooltip": { + "message": "Switch formats: HEX -> RGB -> HSL", + "description": "Tooltip for the switch button in the color picker popup in the style editor." + }, + "colorpickerTooltip": { + "message": "Open color picker", + "description": "Tooltip for the colored squares shown before CSS colors in the style editor." + }, "dysfunctional": { "message": "Stylus cannot function in private windows because Firefox disallows direct connection to the internal background page context of the extension.", "description": "Displayed in Firefox when its settings make Stylus dysfunctional" diff --git a/edit.html b/edit.html index 6e20b003..34e99fd6 100644 --- a/edit.html +++ b/edit.html @@ -190,6 +190,10 @@ +
+ + +
diff --git a/edit/edit.js b/edit/edit.js index 14c88382..0775ede5 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -7,6 +7,12 @@ /* global closeCurrentTab regExpTester messageBox */ 'use strict'; +onDOMready() + .then(() => Promise.all([ + onColorpickerReady(), + ])) + .then(init); + let styleId = null; // only the actually dirty items here let dirty = {}; @@ -362,6 +368,8 @@ function acmeEventListener(event) { } option = 'highlightSelectionMatches'; break; + case 'colorpicker': + return; } CodeMirror.setOption(option, value); } @@ -1298,8 +1306,6 @@ function beautify(event) { } } -onDOMready().then(init); - function init() { initCodeMirror(); getStyle().then(style => { @@ -2065,3 +2071,37 @@ function setGlobalProgress(done, total) { progressElement.remove(); } } + +function onColorpickerReady() { + const scripts = [ + '/vendor-overwrites/colorpicker/colorpicker.css', + '/vendor-overwrites/colorpicker/colorpicker.js', + '/vendor-overwrites/colorpicker/colorview.js', + ]; + prefs.subscribe(['editor.colorpicker'], colorpickerOnDemand); + return prefs.get('editor.colorpicker') && colorpickerOnDemand(null, true); + + function colorpickerOnDemand(id, enabled) { + return loadScript(enabled && scripts) + .then(() => setColorpickerOption(id, enabled)); + } + + function setColorpickerOption(id, enabled) { + CodeMirror.defaults.colorpicker = enabled && { + forceUpdate: editors.length > 0, + tooltip: t('colorpickerTooltip'), + popupOptions: { + tooltipForSwitcher: t('colorpickerSwitchFormatTooltip'), + hexUppercase: prefs.get('editor.colorpicker.hexUppercase'), + hideDelay: 5000, + embedderCallback: state => { + if (state && state.hexUppercase !== prefs.get('editor.colorpicker.hexUppercase')) { + prefs.set('editor.colorpicker.hexUppercase', state.hexUppercase); + } + }, + }, + }; + // on page load runs before CodeMirror.setOption is defined + editors.forEach(cm => cm.setOption('colorpicker', CodeMirror.defaults.colorpicker)); + } +} diff --git a/js/prefs.js b/js/prefs.js index 20903389..1c93a06c 100644 --- a/js/prefs.js +++ b/js/prefs.js @@ -56,6 +56,11 @@ var prefs = new function Prefs() { 'editor.appliesToLineWidget': true, // show applies-to line widget on the editor + // show CSS colors as clickable colored rectangles + 'editor.colorpicker': true, + // #DEAD or #beef + 'editor.colorpicker.hexUppercase': false, + 'iconset': 0, // 0 = dark-themed icon // 1 = light-themed icon diff --git a/vendor-overwrites/colorpicker/colorpicker.css b/vendor-overwrites/colorpicker/colorpicker.css index f80b45c2..532a6a07 100644 --- a/vendor-overwrites/colorpicker/colorpicker.css +++ b/vendor-overwrites/colorpicker/colorpicker.css @@ -1,57 +1,151 @@ - /* codemirror colorview */ -.codemirror-colorview { - border : 1px solid #8e8e8e; - position: relative; - display : inline-block; - box-sizing : border-box; - margin : 0px 2px; - width : 10px; - height : 10px; - cursor: pointer; - background-image : url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAJElEQVQYV2NctWrVfwYkEBYWxojMZ6SDAmT7QGx0K1EcRBsFAADeG/3M/HteAAAAAElFTkSuQmCC"); - background-repeat: repeat; -} - -.codemirror-colorview .codemirror-colorview-background { - content: ""; - position: absolute; - left:0px; - right:0px; - bottom:0px; - top:0px; -} - -.codemirror-colorview:hover { - border-color: #494949; -} - - -/* codemirror-colorpicker */ - -.codemirror-colorpicker { +.cm-colorview { position: relative; - width: 226px; - z-index: 1000; + white-space: nowrap; } -.codemirror-colorpicker > .color { + +.cm-colorview::before { + content: ""; + position: relative; + display: inline-block; + box-sizing: content-box; + margin: 0 3px; + width: 8px; + height: 8px; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAJElEQVQYV2NctWrVfwYkEBYWxojMZ6SDAmT7QGx0K1EcRBsFAADeG/3M/HteAAAAAElFTkSuQmCC"); + background-repeat: repeat; +} + +.cm-colorview + .cm-colorview.cm-overlay::before, +.cm-colorview.cm-overlay + .cm-colorview::before { + content: none; +} + +.codemirror-colorview-background { + position: absolute; + left: 2px; + top: 2px; + width: 10px; + height: 10px; + box-sizing: border-box; + border: 1px solid #8e8e8e; + content: ""; + cursor: pointer; +} + +.codemirror-colorview-background:hover { + border-color: #494949; +} + +/* colorpicker */ + +.colorpicker-theme-light { + --main-background-color: #fff; + --main-border-color: #ccc; + + --label-color: #666; + --label-color-hover: #000; + + --input-background-color: #fff; + --input-background-color-hover: #ddd; + --input-background-color-focus: #fff; + + --input-color: #444; + --input-color-focus: #000; + + --input-border-color: #bbb; + --input-border-color-focus: #888; + --input-border-color-hover: #444; + + --invalid-border-color: hsl(0, 100%, 50%); + --invalid-background-color: hsla(0, 100%, 50%, 0.15); + --invalid-color: hsl(0, 100%, 40%); +} + +.colorpicker-theme-dark { + --main-background-color: #242424; + --main-border-color: #888; + + --label-color: #aaa; + --label-color-hover: #eee; + + --input-background-color: #222; + --input-background-color-hover: #222; + --input-background-color-focus: #383838; + + --input-color: #ddd; + --input-color-focus: #fff; + + --input-border-color: #505050; + --input-border-color-focus: #777; + --input-border-color-hover: #888; + + --invalid-border-color: hsl(0, 100%, 27%); + --invalid-background-color: hsla(0, 100%, 50%, 0.3); + --invalid-color: hsl(0, 100%, 75%); +} + +.colorpicker-popup { + --switcher-width: 30px; + position: relative; + width: 350px; + z-index: 1000; + transition: opacity .5s; + color: var(--label-color); + border: 1px solid var(--main-border-color); + background-color: var(--main-background-color); + box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.12); + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} + +.colorpicker-popup[data-fading="1"] { + opacity: .75; +} + +.colorpicker-popup[data-fading="2"] { + opacity: 0; +} + +.colorpicker-saturation-container { position: relative; height: 120px; overflow: hidden; cursor: pointer; } -.codemirror-colorpicker > .color > .saturation { + +.colorpicker-opacity-bar { + position: absolute; + display: block; + content: ""; + left: 0; + right: 0; + bottom: 0; + top: 0; + background: linear-gradient(to right, rgba(232, 232, 232, 0), rgba(232, 232, 232, 1)); +} + +.colorpicker-saturation { position: relative; width: 100%; height: 100%; + background-color: rgba(204, 154, 129, 0); + background-image: linear-gradient(to right, #FFF, rgba(204, 154, 129, 0)); + background-repeat: repeat-x; } -.codemirror-colorpicker > .color > .saturation > .value { + +.colorpicker-value { position: relative; width: 100%; height: 100%; + background-image: linear-gradient(to top, #000, rgba(204, 154, 129, 0)); } -.codemirror-colorpicker > .color > .saturation > .value > .drag-pointer { + +.colorpicker-drag-pointer { position: absolute; width: 10px; height: 10px; @@ -60,21 +154,25 @@ border-radius: 50%; left: -5px; top: -5px; + border: 1px solid #fff; + box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.05); } -.codemirror-colorpicker > .control { + +.colorpicker-sliders { position: relative; - padding: 18px 0px 14px 0px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - -o-user-select: none; - user-select: none; + padding: 10px 0 6px 0; + border-top: 1px solid transparent; } -.codemirror-colorpicker > .control > .color, -.codemirror-colorpicker > .control > .empty { + +.colorpicker-theme-dark .colorpicker-sliders { + border-color: var(--input-border-color); +} + +.colorpicker-swatch, +.colorpicker-empty { position: absolute; left: 11px; - top: 24px; + top: 17px; width: 30px; height: 30px; -webkit-border-radius: 50%; @@ -84,33 +182,41 @@ -moz-box-sizing: border-box; box-sizing: border-box; } -.codemirror-colorpicker > .control > .hue { + +.colorpicker-empty { + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAJElEQVQYV2NctWrVfwYkEBYWxojMZ6SDAmT7QGx0K1EcRBsFAADeG/3M/HteAAAAAElFTkSuQmCC") repeat; +} + +.colorpicker-hue { position: relative; padding: 6px 12px; - margin: 0px 0px 0px 45px; + margin: 0 0 0 45px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; - cursor: pointer; } -.codemirror-colorpicker > .control > .hue > .hue-container { + +.colorpicker-hue-container { position: relative; width: 100%; height: 10px; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; + cursor: pointer; + background: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); } -.codemirror-colorpicker > .control > .opacity { + +.colorpicker-opacity { position: relative; padding: 3px 12px; - margin: 0px 0px 0px 45px; + margin: 0 0 0 45px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; - cursor: pointer; } -.codemirror-colorpicker > .control > .opacity > .opacity-container { + +.colorpicker-opacity-container { position: relative; width: 100%; height: 10px; @@ -118,10 +224,13 @@ -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; + cursor: pointer; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAJElEQVQYV2NctWrVfwYkEBYWxojMZ6SDAmT7QGx0K1EcRBsFAADeG/3M/HteAAAAAElFTkSuQmCC"); + background-repeat: repeat; } -.codemirror-colorpicker > .control .drag-bar, -.codemirror-colorpicker > .control .drag-bar2 { +.colorpicker-hue-knob, +.colorpicker-opacity-knob { position: absolute; cursor: pointer; top: 50% !important; @@ -132,179 +241,148 @@ -webkit-border-radius: 50px; -moz-border-radius: 50px; border-radius: 50px; + border: 1px solid rgba(0, 0, 0, 0.5); + box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.1); + background-color: #fff; } -.codemirror-colorpicker > .information { + +.colorpicker-input-container { position: relative; -webkit-box-sizing: padding-box; -moz-box-sizing: padding-box; box-sizing: padding-box; } -.codemirror-colorpicker > .information.hex > .information-item.hex { - display: flex; -} - -.codemirror-colorpicker > .information.rgb > .information-item.rgb { - display: flex; -} - -.codemirror-colorpicker > .information.hsl > .information-item.hsl { - display: flex; -} - -.codemirror-colorpicker > .information > .information-item { - display:none; +.colorpicker-input-group { + display: none; position: relative; - padding: 0px 5px; + padding: 0 5px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; - margin-right:40px; + margin-right: calc(var(--switcher-width) - 10px); } -.codemirror-colorpicker > .information > .information-change { - position:absolute; - display:block; - width:40px; - top:0px; - right:0px; - bottom:0px; +.colorpicker-input-group[data-active] { + display: flex; } -.codemirror-colorpicker > .information > .information-change > .format-change-button { - width:100%; - height:100%; - background:transparent; - border:0px; - cursor:pointer; - outline:none; -} - -.codemirror-colorpicker > .information > .information-item > .input-field { - display:block; - flex:1; +.colorpicker-input-field { + display: block; + position: relative; + flex: 1; padding: 5px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } -.codemirror-colorpicker > .information > .information-item > .input-field > input { - text-align: center; - width:100%; - padding:3px 5px; - font-size:11px; - color: #333; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - -webkit-user-select: text; - -moz-user-select: text; - -ms-user-select: text; - -o-user-select: text; - user-select: text; - border: 1px solid #cbcbcb; +.colorpicker-input-field[class$="-a"] { + flex-grow: 1.5; } -.codemirror-colorpicker > .information > .information-item > .input-field > .title { - text-align:center; - font-size:12px; - color:#a9a9a9; -} - -.codemirror-colorpicker > .information > input { +.colorpicker-hsl-h::before { + content: "\b0"; /* degree */ position: absolute; + right: -2px; + top: 8px; +} + +.colorpicker-hsl-s::before, +.colorpicker-hsl-l::before { + content: "%"; + position: absolute; + right: -1ex; + top: 8px; font-size: 10px; - height: 20px; - bottom: 20px; - padding: 0 0 0 2px; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; +} + +.colorpicker-input { + text-align: center; + width: 100%; + padding: 3px 5px; + font-size: 11px; + font-weight: bold; box-sizing: border-box; -webkit-user-select: text; -moz-user-select: text; -ms-user-select: text; -o-user-select: text; user-select: text; + border: 1px solid var(--input-border-color); + background-color: var(--input-background-color); + color: var(--input-color); } - -.codemirror-colorpicker { - border: 1px solid #ececec; - background-color: #fff; - -webkit-box-shadow: 0 0px 10px 0px rgba(0, 0, 0, 0.12); - -moz-box-shadow: 0 0px 10px 0px rgba(0, 0, 0, 0.12); - box-shadow: 0 0px 10px 0px rgba(0, 0, 0, 0.12); -} -.codemirror-colorpicker > .color > .saturation { - background-color: rgba(204, 154, 129, 0); - background-image: -moz-linear-gradient(left, #FFF, rgba(204, 154, 129, 0)); - background-image: -webkit-gradient(linear, 0 0, 100% 0, from(#FFF), to(rgba(204, 154, 129, 0))); - background-image: -webkit-linear-gradient(left, #FFF, rgba(204, 154, 129, 0)); - background-image: -o-linear-gradient(left, #FFF, rgba(204, 154, 129, 0)); - background-image: linear-gradient(to right, #FFF, rgba(204, 154, 129, 0)); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#00cc9a81', GradientType=1); -} -.codemirror-colorpicker > .color > .saturation > .value { - background-image: -webkit-gradient(linear, 0 100%, 0 0, from(#000000), to(rgba(204, 154, 129, 0))); - background-image: -webkit-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0)); - background-image: -moz-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0)); - background-image: -o-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0)); - background-image: -ms-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0)); - background-image: linear-gradient(to top, #000000, rgba(204, 154, 129, 0)); -} -.codemirror-colorpicker > .color > .saturation > .value > .drag-pointer { - border: 1px solid #fff; - -webkit-box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.05); - -moz-box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.05); - box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.05); -} -.codemirror-colorpicker > .control > .hue > .hue-container { - background: -moz-linear-gradient(left, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); - background: -ms-linear-gradient(left, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); - background: -o-linear-gradient(left, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); - background: -webkit-gradient(linear, left top, right top, from(#ff0000), color-stop(0.17, #ffff00), color-stop(0.33, #00ff00), color-stop(0.5, #00ffff), color-stop(0.67, #0000ff), color-stop(0.83, #ff00ff), to(#ff0000)); - background: -webkit-linear-gradient(left, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); - background: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); -} -.codemirror-colorpicker > .control > .opacity > .opacity-container > .color-bar { - position:absolute; - display:block; - content:""; - left:0px; - right:0px; - bottom:0px; - top:0px; - background: -moz-linear-gradient(left, rgba(232, 232, 232, 0), rgba(232, 232, 232, 1)); - background: -ms-linear-gradient(left, rgba(232, 232, 232, 0), rgba(232, 232, 232, 1)); - background: -o-linear-gradient(left, rgba(232, 232, 232, 0), rgba(232, 232, 232, 1)); - background: -webkit-gradient(linear, left top, right top, from(rgba(232, 232, 232, 0)), to(rgba(232, 232, 232, 1))); - background: -webkit-linear-gradient(left, rgba(232, 232, 232, 0), rgba(232, 232, 232, 1)); - background: linear-gradient(to right, rgba(232, 232, 232, 0) , rgba(232, 232, 232, 1) ); -} -.codemirror-colorpicker > .control > .opacity > .opacity-container { - background-image : url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAJElEQVQYV2NctWrVfwYkEBYWxojMZ6SDAmT7QGx0K1EcRBsFAADeG/3M/HteAAAAAElFTkSuQmCC"); - background-repeat: repeat; +.colorpicker-theme-dark .colorpicker-input::-webkit-inner-spin-button { + -webkit-filter: invert(1); + filter: invert(1); } -.codemirror-colorpicker > .control > .empty { - background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAJElEQVQYV2NctWrVfwYkEBYWxojMZ6SDAmT7QGx0K1EcRBsFAADeG/3M/HteAAAAAElFTkSuQmCC") repeat; +.colorpicker-input:hover { + border-color: var(--input-border-color-hover); } -.codemirror-colorpicker > .control .drag-bar, -.codemirror-colorpicker > .control .drag-bar2 { - border: 1px solid rgba(0, 0, 0, 0.5); - -webkit-box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.1); - -moz-box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.1); - box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.1); - background-color: #FFFFFF; + +.colorpicker-input:focus { + color: var(--input-color-focus); + border-color: var(--input-border-color-focus); + background-color: var(--input-background-color-focus); } -.codemirror-colorpicker > .information { - /*border-top: 1px solid #e8e8e8;*/ + +.colorpicker-theme-dark input:focus { + outline: none !important; } -.codemirror-colorpicker > .information > .title { - color: #a3a3a3; + +.colorpicker-input:invalid { + border-color: var(--invalid-border-color); + background-color: var(--invalid-background-color); + color: var(--invalid-color); +} + +.colorpicker-title { + text-align: center; + font-size: 12px; + font-family: monospace; + display: flex; + justify-content: center; + color: var(--label-color); +} + +.colorpicker-title-action { + cursor: pointer; +} + +.colorpicker-title-action[data-active] { + font-weight: bold; + color: var(--input-color); + cursor: default; + pointer-events: none; +} + +.colorpicker-format-change { + position: absolute; + display: block; + width: var(--switcher-width); + top: 0; + right: 0; + bottom: 0; + overflow: hidden; +} + +.colorpicker-format-change-button { + width: 100%; + height: 100%; + background: transparent; + border: 0; + cursor: pointer; + outline: none; + font-family: monospace !important; + font-size: var(--switcher-width) !important; + margin-top: -5px; + color: var(--label-color); + text-align: center; +} + +.colorpicker-format-change-button:hover { + color: var(--label-color-hover); } -.codemirror-colorpicker > .information > .input { - color: #333; -} \ No newline at end of file diff --git a/vendor-overwrites/colorpicker/colorpicker.js b/vendor-overwrites/colorpicker/colorpicker.js index 25fc8741..c430d5cc 100644 --- a/vendor-overwrites/colorpicker/colorpicker.js +++ b/vendor-overwrites/colorpicker/colorpicker.js @@ -1,1369 +1,869 @@ -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("codemirror")); - else if (typeof define == "function" && define.amd) // AMD - define(["codemirror" ], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { - - CodeMirror.defineExtension("colorpicker", function () { - - var cm = this; - - var color = { - - trim : function (str) { - return str.replace(/^\s+|\s+$/g, ''); - }, - - /** - * @method format - * - * convert color to format string - * - * // hex - * color.format({ r : 255, g : 255, b : 255 }, 'hex') // #FFFFFF - * - * // rgb - * color.format({ r : 255, g : 255, b : 255 }, 'rgb') // rgba(255, 255, 255, 0.5); - * - * // rgba - * color.format({ r : 255, g : 255, b : 255, a : 0.5 }, 'rgb') // rgba(255, 255, 255, 0.5); - * - * @param {Object} obj obj has r, g, b and a attributes - * @param {"hex"/"rgb"} type format string type - * @returns {*} - */ - format : function(obj, type) { - if (type == 'hex') { - var r = obj.r.toString(16); - if (obj.r < 16) r = "0" + r; - - var g = obj.g.toString(16); - if (obj.g < 16) g = "0" + g; - - var b = obj.b.toString(16); - if (obj.b < 16) b = "0" + b; - - return "#" + [r, g, b].join(""); - } else if (type == 'rgb') { - if (typeof obj.a == 'undefined') { - return "rgb(" + [obj.r, obj.g, obj.b].join(",") + ")"; - } else { - return "rgba(" + [obj.r, obj.g, obj.b, obj.a].join(",") + ")"; - } - } else if (type == 'hsl') { - if (typeof obj.a == 'undefined') { - return "hsl(" + [obj.h, obj.s + '%', obj.l + '%'].join(",") + ")"; - } else { - return "hsla(" + [obj.h, obj.s + '%', obj.l + '%', obj.a].join(",") + ")"; - } - } - - return obj; - }, - - /** - * @method rgb - * - * parse string to rgb color - * - * color.rgb("#FF0000") === { r : 255, g : 0, b : 0 } - * - * color.rgb("rgb(255, 0, 0)") == { r : 255, g : 0, b : } - * - * @param {String} str color string - * @returns {Object} rgb object - */ - parse : function (str) { - if (typeof str == 'string') { - if (str.indexOf("rgb(") > -1) { - var arr = str.replace("rgb(", "").replace(")","").split(","); - - for(var i = 0, len = arr.length; i < len; i++) { - arr[i] = parseInt(color.trim(arr[i]), 10); - } - - return { type : 'rgb', r : arr[0], g : arr[1], b : arr[2], a : 1 }; - } else if (str.indexOf("rgba(") > -1) { - var arr = str.replace("rgba(", "").replace(")", "").split(","); - - for (var i = 0, len = arr.length; i < len; i++) { - - if (len - 1 == i) { - arr[i] = parseFloat(color.trim(arr[i])); - } else { - arr[i] = parseInt(color.trim(arr[i]), 10); - } - } - - return {type: 'rgb', r: arr[0], g: arr[1], b: arr[2], a: arr[3]}; - } else if (str.indexOf("hsl(") > -1) { - var arr = str.replace("hsl(", "").replace(")","").split(","); - - for(var i = 0, len = arr.length; i < len; i++) { - arr[i] = parseInt(color.trim(arr[i]), 10); - } - - var obj = { type : 'hsl', h : arr[0], s : arr[1], l : arr[2], a : 1 }; - - var temp = color.HSLtoRGB(obj.h, obj.s, obj.l); - - obj.r = temp.r; - obj.g = temp.g; - obj.b = temp.b; - - return obj; - } else if (str.indexOf("hsla(") > -1) { - var arr = str.replace("hsla(", "").replace(")", "").split(","); - - for (var i = 0, len = arr.length; i < len; i++) { - - if (len - 1 == i) { - arr[i] = parseFloat(color.trim(arr[i])); - } else { - arr[i] = parseInt(color.trim(arr[i]), 10); - } - } - - var obj = {type: 'hsl', h: arr[0], s: arr[1], l: arr[2], a: arr[3]}; - - var temp = color.HSLtoRGB(obj.h, obj.s, obj.l); - - obj.r = temp.r; - obj.g = temp.g; - obj.b = temp.b; - - return obj; - } else if (str.indexOf("#") == 0) { - - str = str.replace("#", ""); - - var arr = []; - if (str.length == 3) { - for(var i = 0, len = str.length; i < len; i++) { - var char = str.substr(i, 1); - arr.push(parseInt(char+char, 16)); - } - } else { - for(var i = 0, len = str.length; i < len; i+=2) { - arr.push(parseInt(str.substr(i, 2), 16)); - } - } - - return { type : 'hex', r : arr[0], g : arr[1], b : arr[2], a : 1 }; - } - } - - return str; - - }, - - /** - * @method HSVtoRGB - * - * convert hsv to rgb - * - * color.HSVtoRGB(0,0,1) === #FFFFF === { r : 255, g : 0, b : 0 } - * - * @param {Number} H hue color number (min : 0, max : 360) - * @param {Number} S Saturation number (min : 0, max : 1) - * @param {Number} V Value number (min : 0, max : 1 ) - * @returns {Object} - */ - HSVtoRGB : function (H, S, V) { - - if (H == 360) { - H = 0; - } - - var C = S * V; - var X = C * (1 - Math.abs((H/60) % 2 -1) ); - var m = V - C; - - var temp = []; - - if (0 <= H && H < 60) { temp = [C, X, 0]; } - else if (60 <= H && H < 120) { temp = [X, C, 0]; } - else if (120 <= H && H < 180) { temp = [0, C, X]; } - else if (180 <= H && H < 240) { temp = [0, X, C]; } - else if (240 <= H && H < 300) { temp = [X, 0, C]; } - else if (300 <= H && H < 360) { temp = [C, 0, X]; } - - return { - r : Math.ceil((temp[0] + m) * 255), - g : Math.ceil((temp[1] + m) * 255), - b : Math.ceil((temp[2] + m) * 255) - }; - }, - - /** - * @method RGBtoHSV - * - * convert rgb to hsv - * - * color.RGBtoHSV(0, 0, 255) === { h : 240, s : 1, v : 1 } === '#FFFF00' - * - * @param {Number} R red color value - * @param {Number} G green color value - * @param {Number} B blue color value - * @return {Object} hsv color code - */ - RGBtoHSV : function (R, G, B) { - - var R1 = R / 255; - var G1 = G / 255; - var B1 = B / 255; - - var MaxC = Math.max(R1, G1, B1); - var MinC = Math.min(R1, G1, B1); - - var DeltaC = MaxC - MinC; - - var H = 0; - - if (DeltaC == 0) { H = 0; } - else if (MaxC == R1) { - H = 60 * (( (G1 - B1) / DeltaC) % 6); - } else if (MaxC == G1) { - H = 60 * (( (B1 - R1) / DeltaC) + 2); - } else if (MaxC == B1) { - H = 60 * (( (R1 - G1) / DeltaC) + 4); - } - - if (H < 0) { - H = 360 + H; - } - - var S = 0; - - if (MaxC == 0) S = 0; - else S = DeltaC / MaxC; - - var V = MaxC; - - return { h : H, s : S, v : V }; - }, - - RGBtoHSL : function (r, g, b) { - r /= 255, g /= 255, b /= 255; - var max = Math.max(r, g, b), min = Math.min(r, g, b); - var h, s, l = (max + min) / 2; - - if(max == min){ - h = s = 0; // achromatic - }else{ - var d = max - min; - s = l > 0.5 ? d / (2 - max - min) : d / (max + min); - switch(max){ - case r: h = (g - b) / d + (g < b ? 6 : 0); break; - case g: h = (b - r) / d + 2; break; - case b: h = (r - g) / d + 4; break; - } - h /= 6; - } - - return { h : Math.round(h * 360) , s : Math.round(s * 100), l : Math.round(l * 100)}; - }, - - HUEtoRGB : function (p, q, t) { - if(t < 0) t += 1; - if(t > 1) t -= 1; - if(t < 1/6) return p + (q - p) * 6 * t; - if(t < 1/2) return q; - if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; - return p; - }, - - HSLtoRGB : function (h, s, l) { - var r, g, b; - - h /= 360; - s /= 100; - l /= 100; - - if(s == 0){ - r = g = b = l; // achromatic - }else{ - var q = l < 0.5 ? l * (1 + s) : l + s - l * s; - var p = 2 * l - q; - r = this.HUEtoRGB(p, q, h + 1/3); - g = this.HUEtoRGB(p, q, h); - b = this.HUEtoRGB(p, q, h - 1/3); - } - - return { r : r * 255, g : g * 255, b : b * 255 }; - } - }; - - var hue_color = [ - { rgb : '#ff0000', start : .0 }, - { rgb : '#ffff00', start : .17 }, - { rgb : '#00ff00', start : .33 }, - { rgb : '#00ffff', start : .50 }, - { rgb : '#0000ff', start : .67 }, - { rgb : '#ff00ff', start : .83 }, - { rgb : '#ff0000', start : 1 } - ]; - - var $body, $root, $hue, $color, $value, $saturation, $drag_pointer, $drag_bar, - $control, $controlPattern, $controlColor, $hueContainer, $opacity, $opacityContainer, $opacityColorBar, $formatChangeButton, - $opacity_drag_bar, $information, $informationChange; - - var currentA, currentH, currentS, currentV; - var $hexCode; - var $rgb_r, $rgb_g, $rgb_b, $rgb_a; - var $hsl_h, $hsl_s, $hsl_l, $hsl_a; - var cssPrefix = getCssValuePrefix(); - - var colorpickerCallback = function () {}; - var counter = 0; - var cached = {}; - var isColorPickerShow = false; - var isShortCut = false; - var hideDelay = 2000; - - function dom(tag, className, attr) { - - if (typeof tag != 'string') { - this.el = tag; - } else { - - var el = document.createElement(tag); - - this.uniqId = counter++; - - el.className = className; - - attr = attr || {}; - - for(var k in attr) { - el.setAttribute(k, attr[k]); - } - - this.el = el; - } - } - - dom.prototype.closest = function (cls) { - - var temp = this; - var checkCls = false; - - while(!(checkCls = temp.hasClass(cls))) { - if (temp.el.parentNode) { - temp = new dom(temp.el.parentNode); - } else { - return null; - } - } - - if (checkCls) { - return temp; - } - - return null; - } - - dom.prototype.removeClass = function (cls) { - this.el.className = color.trim((" " + this.el.className + " ").replace(' ' + cls + ' ', ' ')); - } - - dom.prototype.hasClass = function (cls) { - if (!this.el.className) - { - return false; - } else { - var newClass = ' ' + this.el.className + ' '; - return newClass.indexOf(' ' + cls + ' ') > -1; - } - } - - dom.prototype.addClass = function (cls) { - if (!this.hasClass(cls)) { - this.el.className = this.el.className + " " + cls; - } - - } - - dom.prototype.html = function (html) { - this.el.innerHTML = html; - - return this; - } - - dom.prototype.empty = function () { - return this.html(''); - } - - dom.prototype.append = function (el) { - - if (typeof el == 'string') { - this.el.appendChild(document.createTextNode(el)); - } else { - this.el.appendChild(el.el || el); - } - - return this; - } - - dom.prototype.appendTo = function (target) { - var t = target.el ? target.el : target; - - t.appendChild(this.el); - - return this; - } - - dom.prototype.remove = function () { - if (this.el.parentNode) { - this.el.parentNode.removeChild(this.el); - } - - return this; - } - - dom.prototype.text = function () { - return this.el.textContent; - } - - dom.prototype.css = function (key, value) { - if (arguments.length == 2) { - this.el.style[key] = value; - } else if (arguments.length == 1) { - - if (typeof key == 'string') { - return getComputedStyle(this.el)[key]; - } else { - var keys = key || {}; - for(var k in keys) { - this.el.style[k] = keys[k]; - } - } - - } - - return this; - } - - dom.prototype.offset = function () { - var rect = this.el.getBoundingClientRect(); - - return { - top: rect.top + document.body.scrollTop, - left: rect.left + document.body.scrollLeft - }; - } - - dom.prototype.position = function () { - return { - top: parseFloat(this.el.style.top), - left: parseFloat(this.el.style.left) - }; - } - - dom.prototype.width = function () { - return this.el.offsetWidth; - } - - dom.prototype.height = function () { - return this.el.offsetHeight; - } - - dom.prototype.dataKey = function (key) { - return this.uniqId + '.' + key; - } - - dom.prototype.data = function (key, value) { - if (arguments.length == 2) { - cached[this.dataKey(key)] = value; - } else if (arguments.length == 1) { - return cached[this.dataKey(key)]; - } else { - var keys = Object.keys(cached); - - var uniqId = this.uniqId + "."; - return keys.filter(function (key) { - if (key.indexOf(uniqId) == 0) { - return true; - } - - return false; - }).map(function (value) { - return cached[value]; - }) - } - - return this; - } - - dom.prototype.val = function (value) { - if (arguments.length == 0) { - return this.el.value; - } else if (arguments.length == 1) { - this.el.value = value; - } - - return this; - } - - dom.prototype.int = function () { - return parseInt(this.val(), 10); - } - - dom.prototype.show = function () { - return this.css('display', 'block'); - } - - dom.prototype.hide = function () { - return this.css('display', 'none'); - } - - function setRGBInput(r, g, b) { - $rgb_r.val(r); - $rgb_g.val(g); - $rgb_b.val(b); - $rgb_a.val(currentA); - } - - function setHSLInput(h, s, l) { - $hsl_h.val(h); - $hsl_s.val(s + '%'); - $hsl_l.val(l + '%'); - $hsl_a.val(currentA); - } - - function getHexFormat() { - return color.format({ - r : $rgb_r.int(), - g : $rgb_g.int(), - b : $rgb_b.int() - }, 'hex'); - } - - function convertRGB() { - return color.HSVtoRGB(currentH, currentS, currentV); - } - - function convertHEX() { - return color.format(convertRGB(), 'hex'); - } - - function convertHSL() { - var rgb = color.HSVtoRGB(currentH, currentS, currentV); - return color.RGBtoHSL(rgb.r, rgb.g, rgb.b); - } - - function getFormattedColor (format) { - format = format || 'hex'; - - if (format == 'rgb') { - var rgb = convertRGB(); - rgb.a = currentA == 1 ? undefined : currentA; - return color.format(rgb, 'rgb'); - } else if (format == 'hsl') { - var hsl = convertHSL(); - hsl.a = currentA == 1 ? undefined : currentA; - return color.format(hsl, 'hsl'); - } else { - var rgb = convertRGB(); - return color.format(rgb, 'hex'); - } - } - - function setControlColor (color) { - $controlColor.css('background-color', color); - } - - function setInputColor() { - - var format = $information.data('format') || 'hex'; - - var rgb = null; - if (format == 'hex') { - $hexCode.val(convertHEX()); - } else if (format == 'rgb') { - var rgb = convertRGB(); - setRGBInput(rgb.r, rgb.g, rgb.b); - } else if (format == 'hsl') { - var hsl = convertHSL(); - setHSLInput(hsl.h, hsl.s, hsl.l); - } - - // set background - setControlColor(getFormattedColor('rgb')); - - var rgb = convertRGB(); - var colorString = color.format(rgb, 'rgb'); - setOpacityColorBar(colorString); - - if (typeof colorpickerCallback == 'function') { - - if (!isNaN(currentA)) { - colorpickerCallback(getFormattedColor(format)); - } - - } - } - - function setMainColor(e) { - e.preventDefault(); - var pos = $root.position(); // position for screen - var w = $color.width(); - var h = $color.height(); - - var x = e.clientX - pos.left; - var y = e.clientY - pos.top; - - if (x < 0) x = 0; - else if (x > w) x = w; - - if (y < 0) y = 0; - else if (y > h) y = h; - - $drag_pointer.css({ - left: (x - 5) + 'px', - top: (y - 5) + 'px' - }); - - $drag_pointer.data('pos', { x: x, y : y}); - - caculateHSV() - setInputColor(); - } - - function scale (startColor, endColor, t) { - var obj = { - r : parseInt(startColor.r + (endColor.r - startColor.r) * t, 10) , - g : parseInt(startColor.g + (endColor.g - startColor.g) * t, 10), - b : parseInt(startColor.b + (endColor.b - startColor.b) * t, 10) - }; - - return color.format(obj, 'hex'); - - } - - function checkHueColor(p) { - var startColor, endColor; - - for(var i = 0; i < hue_color.length;i++) { - if (hue_color[i].start >= p) { - startColor = hue_color[i-1]; - endColor = hue_color[i]; - break; - } - } - - if (startColor && endColor) { - return scale(startColor, endColor, (p - startColor.start)/(endColor.start - startColor.start)); - } - - return hue_color[0].rgb; - } - - function setBackgroundColor (color) { - $color.css("background-color", color); - } - - function setCurrentH (h) { - currentH = h; - } - - function setHueColor(e) { - var min = $hueContainer.offset().left; - var max = min + $hueContainer.width(); - var current = e ? pos(e).clientX : min + (max - min) * (currentH / 360); - - var dist; - if (current < min) { - dist = 0; - } else if (current > max) { - dist = 100; - } else { - dist = (current - min) / (max - min) * 100; - } - - var x = ($hueContainer.width() * (dist/100)); - - $drag_bar.css({ - left: (x -Math.ceil($drag_bar.width()/2)) + 'px' - }); - - $drag_bar.data('pos', { x : x}); - - var hueColor = checkHueColor(dist/100); - - setBackgroundColor(hueColor); - setCurrentH((dist/100) * 360); - setInputColor(); - } - - function getCssValuePrefix() - { - var rtrnVal = '';//default to standard syntax - var prefixes = ['', '-o-', '-ms-', '-moz-', '-webkit-']; - - // Create a temporary DOM object for testing - var dom = document.createElement('div'); - - for (var i = 0; i < prefixes.length; i++) - { - // Attempt to set the style - dom.style.background = prefixes[i] + 'linear-gradient(#000000, #ffffff)'; - - // Detect if the style was successfully set - if (dom.style.background) - { - rtrnVal = prefixes[i]; - } - } - - dom = null; - delete dom; - - return rtrnVal; - } - - function setOpacityColorBar(hueColor) { - var rgb = color.parse(hueColor); - - rgb.a = 0; - var start = color.format(rgb, 'rgb'); - - rgb.a = 1; - var end = color.format(rgb, 'rgb'); - - var prefix = cssPrefix; - $opacityColorBar.css('background', 'linear-gradient(to right, ' + start + ', ' + end + ')'); - } - - function setOpacity(e) { - var min = $opacityContainer.offset().left; - var max = min + $opacityContainer.width(); - var current = pos(e).clientX; - var dist; - - if (current < min) { - dist = 0; - } else if (current > max) { - dist = 100; - } else { - dist = (current - min) / (max - min) * 100; - } - - var x = ($opacityContainer.width() * (dist/100)); - - $opacity_drag_bar.css({ - left: (x -Math.ceil($opacity_drag_bar.width()/2)) + 'px' - }); - - $opacity_drag_bar.data('pos', { x : x }); - - caculateOpacity(); - currentFormat(); - setInputColor(); - } - - function caculateOpacity() { - var opacityPos = $opacity_drag_bar.data('pos') || { x : 0 }; - var a = Math.round((opacityPos.x / $opacityContainer.width()) * 100) / 100; - - currentA = isNaN(a) ? 1 : a; - } - - function caculateHSV() { - var pos = $drag_pointer.data('pos') || { x : 0, y : 0 }; - var huePos = $drag_bar.data('pos') || { x : 0 }; - - var width = $color.width(); - var height = $color.height(); - - var h = (huePos.x / $hueContainer.width()) * 360; - var s = (pos.x / width); - var v = ((height - pos.y) / height); - - if (width == 0) { - h = 0; - s = 0; - v = 0; - } - - currentH = h; - currentS = s; - currentV = v; - } - - function pos(e) { - if (e.touches && e.touches[0]) { - return e.touches[0]; - } - - return e; - } - - function checkNumberKey(e) { - var code = e.which, - isExcept = false; - - if(code == 37 || code == 39 || code == 8 || code == 46 || code == 9) - isExcept = true; - - if(!isExcept && (code < 48 || code > 57)) - return false; - - return true; - } - - function setRGBtoHexColor(e) { - var r = $rgb_r.val(), - g = $rgb_g.val(), - b = $rgb_b.val(); - - if(r == "" || g == "" || b == "") return; - - if(parseInt(r) > 255) $rgb_r.val(255); - else $rgb_r.val(parseInt(r)); - - if(parseInt(g) > 255) $rgb_g.val(255); - else $rgb_g.val(parseInt(g)); - - if(parseInt(b) > 255) $rgb_b.val(255); - else $rgb_b.val(parseInt(b)); - - initColor(getHexFormat()); - } - - function setColorUI() { - var x = $color.width() * currentS, y = $color.height() * ( 1 - currentV ); - - $drag_pointer.css({ - left : (x - 5) + "px", - top : (y - 5) + "px" - }); - - $drag_pointer.data('pos', { x : x, y : y }); - - var hueX = $hueContainer.width() * (currentH / 360); - - $drag_bar.css({ - left : (hueX - 7.5) + 'px' - }); - - $drag_bar.data('pos', { x : hueX }); - - var opacityX = $opacityContainer.width() * (currentA || 0); - - $opacity_drag_bar.css({ - left : (opacityX - 7.5) + 'px' - }); - - $opacity_drag_bar.data('pos', { x : opacityX }); - } - - function setCurrentHSV (h, s, v, a) { - currentA = a; - currentH = h; - currentS = s; - currentV = v; - } - - function setCurrentFormat (format) { - $information.data('format', format); - initFormat(); - } - - - - function initColor(newColor) { - var c = newColor || "#FF0000", colorObj = color.parse(c); - - setCurrentFormat(colorObj.type); - setBackgroundColor(c); - - var hsv = color.RGBtoHSV(colorObj.r, colorObj.g, colorObj.b); - - setCurrentHSV(hsv.h, hsv.s, hsv.v, colorObj.a); - setColorUI(); - setHueColor(); - setInputColor(); - } - - function addEvent (dom, eventName, callback) { - dom.addEventListener(eventName, callback); - } - - function removeEvent(dom, eventName, callback) { - dom.removeEventListener(eventName, callback); - } - - function EventColorMouseDown(e) { - $color.data('isDown', true); - setMainColor(e); - } - - function EventColorMouseUp(e) { - $color.data('isDown', false); - } - - function EventDragBarMouseDown (e) { - e.preventDefault(); - $hue.data('isDown', true); - } - - function EventOpacityDragBarMouseDown(e) { - e.preventDefault(); - $opacity.data('isDown', true); - } - - function EventHueMouseDown (e) { - $hue.data('isDown', true); - setHueColor(e); - } - - function EventOpacityMouseDown (e) { - $opacity.data('isDown', true); - setOpacity(e); - } - - function EventHexCodeKeyDown(e) { - if(e.which < 65 || e.which > 70) { - return checkNumberKey(e); - } - } - - function EventHexCodeKeyUp (e) { - var code = $hexCode.val(); - - if(code.charAt(0) == '#' && code.length == 7) { - initColor(code); - } - } - - function EventFormatChangeClick(e) { - nextFormat(); - } - - function initEvent() { - addEvent($color.el, 'mousedown', EventColorMouseDown); - addEvent($color.el, 'mouseup', EventColorMouseUp); - addEvent($drag_bar.el, 'mousedown', EventDragBarMouseDown); - addEvent($opacity_drag_bar.el, 'mousedown', EventOpacityDragBarMouseDown); - addEvent($hueContainer.el, 'mousedown', EventHueMouseDown); - addEvent($opacityContainer.el, 'mousedown', EventOpacityMouseDown); - addEvent($hexCode.el, 'keydown', EventHexCodeKeyDown); - addEvent($hexCode.el, 'keyup', EventHexCodeKeyUp); - - addEvent($rgb_r.el, 'keydown', checkNumberKey); - addEvent($rgb_r.el, 'keyup', setRGBtoHexColor); - addEvent($rgb_g.el, 'keydown', checkNumberKey); - addEvent($rgb_g.el, 'keyup', setRGBtoHexColor); - addEvent($rgb_b.el, 'keydown', checkNumberKey); - addEvent($rgb_b.el, 'keyup', setRGBtoHexColor); - - addEvent(document, 'mouseup', EventDocumentMouseUp); - addEvent(document, 'mousemove', EventDocumentMouseMove); - - addEvent($formatChangeButton.el, 'click', EventFormatChangeClick) - } - - function checkColorPickerClass(el) { - var hasColorView = new dom(el).closest('codemirror-colorview'); - var hasColorPicker = new dom(el).closest('codemirror-colorpicker'); - var hasCodeMirror = new dom(el).closest('CodeMirror'); - var IsInHtml = el.nodeName == 'HTML'; - - return !!(hasColorPicker || hasColorView || hasCodeMirror); - } - - function checkInHtml (el) { - var IsInHtml = el.nodeName == 'HTML'; - - return IsInHtml; - } - - function EventDocumentMouseUp (e) { - $color.data('isDown', false); - $hue.data('isDown', false); - $opacity.data('isDown', false); - - // when color picker clicked in outside - if (checkInHtml(e.target)) { - //setHideDelay(hideDelay); - } else if (checkColorPickerClass(e.target) == false ) { - hide(); - } - - } - - function EventDocumentMouseMove(e) { - if ($color.data('isDown')) { - setMainColor(e); - } - - if ($hue.data('isDown')) { - setHueColor(e); - } - - if ($opacity.data('isDown')) { - setOpacity(e); - } - } - - function destroy() { - removeEvent($color.el, 'mousedown', EventColorMouseDown); - removeEvent($color.el, 'mouseup', EventColorMouseUp); - removeEvent($drag_bar.el, 'mousedown', EventDragBarMouseDown); - removeEvent($opacity_drag_bar.el, 'mousedown', EventOpacityDragBarMouseDown); - removeEvent($hueContainer.el, 'mousedown', EventHueMouseDown); - removeEvent($opacityContainer.el, 'mousedown', EventOpacityMouseDown); - removeEvent($hexCode.el, 'keydown', EventHexCodeKeyDown); - removeEvent($hexCode.el, 'keyup', EventHexCodeKeyUp); - removeEvent($rgb_r.el, 'keydown', checkNumberKey); - removeEvent($rgb_r.el, 'keyup', setRGBtoHexColor); - removeEvent($rgb_g.el, 'keydown', checkNumberKey); - removeEvent($rgb_g.el, 'keyup', setRGBtoHexColor); - removeEvent($rgb_b.el, 'keydown', checkNumberKey); - removeEvent($rgb_b.el, 'keyup', setRGBtoHexColor); - removeEvent(document, 'mouseup', EventDocumentMouseUp); - removeEvent(document, 'mousemove', EventDocumentMouseMove); - removeEvent($formatChangeButton.el, 'click', EventFormatChangeClick); - - // remove color picker callback - colorpickerCallback = undefined; - } - - function currentFormat () { - var current_format = $information.data('format') || 'hex'; - if (currentA < 1 && current_format == 'hex' ) { - var next_format = 'rgb'; - $information.removeClass(current_format); - $information.addClass(next_format); - $information.data('format', next_format); - - setInputColor(); - } - } - - function initFormat () { - var current_format = $information.data('format') || 'hex'; - - $information.removeClass('hex'); - $information.removeClass('rgb'); - $information.removeClass('hsl'); - $information.addClass(current_format); - } - - function nextFormat() { - var current_format = $information.data('format') || 'hex'; - - var next_format = 'hex'; - if (current_format == 'hex') { - next_format = 'rgb'; - } else if (current_format == 'rgb') { - next_format = 'hsl'; - } else if (current_format == 'hsl') { - if (currentA == 1) { - next_format = 'hex'; - } else { - next_format = 'rgb'; - } - } - - $information.removeClass(current_format); - $information.addClass(next_format); - $information.data('format', next_format); - - setInputColor(); - } - - function makeInputField(type) { - var item = new dom('div', 'information-item '+ type); - - if (type == 'hex') { - var field = new dom('div', 'input-field hex'); - - $hexCode = new dom('input', 'input', { type : 'text' }); - - field.append($hexCode); - field.append(new dom('div', 'title').html('HEX')); - - item.append(field); - - } else if (type == 'rgb') { - var field = new dom('div', 'input-field rgb-r'); - $rgb_r = new dom('input', 'input', { type : 'text' }); - - field.append($rgb_r); - field.append(new dom('div', 'title').html('R')); - - item.append(field); - - field = new dom('div', 'input-field rgb-g'); - $rgb_g = new dom('input', 'input', { type : 'text' }); - - field.append($rgb_g); - field.append(new dom('div', 'title').html('G')); - - item.append(field); - - field = new dom('div', 'input-field rgb-b'); - $rgb_b = new dom('input', 'input', { type : 'text' }); - - field.append($rgb_b); - field.append(new dom('div', 'title').html('B')); - - item.append(field); - - // rgba - field = new dom('div', 'input-field rgb-a'); - $rgb_a = new dom('input', 'input', { type : 'text' }); - - field.append($rgb_a); - field.append(new dom('div', 'title').html('A')); - - item.append(field); - - } else if (type == 'hsl') { - var field = new dom('div', 'input-field hsl-h'); - $hsl_h = new dom('input', 'input', { type : 'text' }); - - field.append($hsl_h); - field.append(new dom('div', 'title').html('H')); - - item.append(field); - - field = new dom('div', 'input-field hsl-s'); - $hsl_s = new dom('input', 'input', { type : 'text' }); - - field.append($hsl_s); - field.append(new dom('div', 'title').html('S')); - - item.append(field); - - field = new dom('div', 'input-field hsl-l'); - $hsl_l = new dom('input', 'input', { type : 'text' }); - - field.append($hsl_l); - field.append(new dom('div', 'title').html('L')); - - item.append(field); - - // rgba - field = new dom('div', 'input-field hsl-a'); - $hsl_a = new dom('input', 'input', { type : 'text' }); - - field.append($hsl_a); - field.append(new dom('div', 'title').html('A')); - - item.append(field); - } - - return item; - } - - function init() { - $body = new dom(document.body); - - $root = new dom('div', 'codemirror-colorpicker'); - $color = new dom('div', 'color'); - $drag_pointer = new dom('div', 'drag-pointer' ); - $value = new dom( 'div', 'value' ); - $saturation = new dom('div', 'saturation' ); - - $control = new dom('div', 'control' ); - $controlPattern = new dom('div', 'empty' ); - $controlColor = new dom('div', 'color' ); - $hue = new dom('div', 'hue' ); - $hueContainer = new dom('div', 'hue-container' ); - $drag_bar = new dom('div', 'drag-bar' ); - $opacity = new dom('div', 'opacity' ); - $opacityContainer = new dom('div', 'opacity-container' ); - $opacityColorBar = new dom('div', 'color-bar' ); - - $opacity_drag_bar = new dom('div', 'drag-bar2' ); - - $information = new dom('div', 'information hex' ); - - $informationChange = new dom('div', 'information-change'); - - $formatChangeButton = new dom('button', 'format-change-button', { type : 'button'}).html('↔'); - $informationChange.append($formatChangeButton); - - - $information.append(makeInputField('hex')); - $information.append(makeInputField('rgb')); - $information.append(makeInputField('hsl')); - $information.append($informationChange); - - - $value.append($drag_pointer); - $saturation.append($value); - $color.append($saturation); - - $hueContainer.append($drag_bar); - $hue.append($hueContainer); - - $opacityContainer.append($opacityColorBar); - $opacityContainer.append($opacity_drag_bar); - $opacity.append($opacityContainer); - - $control.append($hue); - $control.append($opacity); - $control.append($controlPattern); - $control.append($controlColor); - - $root.append($color); - $root.append($control); - $root.append($information); - - initHueColors(); - //initEvent(); - initColor(); - }; - - function initHueColors () { - for(var i = 0, len = hue_color.length; i < len; i++) { - var hue = hue_color[i]; - - var obj = color.parse(hue.rgb); - - hue.r = obj.r; - hue.g = obj.g; - hue.b = obj.b; - } - } - - /** - * public methods - */ - function setColor(value) { - if(typeof(value) == "object") { - if(!value.r || !value.g || !value.b) - return; - - initColor(color.format(value, "hex")); - } else if(typeof(value) == "string") { - if(value.charAt(0) != "#") - return; - - initColor(value); - } - } - - function getColor(type) { - caculateHSV(); - var rgb = convertRGB(); - - if (type) { - return color.format(rgb, type); - } - - return rgb; - } - - function definePostion (opt) { - - var width = $root.width(); - var height = $root.height(); - - // set left position for color picker - var elementScreenLeft = opt.left - $body.el.scrollLeft ; - if (width + elementScreenLeft > window.innerWidth) { - elementScreenLeft -= (width + elementScreenLeft) - window.innerWidth; - } - if (elementScreenLeft < 0) { elementScreenLeft = 0; } - - // set top position for color picker - var elementScreenTop = opt.top - $body.el.scrollTop ; - if (height + elementScreenTop > window.innerHeight) { - elementScreenTop -= (height + elementScreenTop) - window.innerHeight; - } - if (elementScreenTop < 0) { elementScreenTop = 0; } - - // set position - $root.css({ - left : elementScreenLeft + 'px', - top : elementScreenTop + 'px' - }); - } - - function show (opt, color, callback) { - destroy(); - initEvent(); - $root.appendTo(document.body); - - $root.css({ - position: 'fixed', // color picker has fixed position - left : '-10000px', - top : '-10000px' - }); - - $root.show(); - - definePostion(opt); - - isColorPickerShow = true; - - isShortCut = opt.isShortCut || false; - - initColor(color); - - // define colorpicker callback - colorpickerCallback = function (colorString) { - callback(colorString); - } - - // define hide delay - hideDelay = opt.hideDelay || 2000; - if (hideDelay > 0) { - setHideDelay(hideDelay); - } - - } - - - var timerCloseColorPicker; - function setHideDelay (delayTime) { - delayTime = delayTime || 0; - removeEvent($root.el, 'mouseenter'); - removeEvent($root.el, 'mouseleave'); - - addEvent($root.el, 'mouseenter', function () { - clearTimeout(timerCloseColorPicker); - }); - - addEvent($root.el, 'mouseleave', function () { - clearTimeout(timerCloseColorPicker); - timerCloseColorPicker = setTimeout(hide, delayTime); - }); - - clearTimeout(timerCloseColorPicker); - timerCloseColorPicker = setTimeout(hide, delayTime); - } - - function hide () { - if (isColorPickerShow) { - destroy(); - $root.hide(); - $root.remove(); - isColorPickerShow = false; - } - - } - +/* global CodeMirror */ +'use strict'; + +CodeMirror.defineExtension('colorpicker', function () { + const cm = this; + const CSS_PREFIX = 'colorpicker-'; + const HUE_COLORS = [ + {hex: '#ff0000', start: .0}, + {hex: '#ffff00', start: .17}, + {hex: '#00ff00', start: .33}, + {hex: '#00ffff', start: .50}, + {hex: '#0000ff', start: .67}, + {hex: '#ff00ff', start: .83}, + {hex: '#ff0000', start: 1} + ]; + + let HSV = {}; + let currentFormat; + + let initialized = false; + let shown = false; + let options = {}; + + let $root; + let $sat, $satPointer; + let $hue, $hueKnob; + let $opacity, $opacityBar, $opacityKnob; + let $swatch; + let $formatChangeButton; + let $hexCode; + const $inputGroups = {}; + const $inputs = {}; + const $rgb = {}; + const $hsl = {}; + const $hexLettercase = {}; + + const dragging = { + saturationPointerPos: {x: 0, y: 0}, + hueKnobPos: 0, + saturation: false, + hue: false, + opacity: false, + }; + + let prevFocusedElement; + let lastOutputColor; + let userActivity; + + let timerCloseColorPicker; + let timerFadeColorPicker; + + const PUBLIC_API = { + $root, + show, + hide, + setColor, + getColor, + options, + }; + return PUBLIC_API; + + //region DOM + + function init() { + // simplified createElement + function $(a, b) { + const cls = typeof a === 'string' || Array.isArray(a) ? a : ''; + const props = b || a; + const {tag = 'div', children} = props || {}; + const el = document.createElement(tag); + el.className = (Array.isArray(cls) ? cls : [cls]) + .map(c => (c ? CSS_PREFIX + c : '')) + .join(' '); + if (!props) { + return el; + } + for (const child of Array.isArray(children) ? children : [children]) { + if (child) { + el.appendChild(child instanceof Node ? child : document.createTextNode(child)); + } + } + delete props.tag; + delete props.children; + return Object.assign(el, props); + } + const alphaPattern = /^\s*(0+\.?|0*\.\d+|0*1\.?|0*1\.0*)?\s*$/.source; + $root = $('popup', {children: [ + $sat = $('saturation-container', {children: [ + $('saturation', {children: [ + $('value', {children: [ + $satPointer = $('drag-pointer'), + ]}), + ]}), + ]}), + $('sliders', {children: [ + $('hue', {children: [ + $hue = $('hue-container', {children: [ + $hueKnob = $('hue-knob'), + ]}), + ]}), + $('opacity', {children: [ + $opacity = $('opacity-container', {children: [ + $opacityBar = $('opacity-bar'), + $opacityKnob = $('opacity-knob'), + ]}), + ]}), + $('empty'), + $swatch = $('swatch'), + ]}), + $(['input-container', 'hex'], {children: [ + $inputGroups.hex = $(['input-group', 'hex'], {children: [ + $(['input-field', 'hex'], {children: [ + $hexCode = $('input', {tag: 'input', type: 'text', spellcheck: false, + pattern: /^\s*#([a-fA-F\d]{3}([a-fA-F\d]([a-fA-F\d]{2}([a-fA-F\d]{2})?)?)?)\s*$/.source + }), + $('title', {children: [ + $hexLettercase.true = $('title-action', {textContent: 'HEX'}), + '\xA0/\xA0', + $hexLettercase.false = $('title-action', {textContent: 'hex'}), + ]}), + ]}), + ]}), + $inputGroups.rgb = $(['input-group', 'rgb'], {children: [ + $(['input-field', 'rgb-r'], {children: [ + $rgb.r = $('input', {tag: 'input', type: 'number', min: 0, max: 255, step: 1}), + $('title', {textContent: 'R'}), + ]}), + $(['input-field', 'rgb-g'], {children: [ + $rgb.g = $('input', {tag: 'input', type: 'number', min: 0, max: 255, step: 1}), + $('title', {textContent: 'G'}), + ]}), + $(['input-field', 'rgb-b'], {children: [ + $rgb.b = $('input', {tag: 'input', type: 'number', min: 0, max: 255, step: 1}), + $('title', {textContent: 'B'}), + ]}), + $(['input-field', 'rgb-a'], {children: [ + $rgb.a = $('input', {tag: 'input', type: 'text', pattern: alphaPattern, spellcheck: false}), + $('title', {textContent: 'A'}), + ]}), + ]}), + $inputGroups.hsl = $(['input-group', 'hsl'], {children: [ + $(['input-field', 'hsl-h'], {children: [ + $hsl.h = $('input', {tag: 'input', type: 'number', step: 1}), + $('title', {textContent: 'H'}), + ]}), + $(['input-field', 'hsl-s'], {children: [ + $hsl.s = $('input', {tag: 'input', type: 'number', min: 0, max: 100, step: 1}), + $('title', {textContent: 'S'}), + ]}), + $(['input-field', 'hsl-l'], {children: [ + $hsl.l = $('input', {tag: 'input', type: 'number', min: 0, max: 100, step: 1}), + $('title', {textContent: 'L'}), + ]}), + $(['input-field', 'hsl-a'], {children: [ + $hsl.a = $('input', {tag: 'input', type: 'text', pattern: alphaPattern, spellcheck: false}), + $('title', {textContent: 'A'}), + ]}), + ]}), + $('format-change', {children: [ + $formatChangeButton = $('format-change-button', {textContent: '↔'}), + ]}), + ]}), + ]}); + + $inputs.hex = [$hexCode]; + $inputs.rgb = [$rgb.r, $rgb.g, $rgb.b, $rgb.a]; + $inputs.hsl = [$hsl.h, $hsl.s, $hsl.l, $hsl.a]; + const inputsToArray = inputs => inputs.map(el => parseFloat(el.value)); + const inputsToHexString = () => $hexCode.value.trim(); + const inputsToRGB = ([r, g, b, a] = inputsToArray($inputs.rgb)) => ({r, g, b, a, type: 'rgb'}); + const inputsToHSL = ([h, s, l, a] = inputsToArray($inputs.hsl)) => ({h, s, l, a, type: 'hsl'}); + Object.defineProperty($inputs.hex, 'color', {get: inputsToHexString}); + Object.defineProperty($inputs.rgb, 'color', {get: inputsToRGB}); + Object.defineProperty($inputs.hsl, 'color', {get: inputsToHSL}); + Object.defineProperty($inputs, 'color', {get: () => $inputs[currentFormat].color}); + + HUE_COLORS.forEach(color => Object.assign(color, stringToColor(color.hex))); + + initialized = true; + } + + //endregion + //region Public API + + function show(opt) { + if (!initialized) { + init(); + } + $root.style = ` + display: block; + position: fixed; + left: -10000px; + top: -10000px; + `.replace(/;/g, '!important;'); + $root.classList.add(CSS_PREFIX + 'theme-' + + (opt.theme === 'dark' || opt.theme === 'light' ? opt.theme : guessTheme())); + document.body.appendChild($root); + + shown = true; + + HSV = {}; + currentFormat = ''; + options = PUBLIC_API.options = opt; + prevFocusedElement = document.activeElement; + userActivity = 0; + lastOutputColor = opt.color; + $formatChangeButton.title = opt.tooltipForSwitcher || ''; + opt.hideDelay = Math.max(0, opt.hideDelay) || 2000; + + registerEvents(); + reposition(); + setFromColor(opt.color); + setFromHexLettercaseElement(); + $inputs[currentFormat][0].focus(); + } + + function hide() { + if (shown) { + unregisterEvents(); + focusNoScroll(prevFocusedElement); + $root.remove(); + shown = false; + } + } + + function setColor(color) { + switch (typeof color) { + case 'string': + color = stringToColor(color); + break; + case 'object': { + const {r, g, b, a} = color; + if (!isNaN(r) && !isNaN(g) && !isNaN(b)) { + color = {r, g, b, a, type: 'rgb'}; + break; + } + const {h, s, l} = color; + if (!isNaN(h) && !isNaN(s) && !isNaN(l)) { + color = {h, s, l, a, type: 'hsl'}; + break; + } + } + // fallthrough + default: + return false; + } + if (color) { + if (!initialized) { init(); + } + setFromColor(color); + } + return Boolean(color); + } - return { - isShortCut : function () { - return isShortCut; - }, - $root: $root, - show: show, - hide: hide, - setColor: setColor, - getColor: getColor - } - }) + function getColor(type) { + if (!initialized) { + return; + } + readCurrentColorFromRamps(); + const color = type === 'hsl' ? HSVtoHSL(HSV) : HSVtoRGB(HSV); + return type ? colorToString(color, type) : color; + } + //endregion + //region DOM-to-state + + function readCurrentColorFromRamps() { + if ($sat.offsetWidth === 0) { + HSV.h = HSV.s = HSV.v = 0; + } else { + const {x, y} = dragging.saturationPointerPos; + HSV.h = snapToInt((dragging.hueKnobPos / $hue.offsetWidth) * 360); + HSV.s = x / $sat.offsetWidth; + HSV.v = ($sat.offsetHeight - y) / $sat.offsetHeight; + } + } + + function setFromSaturationElement(event) { + event.preventDefault(); + const w = $sat.offsetWidth; + const h = $sat.offsetHeight; + const deltaX = event.clientX - parseFloat($root.style.left); + const deltaY = event.clientY - parseFloat($root.style.top); + const x = dragging.saturationPointerPos.x = constrain(0, w, deltaX); + const y = dragging.saturationPointerPos.y = constrain(0, h, deltaY); + + $satPointer.style.left = `${x - 5}px`; + $satPointer.style.top = `${y - 5}px`; + + readCurrentColorFromRamps(); + renderInputs(); + } + + function setFromHueElement(event) { + const {left, width} = getScreenBounds($hue); + const currentX = event ? getTouchPosition(event).clientX : left + width * (HSV.h / 360); + const normalizedH = constrain(0, 1, (currentX - left) / width); + const x = dragging.hueKnobPos = width * normalizedH; + $hueKnob.style.left = (x - Math.round($hueKnob.offsetWidth / 2)) + 'px'; + $sat.style.backgroundColor = hueDistanceToColorString(normalizedH); + HSV.h = event ? Math.round(normalizedH * 360) : HSV.h; + renderInputs(); + } + + function setFromOpacityElement(event) { + const {left, width} = getScreenBounds($opacity); + const normalized = constrain(0, 1, (getTouchPosition(event).clientX - left) / width); + const x = width * normalized; + $opacityKnob.style.left = (x - Math.ceil($opacityKnob.offsetWidth / 2)) + 'px'; + HSV.a = Math.round(normalized * 100) / 100; + renderInputs(); + } + + function setFromFormatElement() { + userActivity = performance.now(); + const nextFormat = {hex: 'rgb', rgb: 'hsl', hsl: 'hex'}[currentFormat]; + HSV.a = isNaN(HSV.a) ? 1 : HSV.a; + switchInputGroup(nextFormat); + renderInputs(); + } + + function setFromHexLettercaseElement() { + const isUpper = Boolean(options.hexUppercase); + $hexLettercase[isUpper].dataset.active = ''; + delete $hexLettercase[!isUpper].dataset.active; + const value = $hexCode.value; + $hexCode.value = isUpper ? value.toUpperCase() : value.toLowerCase(); + setFromInputs(); + } + + function setFromInputs() { + userActivity = performance.now(); + if ($inputs[currentFormat].every(validateInput)) { + setFromColor($inputs.color); + } + } + + function validateInput(el) { + const isAlpha = el.type === 'text'; + let isValid = (isAlpha || el.value.trim()) && el.checkValidity(); + if (!isAlpha && !isValid && currentFormat === 'rgb') { + isValid = parseAs(el, parseInt); + } else if (isAlpha && !isValid) { + isValid = parseAs(el, parseFloat); + } + if (isAlpha && isValid) { + isValid = lastOutputColor !== colorToString($inputs.color); + } + return isValid; + } + //endregion + //region State-to-DOM + + function setFromColor(color = '#FF0000') { + color = typeof color === 'string' ? stringToColor(color) : color; + const newHSV = color.type === 'hsl' ? HSLtoHSV(color) : RGBtoHSV(color); + if (Object.keys(newHSV).every(k => Math.abs(newHSV[k] - HSV[k]) < 1e-3)) { + return; + } + HSV = newHSV; + renderKnobs(color); + switchInputGroup(color.type); + setFromHueElement(); + } + + function switchInputGroup(format) { + if (currentFormat === format) { + return; + } + if (currentFormat) { + delete $inputGroups[currentFormat].dataset.active; + } else { + for (const format in $inputGroups) { + delete $inputGroups[format].dataset.active; + } + } + $inputGroups[format].dataset.active = ''; + currentFormat = format; + } + + function renderKnobs(color) { + const x = $sat.offsetWidth * HSV.s; + const y = $sat.offsetHeight * (1 - HSV.v); + $satPointer.style.left = (x - 5) + 'px'; + $satPointer.style.top = (y - 5) + 'px'; + dragging.saturationPointerPos = {x, y}; + + const hueX = $hue.offsetWidth * (HSV.h / 360); + $hueKnob.style.left = (hueX - 7.5) + 'px'; + dragging.hueKnobPos = hueX; + + const opacityX = $opacity.offsetWidth * (isNaN(HSV.a) ? 1 : HSV.a); + $opacityKnob.style.left = (opacityX - 7.5) + 'px'; + + $sat.style.backgroundColor = color; + } + + function renderInputs() { + const rgb = HSVtoRGB(HSV); + switch (currentFormat) { + case 'hex': + $hexCode.value = colorToString(rgb, 'hex'); + break; + case 'rgb': { + $rgb.r.value = rgb.r; + $rgb.g.value = rgb.g; + $rgb.b.value = rgb.b; + $rgb.a.value = alphaToString() || 1; + break; + } + case 'hsl': { + const {h, s, l} = HSVtoHSL(HSV); + $hsl.h.value = h; + $hsl.s.value = s; + $hsl.l.value = l; + $hsl.a.value = alphaToString() || 1; + } + } + $swatch.style.backgroundColor = colorToString(rgb, 'rgb'); + $opacityBar.style.background = 'linear-gradient(to right,' + + colorToString(Object.assign(rgb, {a: 0}), 'rgb') + ',' + + colorToString(Object.assign(rgb, {a: 1}), 'rgb') + ')'; + colorpickerCallback(); + } + + //endregion + //region Event listeners + + function onHexLettercaseClicked() { + options.hexUppercase = !options.hexUppercase; + setFromHexLettercaseElement(); + } + + function onSaturationMouseDown(event) { + if (event.button === 0) { + setFromSaturationElement(event); + dragging.saturation = true; + captureMouse(); + } + } + + function onSaturationMouseUp() { + if (event.button === 0) { + dragging.saturation = false; + releaseMouse(); + } + } + + function onHueKnobMouseDown(event) { + if (event.button === 0) { + dragging.hue = true; + captureMouse(); + } + } + + function onOpacityKnobMouseDown() { + if (event.button === 0) { + dragging.opacity = true; + captureMouse(); + } + } + + function onHueMouseDown(event) { + if (event.button === 0) { + dragging.hue = true; + setFromHueElement(event); + captureMouse(); + } + } + + function onOpacityMouseDown(event) { + if (event.button === 0) { + dragging.opacity = true; + setFromOpacityElement(event); + captureMouse(); + } + } + + function onMouseUp(event) { + if (event.button === 0) { + releaseMouse(); + dragging.saturation = dragging.hue = dragging.opacity = false; + if (!event.target.closest('.codemirror-colorview, .colorpicker-popup, .CodeMirror')) { + hide(); + } + } + } + + function onMouseMove(event) { + if (event.button !== 0) { + return; + } + if (dragging.saturation) { + setFromSaturationElement(event); + } else if (dragging.hue) { + setFromHueElement(event); + } else if (dragging.opacity) { + setFromOpacityElement(event); + } + } + + function stopSnoozing() { + clearTimeout(timerCloseColorPicker); + clearTimeout(timerFadeColorPicker); + if ($root.dataset.fading) { + delete $root.dataset.fading; + } + } + + function snooze() { + clearTimeout(timerFadeColorPicker); + timerFadeColorPicker = setTimeout(fade, options.hideDelay / 2); + } + + function onKeyDown(e) { + if (!e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) { + switch (e.which) { + case 13: + colorpickerCallback(); + // fallthrough to 27 + case 27: + e.preventDefault(); + e.stopPropagation(); + hide(); + break; + } + } + } + + function onCloseRequest(event) { + if (event.detail !== PUBLIC_API) { + hide(); + } + } + + //endregion + //region Event utilities + + function colorpickerCallback(colorString = currentColorToString()) { + if ( + userActivity && + $inputs[currentFormat].every(el => el.checkValidity()) && + typeof options.callback === 'function' + ) { + lastOutputColor = colorString.replace(/\b0\./g, '.'); + options.callback(lastOutputColor); + } + } + + function captureMouse() { + document.addEventListener('mouseup', onMouseUp); + document.addEventListener('mousemove', onMouseMove); + userActivity = performance.now(); + } + + function releaseMouse() { + document.removeEventListener('mouseup', onMouseUp); + document.removeEventListener('mousemove', onMouseMove); + userActivity = performance.now(); + } + + function getTouchPosition(event) { + return event.touches && event.touches[0] || event; + } + + function registerEvents() { + window.addEventListener('keydown', onKeyDown, true); + window.addEventListener('close-colorpicker-popup', onCloseRequest, true); + $root.addEventListener('mouseleave', snooze); + $root.addEventListener('mouseenter', stopSnoozing); + $root.addEventListener('input', setFromInputs); + $formatChangeButton.addEventListener('click', setFromFormatElement); + $sat.addEventListener('mousedown', onSaturationMouseDown); + $sat.addEventListener('mouseup', onSaturationMouseUp); + $hueKnob.addEventListener('mousedown', onHueKnobMouseDown); + $opacityKnob.addEventListener('mousedown', onOpacityKnobMouseDown); + $hue.addEventListener('mousedown', onHueMouseDown); + $opacity.addEventListener('mousedown', onOpacityMouseDown); + $hexLettercase.true.addEventListener('click', onHexLettercaseClicked); + $hexLettercase.false.addEventListener('click', onHexLettercaseClicked); + + stopSnoozing(); + timerFadeColorPicker = setTimeout(fade, options.hideDelay / 2); + } + + function unregisterEvents() { + window.removeEventListener('keydown', onKeyDown, true); + window.removeEventListener('close-colorpicker-popup', hide, true); + $root.removeEventListener('mouseleave', snooze); + $root.removeEventListener('mouseenter', stopSnoozing); + $root.removeEventListener('input', setFromInputs); + $formatChangeButton.removeEventListener('click', setFromFormatElement); + $sat.removeEventListener('mousedown', onSaturationMouseDown); + $sat.removeEventListener('mouseup', onSaturationMouseUp); + $hueKnob.removeEventListener('mousedown', onHueKnobMouseDown); + $opacityKnob.removeEventListener('mousedown', onOpacityKnobMouseDown); + $hue.removeEventListener('mousedown', onHueMouseDown); + $opacity.removeEventListener('mousedown', onOpacityMouseDown); + $hexLettercase.true.removeEventListener('click', onHexLettercaseClicked); + $hexLettercase.false.removeEventListener('click', onHexLettercaseClicked); + releaseMouse(); + stopSnoozing(); + } + + //endregion + //region Color conversion utilities + + function colorToString({r, g, b, h, s, l, a}, type = currentFormat) { + a = alphaToString(a); + const hasA = Boolean(a); + switch (type) { + case 'hex': { + const rgbStr = (0x1000000 + (r << 16) + (g << 8) + (b | 0)).toString(16).slice(1); + const aStr = hasA ? (0x100 + Math.round(a * 255)).toString(16).slice(1) : ''; + const hexStr = `#${rgbStr + aStr}`.replace(/^#(.)\1(.)\2(.)\3(?:(.)\4)?$/, '#$1$2$3$4'); + return options.hexUppercase ? hexStr.toUpperCase() : hexStr.toLowerCase(); + } + case 'rgb': + return hasA ? + `rgba(${r}, ${g}, ${b}, ${a})` : + `rgb(${r}, ${g}, ${b})`; + case 'hsl': + return hasA ? + `hsla(${h}, ${s}%, ${l}%, ${a})` : + `hsl(${h}, ${s}%, ${l}%)`; + } + } + + function stringToColor(str) { + if (typeof str !== 'string') { + return; + } + str = str.trim(); + if (str.startsWith('rgb')) { + const [r, g, b, a = 1] = str.replace(/rgba?\(|\)/g, '').split(',').map(parseFloat); + return {type: 'rgb', r, g, b, a}; + } + if (str.startsWith('hsl')) { + const [h, s, l, a = 1] = str.replace(/hsla?\(|\)/g, '').split(',').map(parseFloat); + return {type: 'hsl', h, s, l, a}; + } + if (str.startsWith('#')) { + str = str.slice(1); + const [r, g, b, a = 255] = str.length <= 4 ? + str.match(/(.)/g).map(c => parseInt(c + c, 16)) : + str.match(/(..)/g).map(c => parseInt(c, 16)); + return {type: 'hex', r, g, b, a: a === 255 ? undefined : a / 255}; + } + return; + } + + function RGBtoHSV({r, g, b, a}) { + r /= 255; + g /= 255; + b /= 255; + const MaxC = Math.max(r, g, b); + const MinC = Math.min(r, g, b); + const DeltaC = MaxC - MinC; + + let h = + DeltaC === 0 ? 0 : + MaxC === r ? 60 * (((g - b) / DeltaC) % 6) : + MaxC === g ? 60 * (((b - r) / DeltaC) + 2) : + MaxC === b ? 60 * (((r - g) / DeltaC) + 4) : + 0; + h = + h < 0 ? h % 360 + 360 : + h > 360 ? h % 360 : + h; + return { + h, + s: MaxC === 0 ? 0 : DeltaC / MaxC, + v: MaxC, + a, + }; + } + + function HSVtoRGB({h, s, v}) { + if (h === 360) { + h = 0; + } + const C = s * v; + const X = C * (1 - Math.abs((h / 60) % 2 - 1)); + const m = v - C; + const [r, g, b] = + h >= 0 && h < 60 ? [C, X, 0] : + h >= 60 && h < 120 ? [X, C, 0] : + h >= 120 && h < 180 ? [0, C, X] : + h >= 180 && h < 240 ? [0, X, C] : + h >= 240 && h < 300 ? [X, 0, C] : + h >= 300 && h < 360 ? [C, 0, X] : []; + return { + r: snapToInt(Math.round((r + m) * 255)), + g: snapToInt(Math.round((g + m) * 255)), + b: snapToInt(Math.round((b + m) * 255)), + }; + } + + function HSLtoHSV({h, s, l, a}) { + const t = s * (l < 50 ? l : 100 - l) / 100; + return { + h, + s: t + l ? 200 * t / (t + l) / 100 : 0, + v: (t + l) / 100, + a, + }; + } + + function HSVtoHSL({h, s, v}) { + const l = (2 - s) * v / 2; + const t = l < .5 ? l * 2 : 2 - l * 2; + return { + h: Math.round(h), + s: Math.round(t ? s * v / t * 100 : 0), + l: Math.round(l * 100), + }; + } + + function currentColorToString(format = currentFormat, alpha = HSV.a) { + const converted = format === 'hsl' ? HSVtoHSL(HSV) : HSVtoRGB(HSV); + converted.a = isNaN(alpha) || alpha === 1 ? undefined : alpha; + return colorToString(converted, format); + } + + function mixColorToString(start, end, amount) { + const obj = { + r: start.r + (end.r - start.r) * amount, + g: start.g + (end.g - start.g) * amount, + b: start.b + (end.b - start.b) * amount, + a: 1, + }; + return colorToString(obj, 'hex'); + } + + function hueDistanceToColorString(hueRatio) { + let prevColor; + for (const color of HUE_COLORS) { + if (prevColor && color.start >= hueRatio) { + return mixColorToString(prevColor, color, + (hueRatio - prevColor.start) / (color.start - prevColor.start)); + } + prevColor = color; + } + return HUE_COLORS[0].hex; + } + + function alphaToString(a = HSV.a) { + return isNaN(a) ? '' : + a.toString().slice(0, 8) + .replace(/(\.[^0]*)0+$/, '$1') + .replace(/^1$/, ''); + } + + //endregion + //region Miscellaneous utilities + + function reposition() { + const width = $root.offsetWidth; + const height = $root.offsetHeight; + + // set left position for color picker + let elementScreenLeft = options.left - document.scrollingElement.scrollLeft; + const bodyWidth = document.scrollingElement.scrollWidth; + if (width + elementScreenLeft > bodyWidth) { + elementScreenLeft -= (width + elementScreenLeft) - bodyWidth; + } + if (elementScreenLeft < 0) { + elementScreenLeft = 0; + } + + // set top position for color picker + let elementScreenTop = options.top - document.scrollingElement.scrollTop; + if (height + elementScreenTop > window.innerHeight) { + elementScreenTop = window.innerHeight - height; + } + if (elementScreenTop < options.top) { + elementScreenTop = options.top - height - 20; + } + if (elementScreenTop < 0) { + elementScreenTop = 0; + } + + // set position + $root.style.left = elementScreenLeft + 'px'; + $root.style.top = elementScreenTop + 'px'; + } + + function fade({fadingStage = 1} = {}) { + const timeInactive = performance.now() - userActivity; + const delay = options.hideDelay / 2; + if (userActivity && timeInactive < delay) { + timerFadeColorPicker = setTimeout(fade, delay - timeInactive, 2); + clearTimeout(timerCloseColorPicker); + delete $root.dataset.fading; + return; + } + $root.dataset.fading = fadingStage; + if (fadingStage === 1) { + timerFadeColorPicker = setTimeout(fade, Math.max(0, delay - 500), {fadingStage: 2}); + } else { + timerCloseColorPicker = setTimeout(hide, 500); + } + } + + function focusNoScroll(el) { + if (el) { + const {scrollY: y, scrollX: x} = window; + el.focus({preventScroll: true}); + el = null; + if (window.scrollY !== y || window.scrollX !== x) { + window.scrollTo(x, y); + } + } + } + + function getScreenBounds(el) { + const bounds = el.getBoundingClientRect(); + const {scrollTop, scrollLeft} = document.scrollingElement; + return { + top: bounds.top + scrollTop, + left: bounds.left + scrollLeft, + width: bounds.width, + height: bounds.height, + }; + } + + function guessTheme() { + const realColor = {r: 255, g: 255, b: 255, a: 1}; + const start = ((cm.display.renderedView || [])[0] || {}).text || cm.display.lineDiv; + for (let el = start; el; el = el.parentElement) { + const bgColor = getComputedStyle(el).backgroundColor; + const [r, g, b, a = 255] = bgColor.match(/\d+/g).map(Number); + if (!a) { + continue; + } + const mixedA = 1 - (1 - a / 255) * (1 - realColor.a); + const q1 = a / 255 / mixedA; + const q2 = realColor.a * (1 - mixedA) / mixedA; + realColor.r = Math.round(r * q1 + realColor.r * q2); + realColor.g = Math.round(g * q1 + realColor.g * q2); + realColor.b = Math.round(b * q1 + realColor.b * q2); + realColor.a = mixedA; + } + // https://www.w3.org/TR/AERT#color-contrast + const {r, g, b} = realColor; + const brightness = r * .299 + g * .587 + b * .114; + return brightness < 128 ? 'dark' : 'light'; + } + + function constrain(min, max, value) { + return value < min ? min : value > max ? max : value; + } + + function snapToInt(num) { + const int = Math.round(num); + return Math.abs(int - num) < 1e-3 ? int : num; + } + + function parseAs(el, parser) { + const num = parser(el.value); + if (!isNaN(num)) { + el.value = num; + return true; + } + } + + //endregion }); diff --git a/vendor-overwrites/colorpicker/colorview.js b/vendor-overwrites/colorpicker/colorview.js index b53fec3e..7086926b 100644 --- a/vendor-overwrites/colorpicker/colorview.js +++ b/vendor-overwrites/colorpicker/colorview.js @@ -1,434 +1,457 @@ -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("codemirror")); - else if (typeof define == "function" && define.amd) // AMD - define(["codemirror"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { - "use strict"; +/* global CodeMirror */ +'use strict'; - // color names - var color_names={aliceblue:"rgb(240, 248, 255)",antiquewhite:"rgb(250, 235, 215)",aqua:"rgb(0, 255, 255)",aquamarine:"rgb(127, 255, 212)",azure:"rgb(240, 255, 255)",beige:"rgb(245, 245, 220)",bisque:"rgb(255, 228, 196)",black:"rgb(0, 0, 0)",blanchedalmond:"rgb(255, 235, 205)",blue:"rgb(0, 0, 255)",blueviolet:"rgb(138, 43, 226)",brown:"rgb(165, 42, 42)",burlywood:"rgb(222, 184, 135)",cadetblue:"rgb(95, 158, 160)",chartreuse:"rgb(127, 255, 0)",chocolate:"rgb(210, 105, 30)",coral:"rgb(255, 127, 80)",cornflowerblue:"rgb(100, 149, 237)",cornsilk:"rgb(255, 248, 220)",crimson:"rgb(237, 20, 61)",cyan:"rgb(0, 255, 255)",darkblue:"rgb(0, 0, 139)",darkcyan:"rgb(0, 139, 139)",darkgoldenrod:"rgb(184, 134, 11)",darkgray:"rgb(169, 169, 169)",darkgrey:"rgb(169, 169, 169)",darkgreen:"rgb(0, 100, 0)",darkkhaki:"rgb(189, 183, 107)",darkmagenta:"rgb(139, 0, 139)",darkolivegreen:"rgb(85, 107, 47)",darkorange:"rgb(255, 140, 0)",darkorchid:"rgb(153, 50, 204)",darkred:"rgb(139, 0, 0)",darksalmon:"rgb(233, 150, 122)",darkseagreen:"rgb(143, 188, 143)",darkslateblue:"rgb(72, 61, 139)",darkslategray:"rgb(47, 79, 79)",darkslategrey:"rgb(47, 79, 79)",darkturquoise:"rgb(0, 206, 209)",darkviolet:"rgb(148, 0, 211)",deeppink:"rgb(255, 20, 147)",deepskyblue:"rgb(0, 191, 255)",dimgray:"rgb(105, 105, 105)",dimgrey:"rgb(105, 105, 105)",dodgerblue:"rgb(30, 144, 255)",firebrick:"rgb(178, 34, 34)",floralwhite:"rgb(255, 250, 240)",forestgreen:"rgb(34, 139, 34)",fuchsia:"rgb(255, 0, 255)",gainsboro:"rgb(220, 220, 220)",ghostwhite:"rgb(248, 248, 255)",gold:"rgb(255, 215, 0)",goldenrod:"rgb(218, 165, 32)",gray:"rgb(128, 128, 128)",grey:"rgb(128, 128, 128)",green:"rgb(0, 128, 0)",greenyellow:"rgb(173, 255, 47)",honeydew:"rgb(240, 255, 240)",hotpink:"rgb(255, 105, 180)",indianred:"rgb(205, 92, 92)",indigo:"rgb(75, 0, 130)",ivory:"rgb(255, 255, 240)",khaki:"rgb(240, 230, 140)",lavender:"rgb(230, 230, 250)",lavenderblush:"rgb(255, 240, 245)",lawngreen:"rgb(124, 252, 0)",lemonchiffon:"rgb(255, 250, 205)",lightblue:"rgb(173, 216, 230)",lightcoral:"rgb(240, 128, 128)",lightcyan:"rgb(224, 255, 255)",lightgoldenrodyellow:"rgb(250, 250, 210)",lightgreen:"rgb(144, 238, 144)",lightgray:"rgb(211, 211, 211)",lightgrey:"rgb(211, 211, 211)",lightpink:"rgb(255, 182, 193)",lightsalmon:"rgb(255, 160, 122)",lightseagreen:"rgb(32, 178, 170)",lightskyblue:"rgb(135, 206, 250)",lightslategray:"rgb(119, 136, 153)",lightslategrey:"rgb(119, 136, 153)",lightsteelblue:"rgb(176, 196, 222)",lightyellow:"rgb(255, 255, 224)",lime:"rgb(0, 255, 0)",limegreen:"rgb(50, 205, 50)",linen:"rgb(250, 240, 230)",magenta:"rgb(255, 0, 255)",maroon:"rgb(128, 0, 0)",mediumaquamarine:"rgb(102, 205, 170)",mediumblue:"rgb(0, 0, 205)",mediumorchid:"rgb(186, 85, 211)",mediumpurple:"rgb(147, 112, 219)",mediumseagreen:"rgb(60, 179, 113)",mediumslateblue:"rgb(123, 104, 238)",mediumspringgreen:"rgb(0, 250, 154)",mediumturquoise:"rgb(72, 209, 204)",mediumvioletred:"rgb(199, 21, 133)",midnightblue:"rgb(25, 25, 112)",mintcream:"rgb(245, 255, 250)",mistyrose:"rgb(255, 228, 225)",moccasin:"rgb(255, 228, 181)",navajowhite:"rgb(255, 222, 173)",navy:"rgb(0, 0, 128)",oldlace:"rgb(253, 245, 230)",olive:"rgb(128, 128, 0)",olivedrab:"rgb(107, 142, 35)",orange:"rgb(255, 165, 0)",orangered:"rgb(255, 69, 0)",orchid:"rgb(218, 112, 214)",palegoldenrod:"rgb(238, 232, 170)",palegreen:"rgb(152, 251, 152)",paleturquoise:"rgb(175, 238, 238)",palevioletred:"rgb(219, 112, 147)",papayawhip:"rgb(255, 239, 213)",peachpuff:"rgb(255, 218, 185)",peru:"rgb(205, 133, 63)",pink:"rgb(255, 192, 203)",plum:"rgb(221, 160, 221)",powderblue:"rgb(176, 224, 230)",purple:"rgb(128, 0, 128)",rebeccapurple:"rgb(102, 51, 153)",red:"rgb(255, 0, 0)",rosybrown:"rgb(188, 143, 143)",royalblue:"rgb(65, 105, 225)",saddlebrown:"rgb(139, 69, 19)",salmon:"rgb(250, 128, 114)",sandybrown:"rgb(244, 164, 96)",seagreen:"rgb(46, 139, 87)",seashell:"rgb(255, 245, 238)",sienna:"rgb(160, 82, 45)",silver:"rgb(192, 192, 192)",skyblue:"rgb(135, 206, 235)",slateblue:"rgb(106, 90, 205)",slategray:"rgb(112, 128, 144)",slategrey:"rgb(112, 128, 144)",snow:"rgb(255, 250, 250)",springgreen:"rgb(0, 255, 127)",steelblue:"rgb(70, 130, 180)",tan:"rgb(210, 180, 140)",teal:"rgb(0, 128, 128)",thistle:"rgb(216, 191, 216)",tomato:"rgb(255, 99, 71)",turquoise:"rgb(64, 224, 208)",violet:"rgb(238, 130, 238)",wheat:"rgb(245, 222, 179)",white:"rgb(255, 255, 255)",whitesmoke:"rgb(245, 245, 245)",yellow:"rgb(255, 255, 0)",yellowgreen:"rgb(154, 205, 50)",transparent:"rgba(0, 0, 0, 0)"}; +(() => { + const OWN_TOKEN_NAME = 'colorview'; + const OWN_DOM_CLASS = 'cm-' + OWN_TOKEN_NAME; + const OWN_BACKGROUND_CLASS = 'codemirror-colorview-background'; + const HOOKED_TOKEN = new Map([ + ['atom', colorizeAtom], + ['keyword', colorizeKeyword], + ].map(([name, fn]) => [name, {override: name + ' ' + OWN_TOKEN_NAME, process: fn}])); - var colorpicker_class = 'codemirror-colorview'; - var colorpicker_background_class = 'codemirror-colorview-background'; + const NAMED_COLORS = getNamedColorsMap(); + const TRANSPARENT = { + color: 'transparent', + colorValue: 'rgba(0, 0, 0, 0)', // as per the CSS spec + }; + const RX_COLOR = { + hex: /#(?:[a-f\d]{3,4}|[a-f\d]{6}|[a-f\d]{8})\b/yi, + rgb: /rgb\((?:\s*\d{1,3}\s*,\s*){2}\d{1,3}\s*\)/yi, + rgba: /rgba\((?:\s*\d{1,3}\s*,\s*){3}\d*\.?\d+\s*\)/yi, + hsl: /hsl\(\s*(?:-?\d+|-?\d*\.\d+)\s*(?:,\s*(?:-?\d+|-?\d*\.\d+)%\s*){2}\)/yi, + hsla: /hsla\(\s*(?:-?\d+|-?\d*\.\d+)\s*(?:,\s*(?:-?\d+|-?\d*\.\d+)%\s*){2},\s*(?:-?\d+|-?\d*\.\d+)\s*\)/yi, + named: new RegExp([...NAMED_COLORS.keys()].join('|'), 'i'), + }; - // Excluded tokens do not show color views.. - var excluded_token = ['comment']; + const CodeMirrorEvents = { + update(cm) { + if (cm.state.colorpicker.cache.size) { + renderVisibleTokens(cm); + } + }, + keyup(cm) { + const popup = cm.state.colorpicker.popup; + if (popup && popup.options.isShortCut === false) { + popup.hide(); + } + }, + mousedown(cm, event) { + const self = cm.state.colorpicker; + const isMarker = event.button === 0 && event.target.classList.contains(OWN_BACKGROUND_CLASS); + window.dispatchEvent(new CustomEvent('close-colorpicker-popup', {detail: isMarker && self.popup})); + if (isMarker) { + event.preventDefault(); + self.openPopupForToken(event.target.parentNode); + } + }, + }; - CodeMirror.defineOption("colorpicker", false, function (cm, val, old) { + function registerEvents(cm) { + Object.keys(CodeMirrorEvents).forEach(name => cm.on(name, CodeMirrorEvents[name])); + } - if (old && old != CodeMirror.Init) { + function unregisterEvents(cm) { + Object.keys(CodeMirrorEvents).forEach(name => cm.off(name, CodeMirrorEvents[name])); + } - if (cm.state.colorpicker) - { - cm.state.colorpicker.destroy(); - cm.state.colorpicker = null; + function registerHooks() { + const mx = CodeMirror.modeExtensions.css; + if (!mx || mx.token !== colorizeToken) { + CodeMirror.extendMode('css', { + token: colorizeToken, + }); + } + } - } - // remove event listener + function unregisterHooks() { + const mx = CodeMirror.modeExtensions.css; + if (mx && mx.token === colorizeToken) { + delete mx.token; + } + } + + function resetMode(cm) { + cm.setOption('mode', cm.getMode().name); + } + + function colorizeToken(stream, state) { + const token = this._token.apply(this, arguments); + const hookedToken = token && HOOKED_TOKEN.get(token); + if (!token || !hookedToken) { + return token; + } + const data = state.colorpicker = (state.colorpicker || {}); + const cache = data.cache = (data.cache || stream.lineOracle.doc.cm.state.colorpicker.cache); + const string = stream.string; + const sameString = string === data.lastString; + + data.lastString = string; + + let lineCache = data.lineCache = (sameString ? data.lineCache : cache.get(string)); + if (lineCache && lineCache.get(stream.start)) { + return hookedToken.override; + } + + const color = hookedToken.process(stream); + if (color) { + if (!lineCache) { + lineCache = data.lineCache = new Map(); + cache.set(string, lineCache); + } + lineCache.set(stream.start, color); + lineCache.set('lastAccessTime', performance.now()); + return hookedToken.override; + } + + return token; + } + + function colorizeAtom(stream) { + const {start, pos, string} = stream; + const c1 = string.charAt(start); + if ((c1 === 't' || c1 === 'T') && string.slice(start, pos).toLowerCase() === 'transparent') { + return TRANSPARENT; + } + const maybeHex = c1 === '#'; + const s = !maybeHex && string.charAt(pos) === '(' && string.slice(start, pos).toLowerCase(); + if (maybeHex || (s === 'rgb' || s === 'rgba' || s === 'hsl' || s === 'hsla')) { + const rx = maybeHex ? RX_COLOR.hex : RX_COLOR[s]; + rx.lastIndex = start; + const match = rx.exec(string); + return match && {color: match[0]}; + } + } + + function colorizeKeyword(stream) { + const {start, pos, string} = stream; + if (string.charAt(start) !== '!') { + const color = string.slice(start, pos); + const colorValue = NAMED_COLORS.get(color.toLowerCase()); + return colorValue && {color, colorValue}; + } + } + + function renderVisibleTokens(cm) { + const {cache, options} = cm.state.colorpicker; + let line = cm.display.viewFrom - 1; + for (const {line: lineHandle, text} of cm.display.renderedView) { + if (!lineHandle.parent) { + continue; + } + line++; + const styles = lineHandle.styles; + if (!styles) { + continue; + } + const lineCache = cache.get(lineHandle.text); + if (!lineCache) { + continue; + } + let lineCacheAlive = false; + let elementIndex = 0; + let elements; + for (let i = 1; i < styles.length; i += 2) { + const token = styles[i + 1]; + if (!token || !token.includes(OWN_TOKEN_NAME)) { + continue; } - - if (val) - { - cm.state.colorpicker = new codemirror_colorpicker(cm, val); + const start = styles[i - 2] || 0; + const data = lineCache.get(start); + if (!data) { + continue; } - }); - - function onChange(cm, evt) { - if (evt.origin == 'setValue') { // if content is changed by setValue method, it initialize markers - cm.state.colorpicker.close_color_picker(); - cm.state.colorpicker.init_color_update(); - cm.state.colorpicker.style_color_update(); - } else { - cm.state.colorpicker.style_color_update(cm.getCursor().line); + elements = elements || text.getElementsByClassName(OWN_DOM_CLASS); + const el = elements[elementIndex++]; + if (el.colorpickerData && el.colorpickerData.color === data.color) { + continue; } - - } - - function onUpdate(cm, evt) { - if (!cm.state.colorpicker.isUpdate) { - cm.state.colorpicker.isUpdate = true; - cm.state.colorpicker.close_color_picker(); - cm.state.colorpicker.init_color_update(); - cm.state.colorpicker.style_color_update(); + el.colorpickerData = Object.assign({line, ch: start}, data); + let bg = el.firstElementChild; + if (!bg) { + bg = document.createElement('div'); + bg.className = OWN_BACKGROUND_CLASS; + bg.title = options.tooltip; + el.appendChild(bg); } + bg.style.setProperty('background-color', data.color, 'important'); + lineCacheAlive = true; + } + if (lineCacheAlive) { + lineCache.set('lastAccessTime', performance.now()); + } + } + trimCache(cm); + } + + function trimCache(cm, debounced) { + if (!debounced) { + clearTimeout(trimCache.timer); + trimCache.timer = setTimeout(trimCache, 20e3, cm, true); + return; + } + const cutoff = performance.now() - 60e3; + const {cache} = cm.state.colorpicker; + const textToKeep = new Set(); + cm.doc.iter(({text}) => textToKeep.add(text)); + for (const [text, lineCache] of cache.entries()) { + if (lineCache.get('lastAccessTime') < cutoff && !textToKeep.has(text)) { + cache.delete(text); + } + } + } + + function getNamedColorsMap() { + return new Map([ + ['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'], + ['darkgreen', '#006400'], + ['darkgrey', '#a9a9a9'], + ['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'], + ['green', '#008000'], + ['greenyellow', '#adff2f'], + ['grey', '#808080'], + ['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'], + ['lightgreen', '#90ee90'], + ['lightgrey', '#d3d3d3'], + ['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', '#9370db'], + ['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', '#db7093'], + ['papayawhip', '#ffefd5'], + ['peachpuff', '#ffdab9'], + ['peru', '#cd853f'], + ['pink', '#ffc0cb'], + ['plum', '#dda0dd'], + ['powderblue', '#b0e0e6'], + ['purple', '#800080'], + ['rebeccapurple', '#663399'], + ['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'], + ]); + } + + class ColorMarker { + constructor(cm, { + tooltip = 'Open color picker', + popupOptions = {}, + colorpicker, + forceUpdate, + } = {}) { + this.cm = cm; + this.options = { + tooltip, + popup: Object.assign({ + hideDelay: 2000, + hexUppercase: false, + tooltipForSwitcher: 'Switch formats: HEX -> RGB -> HSL', + }, popupOptions), + }; + this.popup = cm.colorpicker ? cm.colorpicker() : colorpicker; + this.cache = new Map(); + registerHooks(cm); + registerEvents(cm); + if (forceUpdate) { + resetMode(cm); + } } - function onRefresh(cm, evt) { - onChange(cm, { origin : 'setValue'}); + destroy() { + unregisterHooks(this.cm); + unregisterEvents(this.cm); + resetMode(this.cm); + this.cm.state.colorpicker = null; } - function onKeyup(cm, evt) { - cm.state.colorpicker.keyup(evt); - } - - function onMousedown(cm, evt) { - if (cm.state.colorpicker.is_edit_mode()) - { - cm.state.colorpicker.check_mousedown(evt); + openPopup(defaultColor = '#FFFFFF') { + const cursor = this.cm.getCursor(); + const data = { + line: cursor.line, + ch: cursor.ch, + color: defaultColor, + isShortCut: true, + }; + for (const {from, marker} of this.cm.getLineHandle(cursor.line).markedSpans || []) { + if (from <= data.ch && (marker.replacedWith || {}).colorpickerData) { + const {color, colorValue} = marker.replacedWith.colorpickerData; + if (data.ch <= from + color.length) { + data.ch = from; + data.color = color; + data.colorValue = colorValue; + break; + } } + } + this.openPopupForToken({colorpickerData: data}); } - function onPaste (cm, evt) { - onChange(cm, { origin : 'setValue'}); + openPopupForToken({colorpickerData: data}) { + if (this.popup) { + const {left, bottom: top} = this.cm.charCoords(data, 'window'); + this.popup.show(Object.assign(this.options.popup, data, { + top, + left, + cm: this.cm, + color: data.colorValue || data.color, + prevColor: data.color, + isShortCut: false, + callback: ColorMarker.popupOnChange, + })); + } } - function onScroll (cm) { - cm.state.colorpicker.close_color_picker(); + closePopup() { + if (this.popup) { + this.popup.hide(); + } } - function debounce (callback, delay) { - - var t = undefined; - - return function (cm, e) { - if (t) { - clearTimeout(t); - } - - t = setTimeout(function () { - callback(cm, e); - }, delay || 300); - } + static popupOnChange(newColor) { + const {cm, line, ch, embedderCallback} = this; + const to = {line, ch: ch + this.prevColor.length}; + if (cm.getRange(this, to) !== newColor) { + this.prevColor = newColor; + cm.replaceRange(newColor, this, to, '*colorpicker'); + } + if (typeof embedderCallback === 'function') { + embedderCallback(this); + } } + } - function has_class(el, cls) { - if (!el || !el.className) { - return false; - } else { - var newClass = ' ' + el.className + ' '; - return newClass.indexOf(' ' + cls + ' ') > -1; - } + CodeMirror.defineOption('colorpicker', false, (cm, value, oldValue) => { + if (oldValue && oldValue !== CodeMirror.Init && cm.state.colorpicker) { + cm.state.colorpicker.destroy(); } - - function codemirror_colorpicker (cm, opt) { - var self = this; - - if (typeof opt == 'boolean') - { - opt = { mode : 'view' }; - } else { - opt = Object.assign({ mode: 'view' }, opt || {}); - } - - this.opt = opt; - this.cm = cm; - this.markers = {}; - - // set excluded token - excluded_token = this.opt.excluded_token || excluded_token; - - if (this.cm.colorpicker) { - this.colorpicker = this.cm.colorpicker(); - } else if (this.opt.colorpicker) { - this.colorpicker = this.opt.colorpicker; - } - - this.init_event(); - + if (value) { + cm.state.colorpicker = new ColorMarker(cm, value); } + }); - codemirror_colorpicker.prototype.init_event = function () { - - this.cm.on('mousedown', onMousedown); - this.cm.on('keyup', onKeyup); - this.cm.on('change', onChange); - this.cm.on('update', onUpdate); - this.cm.on('refresh', onRefresh); - - // create paste callback - this.onPasteCallback = (function (cm, callback) { - return function (evt) { - callback.call(this, cm, evt); - } - })(this.cm, onPaste); - - this.cm.getWrapperElement().addEventListener('paste', this.onPasteCallback); - - if (this.is_edit_mode()) - { - this.cm.on('scroll', debounce(onScroll, 50)); - } - - } - - codemirror_colorpicker.prototype.is_edit_mode = function () { - return this.opt.mode == 'edit'; - } - - codemirror_colorpicker.prototype.is_view_mode = function () { - return this.opt.mode == 'view'; - } - - codemirror_colorpicker.prototype.destroy = function () { - this.cm.off('mousedown', onMousedown); - this.cm.off('keyup', onKeyup); - this.cm.off('change', onChange) - this.cm.getWrapperElement().removeEventListener('paste', this.onPasteCallback); - - if (this.is_edit_mode()) - { - this.cm.off('scroll'); - } - } - - codemirror_colorpicker.prototype.hasClass = function (el, className) { - if (!el.className) - { - return false; - } else { - var newClass = ' ' + el.className + ' '; - return newClass.indexOf(' ' + className + ' ') > -1; - } - } - - codemirror_colorpicker.prototype.check_mousedown = function (evt) { - if (this.hasClass(evt.target, colorpicker_background_class) ) - { - this.open_color_picker(evt.target.parentNode); - } else { - this.close_color_picker(); - } - } - - codemirror_colorpicker.prototype.popup_color_picker = function (defalutColor) { - var cursor = this.cm.getCursor(); - var self = this; - var colorMarker = { - lineNo : cursor.line, - ch : cursor.ch, - color: defalutColor || '#FFFFFF', - isShortCut : true - }; - - Object.keys(this.markers).forEach(function(key) { - var searchKey = "#" + key; - if (searchKey.indexOf( "#" + colorMarker.lineNo + ":") > -1) { - var marker = self.markers[key]; - - if (marker.ch <= colorMarker.ch && colorMarker.ch <= marker.ch + marker.color.length) { - // when cursor has marker - colorMarker.ch = marker.ch; - colorMarker.color = marker.color; - colorMarker.nameColor = marker.nameColor; - } - - } - }); - - this.open_color_picker(colorMarker); - } - - codemirror_colorpicker.prototype.open_color_picker = function (el) { - var lineNo = el.lineNo; - var ch = el.ch; - var nameColor = el.nameColor; - var color = el.color; - - - if (this.colorpicker) { - var self = this; - var prevColor = color; - var pos = this.cm.charCoords({line : lineNo, ch : ch }); - this.colorpicker.show({ - left : pos.left, - top : pos.bottom, - isShortCut : el.isShortCut || false, - hideDelay : self.opt.hideDelay || 2000 - }, nameColor || color, function (newColor) { - self.cm.replaceRange(newColor, { line : lineNo, ch : ch } , { line : lineNo, ch : ch + prevColor.length }, '*colorpicker'); - prevColor = newColor; - }); - - } - - } - - codemirror_colorpicker.prototype.close_color_picker = function (el) { - if (this.colorpicker) - { - this.colorpicker.hide(); - } - } - - codemirror_colorpicker.prototype.key = function (lineNo, ch) { - return [lineNo, ch].join(":"); - } - - - codemirror_colorpicker.prototype.keyup = function (evt) { - - if (this.colorpicker ) { - if (evt.key == 'Escape') { - this.colorpicker.hide(); - } else if (this.colorpicker.isShortCut() == false) { - this.colorpicker.hide(); - } - } - } - - codemirror_colorpicker.prototype.init_color_update = function () { - this.markers = {}; // initialize marker list - } - - codemirror_colorpicker.prototype.style_color_update = function (lineHandle) { - - if (lineHandle) { - this.match(lineHandle); - } else { - var max = this.cm.lineCount(); - - for(var lineNo = 0; lineNo < max; lineNo++) { - this.match(lineNo); - } - } - - } - - codemirror_colorpicker.prototype.empty_marker = function (lineNo, lineHandle) { - var list = lineHandle.markedSpans || []; - - for(var i = 0, len = list.length; i < len; i++) { - var key = this.key(lineNo, list[i].from); - - if (key && has_class(list[i].marker.replacedWith, colorpicker_class)) { - delete this.markers[key]; - list[i].marker.clear(); - } - - } - } - - codemirror_colorpicker.prototype.color_regexp = /(#(?:[\da-f]{3}){1,2}|rgb\((?:\s*\d{1,3},\s*){2}\d{1,3}\s*\)|rgba\((?:\s*\d{1,3},\s*){3}\d*\.?\d+\s*\)|hsl\(\s*\d{1,3}(?:,\s*\d{1,3}%){2}\s*\)|hsla\(\s*\d{1,3}(?:,\s*\d{1,3}%){2},\s*\d*\.?\d+\s*\)|([\w_\-]+))/gi; - - codemirror_colorpicker.prototype.match_result = function (lineHandle) { - return lineHandle.text.match(this.color_regexp); - } - - codemirror_colorpicker.prototype.match = function (lineNo) { - var lineHandle = this.cm.getLineHandle(lineNo); - - this.empty_marker(lineNo, lineHandle); - - var result = this.match_result(lineHandle); - if (result) - { - var obj = { next : 0 }; - for(var i = 0, len = result.length; i < len; i++) { - - if (result[i].indexOf('#') > -1 || result[i].indexOf('rgb') > -1 || result[i].indexOf('hsl') > -1) { - this.render(obj, lineNo, lineHandle, result[i]); - } else { - var nameColor = color_names[result[i]]; - if (nameColor) { - this.render(obj, lineNo, lineHandle, result[i], nameColor); - } - } - } - } - } - - codemirror_colorpicker.prototype.make_element = function () { - var el = document.createElement('div'); - - el.className = colorpicker_class; - - if (this.is_edit_mode()) - { - el.title ="open color picker"; - } else { - el.title =""; - } - - el.back_element = this.make_background_element(); - el.appendChild(el.back_element); - - return el; - } - - codemirror_colorpicker.prototype.make_background_element = function () { - var el = document.createElement('div'); - - el.className = colorpicker_background_class; - - return el; - } - - codemirror_colorpicker.prototype.set_state = function (lineNo, start, color, nameColor) { - var marker = this.create_marker(lineNo, start); - - - marker.lineNo = lineNo; - marker.ch = start; - marker.color = color; - marker.nameColor = nameColor; - - return marker; - } - - codemirror_colorpicker.prototype.create_marker = function (lineNo, start) { - - var key = this.key(lineNo,start); - - if (!this.markers[key]) { - this.markers[key] = this.make_element(); - } - - - return this.markers[key]; - - } - - codemirror_colorpicker.prototype.has_marker = function (lineNo, start) { - var key = this.key(lineNo,start); - return !!(this.markers[key]) - } - - codemirror_colorpicker.prototype.update_element = function (el, color) { - el.back_element.style.backgroundColor = color; - } - - codemirror_colorpicker.prototype.set_mark = function (line, ch, el) { - this.cm.setBookmark({ line : line, ch : ch}, { widget : el, handleMouseEvents : true} ); - - } - - codemirror_colorpicker.prototype.is_excluded_token = function (line, ch) { - var token = this.cm.getTokenAt({line : line, ch : ch}); - var count = 0; - for(var i = 0, len = excluded_token.length; i < len; i++) { - if (token.type === excluded_token[i]) { - count++; - break; - } - } - - return count > 0; // true is that it has a excluded token - } - - codemirror_colorpicker.prototype.render = function (cursor, lineNo, lineHandle, color, nameColor) { - var start = lineHandle.text.indexOf(color, cursor.next); - - if (this.is_excluded_token(lineNo, start) === true) { - // excluded token do not show. - return; - } - - cursor.next = start + color.length; - - if (this.has_marker(lineNo, start)) - { - this.update_element(this.create_marker(lineNo, start), nameColor || color); - this.set_state(lineNo, start, color, nameColor); - return; - } - - var el = this.create_marker(lineNo, start); - - this.update_element(el, nameColor || color); - this.set_state(lineNo, start, color, nameColor || color); - this.set_mark(lineNo, start, el); - } -}); + // initial runMode is performed by CodeMirror before setting our option + // so we register the hooks right away - not a problem as our js is loaded on demand + registerHooks(); +})(); From 8a1908b760d3b1d4dc61df668820c41e28952d40 Mon Sep 17 00:00:00 2001 From: tophf Date: Tue, 21 Nov 2017 09:45:44 +0300 Subject: [PATCH 005/161] protect own style elements (100 times max to avoid deadlocks) fixes #252 --- content/apply.js | 70 +++++++++++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 24 deletions(-) diff --git a/content/apply.js b/content/apply.js index 0d61e475..a78abda2 100644 --- a/content/apply.js +++ b/content/apply.js @@ -224,8 +224,12 @@ function applyStyles(styles) { for (const id in styles) { applySections(id, styles[id].map(section => section.code).join('\n')); } - initDocRewriteObserver(); - initDocRootObserver(); + if (!isOwnPage && !docRewriteObserver && styleElements.size) { + initDocRewriteObserver(); + } + if (!docRootObserver && styleElements.size) { + initDocRootObserver(); + } if (retiredStyleTimers.size) { setTimeout(() => { for (const [id, timer] of retiredStyleTimers.entries()) { @@ -290,9 +294,6 @@ function replaceAll(newStyles) { function initDocRewriteObserver() { - if (isOwnPage || docRewriteObserver || !styleElements.size) { - return; - } // re-add styles if we detect documentElement being recreated const reinjectStyles = () => { if (!styleElements) { @@ -327,36 +328,57 @@ function initDocRewriteObserver() { function initDocRootObserver() { - if (!styleElements.size || document.body || docRootObserver) { - return; + let lastRestorationTime = 0; + let restorationCounter = 0; + + docRootObserver = new MutationObserver(findMisplacedStyles); + connectObserver(); + + function connectObserver() { + docRootObserver.observe(ROOT, {childList: true}); } - // wait for BODY and move all style elements after it - docRootObserver = new MutationObserver(() => { + + function findMisplacedStyles() { let expectedPrevSibling = document.body || document.head; if (!expectedPrevSibling) { return; } - docRootObserver.disconnect(); + const list = []; for (const el of styleElements.values()) { if (el.previousElementSibling !== expectedPrevSibling) { - ROOT.insertBefore(el, expectedPrevSibling.nextSibling); - if (el.disabled !== disableAll) { - // moving an element resets its 'disabled' state - el.disabled = disableAll; - } + list.push({el, before: expectedPrevSibling.nextSibling}); } expectedPrevSibling = el; } - if (document.body) { - docRootObserver = null; - } else { - docRootObserver.connect(); + if (list.length && !restorationLimitExceeded()) { + restoreMisplacedStyles(list); } - }); - docRootObserver.connect = () => { - docRootObserver.observe(ROOT, {childList: true}); - }; - docRootObserver.connect(); + } + + function restoreMisplacedStyles(list) { + docRootObserver.disconnect(); + for (const {el, before} of list) { + ROOT.insertBefore(el, before); + if (el.disabled !== disableAll) { + // moving an element resets its 'disabled' state + el.disabled = disableAll; + } + } + connectObserver(); + } + + function restorationLimitExceeded() { + const t = performance.now(); + if (t - lastRestorationTime > 1000) { + restorationCounter = 0; + } + lastRestorationTime = t; + if (++restorationCounter > 100) { + console.error('Stylus stopped restoring userstyle elements after 100 failed attempts.\n' + + 'Please report on https://github.com/openstyles/stylus/issues'); + return true; + } + } } From 00ead83da91cbac961386125aba543174ac23d0e Mon Sep 17 00:00:00 2001 From: tophf Date: Tue, 21 Nov 2017 09:48:55 +0300 Subject: [PATCH 006/161] don't hardcode homepage URL, extract it from "Get Help" translation --- background/background.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/background/background.js b/background/background.js index 60942b61..47261b53 100644 --- a/background/background.js +++ b/background/background.js @@ -50,9 +50,13 @@ prefs.subscribe(['iconset'], () => updateIcon({id: undefined}, {})); // Open FAQs page once after installation to guide new users. // Do not display it in development mode. if (reason === 'install' && manifest.update_url) { - setTimeout(openURL, 100, { - url: 'http://add0n.com/stylus.html' - }); + // don't hardcode homepage URL, extract it from "Get Help" label translation + // TODO: add a built-in tour page in the extension + const getHelpHtml = chrome.i18n.getMessage('manageText').match(/]+/g); + const url = (getHelpHtml[1] || '').replace(/^.+?=\s*/, '').replace(/^['"]|["']$/g, ''); + if (url) { + setTimeout(openURL, 100, {url}); + } } // reset L10N cache on update if (reason === 'update') { From 46df47bf316769bfdf849ee2598a92723bdb0b70 Mon Sep 17 00:00:00 2001 From: tophf Date: Tue, 21 Nov 2017 10:10:29 +0300 Subject: [PATCH 007/161] bump version to 1.1.6 --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index cfd6d428..d046b96d 100644 --- a/manifest.json +++ b/manifest.json @@ -1,6 +1,6 @@ { "name": "Stylus", - "version": "1.1.5", + "version": "1.1.6", "minimum_chrome_version": "49", "description": "__MSG_description__", "homepage_url": "http://add0n.com/stylus.html", From 24c60b7e8a1359f583d505d1c786af971d141074 Mon Sep 17 00:00:00 2001 From: tophf Date: Tue, 21 Nov 2017 10:59:14 +0300 Subject: [PATCH 008/161] fixup 8a1908b7: don't protect disabled elements --- content/apply.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/content/apply.js b/content/apply.js index a78abda2..0e49743a 100644 --- a/content/apply.js +++ b/content/apply.js @@ -344,8 +344,9 @@ function initDocRootObserver() { return; } const list = []; - for (const el of styleElements.values()) { - if (el.previousElementSibling !== expectedPrevSibling) { + for (const [id, el] of styleElements.entries()) { + if (!disabledElements.has(parseInt(id.substr(ID_PREFIX.length))) && + el.previousElementSibling !== expectedPrevSibling) { list.push({el, before: expectedPrevSibling.nextSibling}); } expectedPrevSibling = el; From 4e9e4d24b19fdd7d605d9fbab378b77ff7ce63af Mon Sep 17 00:00:00 2001 From: tophf Date: Tue, 21 Nov 2017 11:03:22 +0300 Subject: [PATCH 009/161] bump version to 1.1.6.1 --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index d046b96d..4934a7f3 100644 --- a/manifest.json +++ b/manifest.json @@ -1,6 +1,6 @@ { "name": "Stylus", - "version": "1.1.6", + "version": "1.1.6.1", "minimum_chrome_version": "49", "description": "__MSG_description__", "homepage_url": "http://add0n.com/stylus.html", From f1836f399f711ca9b982fa548cd495d482a3ba94 Mon Sep 17 00:00:00 2001 From: tophf Date: Tue, 21 Nov 2017 13:23:32 +0300 Subject: [PATCH 010/161] try to get style json on USO install page twice also report errors fixes #195 --- content/install.js | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/content/install.js b/content/install.js index 25bbd62b..a8b2073a 100644 --- a/content/install.js +++ b/content/install.js @@ -127,16 +127,11 @@ new MutationObserver((mutations, observer) => { we need to fix this URL using "stylish-update-url" meta key */ function getStyleURL() { - const url = getMeta('stylish-code-chrome'); - // TODO: remove when USO is fixed - const directUrl = getMeta('stylish-update-url'); - if (directUrl.includes('?') && !url.includes('?')) { - /* get custom settings from the update url */ - return Object.assign(new URL(url), { - search: (new URL(directUrl)).search - }).href; - } - return url; + const textUrl = getMeta('stylish-update-url') || ''; + const jsonUrl = getMeta('stylish-code-chrome') || + textUrl.replace(/styles\/(\d+)\/[^?]*/, 'styles/chrome/$1.json'); + const paramsMissing = !jsonUrl.includes('?') && textUrl.includes('?'); + return jsonUrl + (paramsMissing ? textUrl.replace(/^[^?]+/, '') : ''); } function checkUpdatability([installedStyle]) { @@ -154,9 +149,9 @@ function checkUpdatability([installedStyle]) { reportUpdatable(md5 !== installedStyle.originalMd5); }); } else { - getResource(getStyleURL()).then(code => { - reportUpdatable(code === null || - !styleSectionsEqual(JSON.parse(code), installedStyle)); + getStyleJson().then(json => { + reportUpdatable(!json || + !styleSectionsEqual(json, installedStyle)); }); } @@ -233,9 +228,14 @@ function saveStyleCode(message, name, addProps) { return; } enableUpdateButton(false); - getResource(getStyleURL()).then(code => { + getStyleJson().then(json => { + if (!json) { + prompt(chrome.i18n.getMessage('styleInstallFailed', ''), + 'https://github.com/openstyles/stylus/issues/195'); + return; + } chrome.runtime.sendMessage( - Object.assign(JSON.parse(code), addProps, { + Object.assign(json, addProps, { method: 'saveStyle', reason: 'update', }), @@ -278,6 +278,18 @@ function getResource(url) { } +function getStyleJson() { + const url = getStyleURL(); + return getResource(url).then(code => { + try { + return JSON.parse(code); + } catch (e) { + return fetch(url).then(r => r.json()).catch(() => null); + } + }); +} + + function styleSectionsEqual({sections: a}, {sections: b}) { if (!a || !b) { return undefined; From e8d0c7d0397b20e50648fae228a6285e7f1a3f96 Mon Sep 17 00:00:00 2001 From: tophf Date: Tue, 21 Nov 2017 13:33:11 +0300 Subject: [PATCH 011/161] update button on USO page now confirms only on the first click --- content/install.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/content/install.js b/content/install.js index a8b2073a..f3e0a300 100644 --- a/content/install.js +++ b/content/install.js @@ -223,10 +223,12 @@ function onUpdate() { function saveStyleCode(message, name, addProps) { return new Promise((resolve, reject) => { - if (!confirm(chrome.i18n.getMessage(message, [name]))) { + const needsConfirmation = message === 'styleInstall' || !saveStyleCode.confirmed; + if (needsConfirmation && !confirm(chrome.i18n.getMessage(message, [name]))) { reject(); return; } + saveStyleCode.confirmed = true; enableUpdateButton(false); getStyleJson().then(json => { if (!json) { From 777adf1baeb94ea1c29d5a143be93dccbbce64c1 Mon Sep 17 00:00:00 2001 From: tophf Date: Tue, 21 Nov 2017 13:42:51 +0300 Subject: [PATCH 012/161] correctly fix the USO bug with populating settings on their site previously we disconnected the interceptor on first XHR response, which is not necessarily for the style, so now we wait until an actual one. --- content/install.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/content/install.js b/content/install.js index f3e0a300..9dd0063e 100644 --- a/content/install.js +++ b/content/install.js @@ -24,17 +24,20 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { document.documentElement.appendChild(document.createElement('script')).text = '(' + function () { let settings; + const originalResponseJson = Response.prototype.json; document.addEventListener('stylusFixBuggyUSOsettings', function _({detail}) { document.removeEventListener('stylusFixBuggyUSOsettings', _); settings = /\?/.test(detail) && new URLSearchParams(new URL(detail).search); + if (!settings) { + Response.prototype.json = originalResponseJson; + } }); - const originalResponseJson = Response.prototype.json; Response.prototype.json = function (...args) { return originalResponseJson.call(this, ...args).then(json => { - Response.prototype.json = originalResponseJson; if (!settings || typeof ((json || {}).style_settings || {}).every !== 'function') { return json; } + Response.prototype.json = originalResponseJson; const images = new Map(); for (const jsonSetting of json.style_settings) { let value = settings.get('ik-' + jsonSetting.install_key); From eee9e30413353b94d720a0972359397319ee2553 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Mon, 20 Nov 2017 14:08:14 -0600 Subject: [PATCH 013/161] Add Discord chat badge --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 4f137088..be20a223 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,13 @@ Stylus is a fork of Stylish for Chrome, also compatible with Firefox as a WebExt ## Help +[![Discord][chat-image]][chat-link] + See the [help docs](http://userstyles.org/help/stylish_chrome) or [ask in userstyles.org forum](https://forum.userstyles.org). For Stylus specific questions and suggestions please use [review section](http://add0n.com/stylus.html#reviews) of the FAQs page. +[chat-image]: https://img.shields.io/discord/379521691774353408.svg +[chat-link]: https://discordapp.com/widget?id=379521691774353408 + ## Contributing The source is hosted on [GitHub](https://github.com/openstyles/stylus) and pull requests are welcome. From aa1ac63c1c695c47fcd192c93b651b541a89ca75 Mon Sep 17 00:00:00 2001 From: tophf Date: Tue, 21 Nov 2017 14:12:25 +0300 Subject: [PATCH 014/161] fix svg icon rendering in FF: symbols don't need size --- edit.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/edit.html b/edit.html index 34e99fd6..ae229084 100644 --- a/edit.html +++ b/edit.html @@ -240,16 +240,16 @@
- + - + - + - + From 7f37b8d1dd2763b8e850ef0a1876ad1eb32112dc Mon Sep 17 00:00:00 2001 From: tophf Date: Tue, 21 Nov 2017 18:17:56 +0300 Subject: [PATCH 015/161] unify markup and CSS for icons within options --- edit.html | 8 +++++--- edit/edit.css | 11 ++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/edit.html b/edit.html index ae229084..984182e6 100644 --- a/edit.html +++ b/edit.html @@ -201,7 +201,9 @@
- + + +
@@ -222,10 +224,10 @@ - + -   +
diff --git a/edit/edit.css b/edit/edit.css index 0ec03229..19f3c4b7 100644 --- a/edit/edit.css +++ b/edit/edit.css @@ -44,7 +44,7 @@ body { .aligned { display: table-row; } -.aligned > *:not(svg) { +.aligned > *:not(.svg-inline-wrapper) { display: table-cell; margin-top: 0.1rem; min-height: 1.4rem; @@ -83,8 +83,6 @@ input[type="checkbox"] { transition: fill .5s; width: 16px; height: 16px; -} -.svg-icon:not(.dismiss) { margin-left: 0.2rem; } h2 .svg-icon, label .svg-icon { @@ -132,7 +130,7 @@ h2 .svg-icon, label .svg-icon { text-align: left; padding-left: .25em; } -#options .option > * { +#options .option.aligned > * { padding-right: 0.25rem; } .set-option-progress { @@ -400,8 +398,7 @@ body[data-match-highlight="selection"] .CodeMirror-selection-highlight-scrollbar margin-left: 10px; font-weight: bold; } -#help-popup .saved-message.show, -#options .linter-settings { +#help-popup .saved-message.show { display: inline-block; } @@ -601,7 +598,7 @@ body[data-match-highlight="selection"] .CodeMirror-selection-highlight-scrollbar #options h2 { margin: 0 0 .5em; } - #options .aligned > *:not(svg) { + #options .aligned > *:not(.svg-inline-wrapper) { margin: 1px 0 0 0; /* workaround the flowing-padding column bug in webkit */ padding-right: 0.4rem; vertical-align: baseline; From ce8a0a2d422c2c0fee9de1e108a8e04243488a75 Mon Sep 17 00:00:00 2001 From: tophf Date: Tue, 21 Nov 2017 18:36:11 +0300 Subject: [PATCH 016/161] assign color swatches correctly: skip overlays in the middle --- vendor-overwrites/colorpicker/colorpicker.css | 8 ++++++-- vendor-overwrites/colorpicker/colorview.js | 11 +++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/vendor-overwrites/colorpicker/colorpicker.css b/vendor-overwrites/colorpicker/colorpicker.css index 532a6a07..21dd4084 100644 --- a/vendor-overwrites/colorpicker/colorpicker.css +++ b/vendor-overwrites/colorpicker/colorpicker.css @@ -17,8 +17,12 @@ background-repeat: repeat; } -.cm-colorview + .cm-colorview.cm-overlay::before, -.cm-colorview.cm-overlay + .cm-colorview::before { +/* overlay at the start splits our colorview: don't style the tail */ +.cm-colorview.cm-overlay + .cm-colorview:not([data-colorpicker])::before, +/* overlay in the middle splits our colorview: don't style the middle */ +.cm-colorview + .cm-colorview.cm-overlay:not([data-colorpicker])::before, +/* ...and the tail */ +.cm-colorview + .cm-colorview.cm-overlay:not([data-colorpicker]) + .cm-colorview:not([data-colorpicker])::before { content: none; } diff --git a/vendor-overwrites/colorpicker/colorview.js b/vendor-overwrites/colorpicker/colorview.js index 7086926b..7bf179ce 100644 --- a/vendor-overwrites/colorpicker/colorview.js +++ b/vendor-overwrites/colorpicker/colorview.js @@ -163,6 +163,17 @@ } elements = elements || text.getElementsByClassName(OWN_DOM_CLASS); const el = elements[elementIndex++]; + while (true) { + const nextStyle = styles[i + 3]; + const nextStart = styles[i]; + if (nextStyle && nextStyle.includes(OWN_TOKEN_NAME) && + nextStart > start && nextStart <= start + data.color.length) { + elementIndex++; + i += 2; + } else { + break; + } + } if (el.colorpickerData && el.colorpickerData.color === data.color) { continue; } From 352846c8b422120170900900781dcf3cf975e2db Mon Sep 17 00:00:00 2001 From: tophf Date: Tue, 21 Nov 2017 18:38:07 +0300 Subject: [PATCH 017/161] fix and update ColorView.openPopup() --- vendor-overwrites/colorpicker/colorview.js | 31 +++++++++------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/vendor-overwrites/colorpicker/colorview.js b/vendor-overwrites/colorpicker/colorview.js index 7bf179ce..3fa3c554 100644 --- a/vendor-overwrites/colorpicker/colorview.js +++ b/vendor-overwrites/colorpicker/colorview.js @@ -397,23 +397,16 @@ this.cm.state.colorpicker = null; } - openPopup(defaultColor = '#FFFFFF') { - const cursor = this.cm.getCursor(); - const data = { - line: cursor.line, - ch: cursor.ch, - color: defaultColor, - isShortCut: true, - }; - for (const {from, marker} of this.cm.getLineHandle(cursor.line).markedSpans || []) { - if (from <= data.ch && (marker.replacedWith || {}).colorpickerData) { - const {color, colorValue} = marker.replacedWith.colorpickerData; - if (data.ch <= from + color.length) { - data.ch = from; - data.color = color; - data.colorValue = colorValue; - break; - } + openPopup(color) { + let {line, ch} = this.cm.getCursor(); + const lineText = this.cm.getLine(line); + ch -= (lineText.lastIndexOf('!important', ch) >= ch - '!important'.length) ? '!important'.length : 0; + const lineCache = this.cm.state.colorpicker.cache.get(lineText); + const data = {line, ch, color, isShortCut: true}; + for (const [start, {color, colorValue}] of lineCache && lineCache.entries() || []) { + if (start <= ch && ch <= start + color.length) { + Object.assign(data, {ch: start, color, colorValue}); + break; } } this.openPopupForToken({colorpickerData: data}); @@ -426,8 +419,8 @@ top, left, cm: this.cm, - color: data.colorValue || data.color, - prevColor: data.color, + color: data.colorValue || data.color || '#fff', + prevColor: data.color || '', isShortCut: false, callback: ColorMarker.popupOnChange, })); From b99391887d4e6d5e4ed80fb3a268edd75047f6f9 Mon Sep 17 00:00:00 2001 From: tophf Date: Tue, 21 Nov 2017 18:39:13 +0300 Subject: [PATCH 018/161] add colorpicker hotkey config icon [default: none] * uses the last submitted color by default * doesn't insert the default color until a change or the Enter key * fix a few bugs in colorpicker introduced in the rewrite --- edit.html | 5 + edit/edit.js | 96 ++++++++++++++++---- js/prefs.js | 4 + vendor-overwrites/colorpicker/colorpicker.js | 13 +-- vendor-overwrites/colorpicker/colorview.js | 11 ++- 5 files changed, 102 insertions(+), 27 deletions(-) diff --git a/edit.html b/edit.html index 984182e6..772fd162 100644 --- a/edit.html +++ b/edit.html @@ -193,6 +193,11 @@
+ + + + +
diff --git a/edit/edit.js b/edit/edit.js index 0775ede5..2307f395 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -1457,6 +1457,7 @@ function initHooks() { $('#sections-help').addEventListener('click', showSectionHelp, false); $('#keyMap-help').addEventListener('click', showKeyMapHelp, false); $('#cancel-button').addEventListener('click', goBackToManage); + $('#colorpicker-settings').addEventListener('click', configureColorpicker); setupOptionsExpand(); initLint(); @@ -1872,8 +1873,8 @@ function showHelp(title, body) { // avoid chaining on multiple showHelp() calls $('.dismiss', div).onclick = closeHelp; } - - div.style.display = 'block'; + // reset any inline styles + div.style = 'display: block'; return div; function closeHelp(e) { @@ -2078,6 +2079,7 @@ function onColorpickerReady() { '/vendor-overwrites/colorpicker/colorpicker.js', '/vendor-overwrites/colorpicker/colorview.js', ]; + prefs.subscribe(['editor.colorpicker.hotkey'], registerHotkey); prefs.subscribe(['editor.colorpicker'], colorpickerOnDemand); return prefs.get('editor.colorpicker') && colorpickerOnDemand(null, true); @@ -2087,21 +2089,83 @@ function onColorpickerReady() { } function setColorpickerOption(id, enabled) { - CodeMirror.defaults.colorpicker = enabled && { - forceUpdate: editors.length > 0, - tooltip: t('colorpickerTooltip'), - popupOptions: { - tooltipForSwitcher: t('colorpickerSwitchFormatTooltip'), - hexUppercase: prefs.get('editor.colorpicker.hexUppercase'), - hideDelay: 5000, - embedderCallback: state => { - if (state && state.hexUppercase !== prefs.get('editor.colorpicker.hexUppercase')) { - prefs.set('editor.colorpicker.hexUppercase', state.hexUppercase); - } + const defaults = CodeMirror.defaults; + const keyName = prefs.get('editor.colorpicker.hotkey'); + delete defaults.extraKeys[keyName]; + defaults.colorpicker = enabled; + if (enabled) { + if (keyName) { + CodeMirror.commands.colorpicker = invokeColorpicker; + defaults.extraKeys[keyName] = 'colorpicker'; + } + defaults.colorpicker = { + forceUpdate: editors.length > 0, + tooltip: t('colorpickerTooltip'), + popupOptions: { + tooltipForSwitcher: t('colorpickerSwitchFormatTooltip'), + hexUppercase: prefs.get('editor.colorpicker.hexUppercase'), + hideDelay: 5000, + embedderCallback: state => { + ['hexUppercase', 'color'] + .filter(name => state[name] !== prefs.get('editor.colorpicker.' + name)) + .forEach(name => prefs.set('editor.colorpicker.' + name, state[name])); + }, }, - }, - }; + }; + } // on page load runs before CodeMirror.setOption is defined - editors.forEach(cm => cm.setOption('colorpicker', CodeMirror.defaults.colorpicker)); + editors.forEach(cm => cm.setOption('colorpicker', defaults.colorpicker)); + } + + function registerHotkey(id, hotkey) { + const extraKeys = CodeMirror.defaults.extraKeys; + for (const key in extraKeys) { + if (extraKeys[key] === 'colorpicker') { + delete extraKeys[key]; + break; + } + } + if (hotkey) { + extraKeys[hotkey] = 'colorpicker'; + } + } + + function invokeColorpicker(cm) { + cm.state.colorpicker.openPopup(prefs.get('editor.colorpicker.color')); } } + +function configureColorpicker() { + const input = $element({ + tag: 'input', + type: 'search', + spellcheck: false, + value: prefs.get('editor.colorpicker.hotkey'), + onkeydown(event) { + const key = CodeMirror.keyName(event); + // ignore: [Shift?] characters, modifiers-only, [Shift?] Esc, Enter, [Shift?] Tab + if (/^(Enter|(Shift-)?(Esc|Tab|[!-~])|(Shift-?|Ctrl-?|Alt-?|Cmd-?)*)$/.test(key)) { + return; + } + event.preventDefault(); + event.stopPropagation(); + prefs.set('editor.colorpicker.hotkey', key); + this.value = key; + }, + oninput() { + // fired on pressing "x" to clear the field + prefs.set('editor.colorpicker.hotkey', ''); + }, + onpaste(event) { + event.preventDefault(); + } + }); + const popup = showHelp(t('helpKeyMapHotkey'), input); + if (this instanceof Element) { + const bounds = this.getBoundingClientRect(); + popup.style.left = bounds.right + 10 + 'px'; + popup.style.top = bounds.top - popup.clientHeight / 2 + 'px'; + popup.style.right = 'auto'; + } + input.focus(); +} diff --git a/js/prefs.js b/js/prefs.js index 1c93a06c..09260bbe 100644 --- a/js/prefs.js +++ b/js/prefs.js @@ -60,6 +60,10 @@ var prefs = new function Prefs() { 'editor.colorpicker': true, // #DEAD or #beef 'editor.colorpicker.hexUppercase': false, + // default hotkey + 'editor.colorpicker.hotkey': '', + // last color + 'editor.colorpicker.color': '', 'iconset': 0, // 0 = dark-themed icon // 1 = light-themed icon diff --git a/vendor-overwrites/colorpicker/colorpicker.js b/vendor-overwrites/colorpicker/colorpicker.js index c430d5cc..013f368f 100644 --- a/vendor-overwrites/colorpicker/colorpicker.js +++ b/vendor-overwrites/colorpicker/colorpicker.js @@ -203,7 +203,7 @@ CodeMirror.defineExtension('colorpicker', function () { options = PUBLIC_API.options = opt; prevFocusedElement = document.activeElement; userActivity = 0; - lastOutputColor = opt.color; + lastOutputColor = opt.color || ''; $formatChangeButton.title = opt.tooltipForSwitcher || ''; opt.hideDelay = Math.max(0, opt.hideDelay) || 2000; @@ -337,7 +337,7 @@ CodeMirror.defineExtension('colorpicker', function () { } function validateInput(el) { - const isAlpha = el.type === 'text'; + const isAlpha = el === $inputs[currentFormat][3]; let isValid = (isAlpha || el.value.trim()) && el.checkValidity(); if (!isAlpha && !isValid && currentFormat === 'rgb') { isValid = parseAs(el, parseInt); @@ -352,8 +352,9 @@ CodeMirror.defineExtension('colorpicker', function () { //endregion //region State-to-DOM - function setFromColor(color = '#FF0000') { + function setFromColor(color) { color = typeof color === 'string' ? stringToColor(color) : color; + color = color || stringToColor('#f00'); const newHSV = color.type === 'hsl' ? HSLtoHSV(color) : RGBtoHSV(color); if (Object.keys(newHSV).every(k => Math.abs(newHSV[k] - HSV[k]) < 1e-3)) { return; @@ -440,7 +441,7 @@ CodeMirror.defineExtension('colorpicker', function () { } } - function onSaturationMouseUp() { + function onSaturationMouseUp(event) { if (event.button === 0) { dragging.saturation = false; releaseMouse(); @@ -454,7 +455,7 @@ CodeMirror.defineExtension('colorpicker', function () { } } - function onOpacityKnobMouseDown() { + function onOpacityKnobMouseDown(event) { if (event.button === 0) { dragging.opacity = true; captureMouse(); @@ -517,6 +518,7 @@ CodeMirror.defineExtension('colorpicker', function () { if (!e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) { switch (e.which) { case 13: + setFromInputs(); colorpickerCallback(); // fallthrough to 27 case 27: @@ -647,7 +649,6 @@ CodeMirror.defineExtension('colorpicker', function () { str.match(/(..)/g).map(c => parseInt(c, 16)); return {type: 'hex', r, g, b, a: a === 255 ? undefined : a / 255}; } - return; } function RGBtoHSV({r, g, b, a}) { diff --git a/vendor-overwrites/colorpicker/colorview.js b/vendor-overwrites/colorpicker/colorview.js index 3fa3c554..93c7713d 100644 --- a/vendor-overwrites/colorpicker/colorview.js +++ b/vendor-overwrites/colorpicker/colorview.js @@ -400,10 +400,11 @@ openPopup(color) { let {line, ch} = this.cm.getCursor(); const lineText = this.cm.getLine(line); - ch -= (lineText.lastIndexOf('!important', ch) >= ch - '!important'.length) ? '!important'.length : 0; + const atImportant = lineText.lastIndexOf('!important', ch); + ch -= (atImportant >= Math.max(0, ch - '!important'.length)) ? '!important'.length : 0; const lineCache = this.cm.state.colorpicker.cache.get(lineText); - const data = {line, ch, color, isShortCut: true}; - for (const [start, {color, colorValue}] of lineCache && lineCache.entries() || []) { + const data = {line, ch, colorValue: color, isShortCut: true}; + for (const [start, {color, colorValue = color}] of lineCache && lineCache.entries() || []) { if (start <= ch && ch <= start + color.length) { Object.assign(data, {ch: start, color, colorValue}); break; @@ -419,7 +420,7 @@ top, left, cm: this.cm, - color: data.colorValue || data.color || '#fff', + color: data.colorValue || data.color, prevColor: data.color || '', isShortCut: false, callback: ColorMarker.popupOnChange, @@ -437,8 +438,8 @@ const {cm, line, ch, embedderCallback} = this; const to = {line, ch: ch + this.prevColor.length}; if (cm.getRange(this, to) !== newColor) { - this.prevColor = newColor; cm.replaceRange(newColor, this, to, '*colorpicker'); + this.prevColor = newColor; } if (typeof embedderCallback === 'function') { embedderCallback(this); From aa2fef7f724d6a68c8c891906116db4070abe3bd Mon Sep 17 00:00:00 2001 From: tophf Date: Wed, 22 Nov 2017 01:15:52 +0300 Subject: [PATCH 019/161] more keyboard control in colorpicker popup hex mode similar to chrome-devtools: * Ctrl-Up/Down for R channel * Shift-Up/Down for G channel * Alt-Up/Down for B channel * Up/Down treats the entire color as a hex integer e.g. #00f -> #010 rgb/hsl modes similar to chrome-devtools: * Ctrl-Up/Down steps by 100 (alpha: by 1) * Shift-Up/Down steps by 10 (alpha: by .1) * Up/Down steps by 1 (alpha: by .01) * PgUp/PgDn to switch format * Tab to switch format when at the first/last input field --- _locales/en/messages.json | 2 +- vendor-overwrites/colorpicker/colorpicker.js | 88 ++++++++++++++++++-- 2 files changed, 81 insertions(+), 9 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 12cba426..584b7f1a 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -176,7 +176,7 @@ "description": "Label for the style editor's CSS theme." }, "colorpickerSwitchFormatTooltip": { - "message": "Switch formats: HEX -> RGB -> HSL", + "message": "Switch formats: HEX -> RGB -> HSL.\nShift-click to reverse the direction.\nAlso via PgUp (PageUp), PgDn (PageDown), Tab keys.", "description": "Tooltip for the switch button in the color picker popup in the style editor." }, "colorpickerTooltip": { diff --git a/vendor-overwrites/colorpicker/colorpicker.js b/vendor-overwrites/colorpicker/colorpicker.js index 013f368f..c1ff8415 100644 --- a/vendor-overwrites/colorpicker/colorpicker.js +++ b/vendor-overwrites/colorpicker/colorpicker.js @@ -211,7 +211,6 @@ CodeMirror.defineExtension('colorpicker', function () { reposition(); setFromColor(opt.color); setFromHexLettercaseElement(); - $inputs[currentFormat][0].focus(); } function hide() { @@ -312,11 +311,13 @@ CodeMirror.defineExtension('colorpicker', function () { renderInputs(); } - function setFromFormatElement() { + function setFromFormatElement({shiftKey}) { userActivity = performance.now(); - const nextFormat = {hex: 'rgb', rgb: 'hsl', hsl: 'hex'}[currentFormat]; HSV.a = isNaN(HSV.a) ? 1 : HSV.a; - switchInputGroup(nextFormat); + const formats = ['hex', 'rgb', 'hsl']; + const dir = shiftKey ? -1 : 1; + const total = formats.length; + switchInputGroup(formats[(formats.indexOf(currentFormat) + dir + total) % total]); renderInputs(); } @@ -336,6 +337,78 @@ CodeMirror.defineExtension('colorpicker', function () { } } + function setFromKeyboard(event) { + const {which, ctrlKey: ctrl, altKey: alt, shiftKey: shift, metaKey: meta} = event; + switch (which) { + case 9: // Tab + case 33: // PgUp + case 34: // PgDn + if (!ctrl && !alt && !meta) { + const el = document.activeElement; + const inputs = $inputs[currentFormat]; + if (which !== 9 && !shift || + el === inputs[0] && shift || + el === inputs[inputs.length - 1] && !shift) { + event.preventDefault(); + setFromFormatElement({shift: which === 33 || shift}); + } + } + return; + case 38: // Up + case 40: // Down + if (!event.metaKey && + document.activeElement.localName === 'input' && + document.activeElement.checkValidity()) { + setFromKeyboardIncrement(event); + } + return; + } + } + + function setFromKeyboardIncrement(event) { + const el = document.activeElement; + const {which, ctrlKey: ctrl, altKey: alt, shiftKey: shift} = event; + const dir = which === 38 ? 1 : -1; + let value, newValue; + if (currentFormat === 'hex') { + value = el.value.trim(); + const isShort = value.length <= 5; + const [r, g, b, a = ''] = el.value.match(isShort ? /[\da-f]/g : /[\da-f]{2}/g); + let ceiling, data; + if (!ctrl && !shift && !alt) { + ceiling = isShort ? 0xFFF : 0xFFFFFF; + data = [[true, r + g + b]]; + } else { + ceiling = isShort ? 15 : 255; + data = [[ctrl, r], [shift, g], [alt, b]]; + } + newValue = '#' + data.map(([affected, part]) => { + part = constrain(0, ceiling, parseInt(part, 16) + dir * (affected ? 1 : 0)); + return (part + ceiling + 1).toString(16).slice(1); + }).join('') + a; + newValue = options.hexUppercase ? newValue.toUpperCase() : newValue.toLowerCase(); + } else if (!alt) { + const delta = + shift && !ctrl ? 10 : + ctrl && !shift ? 100 : + 1; + value = parseFloat(el.value); + const isHue = el === $inputs.hsl[0]; + const isAlpha = el === $inputs[currentFormat][3]; + const min = isHue ? -360 : 0; + const max = isHue ? 360 : isAlpha ? 1 : currentFormat === 'rgb' ? 255 : 100; + const scale = isAlpha ? .01 : 1; + newValue = constrain(min, max, value + delta * scale * dir); + newValue = isAlpha ? alphaToString(newValue) : newValue; + } + event.preventDefault(); + userActivity = performance.now(); + if (newValue !== undefined && newValue !== value) { + el.value = newValue; + setFromColor($inputs.color); + } + } + function validateInput(el) { const isAlpha = el === $inputs[currentFormat][3]; let isValid = (isAlpha || el.value.trim()) && el.checkValidity(); @@ -377,6 +450,7 @@ CodeMirror.defineExtension('colorpicker', function () { } } $inputGroups[format].dataset.active = ''; + $inputs[format][0].focus(); currentFormat = format; } @@ -572,6 +646,7 @@ CodeMirror.defineExtension('colorpicker', function () { $root.addEventListener('mouseleave', snooze); $root.addEventListener('mouseenter', stopSnoozing); $root.addEventListener('input', setFromInputs); + $root.addEventListener('keydown', setFromKeyboard); $formatChangeButton.addEventListener('click', setFromFormatElement); $sat.addEventListener('mousedown', onSaturationMouseDown); $sat.addEventListener('mouseup', onSaturationMouseUp); @@ -747,10 +822,7 @@ CodeMirror.defineExtension('colorpicker', function () { } function alphaToString(a = HSV.a) { - return isNaN(a) ? '' : - a.toString().slice(0, 8) - .replace(/(\.[^0]*)0+$/, '$1') - .replace(/^1$/, ''); + return isNaN(a) ? '' : (a + .5e-6).toFixed(7).slice(0, -1).replace(/^0(?=\.[1-9])|^1\.0+?$|\.?0+$/g, ''); } //endregion From 2a0ecec28ee33f06c9049ca8bb3487bfe0ff7eb6 Mon Sep 17 00:00:00 2001 From: tophf Date: Wed, 22 Nov 2017 01:56:41 +0300 Subject: [PATCH 020/161] show color swatches in usercss with stylus preprocessor --- vendor-overwrites/colorpicker/colorview.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/vendor-overwrites/colorpicker/colorview.js b/vendor-overwrites/colorpicker/colorview.js index 93c7713d..036fdf7a 100644 --- a/vendor-overwrites/colorpicker/colorview.js +++ b/vendor-overwrites/colorpicker/colorview.js @@ -58,9 +58,8 @@ function registerHooks() { const mx = CodeMirror.modeExtensions.css; if (!mx || mx.token !== colorizeToken) { - CodeMirror.extendMode('css', { - token: colorizeToken, - }); + CodeMirror.extendMode('css', {token: colorizeToken}); + CodeMirror.extendMode('stylus', {token: colorizeToken}); } } @@ -128,7 +127,7 @@ if (string.charAt(start) !== '!') { const color = string.slice(start, pos); const colorValue = NAMED_COLORS.get(color.toLowerCase()); - return colorValue && {color, colorValue}; + return colorValue ? {color, colorValue} : colorizeAtom(stream); } } From 6c858d64497a2ff7ff28ebc1ffa93f9a6c3dc1fa Mon Sep 17 00:00:00 2001 From: tophf Date: Wed, 22 Nov 2017 02:20:09 +0300 Subject: [PATCH 021/161] fix colorpicker hotkey config --- edit/edit.css | 4 ++++ edit/edit.js | 9 +++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/edit/edit.css b/edit/edit.css index 19f3c4b7..a550a8e5 100644 --- a/edit/edit.css +++ b/edit/edit.css @@ -103,6 +103,10 @@ h2 .svg-icon, label .svg-icon { .svg-icon.settings:hover { fill: #000; } +input:invalid { + background-color: rgba(255, 0, 0, 0.1); + color: darkred; +} #enabled { margin-left: 0; vertical-align: middle; diff --git a/edit/edit.js b/edit/edit.js index 2307f395..577d4c93 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -2144,12 +2144,17 @@ function configureColorpicker() { onkeydown(event) { const key = CodeMirror.keyName(event); // ignore: [Shift?] characters, modifiers-only, [Shift?] Esc, Enter, [Shift?] Tab - if (/^(Enter|(Shift-)?(Esc|Tab|[!-~])|(Shift-?|Ctrl-?|Alt-?|Cmd-?)*)$/.test(key)) { + if (/^(Enter|(Shift-)?(Esc|Tab))$/.test(key)) { + this.setCustomValidity(''); return; + } else if (/^((Shift-)?[!-~]|(Shift-?|Ctrl-?|Alt-?|Cmd-?)*)$/.test(key)) { + this.setCustomValidity('Not allowed'); + } else { + this.setCustomValidity(''); + prefs.set('editor.colorpicker.hotkey', key); } event.preventDefault(); event.stopPropagation(); - prefs.set('editor.colorpicker.hotkey', key); this.value = key; }, oninput() { From 75da5c50ceb785a983008693cd2e037cab6fef6a Mon Sep 17 00:00:00 2001 From: tophf Date: Wed, 22 Nov 2017 02:26:49 +0300 Subject: [PATCH 022/161] colorpicker Ctrl-up/down steps in 50 on S, L, .5 on alpha --- vendor-overwrites/colorpicker/colorpicker.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/vendor-overwrites/colorpicker/colorpicker.js b/vendor-overwrites/colorpicker/colorpicker.js index c1ff8415..29adf78b 100644 --- a/vendor-overwrites/colorpicker/colorpicker.js +++ b/vendor-overwrites/colorpicker/colorpicker.js @@ -388,16 +388,17 @@ CodeMirror.defineExtension('colorpicker', function () { }).join('') + a; newValue = options.hexUppercase ? newValue.toUpperCase() : newValue.toLowerCase(); } else if (!alt) { - const delta = - shift && !ctrl ? 10 : - ctrl && !shift ? 100 : - 1; value = parseFloat(el.value); const isHue = el === $inputs.hsl[0]; const isAlpha = el === $inputs[currentFormat][3]; + const isRGB = currentFormat === 'rgb'; const min = isHue ? -360 : 0; - const max = isHue ? 360 : isAlpha ? 1 : currentFormat === 'rgb' ? 255 : 100; + const max = isHue ? 360 : isAlpha ? 1 : isRGB ? 255 : 100; const scale = isAlpha ? .01 : 1; + const delta = + shift && !ctrl ? 10 : + ctrl && !shift ? (isHue || isRGB ? 100 : 50) : + 1; newValue = constrain(min, max, value + delta * scale * dir); newValue = isAlpha ? alphaToString(newValue) : newValue; } From 7bae73bcb4260a31b355bfcd447f1cee4154d459 Mon Sep 17 00:00:00 2001 From: tophf Date: Wed, 22 Nov 2017 02:42:56 +0300 Subject: [PATCH 023/161] add onclick for colorpicker-settings icon in usercss mode --- edit/source-editor.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/edit/source-editor.js b/edit/source-editor.js index 7ad3d326..20a26be4 100644 --- a/edit/source-editor.js +++ b/edit/source-editor.js @@ -1,4 +1,4 @@ -/* global CodeMirror dirtyReporter initLint beautify showKeyMapHelp */ +/* global CodeMirror dirtyReporter initLint beautify showKeyMapHelp configureColorpicker */ /* global showToggleStyleHelp goBackToManage updateLintReportIfEnabled */ /* global hotkeyRerouter setupAutocomplete setupOptionsExpand */ /* global editors linterConfig updateLinter regExpTester mozParser */ @@ -145,6 +145,7 @@ ${section} $('#save-button').onclick = save; $('#beautify').onclick = beautify; $('#keyMap-help').onclick = showKeyMapHelp; + $('#colorpicker-settings').addEventListener('click', configureColorpicker); $('#toggle-style-help').onclick = showToggleStyleHelp; $('#cancel-button').onclick = goBackToManage; From 2c82cc3bbca1790a42245c0c50769a5f7abc9155 Mon Sep 17 00:00:00 2001 From: tophf Date: Wed, 22 Nov 2017 02:51:06 +0300 Subject: [PATCH 024/161] fix glitching on highlighting of current token --- vendor-overwrites/colorpicker/colorview.js | 1 + 1 file changed, 1 insertion(+) diff --git a/vendor-overwrites/colorpicker/colorview.js b/vendor-overwrites/colorpicker/colorview.js index 036fdf7a..ed149897 100644 --- a/vendor-overwrites/colorpicker/colorview.js +++ b/vendor-overwrites/colorpicker/colorview.js @@ -176,6 +176,7 @@ if (el.colorpickerData && el.colorpickerData.color === data.color) { continue; } + el.dataset.colorpicker = ''; el.colorpickerData = Object.assign({line, ch: start}, data); let bg = el.firstElementChild; if (!bg) { From fc20479ab435118b70970ef02b7574539b3b6040 Mon Sep 17 00:00:00 2001 From: tophf Date: Wed, 22 Nov 2017 02:54:25 +0300 Subject: [PATCH 025/161] correctly unhook colorpicker in usercss#stylus mode --- vendor-overwrites/colorpicker/colorview.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/vendor-overwrites/colorpicker/colorview.js b/vendor-overwrites/colorpicker/colorview.js index ed149897..1a5c1879 100644 --- a/vendor-overwrites/colorpicker/colorview.js +++ b/vendor-overwrites/colorpicker/colorview.js @@ -64,9 +64,11 @@ } function unregisterHooks() { - const mx = CodeMirror.modeExtensions.css; - if (mx && mx.token === colorizeToken) { - delete mx.token; + for (const name in CodeMirror.modeExtensions) { + const mx = CodeMirror.modeExtensions[name]; + if (mx && mx.token === colorizeToken) { + delete mx.token; + } } } From 786cde781d2acee052a3e958c82b85b3c8acb585 Mon Sep 17 00:00:00 2001 From: tophf Date: Wed, 22 Nov 2017 03:12:05 +0300 Subject: [PATCH 026/161] pull out colorpicker-helper.js --- edit.html | 1 + edit/colorpicker-helper.js | 106 +++++++++++++++++++++++++++++++++++++ edit/edit.js | 105 +----------------------------------- edit/source-editor.js | 3 +- 4 files changed, 109 insertions(+), 106 deletions(-) create mode 100644 edit/colorpicker-helper.js diff --git a/edit.html b/edit.html index 772fd162..c5462681 100644 --- a/edit.html +++ b/edit.html @@ -26,6 +26,7 @@ + diff --git a/edit/colorpicker-helper.js b/edit/colorpicker-helper.js new file mode 100644 index 00000000..66f51e17 --- /dev/null +++ b/edit/colorpicker-helper.js @@ -0,0 +1,106 @@ +/* global CodeMirror loadScript editors */ +'use strict'; + +window.initColorpicker = () => { + onDOMready().then(() => { + $('#colorpicker-settings').onclick = configureColorpicker; + }); + const scripts = [ + '/vendor-overwrites/colorpicker/colorpicker.css', + '/vendor-overwrites/colorpicker/colorpicker.js', + '/vendor-overwrites/colorpicker/colorview.js', + ]; + prefs.subscribe(['editor.colorpicker.hotkey'], registerHotkey); + prefs.subscribe(['editor.colorpicker'], colorpickerOnDemand); + return prefs.get('editor.colorpicker') && colorpickerOnDemand(null, true); + + function colorpickerOnDemand(id, enabled) { + return loadScript(enabled && scripts) + .then(() => setColorpickerOption(id, enabled)); + } + + function setColorpickerOption(id, enabled) { + const defaults = CodeMirror.defaults; + const keyName = prefs.get('editor.colorpicker.hotkey'); + delete defaults.extraKeys[keyName]; + defaults.colorpicker = enabled; + if (enabled) { + if (keyName) { + CodeMirror.commands.colorpicker = invokeColorpicker; + defaults.extraKeys[keyName] = 'colorpicker'; + } + defaults.colorpicker = { + forceUpdate: editors.length > 0, + tooltip: t('colorpickerTooltip'), + popupOptions: { + tooltipForSwitcher: t('colorpickerSwitchFormatTooltip'), + hexUppercase: prefs.get('editor.colorpicker.hexUppercase'), + hideDelay: 5000, + embedderCallback: state => { + ['hexUppercase', 'color'] + .filter(name => state[name] !== prefs.get('editor.colorpicker.' + name)) + .forEach(name => prefs.set('editor.colorpicker.' + name, state[name])); + }, + }, + }; + } + // on page load runs before CodeMirror.setOption is defined + editors.forEach(cm => cm.setOption('colorpicker', defaults.colorpicker)); + } + + function registerHotkey(id, hotkey) { + const extraKeys = CodeMirror.defaults.extraKeys; + for (const key in extraKeys) { + if (extraKeys[key] === 'colorpicker') { + delete extraKeys[key]; + break; + } + } + if (hotkey) { + extraKeys[hotkey] = 'colorpicker'; + } + } + + function invokeColorpicker(cm) { + cm.state.colorpicker.openPopup(prefs.get('editor.colorpicker.color')); + } + + function configureColorpicker() { + const input = $element({ + tag: 'input', + type: 'search', + spellcheck: false, + value: prefs.get('editor.colorpicker.hotkey'), + onkeydown(event) { + const key = CodeMirror.keyName(event); + // ignore: [Shift?] characters, modifiers-only, [Shift?] Esc, Enter, [Shift?] Tab + if (/^(Enter|(Shift-)?(Esc|Tab))$/.test(key)) { + return; + } else if (/^((Shift-)?[!-~]|(Shift-?|Ctrl-?|Alt-?|Cmd-?)*)$/.test(key)) { + this.setCustomValidity('Not allowed'); + } else { + this.setCustomValidity(''); + prefs.set('editor.colorpicker.hotkey', key); + } + event.preventDefault(); + event.stopPropagation(); + this.value = key; + }, + oninput() { + // fired on pressing "x" to clear the field + prefs.set('editor.colorpicker.hotkey', ''); + }, + onpaste(event) { + event.preventDefault(); + } + }); + const popup = showHelp(t('helpKeyMapHotkey'), input); + if (this instanceof Element) { + const bounds = this.getBoundingClientRect(); + popup.style.left = bounds.right + 10 + 'px'; + popup.style.top = bounds.top - popup.clientHeight / 2 + 'px'; + popup.style.right = 'auto'; + } + input.focus(); + } +}; diff --git a/edit/edit.js b/edit/edit.js index 577d4c93..db17aae2 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -9,7 +9,7 @@ onDOMready() .then(() => Promise.all([ - onColorpickerReady(), + window.initColorpicker(), ])) .then(init); @@ -1457,7 +1457,6 @@ function initHooks() { $('#sections-help').addEventListener('click', showSectionHelp, false); $('#keyMap-help').addEventListener('click', showKeyMapHelp, false); $('#cancel-button').addEventListener('click', goBackToManage); - $('#colorpicker-settings').addEventListener('click', configureColorpicker); setupOptionsExpand(); initLint(); @@ -2072,105 +2071,3 @@ function setGlobalProgress(done, total) { progressElement.remove(); } } - -function onColorpickerReady() { - const scripts = [ - '/vendor-overwrites/colorpicker/colorpicker.css', - '/vendor-overwrites/colorpicker/colorpicker.js', - '/vendor-overwrites/colorpicker/colorview.js', - ]; - prefs.subscribe(['editor.colorpicker.hotkey'], registerHotkey); - prefs.subscribe(['editor.colorpicker'], colorpickerOnDemand); - return prefs.get('editor.colorpicker') && colorpickerOnDemand(null, true); - - function colorpickerOnDemand(id, enabled) { - return loadScript(enabled && scripts) - .then(() => setColorpickerOption(id, enabled)); - } - - function setColorpickerOption(id, enabled) { - const defaults = CodeMirror.defaults; - const keyName = prefs.get('editor.colorpicker.hotkey'); - delete defaults.extraKeys[keyName]; - defaults.colorpicker = enabled; - if (enabled) { - if (keyName) { - CodeMirror.commands.colorpicker = invokeColorpicker; - defaults.extraKeys[keyName] = 'colorpicker'; - } - defaults.colorpicker = { - forceUpdate: editors.length > 0, - tooltip: t('colorpickerTooltip'), - popupOptions: { - tooltipForSwitcher: t('colorpickerSwitchFormatTooltip'), - hexUppercase: prefs.get('editor.colorpicker.hexUppercase'), - hideDelay: 5000, - embedderCallback: state => { - ['hexUppercase', 'color'] - .filter(name => state[name] !== prefs.get('editor.colorpicker.' + name)) - .forEach(name => prefs.set('editor.colorpicker.' + name, state[name])); - }, - }, - }; - } - // on page load runs before CodeMirror.setOption is defined - editors.forEach(cm => cm.setOption('colorpicker', defaults.colorpicker)); - } - - function registerHotkey(id, hotkey) { - const extraKeys = CodeMirror.defaults.extraKeys; - for (const key in extraKeys) { - if (extraKeys[key] === 'colorpicker') { - delete extraKeys[key]; - break; - } - } - if (hotkey) { - extraKeys[hotkey] = 'colorpicker'; - } - } - - function invokeColorpicker(cm) { - cm.state.colorpicker.openPopup(prefs.get('editor.colorpicker.color')); - } -} - -function configureColorpicker() { - const input = $element({ - tag: 'input', - type: 'search', - spellcheck: false, - value: prefs.get('editor.colorpicker.hotkey'), - onkeydown(event) { - const key = CodeMirror.keyName(event); - // ignore: [Shift?] characters, modifiers-only, [Shift?] Esc, Enter, [Shift?] Tab - if (/^(Enter|(Shift-)?(Esc|Tab))$/.test(key)) { - this.setCustomValidity(''); - return; - } else if (/^((Shift-)?[!-~]|(Shift-?|Ctrl-?|Alt-?|Cmd-?)*)$/.test(key)) { - this.setCustomValidity('Not allowed'); - } else { - this.setCustomValidity(''); - prefs.set('editor.colorpicker.hotkey', key); - } - event.preventDefault(); - event.stopPropagation(); - this.value = key; - }, - oninput() { - // fired on pressing "x" to clear the field - prefs.set('editor.colorpicker.hotkey', ''); - }, - onpaste(event) { - event.preventDefault(); - } - }); - const popup = showHelp(t('helpKeyMapHotkey'), input); - if (this instanceof Element) { - const bounds = this.getBoundingClientRect(); - popup.style.left = bounds.right + 10 + 'px'; - popup.style.top = bounds.top - popup.clientHeight / 2 + 'px'; - popup.style.right = 'auto'; - } - input.focus(); -} diff --git a/edit/source-editor.js b/edit/source-editor.js index 20a26be4..7ad3d326 100644 --- a/edit/source-editor.js +++ b/edit/source-editor.js @@ -1,4 +1,4 @@ -/* global CodeMirror dirtyReporter initLint beautify showKeyMapHelp configureColorpicker */ +/* global CodeMirror dirtyReporter initLint beautify showKeyMapHelp */ /* global showToggleStyleHelp goBackToManage updateLintReportIfEnabled */ /* global hotkeyRerouter setupAutocomplete setupOptionsExpand */ /* global editors linterConfig updateLinter regExpTester mozParser */ @@ -145,7 +145,6 @@ ${section} $('#save-button').onclick = save; $('#beautify').onclick = beautify; $('#keyMap-help').onclick = showKeyMapHelp; - $('#colorpicker-settings').addEventListener('click', configureColorpicker); $('#toggle-style-help').onclick = showToggleStyleHelp; $('#cancel-button').onclick = goBackToManage; From d767ad7a8c577d657ff9a27eeefdd01cba99a543 Mon Sep 17 00:00:00 2001 From: tophf Date: Wed, 22 Nov 2017 03:38:29 +0300 Subject: [PATCH 027/161] autoLoadMode -> loadScript; chain to avoid flicker on load --- edit.html | 2 - edit/codemirror-default.js | 10 ++-- edit/source-editor.js | 25 ++++----- install-usercss.html | 2 +- vendor/codemirror/addon/mode/loadmode.js | 64 ------------------------ 5 files changed, 19 insertions(+), 84 deletions(-) delete mode 100644 vendor/codemirror/addon/mode/loadmode.js diff --git a/edit.html b/edit.html index c5462681..34b695de 100644 --- a/edit.html +++ b/edit.html @@ -57,8 +57,6 @@ - - diff --git a/edit/codemirror-default.js b/edit/codemirror-default.js index e642ca3e..a46dba00 100644 --- a/edit/codemirror-default.js +++ b/edit/codemirror-default.js @@ -1,4 +1,4 @@ -/* global CodeMirror prefs */ +/* global CodeMirror prefs loadScript */ 'use strict'; @@ -100,16 +100,16 @@ }); } - CodeMirror.modeURL = '/vendor/codemirror/mode/%N/%N.js'; - const MODE = { stylus: 'stylus', uso: 'css' }; CodeMirror.defineExtension('setPreprocessor', function (preprocessor) { - this.setOption('mode', MODE[preprocessor] || 'css'); - CodeMirror.autoLoadMode(this, MODE[preprocessor] || 'css'); + const mode = MODE[preprocessor] || 'css'; + return loadScript(mode !== 'css' && `/vendor/codemirror/mode/${mode}/${mode}.js`).then(() => { + this.setOption('mode', mode); + }); }); CodeMirror.defineExtension('isBlank', function () { diff --git a/edit/source-editor.js b/edit/source-editor.js index 7ad3d326..5cdd60d2 100644 --- a/edit/source-editor.js +++ b/edit/source-editor.js @@ -44,22 +44,23 @@ function createSourceEditor(style) { const cm = CodeMirror.fromTextArea($('#sections textarea')); cm.startOperation(); - cm.setValue(style.sourceCode); - cm.clearHistory(); - cm.markClean(); - editors.push(cm); - updateMeta(); - cm.endOperation(); + updateMeta().then(() => { + cm.setValue(style.sourceCode); + cm.clearHistory(); + cm.markClean(); + editors.push(cm); + cm.endOperation(); - initHooks(); - initAppliesToLineWidget(); + initHooks(); + initAppliesToLineWidget(); + + // focus must be the last action, otherwise the style is duplicated on saving + cm.focus(); + }); initLint(); initLinterSwitch(); - // focus must be the last action, otherwise the style is duplicated on saving - cm.focus(); - function initAppliesToLineWidget() { const PREF_NAME = 'editor.appliesToLineWidget'; const widget = createAppliesToLineWidget(cm); @@ -183,10 +184,10 @@ ${section} $('#enabled').checked = style.enabled; $('#url').href = style.url; const {usercssData: {preprocessor} = {}} = style; - cm.setPreprocessor(preprocessor); // beautify only works with regular CSS $('#beautify').disabled = cm.getOption('mode') !== 'css'; updateTitle(); + return cm.setPreprocessor(preprocessor); } function updateTitle() { diff --git a/install-usercss.html b/install-usercss.html index ca301b1d..b8b473d7 100644 --- a/install-usercss.html +++ b/install-usercss.html @@ -12,6 +12,7 @@ + @@ -47,7 +48,6 @@ -
diff --git a/vendor/codemirror/addon/mode/loadmode.js b/vendor/codemirror/addon/mode/loadmode.js deleted file mode 100644 index 10117ec2..00000000 --- a/vendor/codemirror/addon/mode/loadmode.js +++ /dev/null @@ -1,64 +0,0 @@ -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: http://codemirror.net/LICENSE - -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror"), "cjs"); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror"], function(CM) { mod(CM, "amd"); }); - else // Plain browser env - mod(CodeMirror, "plain"); -})(function(CodeMirror, env) { - if (!CodeMirror.modeURL) CodeMirror.modeURL = "../mode/%N/%N.js"; - - var loading = {}; - function splitCallback(cont, n) { - var countDown = n; - return function() { if (--countDown == 0) cont(); }; - } - function ensureDeps(mode, cont) { - var deps = CodeMirror.modes[mode].dependencies; - if (!deps) return cont(); - var missing = []; - for (var i = 0; i < deps.length; ++i) { - if (!CodeMirror.modes.hasOwnProperty(deps[i])) - missing.push(deps[i]); - } - if (!missing.length) return cont(); - var split = splitCallback(cont, missing.length); - for (var i = 0; i < missing.length; ++i) - CodeMirror.requireMode(missing[i], split); - } - - CodeMirror.requireMode = function(mode, cont) { - if (typeof mode != "string") mode = mode.name; - if (CodeMirror.modes.hasOwnProperty(mode)) return ensureDeps(mode, cont); - if (loading.hasOwnProperty(mode)) return loading[mode].push(cont); - - var file = CodeMirror.modeURL.replace(/%N/g, mode); - if (env == "plain") { - var script = document.createElement("script"); - script.src = file; - var others = document.getElementsByTagName("script")[0]; - var list = loading[mode] = [cont]; - CodeMirror.on(script, "load", function() { - ensureDeps(mode, function() { - for (var i = 0; i < list.length; ++i) list[i](); - }); - }); - others.parentNode.insertBefore(script, others); - } else if (env == "cjs") { - require(file); - cont(); - } else if (env == "amd") { - requirejs([file], cont); - } - }; - - CodeMirror.autoLoadMode = function(instance, mode) { - if (!CodeMirror.modes.hasOwnProperty(mode)) - CodeMirror.requireMode(mode, function() { - instance.setOption("mode", instance.getOption("mode")); - }); - }; -}); From e9abcc803127a7561f57cb3186b1de41dec3f7fd Mon Sep 17 00:00:00 2001 From: tophf Date: Wed, 22 Nov 2017 03:50:19 +0300 Subject: [PATCH 028/161] install-usercss: css tweaks * pad the Install button * remove global * selector * add narrow @media query * simplify css --- install-usercss/install-usercss.css | 45 ++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/install-usercss/install-usercss.css b/install-usercss/install-usercss.css index eb4df0a6..910f9295 100644 --- a/install-usercss/install-usercss.css +++ b/install-usercss/install-usercss.css @@ -4,10 +4,6 @@ body { background: white; } -* { - box-sizing: border-box; -} - a { color: #000; transition: color .5s; @@ -32,6 +28,7 @@ svg.icon { .header { flex: 0 0 280px; + box-sizing: border-box; padding: 15px; border-right: 1px dashed #aaa; box-shadow: 0 0 50px -18px black; @@ -53,9 +50,7 @@ h1 small { .warning { padding: 3px 6px; - border: 1px dashed black; - - border-color: #ef6969; + border: 1px dashed #ef6969; background: #ffe2e2; } @@ -68,13 +63,19 @@ h1 small { } .actions label { - max-width: fit-content; max-width: -moz-fit-content; + max-width: fit-content; display: flex; align-items: center; margin: 0.5em 0; } +.install { + padding: 1ex 1em; + font-weight: bold; + margin-bottom: 1ex; +} + .actions label input { margin: 0 0.5em 0 0; flex: 0 0 auto; @@ -92,6 +93,10 @@ h1 small { margin: 0 7.5px; } +li { + margin-left: -2em; +} + .code { padding: 2em; } @@ -101,9 +106,7 @@ h1 small { overflow: hidden; overflow-wrap: break-word; min-width: 0; - display: flex; - flex-direction: column; } .main > :first-child { @@ -119,3 +122,25 @@ h1 small { .main .CodeMirror { height: 100%; } + +/************ reponsive layouts ************/ +@media (max-width:7.5in) { + .container { + flex-direction: column; + } + .header { + flex-basis: auto; + -webkit-column-count: 2; + -moz-column-count: 2; + column-count: 2; + border-right: none; + border-bottom: 1px dashed #AAA; + z-index: 2; + } + .main { + padding-left: 0; + } + .code { + padding: 0; + } +} From 5d00602adaca03bb8eb0b6601a8c68030c917e67 Mon Sep 17 00:00:00 2001 From: tophf Date: Wed, 22 Nov 2017 04:32:20 +0300 Subject: [PATCH 029/161] switch colorpicker popup format on PgUp/PgDn only, not Tab --- _locales/en/messages.json | 2 +- vendor-overwrites/colorpicker/colorpicker.js | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 584b7f1a..f019c47e 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -176,7 +176,7 @@ "description": "Label for the style editor's CSS theme." }, "colorpickerSwitchFormatTooltip": { - "message": "Switch formats: HEX -> RGB -> HSL.\nShift-click to reverse the direction.\nAlso via PgUp (PageUp), PgDn (PageDown), Tab keys.", + "message": "Switch formats: HEX -> RGB -> HSL.\nShift-click to reverse the direction.\nAlso via PgUp (PageUp), PgDn (PageDown) keys.", "description": "Tooltip for the switch button in the color picker popup in the style editor." }, "colorpickerTooltip": { diff --git a/vendor-overwrites/colorpicker/colorpicker.js b/vendor-overwrites/colorpicker/colorpicker.js index 29adf78b..834cd313 100644 --- a/vendor-overwrites/colorpicker/colorpicker.js +++ b/vendor-overwrites/colorpicker/colorpicker.js @@ -346,12 +346,17 @@ CodeMirror.defineExtension('colorpicker', function () { if (!ctrl && !alt && !meta) { const el = document.activeElement; const inputs = $inputs[currentFormat]; - if (which !== 9 && !shift || - el === inputs[0] && shift || - el === inputs[inputs.length - 1] && !shift) { - event.preventDefault(); + const lastInput = inputs[inputs.length - 1]; + if (which === 9 && shift && el === inputs[0]) { + lastInput.focus(); + } else if (which === 9 && !shift && el === lastInput) { + inputs[0].focus(); + } else if (which !== 9 && !shift) { setFromFormatElement({shift: which === 33 || shift}); + } else { + return; } + event.preventDefault(); } return; case 38: // Up From 64cb128b069e906b69e44d01f1919c3937ee1d31 Mon Sep 17 00:00:00 2001 From: tophf Date: Wed, 22 Nov 2017 04:36:26 +0300 Subject: [PATCH 030/161] intercept the hotkey assigned to colorpicker via hotkeyRerouter --- edit/edit.js | 1 + 1 file changed, 1 insertion(+) diff --git a/edit/edit.js b/edit/edit.js index db17aae2..40971820 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -76,6 +76,7 @@ const hotkeyRerouter = { save: true, jumpToLine: true, nextEditor: true, prevEditor: true, find: true, findNext: true, findPrev: true, replace: true, replaceAll: true, toggleStyle: true, + colorpicker: true, }, setState: enable => { setTimeout(() => { From 612b21c732251710c7779b984bd5562841fcd2e7 Mon Sep 17 00:00:00 2001 From: tophf Date: Wed, 22 Nov 2017 05:15:52 +0300 Subject: [PATCH 031/161] register colorpicker command on first use --- edit/colorpicker-helper.js | 1 + 1 file changed, 1 insertion(+) diff --git a/edit/colorpicker-helper.js b/edit/colorpicker-helper.js index 66f51e17..66c7d4e6 100644 --- a/edit/colorpicker-helper.js +++ b/edit/colorpicker-helper.js @@ -49,6 +49,7 @@ window.initColorpicker = () => { } function registerHotkey(id, hotkey) { + CodeMirror.commands.colorpicker = invokeColorpicker; const extraKeys = CodeMirror.defaults.extraKeys; for (const key in extraKeys) { if (extraKeys[key] === 'colorpicker') { From 78bf2c8d9bd45d6c35cf3e22d7d49ce15ab7e722 Mon Sep 17 00:00:00 2001 From: tophf Date: Wed, 22 Nov 2017 05:35:24 +0300 Subject: [PATCH 032/161] close colorpicker config on Enter --- edit/colorpicker-helper.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/edit/colorpicker-helper.js b/edit/colorpicker-helper.js index 66c7d4e6..c65e6a31 100644 --- a/edit/colorpicker-helper.js +++ b/edit/colorpicker-helper.js @@ -1,4 +1,4 @@ -/* global CodeMirror loadScript editors */ +/* global CodeMirror loadScript editors showHelp */ 'use strict'; window.initColorpicker = () => { @@ -75,9 +75,10 @@ window.initColorpicker = () => { onkeydown(event) { const key = CodeMirror.keyName(event); // ignore: [Shift?] characters, modifiers-only, [Shift?] Esc, Enter, [Shift?] Tab - if (/^(Enter|(Shift-)?(Esc|Tab))$/.test(key)) { + if (key === 'Enter' || key === 'Esc') { + $('#help-popup .dismiss').onclick(); return; - } else if (/^((Shift-)?[!-~]|(Shift-?|Ctrl-?|Alt-?|Cmd-?)*)$/.test(key)) { + } else if (/^((Shift-)?(Esc|Tab|[!-~])|(Shift-?|Ctrl-?|Alt-?|Cmd-?)*)$/.test(key)) { this.setCustomValidity('Not allowed'); } else { this.setCustomValidity(''); From 8c5471092c46b90f13c533db0af7cedf48fb189b Mon Sep 17 00:00:00 2001 From: tophf Date: Wed, 22 Nov 2017 13:19:27 +0300 Subject: [PATCH 033/161] rework install-usercss: CSS, layout, warnings --- background/usercss-helper.js | 8 +- install-usercss.html | 4 +- install-usercss/install-usercss.css | 148 ++++++++++++++++++++-------- install-usercss/install-usercss.js | 40 ++++++-- 4 files changed, 143 insertions(+), 57 deletions(-) diff --git a/background/usercss-helper.js b/background/usercss-helper.js index 1b4eb71e..dccc7a76 100644 --- a/background/usercss-helper.js +++ b/background/usercss-helper.js @@ -22,8 +22,12 @@ var usercssHelper = (() => { } function wrapReject(pending) { - return pending.then(result => ({success: true, result})) - .catch(err => ({success: false, result: err.message || String(err)})); + return pending + .then(result => ({success: true, result})) + .catch(err => ({ + success: false, + result: Array.isArray(err) ? err.join('\n') : err.message || String(err), + })); } // Parse the source and find the duplication diff --git a/install-usercss.html b/install-usercss.html index b8b473d7..1b40c75e 100644 --- a/install-usercss.html +++ b/install-usercss.html @@ -82,9 +82,7 @@
-
- -
+
diff --git a/install-usercss/install-usercss.css b/install-usercss/install-usercss.css index 910f9295..6d4b3400 100644 --- a/install-usercss/install-usercss.css +++ b/install-usercss/install-usercss.css @@ -26,14 +26,57 @@ svg.icon { align-items: stretch; } -.header { +.main { + display: flex; + flex-direction: column; + flex-grow: 1; +} + +.header, +.warnings { flex: 0 0 280px; box-sizing: border-box; padding: 15px; border-right: 1px dashed #aaa; box-shadow: 0 0 50px -18px black; overflow-wrap: break-word; - overflow: auto; + overflow-y: auto; + z-index: 100; +} + +.header:not(.meta-init) { + visibility: hidden; +} + +.header.meta-init-error { + display: none; +} + +.warnings { + display: none; + padding-bottom: 0; + flex-basis: auto; + background: #ffe2e2; + border-right: none; + border-bottom: 1px dashed #aaa; +} + +.has-warnings .warnings { + display: initial; +} + +.warning { + font-weight: bold; + font-size: 125%; + margin-bottom: 1em; +} + +.warning pre { + overflow-wrap: break-word; + white-space: pre-wrap; + margin: 1ex 0 0; + font-weight: normal; + font-size: 80%; } .header > :first-child { @@ -48,18 +91,8 @@ h1 small { content: " v"; } -.warning { - padding: 3px 6px; - border: 1px dashed #ef6969; - background: #ffe2e2; -} - -.header .warning { - margin: 3px 0; -} - .actions { - margin: 15px 0; + margin-bottom: 1em; } .actions label { @@ -97,50 +130,81 @@ li { margin-left: -2em; } -.code { - padding: 2em; -} - .main { - flex: 1 1 auto; overflow: hidden; - overflow-wrap: break-word; - min-width: 0; - display: flex; } -.main > :first-child { - flex: 0 0 auto; -} - -.main > :last-child { - flex: 1 1 auto; - min-height: 0; -} - -.main .code, .main .CodeMirror { - height: 100%; + height: auto; + border: none; } /************ reponsive layouts ************/ -@media (max-width:7.5in) { + +@media (max-width:10in) { .container { flex-direction: column; } .header { flex-basis: auto; + border-right: none; + border-bottom: 1px dashed #AAA; + max-height: 50%; + overflow-x: auto; + overflow-y: hidden; + } + .has-warnings .header { + min-height: 4em; + max-height: 20%; + } + .warnings { + max-height: 20%; + } + .warning:not(:last-child) { + border-bottom: 1px dashed #b57c7c; + padding-bottom: 1em; + } + .header, + .warning { + -webkit-column-count: 3; + -moz-column-count: 3; + column-count: 3; + } + h1 { + -webkit-column-span: all; + column-span: all; + margin-bottom: .5em; + } + .actions { + display: flex; + } + .install { + margin-right: 1em; + } +} + +@media (max-width:7in) { + .header, + .warning { -webkit-column-count: 2; -moz-column-count: 2; column-count: 2; - border-right: none; - border-bottom: 1px dashed #AAA; - z-index: 2; - } - .main { - padding-left: 0; - } - .code { - padding: 0; + } +} + +@media (max-width:4.5in) { + .header { + overflow-x: hidden; + overflow-y: auto; + } + .header, + .warning { + -webkit-column-count: 1; + -moz-column-count: 1; + column-count: 1; + } + .warning { + border: none; + padding-bottom: unset; } } diff --git a/install-usercss/install-usercss.js b/install-usercss/install-usercss.js index 80986223..da5fc168 100644 --- a/install-usercss/install-usercss.js +++ b/install-usercss/install-usercss.js @@ -32,8 +32,9 @@ }); port.onDisconnect.addListener(closeCurrentTab); - const cm = CodeMirror.fromTextArea($('.code textarea'), {readOnly: true}); + const cm = CodeMirror($('.main'), {readOnly: true}); let liveReloadPending = Promise.resolve(); + window.addEventListener('resize', adjustCodeHeight); function liveReloadUpdate(sourceCode) { liveReloadPending = liveReloadPending.then(() => { @@ -54,8 +55,6 @@ } function updateMeta(style, dup) { - $$('.main .warning').forEach(e => e.remove()); - const data = style.usercssData; const dupData = dup && dup.usercssData; const versionTest = dup && semverCompare(data.version, dupData.version); @@ -94,6 +93,11 @@ $('.external-link').appendChild(externalLink); } + $('.header').classList.add('meta-init'); + $('.header').classList.remove('meta-init-error'); + showError(''); + requestAnimationFrame(adjustCodeHeight); + function makeAuthor(text) { const match = text.match(/^(.+?)(?:\s+<(.+?)>)?(?:\s+\((.+?)\))$/); if (!match) { @@ -154,9 +158,13 @@ } function showError(err) { - $$('.main .warning').forEach(e => e.remove()); - const main = $('.main'); - main.insertBefore(buildWarning(err), main.firstChild); + $('.warnings').textContent = ''; + if (err) { + $('.warnings').appendChild(buildWarning(err)); + } + $('.warnings').classList.toggle('visible', Boolean(err)); + $('.container').classList.toggle('has-warnings', Boolean(err)); + adjustCodeHeight(); } function install(style) { @@ -193,16 +201,17 @@ function initSourceCode(sourceCode) { cm.setValue(sourceCode); + cm.refresh(); runtimeSend({ method: 'buildUsercss', sourceCode, checkDup: true - }).then(init, initError); + }).then(init, onInitError); } - function initError(err) { - $('.main').insertBefore(buildWarning(err), $('.main').childNodes[0]); - $('.header').style.display = 'none'; + function onInitError(err) { + $('.header').classList.add('meta-init-error'); + showError(err); } function buildWarning(err) { @@ -296,4 +305,15 @@ } return result; } + + function adjustCodeHeight() { + // Chrome-only bug (apparently): it doesn't limit the scroller element height + const scroller = cm.display.scroller; + const prevWindowHeight = adjustCodeHeight.prevWindowHeight; + if (scroller.scrollHeight === scroller.clientHeight || + prevWindowHeight && window.innerHeight !== prevWindowHeight) { + adjustCodeHeight.prevWindowHeight = window.innerHeight; + cm.setSize(null, $('.main').offsetHeight - $('.warnings').offsetHeight); + } + } })(); From e0ea76a9400cbcc28fc1a5e0ca33d4b52515587f Mon Sep 17 00:00:00 2001 From: tophf Date: Wed, 22 Nov 2017 15:08:20 +0300 Subject: [PATCH 034/161] colorpicker hotkey: find color at cursor in strings/comments --- vendor-overwrites/colorpicker/colorview.js | 38 +++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/vendor-overwrites/colorpicker/colorview.js b/vendor-overwrites/colorpicker/colorview.js index 1a5c1879..aa6f7058 100644 --- a/vendor-overwrites/colorpicker/colorview.js +++ b/vendor-overwrites/colorpicker/colorview.js @@ -21,7 +21,6 @@ rgba: /rgba\((?:\s*\d{1,3}\s*,\s*){3}\d*\.?\d+\s*\)/yi, hsl: /hsl\(\s*(?:-?\d+|-?\d*\.\d+)\s*(?:,\s*(?:-?\d+|-?\d*\.\d+)%\s*){2}\)/yi, hsla: /hsla\(\s*(?:-?\d+|-?\d*\.\d+)\s*(?:,\s*(?:-?\d+|-?\d*\.\d+)%\s*){2},\s*(?:-?\d+|-?\d*\.\d+)\s*\)/yi, - named: new RegExp([...NAMED_COLORS.keys()].join('|'), 'i'), }; const CodeMirrorEvents = { @@ -214,6 +213,34 @@ } } + function parseColorAtCursor(lineText, lineTextLC = lineText.toLowerCase(), ch) { + const iHex = lineTextLC.lastIndexOf('#', ch); + const iParen = lineTextLC.lastIndexOf('(', ch); + let start = Math.max(iHex, iParen); + let match, end, color, colorValue; + if (start >= 0) { + if (start === iHex) { + match = RX_COLOR.hex; + } else { + const tokenLen = lineTextLC.charAt(start - 1) === 'a' ? 4 : 3; + start -= tokenLen; + match = RX_COLOR[lineTextLC.substr(start, tokenLen)]; + } + if (match) { + match.lastIndex = start; + ([color] = match.exec(lineText) || []); + } + } else { + const isLetterAt = (i, code = lineTextLC.charCodeAt(i)) => code >= 97 && code <= 122; + for (start = ch; isLetterAt(start); start--) {} // eslint-disable-line no-empty + for (end = ch; isLetterAt(end); end++) {} // eslint-disable-line no-empty + start++; + (color = lineTextLC.slice(start, end)); + colorValue = NAMED_COLORS.get(color); + } + return color && {ch: start, color, colorValue}; + } + function getNamedColorsMap() { return new Map([ ['aliceblue', '#f0f8ff'], @@ -401,17 +428,20 @@ openPopup(color) { let {line, ch} = this.cm.getCursor(); - const lineText = this.cm.getLine(line); - const atImportant = lineText.lastIndexOf('!important', ch); + const lineText = this.cm.getLine(line).toLowerCase(); + const lineTextLC = lineText.toLowerCase(); + const atImportant = lineTextLC.lastIndexOf('!important', ch); ch -= (atImportant >= Math.max(0, ch - '!important'.length)) ? '!important'.length : 0; const lineCache = this.cm.state.colorpicker.cache.get(lineText); const data = {line, ch, colorValue: color, isShortCut: true}; for (const [start, {color, colorValue = color}] of lineCache && lineCache.entries() || []) { if (start <= ch && ch <= start + color.length) { Object.assign(data, {ch: start, color, colorValue}); - break; + this.openPopupForToken({colorpickerData: data}); + return; } } + Object.assign(data, parseColorAtCursor(lineText, lineTextLC, ch)); this.openPopupForToken({colorpickerData: data}); } From 0ed6e3d4bfa77151b2b1184d814d8a338d507fd3 Mon Sep 17 00:00:00 2001 From: tophf Date: Wed, 22 Nov 2017 15:20:10 +0300 Subject: [PATCH 035/161] don't reinsert reinterpreted color on start do it only on the Enter key or once the user explicitly changed the color at least once --- vendor-overwrites/colorpicker/colorpicker.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vendor-overwrites/colorpicker/colorpicker.js b/vendor-overwrites/colorpicker/colorpicker.js index 834cd313..3e8ab026 100644 --- a/vendor-overwrites/colorpicker/colorpicker.js +++ b/vendor-overwrites/colorpicker/colorpicker.js @@ -330,8 +330,8 @@ CodeMirror.defineExtension('colorpicker', function () { setFromInputs(); } - function setFromInputs() { - userActivity = performance.now(); + function setFromInputs(event) { + userActivity = event ? performance.now() : userActivity; if ($inputs[currentFormat].every(validateInput)) { setFromColor($inputs.color); } @@ -598,7 +598,7 @@ CodeMirror.defineExtension('colorpicker', function () { if (!e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) { switch (e.which) { case 13: - setFromInputs(); + setFromInputs({}); colorpickerCallback(); // fallthrough to 27 case 27: From e1fae23927967ae1e121bea85d43f9f97f17019a Mon Sep 17 00:00:00 2001 From: tophf Date: Wed, 22 Nov 2017 16:28:27 +0300 Subject: [PATCH 036/161] init CM to usercss @preprocessor before setting the code --- edit/source-editor.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/edit/source-editor.js b/edit/source-editor.js index 5cdd60d2..3f7d51f2 100644 --- a/edit/source-editor.js +++ b/edit/source-editor.js @@ -43,13 +43,14 @@ function createSourceEditor(style) { } const cm = CodeMirror.fromTextArea($('#sections textarea')); - cm.startOperation(); + editors.push(cm); updateMeta().then(() => { + initLint(); + initLinterSwitch(); + cm.setValue(style.sourceCode); cm.clearHistory(); cm.markClean(); - editors.push(cm); - cm.endOperation(); initHooks(); initAppliesToLineWidget(); @@ -58,9 +59,6 @@ function createSourceEditor(style) { cm.focus(); }); - initLint(); - initLinterSwitch(); - function initAppliesToLineWidget() { const PREF_NAME = 'editor.appliesToLineWidget'; const widget = createAppliesToLineWidget(cm); @@ -104,6 +102,7 @@ function createSourceEditor(style) { update(); }); linterEl.addEventListener('change', update); + update(); function update() { linterEl.value = linterConfig.getDefault(); From a7b8286e4fc8b2f756d9974887f34b3c9b395999 Mon Sep 17 00:00:00 2001 From: tophf Date: Wed, 22 Nov 2017 16:28:50 +0300 Subject: [PATCH 037/161] remove textareas --- edit.html | 3 +-- edit/edit.js | 25 ++++++++++++------------- edit/source-editor.js | 6 ++---- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/edit.html b/edit.html index 34b695de..86cd5e57 100644 --- a/edit.html +++ b/edit.html @@ -85,8 +85,7 @@