├── .gitignore ├── README.md ├── compile.sh ├── css ├── bootstrap.min.css ├── github.css ├── spectrum.css └── themes.css ├── index.html ├── js ├── lib │ └── spectrum.js ├── templates │ ├── deftheme-panel.handlebars │ ├── deftheme.handlebars │ ├── face-list.handlebars │ ├── python.handlebars │ ├── theme-selector.handlebars │ └── user-themes.handlebars └── themes.js └── tools └── getEmacsColors.el /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | \#* 3 | *# 4 | *.tmp 5 | *.bak 6 | .svn/** 7 | .DS_Store 8 | .\#* 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > Hi all, Please consider donating to this or any of my many of opensource projects. 2 | > 3 | > Buy Me a Coffee at ko-fi.com 4 | 5 | # Emacs Theme Editor 6 | 7 | An interactive theme editor for Emacs. 8 | 9 | ![](http://i.imgur.com/d3KNirF.png) 10 | 11 | ### Github repo : https://github.com/emacsfodder/emacs-theme-editor 12 | 13 | ## Overview 14 | 15 | Use one of a small collection of starter themes, edit a font face by clicking the color blob next to it's name, change the color, watch it update live, and when you've done your best, click save. 16 | 17 | You can keep work in progress by clicking the + button under the font faces list, to keep it in your browser's `localStorage`. 18 | 19 | The list of starter themes is held in https://github.com/emacsfodder/emacs-theme-editor/blob/gh-pages/js/starter-themes.coffee if you'd like to add one, you can submit a pull request adding in the same format. 20 | 21 | To get the live theme you're editing in JSON format do: `JSON.stringify(App.liveTheme)` in the Browser dev console. 22 | 23 | ## Quick roadmap (TODO list): 24 | 25 | - Theme import (button is currently dead :( ) 26 | - Undo facility 27 | - Overall or by selected group, colour edits, brightness, saturation, hue, contrast, etc. 28 | - Allow a description to be used. 29 | - Sanitise a given theme name, remove: 30 | - junk chars and trim space 31 | - spaces to dashes 32 | - detect and remove "theme" from the name (it's auto appended only to the filename, but should not be in the theme name itself) 33 | - Add proper package header 34 | - Add more faces, I'll be happy to see [issues open on github](https://github.com/emacsfodder/emacs-theme-editor/issues/new) requesting different mode custom faces support. 35 | - Each face should be editable as one line (with foreground/background bold, italic, underline at least) 36 | - More things I haven't thought of yet... 37 | 38 | Things I definitely won't do. 39 | 40 | - Support complex theme importing. 41 | - Add support for custom theme variables 42 | -------------------------------------------------------------------------------- /compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cat \ 3 | js/theme-generators.coffee\ 4 | js/face-table.coffee\ 5 | js/set-color-tools.coffee\ 6 | js/local-storage-tools.coffee\ 7 | js/undo-tools.coffee\ 8 | js/themes.coffee\ 9 | js/starter-themes.coffee\ 10 | | coffee --bare --compile --stdio > js/themes.js 11 | 12 | -------------------------------------------------------------------------------- /css/github.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | github.com style (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | color: #333; 12 | background: #f8f8f8; 13 | -webkit-text-size-adjust: none; 14 | } 15 | 16 | .hljs-comment, 17 | .diff .hljs-header, 18 | .hljs-javadoc { 19 | color: #998; 20 | font-style: italic; 21 | } 22 | 23 | .hljs-keyword, 24 | .css .rule .hljs-keyword, 25 | .hljs-winutils, 26 | .nginx .hljs-title, 27 | .hljs-subst, 28 | .hljs-request, 29 | .hljs-status { 30 | color: #333; 31 | font-weight: bold; 32 | } 33 | 34 | .hljs-number, 35 | .hljs-hexcolor, 36 | .ruby .hljs-constant { 37 | color: #008080; 38 | } 39 | 40 | .hljs-string, 41 | .hljs-tag .hljs-value, 42 | .hljs-phpdoc, 43 | .hljs-dartdoc, 44 | .tex .hljs-formula { 45 | color: #d14; 46 | } 47 | 48 | .hljs-title, 49 | .hljs-id, 50 | .scss .hljs-preprocessor { 51 | color: #900; 52 | font-weight: bold; 53 | } 54 | 55 | .hljs-list .hljs-keyword, 56 | .hljs-subst { 57 | font-weight: normal; 58 | } 59 | 60 | .hljs-class .hljs-title, 61 | .hljs-type, 62 | .vhdl .hljs-literal, 63 | .tex .hljs-command { 64 | color: #458; 65 | font-weight: bold; 66 | } 67 | 68 | .hljs-tag, 69 | .hljs-tag .hljs-title, 70 | .hljs-rules .hljs-property, 71 | .django .hljs-tag .hljs-keyword { 72 | color: #000080; 73 | font-weight: normal; 74 | } 75 | 76 | .hljs-attribute, 77 | .hljs-variable, 78 | .lisp .hljs-body { 79 | color: #008080; 80 | } 81 | 82 | .hljs-regexp { 83 | color: #009926; 84 | } 85 | 86 | .hljs-symbol, 87 | .ruby .hljs-symbol .hljs-string, 88 | .lisp .hljs-keyword, 89 | .clojure .hljs-keyword, 90 | .scheme .hljs-keyword, 91 | .tex .hljs-special, 92 | .hljs-prompt { 93 | color: #990073; 94 | } 95 | 96 | .hljs-built_in { 97 | color: #0086b3; 98 | } 99 | 100 | .hljs-preprocessor, 101 | .hljs-pragma, 102 | .hljs-pi, 103 | .hljs-doctype, 104 | .hljs-shebang, 105 | .hljs-cdata { 106 | color: #999; 107 | font-weight: bold; 108 | } 109 | 110 | .hljs-deletion { 111 | background: #fdd; 112 | } 113 | 114 | .hljs-addition { 115 | background: #dfd; 116 | } 117 | 118 | .diff .hljs-change { 119 | background: #0086b3; 120 | } 121 | 122 | .hljs-chunk { 123 | color: #aaa; 124 | } 125 | -------------------------------------------------------------------------------- /css/spectrum.css: -------------------------------------------------------------------------------- 1 | /*** 2 | Spectrum Colorpicker v1.6.1 3 | https://github.com/bgrins/spectrum 4 | Author: Brian Grinstead 5 | License: MIT 6 | ***/ 7 | 8 | .sp-container { 9 | position:absolute; 10 | top:0; 11 | left:0; 12 | display:inline-block; 13 | *display: inline; 14 | *zoom: 1; 15 | /* https://github.com/bgrins/spectrum/issues/40 */ 16 | z-index: 9999994; 17 | overflow: hidden; 18 | } 19 | .sp-container.sp-flat { 20 | position: relative; 21 | } 22 | 23 | /* Fix for * { box-sizing: border-box; } */ 24 | .sp-container, 25 | .sp-container * { 26 | -webkit-box-sizing: content-box; 27 | -moz-box-sizing: content-box; 28 | box-sizing: content-box; 29 | } 30 | 31 | /* http://ansciath.tumblr.com/post/7347495869/css-aspect-ratio */ 32 | .sp-top { 33 | position:relative; 34 | width: 100%; 35 | display:inline-block; 36 | } 37 | .sp-top-inner { 38 | position:absolute; 39 | top:0; 40 | left:0; 41 | bottom:0; 42 | right:0; 43 | } 44 | .sp-color { 45 | position: absolute; 46 | top:0; 47 | left:0; 48 | bottom:0; 49 | right:20%; 50 | } 51 | .sp-hue { 52 | position: absolute; 53 | top:0; 54 | right:0; 55 | bottom:0; 56 | left:84%; 57 | height: 100%; 58 | } 59 | 60 | .sp-clear-enabled .sp-hue { 61 | top:33px; 62 | height: 77.5%; 63 | } 64 | 65 | .sp-fill { 66 | padding-top: 80%; 67 | } 68 | .sp-sat, .sp-val { 69 | position: absolute; 70 | top:0; 71 | left:0; 72 | right:0; 73 | bottom:0; 74 | } 75 | 76 | .sp-alpha-enabled .sp-top { 77 | margin-bottom: 18px; 78 | } 79 | .sp-alpha-enabled .sp-alpha { 80 | display: block; 81 | } 82 | .sp-alpha-handle { 83 | position:absolute; 84 | top:-4px; 85 | bottom: -4px; 86 | width: 6px; 87 | left: 50%; 88 | cursor: pointer; 89 | border: 1px solid black; 90 | background: white; 91 | opacity: .8; 92 | } 93 | .sp-alpha { 94 | display: none; 95 | position: absolute; 96 | bottom: -14px; 97 | right: 0; 98 | left: 0; 99 | height: 8px; 100 | } 101 | .sp-alpha-inner { 102 | border: solid 1px #333; 103 | } 104 | 105 | .sp-clear { 106 | display: none; 107 | } 108 | 109 | .sp-clear.sp-clear-display { 110 | background-position: center; 111 | } 112 | 113 | .sp-clear-enabled .sp-clear { 114 | display: block; 115 | position:absolute; 116 | top:0px; 117 | right:0; 118 | bottom:0; 119 | left:84%; 120 | height: 28px; 121 | } 122 | 123 | /* Don't allow text selection */ 124 | .sp-container, .sp-replacer, .sp-preview, .sp-dragger, .sp-slider, .sp-alpha, .sp-clear, .sp-alpha-handle, .sp-container.sp-dragging .sp-input, .sp-container button { 125 | -webkit-user-select:none; 126 | -moz-user-select: -moz-none; 127 | -o-user-select:none; 128 | user-select: none; 129 | } 130 | 131 | .sp-container.sp-input-disabled .sp-input-container { 132 | display: none; 133 | } 134 | .sp-container.sp-buttons-disabled .sp-button-container { 135 | display: none; 136 | } 137 | .sp-container.sp-palette-buttons-disabled .sp-palette-button-container { 138 | display: none; 139 | } 140 | .sp-palette-only .sp-picker-container { 141 | display: none; 142 | } 143 | .sp-palette-disabled .sp-palette-container { 144 | display: none; 145 | } 146 | 147 | .sp-initial-disabled .sp-initial { 148 | display: none; 149 | } 150 | 151 | 152 | /* Gradients for hue, saturation and value instead of images. Not pretty... but it works */ 153 | .sp-sat { 154 | background-image: -webkit-gradient(linear, 0 0, 100% 0, from(#FFF), to(rgba(204, 154, 129, 0))); 155 | background-image: -webkit-linear-gradient(left, #FFF, rgba(204, 154, 129, 0)); 156 | background-image: -moz-linear-gradient(left, #fff, rgba(204, 154, 129, 0)); 157 | background-image: -o-linear-gradient(left, #fff, rgba(204, 154, 129, 0)); 158 | background-image: -ms-linear-gradient(left, #fff, rgba(204, 154, 129, 0)); 159 | background-image: linear-gradient(to right, #fff, rgba(204, 154, 129, 0)); 160 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr=#FFFFFFFF, endColorstr=#00CC9A81)"; 161 | filter : progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr='#FFFFFFFF', endColorstr='#00CC9A81'); 162 | } 163 | .sp-val { 164 | background-image: -webkit-gradient(linear, 0 100%, 0 0, from(#000000), to(rgba(204, 154, 129, 0))); 165 | background-image: -webkit-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0)); 166 | background-image: -moz-linear-gradient(bottom, #000, rgba(204, 154, 129, 0)); 167 | background-image: -o-linear-gradient(bottom, #000, rgba(204, 154, 129, 0)); 168 | background-image: -ms-linear-gradient(bottom, #000, rgba(204, 154, 129, 0)); 169 | background-image: linear-gradient(to top, #000, rgba(204, 154, 129, 0)); 170 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#00CC9A81, endColorstr=#FF000000)"; 171 | filter : progid:DXImageTransform.Microsoft.gradient(startColorstr='#00CC9A81', endColorstr='#FF000000'); 172 | } 173 | 174 | .sp-hue { 175 | background: -moz-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); 176 | background: -ms-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); 177 | background: -o-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); 178 | background: -webkit-gradient(linear, left top, left bottom, 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)); 179 | background: -webkit-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); 180 | background: linear-gradient(to bottom, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); 181 | } 182 | 183 | /* IE filters do not support multiple color stops. 184 | Generate 6 divs, line them up, and do two color gradients for each. 185 | Yes, really. 186 | */ 187 | .sp-1 { 188 | height:17%; 189 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0000', endColorstr='#ffff00'); 190 | } 191 | .sp-2 { 192 | height:16%; 193 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff00', endColorstr='#00ff00'); 194 | } 195 | .sp-3 { 196 | height:17%; 197 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ff00', endColorstr='#00ffff'); 198 | } 199 | .sp-4 { 200 | height:17%; 201 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffff', endColorstr='#0000ff'); 202 | } 203 | .sp-5 { 204 | height:16%; 205 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0000ff', endColorstr='#ff00ff'); 206 | } 207 | .sp-6 { 208 | height:17%; 209 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff00ff', endColorstr='#ff0000'); 210 | } 211 | 212 | .sp-hidden { 213 | display: none !important; 214 | } 215 | 216 | /* Clearfix hack */ 217 | .sp-cf:before, .sp-cf:after { content: ""; display: table; } 218 | .sp-cf:after { clear: both; } 219 | .sp-cf { *zoom: 1; } 220 | 221 | /* Mobile devices, make hue slider bigger so it is easier to slide */ 222 | @media (max-device-width: 480px) { 223 | .sp-color { right: 40%; } 224 | .sp-hue { left: 63%; } 225 | .sp-fill { padding-top: 60%; } 226 | } 227 | .sp-dragger { 228 | border-radius: 5px; 229 | height: 5px; 230 | width: 5px; 231 | border: 1px solid #fff; 232 | background: #000; 233 | cursor: pointer; 234 | position:absolute; 235 | top:0; 236 | left: 0; 237 | } 238 | .sp-slider { 239 | position: absolute; 240 | top:0; 241 | cursor:pointer; 242 | height: 3px; 243 | left: -1px; 244 | right: -1px; 245 | border: 1px solid #000; 246 | background: white; 247 | opacity: .8; 248 | } 249 | 250 | /* 251 | Theme authors: 252 | Here are the basic themeable display options (colors, fonts, global widths). 253 | See http://bgrins.github.io/spectrum/themes/ for instructions. 254 | */ 255 | 256 | .sp-container { 257 | border-radius: 0; 258 | background-color: #ECECEC; 259 | border: solid 1px #f0c49B; 260 | padding: 0; 261 | } 262 | .sp-container, .sp-container button, .sp-container input, .sp-color, .sp-hue, .sp-clear { 263 | font: normal 12px "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; 264 | -webkit-box-sizing: border-box; 265 | -moz-box-sizing: border-box; 266 | -ms-box-sizing: border-box; 267 | box-sizing: border-box; 268 | } 269 | .sp-top { 270 | margin-bottom: 3px; 271 | } 272 | .sp-color, .sp-hue, .sp-clear { 273 | border: solid 1px #666; 274 | } 275 | 276 | /* Input */ 277 | .sp-input-container { 278 | float:right; 279 | width: 100px; 280 | margin-bottom: 4px; 281 | } 282 | .sp-initial-disabled .sp-input-container { 283 | width: 100%; 284 | } 285 | .sp-input { 286 | font-size: 12px !important; 287 | border: 1px inset; 288 | padding: 4px 5px; 289 | margin: 0; 290 | width: 100%; 291 | background:transparent; 292 | border-radius: 3px; 293 | color: #222; 294 | } 295 | .sp-input:focus { 296 | border: 1px solid orange; 297 | } 298 | .sp-input.sp-validation-error { 299 | border: 1px solid red; 300 | background: #fdd; 301 | } 302 | .sp-picker-container , .sp-palette-container { 303 | float:left; 304 | position: relative; 305 | padding: 10px; 306 | padding-bottom: 300px; 307 | margin-bottom: -290px; 308 | } 309 | .sp-picker-container { 310 | width: 172px; 311 | border-left: solid 1px #fff; 312 | } 313 | 314 | /* Palettes */ 315 | .sp-palette-container { 316 | border-right: solid 1px #ccc; 317 | } 318 | 319 | .sp-palette-only .sp-palette-container { 320 | border: 0; 321 | } 322 | 323 | .sp-palette .sp-thumb-el { 324 | display: block; 325 | position:relative; 326 | float:left; 327 | width: 24px; 328 | height: 15px; 329 | margin: 3px; 330 | cursor: pointer; 331 | border:solid 2px transparent; 332 | } 333 | .sp-palette .sp-thumb-el:hover, .sp-palette .sp-thumb-el.sp-thumb-active { 334 | border-color: orange; 335 | } 336 | .sp-thumb-el { 337 | position:relative; 338 | } 339 | 340 | /* Initial */ 341 | .sp-initial { 342 | float: left; 343 | border: solid 1px #333; 344 | } 345 | .sp-initial span { 346 | width: 30px; 347 | height: 25px; 348 | border:none; 349 | display:block; 350 | float:left; 351 | margin:0; 352 | } 353 | 354 | .sp-initial .sp-clear-display { 355 | background-position: center; 356 | } 357 | 358 | /* Buttons */ 359 | .sp-palette-button-container, 360 | .sp-button-container { 361 | float: right; 362 | } 363 | 364 | /* Replacer (the little preview div that shows up instead of the ) */ 365 | .sp-replacer { 366 | margin:0; 367 | overflow:hidden; 368 | cursor:pointer; 369 | padding: 4px; 370 | display:inline-block; 371 | *zoom: 1; 372 | *display: inline; 373 | border: solid 1px #91765d; 374 | background: #eee; 375 | color: #333; 376 | vertical-align: middle; 377 | } 378 | .sp-replacer:hover, .sp-replacer.sp-active { 379 | border-color: #F0C49B; 380 | color: #111; 381 | } 382 | .sp-replacer.sp-disabled { 383 | cursor:default; 384 | border-color: silver; 385 | color: silver; 386 | } 387 | .sp-dd { 388 | padding: 2px 0; 389 | height: 16px; 390 | line-height: 16px; 391 | float:left; 392 | font-size:10px; 393 | } 394 | .sp-preview { 395 | position:relative; 396 | width:25px; 397 | height: 20px; 398 | border: solid 1px #222; 399 | margin-right: 5px; 400 | float:left; 401 | z-index: 0; 402 | } 403 | 404 | .sp-palette { 405 | *width: 220px; 406 | max-width: 220px; 407 | } 408 | .sp-palette .sp-thumb-el { 409 | width:16px; 410 | height: 16px; 411 | margin:2px 1px; 412 | border: solid 1px #d0d0d0; 413 | } 414 | 415 | .sp-container { 416 | padding-bottom:0; 417 | } 418 | 419 | 420 | /* Buttons: http://hellohappy.org/css3-buttons/ */ 421 | .sp-container button { 422 | background-color: #eeeeee; 423 | background-image: -webkit-linear-gradient(top, #eeeeee, #cccccc); 424 | background-image: -moz-linear-gradient(top, #eeeeee, #cccccc); 425 | background-image: -ms-linear-gradient(top, #eeeeee, #cccccc); 426 | background-image: -o-linear-gradient(top, #eeeeee, #cccccc); 427 | background-image: linear-gradient(to bottom, #eeeeee, #cccccc); 428 | border: 1px solid #ccc; 429 | border-bottom: 1px solid #bbb; 430 | border-radius: 3px; 431 | color: #333; 432 | font-size: 14px; 433 | line-height: 1; 434 | padding: 5px 4px; 435 | text-align: center; 436 | text-shadow: 0 1px 0 #eee; 437 | vertical-align: middle; 438 | } 439 | .sp-container button:hover { 440 | background-color: #dddddd; 441 | background-image: -webkit-linear-gradient(top, #dddddd, #bbbbbb); 442 | background-image: -moz-linear-gradient(top, #dddddd, #bbbbbb); 443 | background-image: -ms-linear-gradient(top, #dddddd, #bbbbbb); 444 | background-image: -o-linear-gradient(top, #dddddd, #bbbbbb); 445 | background-image: linear-gradient(to bottom, #dddddd, #bbbbbb); 446 | border: 1px solid #bbb; 447 | border-bottom: 1px solid #999; 448 | cursor: pointer; 449 | text-shadow: 0 1px 0 #ddd; 450 | } 451 | .sp-container button:active { 452 | border: 1px solid #aaa; 453 | border-bottom: 1px solid #888; 454 | -webkit-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 455 | -moz-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 456 | -ms-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 457 | -o-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 458 | box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 459 | } 460 | .sp-cancel { 461 | font-size: 11px; 462 | color: #d93f3f !important; 463 | margin:0; 464 | padding:2px; 465 | margin-right: 5px; 466 | vertical-align: middle; 467 | text-decoration:none; 468 | 469 | } 470 | .sp-cancel:hover { 471 | color: #d93f3f !important; 472 | text-decoration: underline; 473 | } 474 | 475 | 476 | .sp-palette span:hover, .sp-palette span.sp-thumb-active { 477 | border-color: #000; 478 | } 479 | 480 | .sp-preview, .sp-alpha, .sp-thumb-el { 481 | position:relative; 482 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==); 483 | } 484 | .sp-preview-inner, .sp-alpha-inner, .sp-thumb-inner { 485 | display:block; 486 | position:absolute; 487 | top:0;left:0;bottom:0;right:0; 488 | } 489 | 490 | .sp-palette .sp-thumb-inner { 491 | background-position: 50% 50%; 492 | background-repeat: no-repeat; 493 | } 494 | 495 | .sp-palette .sp-thumb-light.sp-thumb-active .sp-thumb-inner { 496 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIVJREFUeNpiYBhsgJFMffxAXABlN5JruT4Q3wfi/0DsT64h8UD8HmpIPCWG/KemIfOJCUB+Aoacx6EGBZyHBqI+WsDCwuQ9mhxeg2A210Ntfo8klk9sOMijaURm7yc1UP2RNCMbKE9ODK1HM6iegYLkfx8pligC9lCD7KmRof0ZhjQACDAAceovrtpVBRkAAAAASUVORK5CYII=); 497 | } 498 | 499 | .sp-palette .sp-thumb-dark.sp-thumb-active .sp-thumb-inner { 500 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAMdJREFUOE+tkgsNwzAMRMugEAahEAahEAZhEAqlEAZhEAohEAYh81X2dIm8fKpEspLGvudPOsUYpxE2BIJCroJmEW9qJ+MKaBFhEMNabSy9oIcIPwrB+afvAUFoK4H0tMaQ3XtlrggDhOVVMuT4E5MMG0FBbCEYzjYT7OxLEvIHQLY2zWwQ3D+9luyOQTfKDiFD3iUIfPk8VqrKjgAiSfGFPecrg6HN6m/iBcwiDAo7WiBeawa+Kwh7tZoSCGLMqwlSAzVDhoK+6vH4G0P5wdkAAAAASUVORK5CYII=); 501 | } 502 | 503 | .sp-clear-display { 504 | background-repeat:no-repeat; 505 | background-position: center; 506 | background-image: url(data:image/gif;base64,R0lGODlhFAAUAPcAAAAAAJmZmZ2dnZ6enqKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq/Hx8fLy8vT09PX19ff39/j4+Pn5+fr6+vv7+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAP8ALAAAAAAUABQAAAihAP9FoPCvoMGDBy08+EdhQAIJCCMybCDAAYUEARBAlFiQQoMABQhKUJBxY0SPICEYHBnggEmDKAuoPMjS5cGYMxHW3IiT478JJA8M/CjTZ0GgLRekNGpwAsYABHIypcAgQMsITDtWJYBR6NSqMico9cqR6tKfY7GeBCuVwlipDNmefAtTrkSzB1RaIAoXodsABiZAEFB06gIBWC1mLVgBa0AAOw==); 507 | } 508 | -------------------------------------------------------------------------------- /css/themes.css: -------------------------------------------------------------------------------- 1 | .navbar-static-top { 2 | margin-bottom:20px; 3 | } 4 | 5 | footer { 6 | margin-top:10px; 7 | padding-top:10px; 8 | padding-bottom:10px; 9 | background-color:#000000; 10 | color: white; 11 | } 12 | 13 | pre { 14 | border: inherit; 15 | border-radius: inherit; 16 | color: inherit; 17 | background-color: inherit; 18 | padding: 0; 19 | margin: 0; 20 | } 21 | 22 | .form-group { 23 | 24 | } 25 | 26 | .color-controls-panel { 27 | padding: 0px; 28 | } 29 | 30 | .color-controls-panel li.padded { 31 | padding: 10px 8px 10px 8px; 32 | } 33 | 34 | .color-controls-panel li.padded .form-group { 35 | margin: 0; 36 | } 37 | 38 | .color-controls-panel ul li { 39 | 40 | } 41 | 42 | .code-panel { 43 | padding: 0; 44 | } 45 | 46 | .color-group { 47 | margin: 0; 48 | } 49 | 50 | .side-label { 51 | display: block; 52 | width: 100%; 53 | padding: 5px; 54 | float: left; 55 | margin: 0; 56 | border-bottom: 1px solid rgba(0,0,0,0.05); 57 | } 58 | 59 | .color-group label { 60 | display: block; 61 | text-transform: lowercase; 62 | background-color: rgba(0,0,0,0.07); 63 | width: 100%; 64 | padding: 5px; 65 | float: left; 66 | margin: 0; 67 | border-bottom: 1px solid rgba(0,0,0,0.05); 68 | } 69 | 70 | #action-bar { 71 | } 72 | 73 | 74 | label { 75 | font-weight: normal; 76 | } 77 | 78 | .modeline { 79 | padding: 1px 0 0 0; 80 | margin: 0 0 0 -5px; 81 | } 82 | 83 | .modeline-text { 84 | margin: 0; 85 | overflow: hidden; 86 | white-space: nowrap; 87 | } 88 | 89 | .prompt { 90 | padding: 3px 0px 2px 5px; 91 | margin: 0 0 0 -5px; 92 | border: none; 93 | } 94 | 95 | .navbar-brand { 96 | font-size: 20pt; 97 | text-transform: uppercase; 98 | font-family: 'Oswald'; 99 | color: white !important; 100 | } 101 | 102 | .navbar-inverse { 103 | background-color: #0F1411 !important; 104 | } 105 | 106 | #code-sample { 107 | border-left: 5px solid; 108 | padding: 0; 109 | } 110 | 111 | .sp-replacer { 112 | padding: 0; 113 | border: none !important; 114 | float: right; 115 | margin-top: -28px; 116 | display: inline; 117 | } 118 | 119 | .sp-preview { 120 | width: 40px; 121 | height: 23px; 122 | border-radius: 23px; 123 | border: none; 124 | } 125 | .sp-preview-inner { 126 | border-radius: 23px; 127 | border: 1px solid rgba(128,128,128,0.5); 128 | } 129 | 130 | .sp-container { 131 | } 132 | 133 | .sp-button-container { 134 | margin-top: 6px; 135 | } 136 | 137 | button.sp-cancel { 138 | background-image: linear-gradient(#FF5454, #710000); 139 | color: white !important; 140 | text-shadow: 0 1px 3px rgba(0,0,0,0.3); 141 | text-decoration: none !important; 142 | } 143 | 144 | button.sp-cancel:hover { 145 | text-decoration: none !important; 146 | } 147 | 148 | .user-theme-removal-icon { 149 | top: 4px; 150 | right: -5px; 151 | color: darkred; 152 | float: right; 153 | cursor: arrow; 154 | } 155 | 156 | #user-themes li { 157 | } 158 | 159 | #theme-generated pre { 160 | background-color: rgba(0,0,0,0.1); 161 | border: 1px solid lightgray; 162 | border-radius: 4px; 163 | padding: 8px; 164 | } 165 | 166 | code { 167 | color: darkslategray; 168 | } 169 | 170 | .emacs-action { 171 | font-size: 120%; 172 | background-color: black; 173 | border-radius: 10px; 174 | padding: 10px; 175 | color: white; 176 | } 177 | 178 | .emacs-action .emacs-prompt { 179 | color: lightblue; 180 | font-weight: bold; 181 | } 182 | 183 | .emacs-action code { 184 | background-color: transparent; 185 | color: lightgray; 186 | } 187 | 188 | kbd { 189 | background-color: white; 190 | padding: 1px 3px; 191 | color: black; 192 | border-radius: 5px; 193 | border: 1px solid rgba(0,0,0,0.23); 194 | } 195 | 196 | .decorative-heading { 197 | font-family: 'Oswald'; 198 | text-transform: uppercase; 199 | } 200 | 201 | #github-banner { 202 | height: 149px; 203 | width: 149px; 204 | overflow:hidden; 205 | padding: 0; 206 | margin: 0; 207 | position: absolute; 208 | top: 0; 209 | right: 0; 210 | z-index: 9999999; 211 | } 212 | 213 | #github-banner a { 214 | display: block; 215 | width: 190px; 216 | font-size: 14px; 217 | font-family: Frutiger, "Frutiger Linotype", Univers, Calibri, "Gill Sans", "Gill Sans MT", 218 | "Myriad Pro", Myriad, "DejaVu Sans Condensed", "Liberation Sans", "Nimbus Sans L", Tahoma, 219 | Geneva, "Helvetica Neue", Helvetica, Arial, sans serif; 220 | background-color: #333; 221 | color: #FFF; 222 | word-spacing: 2px; 223 | text-decoration: none; 224 | padding: 5px 15px 5px 25px; 225 | 226 | position:relative; 227 | right: -36px; 228 | top: -29px; 229 | text-align: center; 230 | 231 | transform-origin: 0 0 ; 232 | transform:rotate(45deg); 233 | /* box-shadow: 1px 1px 5px 1px #666; */ 234 | 235 | background-image: linear-gradient(bottom, #000000 3%, #DDDDDD 5%, #000000 7%, #000000 93%, #DDDDDD 95%, #000000 97%); 236 | background-image: -o-linear-gradient(bottom, #000000 3%, #DDDDDD 5%, #000000 7%, #000000 93%, #DDDDDD 95%, #000000 97%); 237 | background-image: -moz-linear-gradient(bottom, #000000 3%, #DDDDDD 5%, #000000 7%, #000000 93%, #DDDDDD 95%, #000000 97%); 238 | background-image: -webkit-linear-gradient(bottom, #000000 3%, #DDDDDD 5%, #000000 7%, #000000 93%, #DDDDDD 95%, #000000 97%); 239 | background-image: -ms-linear-gradient(bottom, #000000 3%, #DDDDDD 5%, #000000 7%, #000000 93%, #DDDDDD 95%, #000000 97%); 240 | 241 | background-image: -webkit-gradient( 242 | linear, 243 | left bottom, 244 | left top, 245 | color-stop(0.03, #000000), 246 | color-stop(0.05, #DDDDDD), 247 | color-stop(0.07, #000000), 248 | color-stop(0.93, #000000), 249 | color-stop(0.95, #DDDDDD), 250 | color-stop(0.97, #000000) 251 | ); 252 | 253 | 254 | 255 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Emacs Theme Editor 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 |
21 | Fork me on GitHub 22 |
23 | 24 | 37 | 38 | 39 |
40 |
41 |
42 |
43 | 44 |
45 |
46 | 47 | 48 |
49 |
50 |
51 |
    52 |
      53 |
    • 54 |
      55 |
      56 | 59 | 62 |
      63 |
      64 |
    • 65 |
    • 66 |
      67 |
      68 |
      69 |
    • 70 |
    71 |
    72 |
    73 |
    74 | 75 |
    76 | 77 |
    78 |
    79 |
    80 |
    81 |
    82 |
    83 |
    84 |
    85 |
    86 |
    87 | 88 |
    89 |
    90 |
    91 |
    92 | 93 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /js/lib/spectrum.js: -------------------------------------------------------------------------------- 1 | // Spectrum Colorpicker v1.6.1 2 | // https://github.com/bgrins/spectrum 3 | // Author: Brian Grinstead 4 | // License: MIT 5 | 6 | (function (factory) { 7 | "use strict"; 8 | 9 | if (typeof define === 'function' && define.amd) { // AMD 10 | define(['jquery'], factory); 11 | } 12 | else if (typeof exports == "object" && typeof module == "object") { // CommonJS 13 | module.exports = factory; 14 | } 15 | else { // Browser 16 | factory(jQuery); 17 | } 18 | })(function($, undefined) { 19 | "use strict"; 20 | 21 | var defaultOpts = { 22 | 23 | // Callbacks 24 | beforeShow: noop, 25 | move: noop, 26 | change: noop, 27 | show: noop, 28 | hide: noop, 29 | 30 | // Options 31 | color: false, 32 | flat: false, 33 | showInput: false, 34 | allowEmpty: false, 35 | showButtons: true, 36 | clickoutFiresChange: false, 37 | showInitial: false, 38 | showPalette: false, 39 | showPaletteOnly: false, 40 | hideAfterPaletteSelect: false, 41 | togglePaletteOnly: false, 42 | showSelectionPalette: true, 43 | localStorageKey: false, 44 | appendTo: "body", 45 | maxSelectionSize: 7, 46 | cancelText: "cancel", 47 | chooseText: "choose", 48 | togglePaletteMoreText: "more", 49 | togglePaletteLessText: "less", 50 | clearText: "Clear Color Selection", 51 | noColorSelectedText: "No Color Selected", 52 | preferredFormat: false, 53 | className: "", // Deprecated - use containerClassName and replacerClassName instead. 54 | containerClassName: "", 55 | replacerClassName: "", 56 | showAlpha: false, 57 | theme: "sp-light", 58 | palette: [["#ffffff", "#000000", "#ff0000", "#ff8000", "#ffff00", "#008000", "#0000ff", "#4b0082", "#9400d3"]], 59 | selectionPalette: [], 60 | disabled: false, 61 | offset: null 62 | }, 63 | spectrums = [], 64 | IE = !!/msie/i.exec( window.navigator.userAgent ), 65 | rgbaSupport = (function() { 66 | function contains( str, substr ) { 67 | return !!~('' + str).indexOf(substr); 68 | } 69 | 70 | var elem = document.createElement('div'); 71 | var style = elem.style; 72 | style.cssText = 'background-color:rgba(0,0,0,.5)'; 73 | return contains(style.backgroundColor, 'rgba') || contains(style.backgroundColor, 'hsla'); 74 | })(), 75 | inputTypeColorSupport = (function() { 76 | var colorInput = $("")[0]; 77 | return colorInput.type === "color" && colorInput.value !== "!"; 78 | })(), 79 | replaceInput = [ 80 | "
    ", 81 | "
    ", 82 | "
    " 83 | ].join(''), 84 | markup = (function () { 85 | 86 | // IE does not support gradients with multiple stops, so we need to simulate 87 | // that for the rainbow slider with 8 divs that each have a single gradient 88 | var gradientFix = ""; 89 | if (IE) { 90 | for (var i = 1; i <= 6; i++) { 91 | gradientFix += "
    "; 92 | } 93 | } 94 | 95 | return [ 96 | "
    ", 97 | "
    ", 98 | "
    ", 99 | "
    ", 100 | "", 101 | "
    ", 102 | "
    ", 103 | "
    ", 104 | "
    ", 105 | "
    ", 106 | "
    ", 107 | "
    ", 108 | "
    ", 109 | "
    ", 110 | "
    ", 111 | "
    ", 112 | "
    ", 113 | "
    ", 114 | "
    ", 115 | "
    ", 116 | "
    ", 117 | "
    ", 118 | gradientFix, 119 | "
    ", 120 | "
    ", 121 | "
    ", 122 | "
    ", 123 | "
    ", 124 | "", 125 | "
    ", 126 | "
    ", 127 | "
    ", 128 | " ", 129 | "", 130 | "
    ", 131 | "
    ", 132 | "
    " 133 | ].join(""); 134 | })(); 135 | 136 | function paletteTemplate (p, color, className, opts) { 137 | var html = []; 138 | for (var i = 0; i < p.length; i++) { 139 | var current = p[i]; 140 | if(current) { 141 | var tiny = tinycolor(current); 142 | var c = tiny.toHsl().l < 0.5 ? "sp-thumb-el sp-thumb-dark" : "sp-thumb-el sp-thumb-light"; 143 | c += (tinycolor.equals(color, current)) ? " sp-thumb-active" : ""; 144 | var formattedString = tiny.toString(opts.preferredFormat || "rgb"); 145 | var swatchStyle = rgbaSupport ? ("background-color:" + tiny.toRgbString()) : "filter:" + tiny.toFilter(); 146 | html.push(''); 147 | } else { 148 | var cls = 'sp-clear-display'; 149 | html.push($('
    ') 150 | .append($('') 151 | .attr('title', opts.noColorSelectedText) 152 | ) 153 | .html() 154 | ); 155 | } 156 | } 157 | return "
    " + html.join('') + "
    "; 158 | } 159 | 160 | function hideAll() { 161 | for (var i = 0; i < spectrums.length; i++) { 162 | if (spectrums[i]) { 163 | spectrums[i].hide(); 164 | } 165 | } 166 | } 167 | 168 | function instanceOptions(o, callbackContext) { 169 | var opts = $.extend({}, defaultOpts, o); 170 | opts.callbacks = { 171 | 'move': bind(opts.move, callbackContext), 172 | 'change': bind(opts.change, callbackContext), 173 | 'show': bind(opts.show, callbackContext), 174 | 'hide': bind(opts.hide, callbackContext), 175 | 'beforeShow': bind(opts.beforeShow, callbackContext) 176 | }; 177 | 178 | return opts; 179 | } 180 | 181 | function spectrum(element, o) { 182 | 183 | var opts = instanceOptions(o, element), 184 | flat = opts.flat, 185 | showSelectionPalette = opts.showSelectionPalette, 186 | localStorageKey = opts.localStorageKey, 187 | theme = opts.theme, 188 | callbacks = opts.callbacks, 189 | resize = throttle(reflow, 10), 190 | visible = false, 191 | dragWidth = 0, 192 | dragHeight = 0, 193 | dragHelperHeight = 0, 194 | slideHeight = 0, 195 | slideWidth = 0, 196 | alphaWidth = 0, 197 | alphaSlideHelperWidth = 0, 198 | slideHelperHeight = 0, 199 | currentHue = 0, 200 | currentSaturation = 0, 201 | currentValue = 0, 202 | currentAlpha = 1, 203 | palette = [], 204 | paletteArray = [], 205 | paletteLookup = {}, 206 | selectionPalette = opts.selectionPalette.slice(0), 207 | maxSelectionSize = opts.maxSelectionSize, 208 | draggingClass = "sp-dragging", 209 | shiftMovementDirection = null; 210 | 211 | var doc = element.ownerDocument, 212 | body = doc.body, 213 | boundElement = $(element), 214 | disabled = false, 215 | container = $(markup, doc).addClass(theme), 216 | pickerContainer = container.find(".sp-picker-container"), 217 | dragger = container.find(".sp-color"), 218 | dragHelper = container.find(".sp-dragger"), 219 | slider = container.find(".sp-hue"), 220 | slideHelper = container.find(".sp-slider"), 221 | alphaSliderInner = container.find(".sp-alpha-inner"), 222 | alphaSlider = container.find(".sp-alpha"), 223 | alphaSlideHelper = container.find(".sp-alpha-handle"), 224 | textInput = container.find(".sp-input"), 225 | paletteContainer = container.find(".sp-palette"), 226 | initialColorContainer = container.find(".sp-initial"), 227 | cancelButton = container.find(".sp-cancel"), 228 | clearButton = container.find(".sp-clear"), 229 | chooseButton = container.find(".sp-choose"), 230 | toggleButton = container.find(".sp-palette-toggle"), 231 | isInput = boundElement.is("input"), 232 | isInputTypeColor = isInput && inputTypeColorSupport && boundElement.attr("type") === "color", 233 | shouldReplace = isInput && !flat, 234 | replacer = (shouldReplace) ? $(replaceInput).addClass(theme).addClass(opts.className).addClass(opts.replacerClassName) : $([]), 235 | offsetElement = (shouldReplace) ? replacer : boundElement, 236 | previewElement = replacer.find(".sp-preview-inner"), 237 | initialColor = opts.color || (isInput && boundElement.val()), 238 | colorOnShow = false, 239 | preferredFormat = opts.preferredFormat, 240 | currentPreferredFormat = preferredFormat, 241 | clickoutFiresChange = !opts.showButtons || opts.clickoutFiresChange, 242 | isEmpty = !initialColor, 243 | allowEmpty = opts.allowEmpty && !isInputTypeColor; 244 | 245 | function applyOptions() { 246 | 247 | if (opts.showPaletteOnly) { 248 | opts.showPalette = true; 249 | } 250 | 251 | toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText); 252 | 253 | if (opts.palette) { 254 | palette = opts.palette.slice(0); 255 | paletteArray = $.isArray(palette[0]) ? palette : [palette]; 256 | paletteLookup = {}; 257 | for (var i = 0; i < paletteArray.length; i++) { 258 | for (var j = 0; j < paletteArray[i].length; j++) { 259 | var rgb = tinycolor(paletteArray[i][j]).toRgbString(); 260 | paletteLookup[rgb] = true; 261 | } 262 | } 263 | } 264 | 265 | container.toggleClass("sp-flat", flat); 266 | container.toggleClass("sp-input-disabled", !opts.showInput); 267 | container.toggleClass("sp-alpha-enabled", opts.showAlpha); 268 | container.toggleClass("sp-clear-enabled", allowEmpty); 269 | container.toggleClass("sp-buttons-disabled", !opts.showButtons); 270 | container.toggleClass("sp-palette-buttons-disabled", !opts.togglePaletteOnly); 271 | container.toggleClass("sp-palette-disabled", !opts.showPalette); 272 | container.toggleClass("sp-palette-only", opts.showPaletteOnly); 273 | container.toggleClass("sp-initial-disabled", !opts.showInitial); 274 | container.addClass(opts.className).addClass(opts.containerClassName); 275 | 276 | reflow(); 277 | } 278 | 279 | function initialize() { 280 | 281 | if (IE) { 282 | container.find("*:not(input)").attr("unselectable", "on"); 283 | } 284 | 285 | applyOptions(); 286 | 287 | if (shouldReplace) { 288 | boundElement.after(replacer).hide(); 289 | } 290 | 291 | if (!allowEmpty) { 292 | clearButton.hide(); 293 | } 294 | 295 | if (flat) { 296 | boundElement.after(container).hide(); 297 | } 298 | else { 299 | 300 | var appendTo = opts.appendTo === "parent" ? boundElement.parent() : $(opts.appendTo); 301 | if (appendTo.length !== 1) { 302 | appendTo = $("body"); 303 | } 304 | 305 | appendTo.append(container); 306 | } 307 | 308 | updateSelectionPaletteFromStorage(); 309 | 310 | offsetElement.bind("click.spectrum touchstart.spectrum", function (e) { 311 | if (!disabled) { 312 | toggle(); 313 | } 314 | 315 | e.stopPropagation(); 316 | 317 | if (!$(e.target).is("input")) { 318 | e.preventDefault(); 319 | } 320 | }); 321 | 322 | if(boundElement.is(":disabled") || (opts.disabled === true)) { 323 | disable(); 324 | } 325 | 326 | // Prevent clicks from bubbling up to document. This would cause it to be hidden. 327 | container.click(stopPropagation); 328 | 329 | // Handle user typed input 330 | textInput.change(setFromTextInput); 331 | textInput.bind("paste", function () { 332 | setTimeout(setFromTextInput, 1); 333 | }); 334 | textInput.keydown(function (e) { if (e.keyCode == 13) { setFromTextInput(); } }); 335 | 336 | cancelButton.text(opts.cancelText); 337 | cancelButton.bind("click.spectrum", function (e) { 338 | e.stopPropagation(); 339 | e.preventDefault(); 340 | revert(); 341 | hide(); 342 | }); 343 | 344 | clearButton.attr("title", opts.clearText); 345 | clearButton.bind("click.spectrum", function (e) { 346 | e.stopPropagation(); 347 | e.preventDefault(); 348 | isEmpty = true; 349 | move(); 350 | 351 | if(flat) { 352 | //for the flat style, this is a change event 353 | updateOriginalInput(true); 354 | } 355 | }); 356 | 357 | chooseButton.text(opts.chooseText); 358 | chooseButton.bind("click.spectrum", function (e) { 359 | e.stopPropagation(); 360 | e.preventDefault(); 361 | 362 | if (IE && textInput.is(":focus")) { 363 | textInput.trigger('change'); 364 | } 365 | 366 | if (isValid()) { 367 | updateOriginalInput(true); 368 | hide(); 369 | } 370 | }); 371 | 372 | toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText); 373 | toggleButton.bind("click.spectrum", function (e) { 374 | e.stopPropagation(); 375 | e.preventDefault(); 376 | 377 | opts.showPaletteOnly = !opts.showPaletteOnly; 378 | 379 | // To make sure the Picker area is drawn on the right, next to the 380 | // Palette area (and not below the palette), first move the Palette 381 | // to the left to make space for the picker, plus 5px extra. 382 | // The 'applyOptions' function puts the whole container back into place 383 | // and takes care of the button-text and the sp-palette-only CSS class. 384 | if (!opts.showPaletteOnly && !flat) { 385 | container.css('left', '-=' + (pickerContainer.outerWidth(true) + 5)); 386 | } 387 | applyOptions(); 388 | }); 389 | 390 | draggable(alphaSlider, function (dragX, dragY, e) { 391 | currentAlpha = (dragX / alphaWidth); 392 | isEmpty = false; 393 | if (e.shiftKey) { 394 | currentAlpha = Math.round(currentAlpha * 10) / 10; 395 | } 396 | 397 | move(); 398 | }, dragStart, dragStop); 399 | 400 | draggable(slider, function (dragX, dragY) { 401 | currentHue = parseFloat(dragY / slideHeight); 402 | isEmpty = false; 403 | if (!opts.showAlpha) { 404 | currentAlpha = 1; 405 | } 406 | move(); 407 | }, dragStart, dragStop); 408 | 409 | draggable(dragger, function (dragX, dragY, e) { 410 | 411 | // shift+drag should snap the movement to either the x or y axis. 412 | if (!e.shiftKey) { 413 | shiftMovementDirection = null; 414 | } 415 | else if (!shiftMovementDirection) { 416 | var oldDragX = currentSaturation * dragWidth; 417 | var oldDragY = dragHeight - (currentValue * dragHeight); 418 | var furtherFromX = Math.abs(dragX - oldDragX) > Math.abs(dragY - oldDragY); 419 | 420 | shiftMovementDirection = furtherFromX ? "x" : "y"; 421 | } 422 | 423 | var setSaturation = !shiftMovementDirection || shiftMovementDirection === "x"; 424 | var setValue = !shiftMovementDirection || shiftMovementDirection === "y"; 425 | 426 | if (setSaturation) { 427 | currentSaturation = parseFloat(dragX / dragWidth); 428 | } 429 | if (setValue) { 430 | currentValue = parseFloat((dragHeight - dragY) / dragHeight); 431 | } 432 | 433 | isEmpty = false; 434 | if (!opts.showAlpha) { 435 | currentAlpha = 1; 436 | } 437 | 438 | move(); 439 | 440 | }, dragStart, dragStop); 441 | 442 | if (!!initialColor) { 443 | set(initialColor); 444 | 445 | // In case color was black - update the preview UI and set the format 446 | // since the set function will not run (default color is black). 447 | updateUI(); 448 | currentPreferredFormat = preferredFormat || tinycolor(initialColor).format; 449 | 450 | addColorToSelectionPalette(initialColor); 451 | } 452 | else { 453 | updateUI(); 454 | } 455 | 456 | if (flat) { 457 | show(); 458 | } 459 | 460 | function paletteElementClick(e) { 461 | if (e.data && e.data.ignore) { 462 | set($(e.target).closest(".sp-thumb-el").data("color")); 463 | move(); 464 | } 465 | else { 466 | set($(e.target).closest(".sp-thumb-el").data("color")); 467 | move(); 468 | updateOriginalInput(true); 469 | if (opts.hideAfterPaletteSelect) { 470 | hide(); 471 | } 472 | } 473 | 474 | return false; 475 | } 476 | 477 | var paletteEvent = IE ? "mousedown.spectrum" : "click.spectrum touchstart.spectrum"; 478 | paletteContainer.delegate(".sp-thumb-el", paletteEvent, paletteElementClick); 479 | initialColorContainer.delegate(".sp-thumb-el:nth-child(1)", paletteEvent, { ignore: true }, paletteElementClick); 480 | } 481 | 482 | function updateSelectionPaletteFromStorage() { 483 | 484 | if (localStorageKey && window.localStorage) { 485 | 486 | // Migrate old palettes over to new format. May want to remove this eventually. 487 | try { 488 | var oldPalette = window.localStorage[localStorageKey].split(",#"); 489 | if (oldPalette.length > 1) { 490 | delete window.localStorage[localStorageKey]; 491 | $.each(oldPalette, function(i, c) { 492 | addColorToSelectionPalette(c); 493 | }); 494 | } 495 | } 496 | catch(e) { } 497 | 498 | try { 499 | selectionPalette = window.localStorage[localStorageKey].split(";"); 500 | } 501 | catch (e) { } 502 | } 503 | } 504 | 505 | function addColorToSelectionPalette(color) { 506 | if (showSelectionPalette) { 507 | var rgb = tinycolor(color).toRgbString(); 508 | if (!paletteLookup[rgb] && $.inArray(rgb, selectionPalette) === -1) { 509 | selectionPalette.push(rgb); 510 | while(selectionPalette.length > maxSelectionSize) { 511 | selectionPalette.shift(); 512 | } 513 | } 514 | 515 | if (localStorageKey && window.localStorage) { 516 | try { 517 | window.localStorage[localStorageKey] = selectionPalette.join(";"); 518 | } 519 | catch(e) { } 520 | } 521 | } 522 | } 523 | 524 | function getUniqueSelectionPalette() { 525 | var unique = []; 526 | if (opts.showPalette) { 527 | for (var i = 0; i < selectionPalette.length; i++) { 528 | var rgb = tinycolor(selectionPalette[i]).toRgbString(); 529 | 530 | if (!paletteLookup[rgb]) { 531 | unique.push(selectionPalette[i]); 532 | } 533 | } 534 | } 535 | 536 | return unique.reverse().slice(0, opts.maxSelectionSize); 537 | } 538 | 539 | function drawPalette() { 540 | 541 | var currentColor = get(); 542 | 543 | var html = $.map(paletteArray, function (palette, i) { 544 | return paletteTemplate(palette, currentColor, "sp-palette-row sp-palette-row-" + i, opts); 545 | }); 546 | 547 | updateSelectionPaletteFromStorage(); 548 | 549 | if (selectionPalette) { 550 | html.push(paletteTemplate(getUniqueSelectionPalette(), currentColor, "sp-palette-row sp-palette-row-selection", opts)); 551 | } 552 | 553 | paletteContainer.html(html.join("")); 554 | } 555 | 556 | function drawInitial() { 557 | if (opts.showInitial) { 558 | var initial = colorOnShow; 559 | var current = get(); 560 | initialColorContainer.html(paletteTemplate([initial, current], current, "sp-palette-row-initial", opts)); 561 | } 562 | } 563 | 564 | function dragStart() { 565 | if (dragHeight <= 0 || dragWidth <= 0 || slideHeight <= 0) { 566 | reflow(); 567 | } 568 | container.addClass(draggingClass); 569 | shiftMovementDirection = null; 570 | boundElement.trigger('dragstart.spectrum', [ get() ]); 571 | } 572 | 573 | function dragStop() { 574 | container.removeClass(draggingClass); 575 | boundElement.trigger('dragstop.spectrum', [ get() ]); 576 | } 577 | 578 | function setFromTextInput() { 579 | 580 | var value = textInput.val(); 581 | 582 | if ((value === null || value === "") && allowEmpty) { 583 | set(null); 584 | updateOriginalInput(true); 585 | } 586 | else { 587 | var tiny = tinycolor(value); 588 | if (tiny.isValid()) { 589 | set(tiny); 590 | updateOriginalInput(true); 591 | } 592 | else { 593 | textInput.addClass("sp-validation-error"); 594 | } 595 | } 596 | } 597 | 598 | function toggle() { 599 | if (visible) { 600 | hide(); 601 | } 602 | else { 603 | show(); 604 | } 605 | } 606 | 607 | function show() { 608 | var event = $.Event('beforeShow.spectrum'); 609 | 610 | if (visible) { 611 | reflow(); 612 | return; 613 | } 614 | 615 | boundElement.trigger(event, [ get() ]); 616 | 617 | if (callbacks.beforeShow(get()) === false || event.isDefaultPrevented()) { 618 | return; 619 | } 620 | 621 | hideAll(); 622 | visible = true; 623 | 624 | $(doc).bind("click.spectrum", clickout); 625 | $(window).bind("resize.spectrum", resize); 626 | replacer.addClass("sp-active"); 627 | container.removeClass("sp-hidden"); 628 | 629 | reflow(); 630 | updateUI(); 631 | 632 | colorOnShow = get(); 633 | 634 | drawInitial(); 635 | callbacks.show(colorOnShow); 636 | boundElement.trigger('show.spectrum', [ colorOnShow ]); 637 | } 638 | 639 | function clickout(e) { 640 | // Return on right click. 641 | if (e.button == 2) { return; } 642 | 643 | if (clickoutFiresChange) { 644 | updateOriginalInput(true); 645 | } 646 | else { 647 | revert(); 648 | } 649 | hide(); 650 | } 651 | 652 | function hide() { 653 | // Return if hiding is unnecessary 654 | if (!visible || flat) { return; } 655 | visible = false; 656 | 657 | $(doc).unbind("click.spectrum", clickout); 658 | $(window).unbind("resize.spectrum", resize); 659 | 660 | replacer.removeClass("sp-active"); 661 | container.addClass("sp-hidden"); 662 | 663 | callbacks.hide(get()); 664 | boundElement.trigger('hide.spectrum', [ get() ]); 665 | } 666 | 667 | function revert() { 668 | set(colorOnShow, true); 669 | } 670 | 671 | function set(color, ignoreFormatChange) { 672 | if (tinycolor.equals(color, get())) { 673 | // Update UI just in case a validation error needs 674 | // to be cleared. 675 | updateUI(); 676 | return; 677 | } 678 | 679 | var newColor, newHsv; 680 | if (!color && allowEmpty) { 681 | isEmpty = true; 682 | } else { 683 | isEmpty = false; 684 | newColor = tinycolor(color); 685 | newHsv = newColor.toHsv(); 686 | 687 | currentHue = (newHsv.h % 360) / 360; 688 | currentSaturation = newHsv.s; 689 | currentValue = newHsv.v; 690 | currentAlpha = newHsv.a; 691 | } 692 | updateUI(); 693 | 694 | if (newColor && newColor.isValid() && !ignoreFormatChange) { 695 | currentPreferredFormat = preferredFormat || newColor.getFormat(); 696 | } 697 | } 698 | 699 | function get(opts) { 700 | opts = opts || { }; 701 | 702 | if (allowEmpty && isEmpty) { 703 | return null; 704 | } 705 | 706 | return tinycolor.fromRatio({ 707 | h: currentHue, 708 | s: currentSaturation, 709 | v: currentValue, 710 | a: Math.round(currentAlpha * 100) / 100 711 | }, { format: opts.format || currentPreferredFormat }); 712 | } 713 | 714 | function isValid() { 715 | return !textInput.hasClass("sp-validation-error"); 716 | } 717 | 718 | function move() { 719 | updateUI(); 720 | 721 | callbacks.move(get()); 722 | boundElement.trigger('move.spectrum', [ get() ]); 723 | } 724 | 725 | function updateUI() { 726 | 727 | textInput.removeClass("sp-validation-error"); 728 | 729 | updateHelperLocations(); 730 | 731 | // Update dragger background color (gradients take care of saturation and value). 732 | var flatColor = tinycolor.fromRatio({ h: currentHue, s: 1, v: 1 }); 733 | dragger.css("background-color", flatColor.toHexString()); 734 | 735 | // Get a format that alpha will be included in (hex and names ignore alpha) 736 | var format = currentPreferredFormat; 737 | if (currentAlpha < 1 && !(currentAlpha === 0 && format === "name")) { 738 | if (format === "hex" || format === "hex3" || format === "hex6" || format === "name") { 739 | format = "rgb"; 740 | } 741 | } 742 | 743 | var realColor = get({ format: format }), 744 | displayColor = ''; 745 | 746 | //reset background info for preview element 747 | previewElement.removeClass("sp-clear-display"); 748 | previewElement.css('background-color', 'transparent'); 749 | 750 | if (!realColor && allowEmpty) { 751 | // Update the replaced elements background with icon indicating no color selection 752 | previewElement.addClass("sp-clear-display"); 753 | } 754 | else { 755 | var realHex = realColor.toHexString(), 756 | realRgb = realColor.toRgbString(); 757 | 758 | // Update the replaced elements background color (with actual selected color) 759 | if (rgbaSupport || realColor.alpha === 1) { 760 | previewElement.css("background-color", realRgb); 761 | } 762 | else { 763 | previewElement.css("background-color", "transparent"); 764 | previewElement.css("filter", realColor.toFilter()); 765 | } 766 | 767 | if (opts.showAlpha) { 768 | var rgb = realColor.toRgb(); 769 | rgb.a = 0; 770 | var realAlpha = tinycolor(rgb).toRgbString(); 771 | var gradient = "linear-gradient(left, " + realAlpha + ", " + realHex + ")"; 772 | 773 | if (IE) { 774 | alphaSliderInner.css("filter", tinycolor(realAlpha).toFilter({ gradientType: 1 }, realHex)); 775 | } 776 | else { 777 | alphaSliderInner.css("background", "-webkit-" + gradient); 778 | alphaSliderInner.css("background", "-moz-" + gradient); 779 | alphaSliderInner.css("background", "-ms-" + gradient); 780 | // Use current syntax gradient on unprefixed property. 781 | alphaSliderInner.css("background", 782 | "linear-gradient(to right, " + realAlpha + ", " + realHex + ")"); 783 | } 784 | } 785 | 786 | displayColor = realColor.toString(format); 787 | } 788 | 789 | // Update the text entry input as it changes happen 790 | if (opts.showInput) { 791 | textInput.val(displayColor); 792 | } 793 | 794 | if (opts.showPalette) { 795 | drawPalette(); 796 | } 797 | 798 | drawInitial(); 799 | } 800 | 801 | function updateHelperLocations() { 802 | var s = currentSaturation; 803 | var v = currentValue; 804 | 805 | if(allowEmpty && isEmpty) { 806 | //if selected color is empty, hide the helpers 807 | alphaSlideHelper.hide(); 808 | slideHelper.hide(); 809 | dragHelper.hide(); 810 | } 811 | else { 812 | //make sure helpers are visible 813 | alphaSlideHelper.show(); 814 | slideHelper.show(); 815 | dragHelper.show(); 816 | 817 | // Where to show the little circle in that displays your current selected color 818 | var dragX = s * dragWidth; 819 | var dragY = dragHeight - (v * dragHeight); 820 | dragX = Math.max( 821 | -dragHelperHeight, 822 | Math.min(dragWidth - dragHelperHeight, dragX - dragHelperHeight) 823 | ); 824 | dragY = Math.max( 825 | -dragHelperHeight, 826 | Math.min(dragHeight - dragHelperHeight, dragY - dragHelperHeight) 827 | ); 828 | dragHelper.css({ 829 | "top": dragY + "px", 830 | "left": dragX + "px" 831 | }); 832 | 833 | var alphaX = currentAlpha * alphaWidth; 834 | alphaSlideHelper.css({ 835 | "left": (alphaX - (alphaSlideHelperWidth / 2)) + "px" 836 | }); 837 | 838 | // Where to show the bar that displays your current selected hue 839 | var slideY = (currentHue) * slideHeight; 840 | slideHelper.css({ 841 | "top": (slideY - slideHelperHeight) + "px" 842 | }); 843 | } 844 | } 845 | 846 | function updateOriginalInput(fireCallback) { 847 | var color = get(), 848 | displayColor = '', 849 | hasChanged = !tinycolor.equals(color, colorOnShow); 850 | 851 | if (color) { 852 | displayColor = color.toString(currentPreferredFormat); 853 | // Update the selection palette with the current color 854 | addColorToSelectionPalette(color); 855 | } 856 | 857 | if (isInput) { 858 | boundElement.val(displayColor); 859 | } 860 | 861 | if (fireCallback && hasChanged) { 862 | callbacks.change(color); 863 | boundElement.trigger('change', [ color ]); 864 | } 865 | } 866 | 867 | function reflow() { 868 | dragWidth = dragger.width(); 869 | dragHeight = dragger.height(); 870 | dragHelperHeight = dragHelper.height(); 871 | slideWidth = slider.width(); 872 | slideHeight = slider.height(); 873 | slideHelperHeight = slideHelper.height(); 874 | alphaWidth = alphaSlider.width(); 875 | alphaSlideHelperWidth = alphaSlideHelper.width(); 876 | 877 | if (!flat) { 878 | container.css("position", "absolute"); 879 | if (opts.offset) { 880 | container.offset(opts.offset); 881 | } else { 882 | container.offset(getOffset(container, offsetElement)); 883 | } 884 | } 885 | 886 | updateHelperLocations(); 887 | 888 | if (opts.showPalette) { 889 | drawPalette(); 890 | } 891 | 892 | boundElement.trigger('reflow.spectrum'); 893 | } 894 | 895 | function destroy() { 896 | boundElement.show(); 897 | offsetElement.unbind("click.spectrum touchstart.spectrum"); 898 | container.remove(); 899 | replacer.remove(); 900 | spectrums[spect.id] = null; 901 | } 902 | 903 | function option(optionName, optionValue) { 904 | if (optionName === undefined) { 905 | return $.extend({}, opts); 906 | } 907 | if (optionValue === undefined) { 908 | return opts[optionName]; 909 | } 910 | 911 | opts[optionName] = optionValue; 912 | applyOptions(); 913 | } 914 | 915 | function enable() { 916 | disabled = false; 917 | boundElement.attr("disabled", false); 918 | offsetElement.removeClass("sp-disabled"); 919 | } 920 | 921 | function disable() { 922 | hide(); 923 | disabled = true; 924 | boundElement.attr("disabled", true); 925 | offsetElement.addClass("sp-disabled"); 926 | } 927 | 928 | function setOffset(coord) { 929 | opts.offset = coord; 930 | reflow(); 931 | } 932 | 933 | initialize(); 934 | 935 | var spect = { 936 | show: show, 937 | hide: hide, 938 | toggle: toggle, 939 | reflow: reflow, 940 | option: option, 941 | enable: enable, 942 | disable: disable, 943 | offset: setOffset, 944 | set: function (c) { 945 | set(c); 946 | updateOriginalInput(); 947 | }, 948 | get: get, 949 | destroy: destroy, 950 | container: container 951 | }; 952 | 953 | spect.id = spectrums.push(spect) - 1; 954 | 955 | return spect; 956 | } 957 | 958 | /** 959 | * checkOffset - get the offset below/above and left/right element depending on screen position 960 | * Thanks https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js 961 | */ 962 | function getOffset(picker, input) { 963 | var extraY = 0; 964 | var dpWidth = picker.outerWidth(); 965 | var dpHeight = picker.outerHeight(); 966 | var inputHeight = input.outerHeight(); 967 | var doc = picker[0].ownerDocument; 968 | var docElem = doc.documentElement; 969 | var viewWidth = docElem.clientWidth + $(doc).scrollLeft(); 970 | var viewHeight = docElem.clientHeight + $(doc).scrollTop(); 971 | var offset = input.offset(); 972 | offset.top += inputHeight; 973 | 974 | offset.left -= 975 | Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ? 976 | Math.abs(offset.left + dpWidth - viewWidth) : 0); 977 | 978 | offset.top -= 979 | Math.min(offset.top, ((offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ? 980 | Math.abs(dpHeight + inputHeight - extraY) : extraY)); 981 | 982 | return offset; 983 | } 984 | 985 | /** 986 | * noop - do nothing 987 | */ 988 | function noop() { 989 | 990 | } 991 | 992 | /** 993 | * stopPropagation - makes the code only doing this a little easier to read in line 994 | */ 995 | function stopPropagation(e) { 996 | e.stopPropagation(); 997 | } 998 | 999 | /** 1000 | * Create a function bound to a given object 1001 | * Thanks to underscore.js 1002 | */ 1003 | function bind(func, obj) { 1004 | var slice = Array.prototype.slice; 1005 | var args = slice.call(arguments, 2); 1006 | return function () { 1007 | return func.apply(obj, args.concat(slice.call(arguments))); 1008 | }; 1009 | } 1010 | 1011 | /** 1012 | * Lightweight drag helper. Handles containment within the element, so that 1013 | * when dragging, the x is within [0,element.width] and y is within [0,element.height] 1014 | */ 1015 | function draggable(element, onmove, onstart, onstop) { 1016 | onmove = onmove || function () { }; 1017 | onstart = onstart || function () { }; 1018 | onstop = onstop || function () { }; 1019 | var doc = document; 1020 | var dragging = false; 1021 | var offset = {}; 1022 | var maxHeight = 0; 1023 | var maxWidth = 0; 1024 | var hasTouch = ('ontouchstart' in window); 1025 | 1026 | var duringDragEvents = {}; 1027 | duringDragEvents["selectstart"] = prevent; 1028 | duringDragEvents["dragstart"] = prevent; 1029 | duringDragEvents["touchmove mousemove"] = move; 1030 | duringDragEvents["touchend mouseup"] = stop; 1031 | 1032 | function prevent(e) { 1033 | if (e.stopPropagation) { 1034 | e.stopPropagation(); 1035 | } 1036 | if (e.preventDefault) { 1037 | e.preventDefault(); 1038 | } 1039 | e.returnValue = false; 1040 | } 1041 | 1042 | function move(e) { 1043 | if (dragging) { 1044 | // Mouseup happened outside of window 1045 | if (IE && doc.documentMode < 9 && !e.button) { 1046 | return stop(); 1047 | } 1048 | 1049 | var touches = e.originalEvent && e.originalEvent.touches; 1050 | var pageX = touches ? touches[0].pageX : e.pageX; 1051 | var pageY = touches ? touches[0].pageY : e.pageY; 1052 | 1053 | var dragX = Math.max(0, Math.min(pageX - offset.left, maxWidth)); 1054 | var dragY = Math.max(0, Math.min(pageY - offset.top, maxHeight)); 1055 | 1056 | if (hasTouch) { 1057 | // Stop scrolling in iOS 1058 | prevent(e); 1059 | } 1060 | 1061 | onmove.apply(element, [dragX, dragY, e]); 1062 | } 1063 | } 1064 | 1065 | function start(e) { 1066 | var rightclick = (e.which) ? (e.which == 3) : (e.button == 2); 1067 | 1068 | if (!rightclick && !dragging) { 1069 | if (onstart.apply(element, arguments) !== false) { 1070 | dragging = true; 1071 | maxHeight = $(element).height(); 1072 | maxWidth = $(element).width(); 1073 | offset = $(element).offset(); 1074 | 1075 | $(doc).bind(duringDragEvents); 1076 | $(doc.body).addClass("sp-dragging"); 1077 | 1078 | if (!hasTouch) { 1079 | move(e); 1080 | } 1081 | 1082 | prevent(e); 1083 | } 1084 | } 1085 | } 1086 | 1087 | function stop() { 1088 | if (dragging) { 1089 | $(doc).unbind(duringDragEvents); 1090 | $(doc.body).removeClass("sp-dragging"); 1091 | onstop.apply(element, arguments); 1092 | } 1093 | dragging = false; 1094 | } 1095 | 1096 | $(element).bind("touchstart mousedown", start); 1097 | } 1098 | 1099 | function throttle(func, wait, debounce) { 1100 | var timeout; 1101 | return function () { 1102 | var context = this, args = arguments; 1103 | var throttler = function () { 1104 | timeout = null; 1105 | func.apply(context, args); 1106 | }; 1107 | if (debounce) clearTimeout(timeout); 1108 | if (debounce || !timeout) timeout = setTimeout(throttler, wait); 1109 | }; 1110 | } 1111 | 1112 | /** 1113 | * Define a jQuery plugin 1114 | */ 1115 | var dataID = "spectrum.id"; 1116 | $.fn.spectrum = function (opts, extra) { 1117 | 1118 | if (typeof opts == "string") { 1119 | 1120 | var returnValue = this; 1121 | var args = Array.prototype.slice.call( arguments, 1 ); 1122 | 1123 | this.each(function () { 1124 | var spect = spectrums[$(this).data(dataID)]; 1125 | if (spect) { 1126 | var method = spect[opts]; 1127 | if (!method) { 1128 | throw new Error( "Spectrum: no such method: '" + opts + "'" ); 1129 | } 1130 | 1131 | if (opts == "get") { 1132 | returnValue = spect.get(); 1133 | } 1134 | else if (opts == "container") { 1135 | returnValue = spect.container; 1136 | } 1137 | else if (opts == "option") { 1138 | returnValue = spect.option.apply(spect, args); 1139 | } 1140 | else if (opts == "destroy") { 1141 | spect.destroy(); 1142 | $(this).removeData(dataID); 1143 | } 1144 | else { 1145 | method.apply(spect, args); 1146 | } 1147 | } 1148 | }); 1149 | 1150 | return returnValue; 1151 | } 1152 | 1153 | // Initializing a new instance of spectrum 1154 | return this.spectrum("destroy").each(function () { 1155 | var options = $.extend({}, opts, $(this).data()); 1156 | var spect = spectrum(this, options); 1157 | $(this).data(dataID, spect.id); 1158 | }); 1159 | }; 1160 | 1161 | $.fn.spectrum.load = true; 1162 | $.fn.spectrum.loadOpts = {}; 1163 | $.fn.spectrum.draggable = draggable; 1164 | $.fn.spectrum.defaults = defaultOpts; 1165 | 1166 | $.spectrum = { }; 1167 | $.spectrum.localization = { }; 1168 | $.spectrum.palettes = { }; 1169 | 1170 | $.fn.spectrum.processNativeColorInputs = function () { 1171 | if (!inputTypeColorSupport) { 1172 | $("input[type=color]").spectrum({ 1173 | preferredFormat: "hex6" 1174 | }); 1175 | } 1176 | }; 1177 | 1178 | // TinyColor v1.1.2 1179 | // https://github.com/bgrins/TinyColor 1180 | // Brian Grinstead, MIT License 1181 | 1182 | (function() { 1183 | 1184 | var trimLeft = /^[\s,#]+/, 1185 | trimRight = /\s+$/, 1186 | tinyCounter = 0, 1187 | math = Math, 1188 | mathRound = math.round, 1189 | mathMin = math.min, 1190 | mathMax = math.max, 1191 | mathRandom = math.random; 1192 | 1193 | var tinycolor = function(color, opts) { 1194 | 1195 | color = (color) ? color : ''; 1196 | opts = opts || { }; 1197 | 1198 | // If input is already a tinycolor, return itself 1199 | if (color instanceof tinycolor) { 1200 | return color; 1201 | } 1202 | // If we are called as a function, call using new instead 1203 | if (!(this instanceof tinycolor)) { 1204 | return new tinycolor(color, opts); 1205 | } 1206 | 1207 | var rgb = inputToRGB(color); 1208 | this._originalInput = color, 1209 | this._r = rgb.r, 1210 | this._g = rgb.g, 1211 | this._b = rgb.b, 1212 | this._a = rgb.a, 1213 | this._roundA = mathRound(100*this._a) / 100, 1214 | this._format = opts.format || rgb.format; 1215 | this._gradientType = opts.gradientType; 1216 | 1217 | // Don't let the range of [0,255] come back in [0,1]. 1218 | // Potentially lose a little bit of precision here, but will fix issues where 1219 | // .5 gets interpreted as half of the total, instead of half of 1 1220 | // If it was supposed to be 128, this was already taken care of by `inputToRgb` 1221 | if (this._r < 1) { this._r = mathRound(this._r); } 1222 | if (this._g < 1) { this._g = mathRound(this._g); } 1223 | if (this._b < 1) { this._b = mathRound(this._b); } 1224 | 1225 | this._ok = rgb.ok; 1226 | this._tc_id = tinyCounter++; 1227 | }; 1228 | 1229 | tinycolor.prototype = { 1230 | isDark: function() { 1231 | return this.getBrightness() < 128; 1232 | }, 1233 | isLight: function() { 1234 | return !this.isDark(); 1235 | }, 1236 | isValid: function() { 1237 | return this._ok; 1238 | }, 1239 | getOriginalInput: function() { 1240 | return this._originalInput; 1241 | }, 1242 | getFormat: function() { 1243 | return this._format; 1244 | }, 1245 | getAlpha: function() { 1246 | return this._a; 1247 | }, 1248 | getBrightness: function() { 1249 | var rgb = this.toRgb(); 1250 | return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000; 1251 | }, 1252 | setAlpha: function(value) { 1253 | this._a = boundAlpha(value); 1254 | this._roundA = mathRound(100*this._a) / 100; 1255 | return this; 1256 | }, 1257 | toHsv: function() { 1258 | var hsv = rgbToHsv(this._r, this._g, this._b); 1259 | return { h: hsv.h * 360, s: hsv.s, v: hsv.v, a: this._a }; 1260 | }, 1261 | toHsvString: function() { 1262 | var hsv = rgbToHsv(this._r, this._g, this._b); 1263 | var h = mathRound(hsv.h * 360), s = mathRound(hsv.s * 100), v = mathRound(hsv.v * 100); 1264 | return (this._a == 1) ? 1265 | "hsv(" + h + ", " + s + "%, " + v + "%)" : 1266 | "hsva(" + h + ", " + s + "%, " + v + "%, "+ this._roundA + ")"; 1267 | }, 1268 | toHsl: function() { 1269 | var hsl = rgbToHsl(this._r, this._g, this._b); 1270 | return { h: hsl.h * 360, s: hsl.s, l: hsl.l, a: this._a }; 1271 | }, 1272 | toHslString: function() { 1273 | var hsl = rgbToHsl(this._r, this._g, this._b); 1274 | var h = mathRound(hsl.h * 360), s = mathRound(hsl.s * 100), l = mathRound(hsl.l * 100); 1275 | return (this._a == 1) ? 1276 | "hsl(" + h + ", " + s + "%, " + l + "%)" : 1277 | "hsla(" + h + ", " + s + "%, " + l + "%, "+ this._roundA + ")"; 1278 | }, 1279 | toHex: function(allow3Char) { 1280 | return rgbToHex(this._r, this._g, this._b, allow3Char); 1281 | }, 1282 | toHexString: function(allow3Char) { 1283 | return '#' + this.toHex(allow3Char); 1284 | }, 1285 | toHex8: function() { 1286 | return rgbaToHex(this._r, this._g, this._b, this._a); 1287 | }, 1288 | toHex8String: function() { 1289 | return '#' + this.toHex8(); 1290 | }, 1291 | toRgb: function() { 1292 | return { r: mathRound(this._r), g: mathRound(this._g), b: mathRound(this._b), a: this._a }; 1293 | }, 1294 | toRgbString: function() { 1295 | return (this._a == 1) ? 1296 | "rgb(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ")" : 1297 | "rgba(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ", " + this._roundA + ")"; 1298 | }, 1299 | toPercentageRgb: function() { 1300 | return { r: mathRound(bound01(this._r, 255) * 100) + "%", g: mathRound(bound01(this._g, 255) * 100) + "%", b: mathRound(bound01(this._b, 255) * 100) + "%", a: this._a }; 1301 | }, 1302 | toPercentageRgbString: function() { 1303 | return (this._a == 1) ? 1304 | "rgb(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%)" : 1305 | "rgba(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%, " + this._roundA + ")"; 1306 | }, 1307 | toName: function() { 1308 | if (this._a === 0) { 1309 | return "transparent"; 1310 | } 1311 | 1312 | if (this._a < 1) { 1313 | return false; 1314 | } 1315 | 1316 | return hexNames[rgbToHex(this._r, this._g, this._b, true)] || false; 1317 | }, 1318 | toFilter: function(secondColor) { 1319 | var hex8String = '#' + rgbaToHex(this._r, this._g, this._b, this._a); 1320 | var secondHex8String = hex8String; 1321 | var gradientType = this._gradientType ? "GradientType = 1, " : ""; 1322 | 1323 | if (secondColor) { 1324 | var s = tinycolor(secondColor); 1325 | secondHex8String = s.toHex8String(); 1326 | } 1327 | 1328 | return "progid:DXImageTransform.Microsoft.gradient("+gradientType+"startColorstr="+hex8String+",endColorstr="+secondHex8String+")"; 1329 | }, 1330 | toString: function(format) { 1331 | var formatSet = !!format; 1332 | format = format || this._format; 1333 | 1334 | var formattedString = false; 1335 | var hasAlpha = this._a < 1 && this._a >= 0; 1336 | var needsAlphaFormat = !formatSet && hasAlpha && (format === "hex" || format === "hex6" || format === "hex3" || format === "name"); 1337 | 1338 | if (needsAlphaFormat) { 1339 | // Special case for "transparent", all other non-alpha formats 1340 | // will return rgba when there is transparency. 1341 | if (format === "name" && this._a === 0) { 1342 | return this.toName(); 1343 | } 1344 | return this.toRgbString(); 1345 | } 1346 | if (format === "rgb") { 1347 | formattedString = this.toRgbString(); 1348 | } 1349 | if (format === "prgb") { 1350 | formattedString = this.toPercentageRgbString(); 1351 | } 1352 | if (format === "hex" || format === "hex6") { 1353 | formattedString = this.toHexString(); 1354 | } 1355 | if (format === "hex3") { 1356 | formattedString = this.toHexString(true); 1357 | } 1358 | if (format === "hex8") { 1359 | formattedString = this.toHex8String(); 1360 | } 1361 | if (format === "name") { 1362 | formattedString = this.toName(); 1363 | } 1364 | if (format === "hsl") { 1365 | formattedString = this.toHslString(); 1366 | } 1367 | if (format === "hsv") { 1368 | formattedString = this.toHsvString(); 1369 | } 1370 | 1371 | return formattedString || this.toHexString(); 1372 | }, 1373 | 1374 | _applyModification: function(fn, args) { 1375 | var color = fn.apply(null, [this].concat([].slice.call(args))); 1376 | this._r = color._r; 1377 | this._g = color._g; 1378 | this._b = color._b; 1379 | this.setAlpha(color._a); 1380 | return this; 1381 | }, 1382 | lighten: function() { 1383 | return this._applyModification(lighten, arguments); 1384 | }, 1385 | brighten: function() { 1386 | return this._applyModification(brighten, arguments); 1387 | }, 1388 | darken: function() { 1389 | return this._applyModification(darken, arguments); 1390 | }, 1391 | desaturate: function() { 1392 | return this._applyModification(desaturate, arguments); 1393 | }, 1394 | saturate: function() { 1395 | return this._applyModification(saturate, arguments); 1396 | }, 1397 | greyscale: function() { 1398 | return this._applyModification(greyscale, arguments); 1399 | }, 1400 | spin: function() { 1401 | return this._applyModification(spin, arguments); 1402 | }, 1403 | 1404 | _applyCombination: function(fn, args) { 1405 | return fn.apply(null, [this].concat([].slice.call(args))); 1406 | }, 1407 | analogous: function() { 1408 | return this._applyCombination(analogous, arguments); 1409 | }, 1410 | complement: function() { 1411 | return this._applyCombination(complement, arguments); 1412 | }, 1413 | monochromatic: function() { 1414 | return this._applyCombination(monochromatic, arguments); 1415 | }, 1416 | splitcomplement: function() { 1417 | return this._applyCombination(splitcomplement, arguments); 1418 | }, 1419 | triad: function() { 1420 | return this._applyCombination(triad, arguments); 1421 | }, 1422 | tetrad: function() { 1423 | return this._applyCombination(tetrad, arguments); 1424 | } 1425 | }; 1426 | 1427 | // If input is an object, force 1 into "1.0" to handle ratios properly 1428 | // String input requires "1.0" as input, so 1 will be treated as 1 1429 | tinycolor.fromRatio = function(color, opts) { 1430 | if (typeof color == "object") { 1431 | var newColor = {}; 1432 | for (var i in color) { 1433 | if (color.hasOwnProperty(i)) { 1434 | if (i === "a") { 1435 | newColor[i] = color[i]; 1436 | } 1437 | else { 1438 | newColor[i] = convertToPercentage(color[i]); 1439 | } 1440 | } 1441 | } 1442 | color = newColor; 1443 | } 1444 | 1445 | return tinycolor(color, opts); 1446 | }; 1447 | 1448 | // Given a string or object, convert that input to RGB 1449 | // Possible string inputs: 1450 | // 1451 | // "red" 1452 | // "#f00" or "f00" 1453 | // "#ff0000" or "ff0000" 1454 | // "#ff000000" or "ff000000" 1455 | // "rgb 255 0 0" or "rgb (255, 0, 0)" 1456 | // "rgb 1.0 0 0" or "rgb (1, 0, 0)" 1457 | // "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1" 1458 | // "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1" 1459 | // "hsl(0, 100%, 50%)" or "hsl 0 100% 50%" 1460 | // "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1" 1461 | // "hsv(0, 100%, 100%)" or "hsv 0 100% 100%" 1462 | // 1463 | function inputToRGB(color) { 1464 | 1465 | var rgb = { r: 0, g: 0, b: 0 }; 1466 | var a = 1; 1467 | var ok = false; 1468 | var format = false; 1469 | 1470 | if (typeof color == "string") { 1471 | color = stringInputToObject(color); 1472 | } 1473 | 1474 | if (typeof color == "object") { 1475 | if (color.hasOwnProperty("r") && color.hasOwnProperty("g") && color.hasOwnProperty("b")) { 1476 | rgb = rgbToRgb(color.r, color.g, color.b); 1477 | ok = true; 1478 | format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb"; 1479 | } 1480 | else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("v")) { 1481 | color.s = convertToPercentage(color.s); 1482 | color.v = convertToPercentage(color.v); 1483 | rgb = hsvToRgb(color.h, color.s, color.v); 1484 | ok = true; 1485 | format = "hsv"; 1486 | } 1487 | else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("l")) { 1488 | color.s = convertToPercentage(color.s); 1489 | color.l = convertToPercentage(color.l); 1490 | rgb = hslToRgb(color.h, color.s, color.l); 1491 | ok = true; 1492 | format = "hsl"; 1493 | } 1494 | 1495 | if (color.hasOwnProperty("a")) { 1496 | a = color.a; 1497 | } 1498 | } 1499 | 1500 | a = boundAlpha(a); 1501 | 1502 | return { 1503 | ok: ok, 1504 | format: color.format || format, 1505 | r: mathMin(255, mathMax(rgb.r, 0)), 1506 | g: mathMin(255, mathMax(rgb.g, 0)), 1507 | b: mathMin(255, mathMax(rgb.b, 0)), 1508 | a: a 1509 | }; 1510 | } 1511 | 1512 | 1513 | // Conversion Functions 1514 | // -------------------- 1515 | 1516 | // `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from: 1517 | // 1518 | 1519 | // `rgbToRgb` 1520 | // Handle bounds / percentage checking to conform to CSS color spec 1521 | // 1522 | // *Assumes:* r, g, b in [0, 255] or [0, 1] 1523 | // *Returns:* { r, g, b } in [0, 255] 1524 | function rgbToRgb(r, g, b){ 1525 | return { 1526 | r: bound01(r, 255) * 255, 1527 | g: bound01(g, 255) * 255, 1528 | b: bound01(b, 255) * 255 1529 | }; 1530 | } 1531 | 1532 | // `rgbToHsl` 1533 | // Converts an RGB color value to HSL. 1534 | // *Assumes:* r, g, and b are contained in [0, 255] or [0, 1] 1535 | // *Returns:* { h, s, l } in [0,1] 1536 | function rgbToHsl(r, g, b) { 1537 | 1538 | r = bound01(r, 255); 1539 | g = bound01(g, 255); 1540 | b = bound01(b, 255); 1541 | 1542 | var max = mathMax(r, g, b), min = mathMin(r, g, b); 1543 | var h, s, l = (max + min) / 2; 1544 | 1545 | if(max == min) { 1546 | h = s = 0; // achromatic 1547 | } 1548 | else { 1549 | var d = max - min; 1550 | s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 1551 | switch(max) { 1552 | case r: h = (g - b) / d + (g < b ? 6 : 0); break; 1553 | case g: h = (b - r) / d + 2; break; 1554 | case b: h = (r - g) / d + 4; break; 1555 | } 1556 | 1557 | h /= 6; 1558 | } 1559 | 1560 | return { h: h, s: s, l: l }; 1561 | } 1562 | 1563 | // `hslToRgb` 1564 | // Converts an HSL color value to RGB. 1565 | // *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100] 1566 | // *Returns:* { r, g, b } in the set [0, 255] 1567 | function hslToRgb(h, s, l) { 1568 | var r, g, b; 1569 | 1570 | h = bound01(h, 360); 1571 | s = bound01(s, 100); 1572 | l = bound01(l, 100); 1573 | 1574 | function hue2rgb(p, q, t) { 1575 | if(t < 0) t += 1; 1576 | if(t > 1) t -= 1; 1577 | if(t < 1/6) return p + (q - p) * 6 * t; 1578 | if(t < 1/2) return q; 1579 | if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; 1580 | return p; 1581 | } 1582 | 1583 | if(s === 0) { 1584 | r = g = b = l; // achromatic 1585 | } 1586 | else { 1587 | var q = l < 0.5 ? l * (1 + s) : l + s - l * s; 1588 | var p = 2 * l - q; 1589 | r = hue2rgb(p, q, h + 1/3); 1590 | g = hue2rgb(p, q, h); 1591 | b = hue2rgb(p, q, h - 1/3); 1592 | } 1593 | 1594 | return { r: r * 255, g: g * 255, b: b * 255 }; 1595 | } 1596 | 1597 | // `rgbToHsv` 1598 | // Converts an RGB color value to HSV 1599 | // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1] 1600 | // *Returns:* { h, s, v } in [0,1] 1601 | function rgbToHsv(r, g, b) { 1602 | 1603 | r = bound01(r, 255); 1604 | g = bound01(g, 255); 1605 | b = bound01(b, 255); 1606 | 1607 | var max = mathMax(r, g, b), min = mathMin(r, g, b); 1608 | var h, s, v = max; 1609 | 1610 | var d = max - min; 1611 | s = max === 0 ? 0 : d / max; 1612 | 1613 | if(max == min) { 1614 | h = 0; // achromatic 1615 | } 1616 | else { 1617 | switch(max) { 1618 | case r: h = (g - b) / d + (g < b ? 6 : 0); break; 1619 | case g: h = (b - r) / d + 2; break; 1620 | case b: h = (r - g) / d + 4; break; 1621 | } 1622 | h /= 6; 1623 | } 1624 | return { h: h, s: s, v: v }; 1625 | } 1626 | 1627 | // `hsvToRgb` 1628 | // Converts an HSV color value to RGB. 1629 | // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100] 1630 | // *Returns:* { r, g, b } in the set [0, 255] 1631 | function hsvToRgb(h, s, v) { 1632 | 1633 | h = bound01(h, 360) * 6; 1634 | s = bound01(s, 100); 1635 | v = bound01(v, 100); 1636 | 1637 | var i = math.floor(h), 1638 | f = h - i, 1639 | p = v * (1 - s), 1640 | q = v * (1 - f * s), 1641 | t = v * (1 - (1 - f) * s), 1642 | mod = i % 6, 1643 | r = [v, q, p, p, t, v][mod], 1644 | g = [t, v, v, q, p, p][mod], 1645 | b = [p, p, t, v, v, q][mod]; 1646 | 1647 | return { r: r * 255, g: g * 255, b: b * 255 }; 1648 | } 1649 | 1650 | // `rgbToHex` 1651 | // Converts an RGB color to hex 1652 | // Assumes r, g, and b are contained in the set [0, 255] 1653 | // Returns a 3 or 6 character hex 1654 | function rgbToHex(r, g, b, allow3Char) { 1655 | 1656 | var hex = [ 1657 | pad2(mathRound(r).toString(16)), 1658 | pad2(mathRound(g).toString(16)), 1659 | pad2(mathRound(b).toString(16)) 1660 | ]; 1661 | 1662 | // Return a 3 character hex if possible 1663 | if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) { 1664 | return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0); 1665 | } 1666 | 1667 | return hex.join(""); 1668 | } 1669 | // `rgbaToHex` 1670 | // Converts an RGBA color plus alpha transparency to hex 1671 | // Assumes r, g, b and a are contained in the set [0, 255] 1672 | // Returns an 8 character hex 1673 | function rgbaToHex(r, g, b, a) { 1674 | 1675 | var hex = [ 1676 | pad2(convertDecimalToHex(a)), 1677 | pad2(mathRound(r).toString(16)), 1678 | pad2(mathRound(g).toString(16)), 1679 | pad2(mathRound(b).toString(16)) 1680 | ]; 1681 | 1682 | return hex.join(""); 1683 | } 1684 | 1685 | // `equals` 1686 | // Can be called with any tinycolor input 1687 | tinycolor.equals = function (color1, color2) { 1688 | if (!color1 || !color2) { return false; } 1689 | return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString(); 1690 | }; 1691 | tinycolor.random = function() { 1692 | return tinycolor.fromRatio({ 1693 | r: mathRandom(), 1694 | g: mathRandom(), 1695 | b: mathRandom() 1696 | }); 1697 | }; 1698 | 1699 | 1700 | // Modification Functions 1701 | // ---------------------- 1702 | // Thanks to less.js for some of the basics here 1703 | // 1704 | 1705 | function desaturate(color, amount) { 1706 | amount = (amount === 0) ? 0 : (amount || 10); 1707 | var hsl = tinycolor(color).toHsl(); 1708 | hsl.s -= amount / 100; 1709 | hsl.s = clamp01(hsl.s); 1710 | return tinycolor(hsl); 1711 | } 1712 | 1713 | function saturate(color, amount) { 1714 | amount = (amount === 0) ? 0 : (amount || 10); 1715 | var hsl = tinycolor(color).toHsl(); 1716 | hsl.s += amount / 100; 1717 | hsl.s = clamp01(hsl.s); 1718 | return tinycolor(hsl); 1719 | } 1720 | 1721 | function greyscale(color) { 1722 | return tinycolor(color).desaturate(100); 1723 | } 1724 | 1725 | function lighten (color, amount) { 1726 | amount = (amount === 0) ? 0 : (amount || 10); 1727 | var hsl = tinycolor(color).toHsl(); 1728 | hsl.l += amount / 100; 1729 | hsl.l = clamp01(hsl.l); 1730 | return tinycolor(hsl); 1731 | } 1732 | 1733 | function brighten(color, amount) { 1734 | amount = (amount === 0) ? 0 : (amount || 10); 1735 | var rgb = tinycolor(color).toRgb(); 1736 | rgb.r = mathMax(0, mathMin(255, rgb.r - mathRound(255 * - (amount / 100)))); 1737 | rgb.g = mathMax(0, mathMin(255, rgb.g - mathRound(255 * - (amount / 100)))); 1738 | rgb.b = mathMax(0, mathMin(255, rgb.b - mathRound(255 * - (amount / 100)))); 1739 | return tinycolor(rgb); 1740 | } 1741 | 1742 | function darken (color, amount) { 1743 | amount = (amount === 0) ? 0 : (amount || 10); 1744 | var hsl = tinycolor(color).toHsl(); 1745 | hsl.l -= amount / 100; 1746 | hsl.l = clamp01(hsl.l); 1747 | return tinycolor(hsl); 1748 | } 1749 | 1750 | // Spin takes a positive or negative amount within [-360, 360] indicating the change of hue. 1751 | // Values outside of this range will be wrapped into this range. 1752 | function spin(color, amount) { 1753 | var hsl = tinycolor(color).toHsl(); 1754 | var hue = (mathRound(hsl.h) + amount) % 360; 1755 | hsl.h = hue < 0 ? 360 + hue : hue; 1756 | return tinycolor(hsl); 1757 | } 1758 | 1759 | // Combination Functions 1760 | // --------------------- 1761 | // Thanks to jQuery xColor for some of the ideas behind these 1762 | // 1763 | 1764 | function complement(color) { 1765 | var hsl = tinycolor(color).toHsl(); 1766 | hsl.h = (hsl.h + 180) % 360; 1767 | return tinycolor(hsl); 1768 | } 1769 | 1770 | function triad(color) { 1771 | var hsl = tinycolor(color).toHsl(); 1772 | var h = hsl.h; 1773 | return [ 1774 | tinycolor(color), 1775 | tinycolor({ h: (h + 120) % 360, s: hsl.s, l: hsl.l }), 1776 | tinycolor({ h: (h + 240) % 360, s: hsl.s, l: hsl.l }) 1777 | ]; 1778 | } 1779 | 1780 | function tetrad(color) { 1781 | var hsl = tinycolor(color).toHsl(); 1782 | var h = hsl.h; 1783 | return [ 1784 | tinycolor(color), 1785 | tinycolor({ h: (h + 90) % 360, s: hsl.s, l: hsl.l }), 1786 | tinycolor({ h: (h + 180) % 360, s: hsl.s, l: hsl.l }), 1787 | tinycolor({ h: (h + 270) % 360, s: hsl.s, l: hsl.l }) 1788 | ]; 1789 | } 1790 | 1791 | function splitcomplement(color) { 1792 | var hsl = tinycolor(color).toHsl(); 1793 | var h = hsl.h; 1794 | return [ 1795 | tinycolor(color), 1796 | tinycolor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l}), 1797 | tinycolor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l}) 1798 | ]; 1799 | } 1800 | 1801 | function analogous(color, results, slices) { 1802 | results = results || 6; 1803 | slices = slices || 30; 1804 | 1805 | var hsl = tinycolor(color).toHsl(); 1806 | var part = 360 / slices; 1807 | var ret = [tinycolor(color)]; 1808 | 1809 | for (hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360; --results; ) { 1810 | hsl.h = (hsl.h + part) % 360; 1811 | ret.push(tinycolor(hsl)); 1812 | } 1813 | return ret; 1814 | } 1815 | 1816 | function monochromatic(color, results) { 1817 | results = results || 6; 1818 | var hsv = tinycolor(color).toHsv(); 1819 | var h = hsv.h, s = hsv.s, v = hsv.v; 1820 | var ret = []; 1821 | var modification = 1 / results; 1822 | 1823 | while (results--) { 1824 | ret.push(tinycolor({ h: h, s: s, v: v})); 1825 | v = (v + modification) % 1; 1826 | } 1827 | 1828 | return ret; 1829 | } 1830 | 1831 | // Utility Functions 1832 | // --------------------- 1833 | 1834 | tinycolor.mix = function(color1, color2, amount) { 1835 | amount = (amount === 0) ? 0 : (amount || 50); 1836 | 1837 | var rgb1 = tinycolor(color1).toRgb(); 1838 | var rgb2 = tinycolor(color2).toRgb(); 1839 | 1840 | var p = amount / 100; 1841 | var w = p * 2 - 1; 1842 | var a = rgb2.a - rgb1.a; 1843 | 1844 | var w1; 1845 | 1846 | if (w * a == -1) { 1847 | w1 = w; 1848 | } else { 1849 | w1 = (w + a) / (1 + w * a); 1850 | } 1851 | 1852 | w1 = (w1 + 1) / 2; 1853 | 1854 | var w2 = 1 - w1; 1855 | 1856 | var rgba = { 1857 | r: rgb2.r * w1 + rgb1.r * w2, 1858 | g: rgb2.g * w1 + rgb1.g * w2, 1859 | b: rgb2.b * w1 + rgb1.b * w2, 1860 | a: rgb2.a * p + rgb1.a * (1 - p) 1861 | }; 1862 | 1863 | return tinycolor(rgba); 1864 | }; 1865 | 1866 | 1867 | // Readability Functions 1868 | // --------------------- 1869 | // 1870 | 1871 | // `readability` 1872 | // Analyze the 2 colors and returns an object with the following properties: 1873 | // `brightness`: difference in brightness between the two colors 1874 | // `color`: difference in color/hue between the two colors 1875 | tinycolor.readability = function(color1, color2) { 1876 | var c1 = tinycolor(color1); 1877 | var c2 = tinycolor(color2); 1878 | var rgb1 = c1.toRgb(); 1879 | var rgb2 = c2.toRgb(); 1880 | var brightnessA = c1.getBrightness(); 1881 | var brightnessB = c2.getBrightness(); 1882 | var colorDiff = ( 1883 | Math.max(rgb1.r, rgb2.r) - Math.min(rgb1.r, rgb2.r) + 1884 | Math.max(rgb1.g, rgb2.g) - Math.min(rgb1.g, rgb2.g) + 1885 | Math.max(rgb1.b, rgb2.b) - Math.min(rgb1.b, rgb2.b) 1886 | ); 1887 | 1888 | return { 1889 | brightness: Math.abs(brightnessA - brightnessB), 1890 | color: colorDiff 1891 | }; 1892 | }; 1893 | 1894 | // `readable` 1895 | // http://www.w3.org/TR/AERT#color-contrast 1896 | // Ensure that foreground and background color combinations provide sufficient contrast. 1897 | // *Example* 1898 | // tinycolor.isReadable("#000", "#111") => false 1899 | tinycolor.isReadable = function(color1, color2) { 1900 | var readability = tinycolor.readability(color1, color2); 1901 | return readability.brightness > 125 && readability.color > 500; 1902 | }; 1903 | 1904 | // `mostReadable` 1905 | // Given a base color and a list of possible foreground or background 1906 | // colors for that base, returns the most readable color. 1907 | // *Example* 1908 | // tinycolor.mostReadable("#123", ["#fff", "#000"]) => "#000" 1909 | tinycolor.mostReadable = function(baseColor, colorList) { 1910 | var bestColor = null; 1911 | var bestScore = 0; 1912 | var bestIsReadable = false; 1913 | for (var i=0; i < colorList.length; i++) { 1914 | 1915 | // We normalize both around the "acceptable" breaking point, 1916 | // but rank brightness constrast higher than hue. 1917 | 1918 | var readability = tinycolor.readability(baseColor, colorList[i]); 1919 | var readable = readability.brightness > 125 && readability.color > 500; 1920 | var score = 3 * (readability.brightness / 125) + (readability.color / 500); 1921 | 1922 | if ((readable && ! bestIsReadable) || 1923 | (readable && bestIsReadable && score > bestScore) || 1924 | ((! readable) && (! bestIsReadable) && score > bestScore)) { 1925 | bestIsReadable = readable; 1926 | bestScore = score; 1927 | bestColor = tinycolor(colorList[i]); 1928 | } 1929 | } 1930 | return bestColor; 1931 | }; 1932 | 1933 | 1934 | // Big List of Colors 1935 | // ------------------ 1936 | // 1937 | var names = tinycolor.names = { 1938 | aliceblue: "f0f8ff", 1939 | antiquewhite: "faebd7", 1940 | aqua: "0ff", 1941 | aquamarine: "7fffd4", 1942 | azure: "f0ffff", 1943 | beige: "f5f5dc", 1944 | bisque: "ffe4c4", 1945 | black: "000", 1946 | blanchedalmond: "ffebcd", 1947 | blue: "00f", 1948 | blueviolet: "8a2be2", 1949 | brown: "a52a2a", 1950 | burlywood: "deb887", 1951 | burntsienna: "ea7e5d", 1952 | cadetblue: "5f9ea0", 1953 | chartreuse: "7fff00", 1954 | chocolate: "d2691e", 1955 | coral: "ff7f50", 1956 | cornflowerblue: "6495ed", 1957 | cornsilk: "fff8dc", 1958 | crimson: "dc143c", 1959 | cyan: "0ff", 1960 | darkblue: "00008b", 1961 | darkcyan: "008b8b", 1962 | darkgoldenrod: "b8860b", 1963 | darkgray: "a9a9a9", 1964 | darkgreen: "006400", 1965 | darkgrey: "a9a9a9", 1966 | darkkhaki: "bdb76b", 1967 | darkmagenta: "8b008b", 1968 | darkolivegreen: "556b2f", 1969 | darkorange: "ff8c00", 1970 | darkorchid: "9932cc", 1971 | darkred: "8b0000", 1972 | darksalmon: "e9967a", 1973 | darkseagreen: "8fbc8f", 1974 | darkslateblue: "483d8b", 1975 | darkslategray: "2f4f4f", 1976 | darkslategrey: "2f4f4f", 1977 | darkturquoise: "00ced1", 1978 | darkviolet: "9400d3", 1979 | deeppink: "ff1493", 1980 | deepskyblue: "00bfff", 1981 | dimgray: "696969", 1982 | dimgrey: "696969", 1983 | dodgerblue: "1e90ff", 1984 | firebrick: "b22222", 1985 | floralwhite: "fffaf0", 1986 | forestgreen: "228b22", 1987 | fuchsia: "f0f", 1988 | gainsboro: "dcdcdc", 1989 | ghostwhite: "f8f8ff", 1990 | gold: "ffd700", 1991 | goldenrod: "daa520", 1992 | gray: "808080", 1993 | green: "008000", 1994 | greenyellow: "adff2f", 1995 | grey: "808080", 1996 | honeydew: "f0fff0", 1997 | hotpink: "ff69b4", 1998 | indianred: "cd5c5c", 1999 | indigo: "4b0082", 2000 | ivory: "fffff0", 2001 | khaki: "f0e68c", 2002 | lavender: "e6e6fa", 2003 | lavenderblush: "fff0f5", 2004 | lawngreen: "7cfc00", 2005 | lemonchiffon: "fffacd", 2006 | lightblue: "add8e6", 2007 | lightcoral: "f08080", 2008 | lightcyan: "e0ffff", 2009 | lightgoldenrodyellow: "fafad2", 2010 | lightgray: "d3d3d3", 2011 | lightgreen: "90ee90", 2012 | lightgrey: "d3d3d3", 2013 | lightpink: "ffb6c1", 2014 | lightsalmon: "ffa07a", 2015 | lightseagreen: "20b2aa", 2016 | lightskyblue: "87cefa", 2017 | lightslategray: "789", 2018 | lightslategrey: "789", 2019 | lightsteelblue: "b0c4de", 2020 | lightyellow: "ffffe0", 2021 | lime: "0f0", 2022 | limegreen: "32cd32", 2023 | linen: "faf0e6", 2024 | magenta: "f0f", 2025 | maroon: "800000", 2026 | mediumaquamarine: "66cdaa", 2027 | mediumblue: "0000cd", 2028 | mediumorchid: "ba55d3", 2029 | mediumpurple: "9370db", 2030 | mediumseagreen: "3cb371", 2031 | mediumslateblue: "7b68ee", 2032 | mediumspringgreen: "00fa9a", 2033 | mediumturquoise: "48d1cc", 2034 | mediumvioletred: "c71585", 2035 | midnightblue: "191970", 2036 | mintcream: "f5fffa", 2037 | mistyrose: "ffe4e1", 2038 | moccasin: "ffe4b5", 2039 | navajowhite: "ffdead", 2040 | navy: "000080", 2041 | oldlace: "fdf5e6", 2042 | olive: "808000", 2043 | olivedrab: "6b8e23", 2044 | orange: "ffa500", 2045 | orangered: "ff4500", 2046 | orchid: "da70d6", 2047 | palegoldenrod: "eee8aa", 2048 | palegreen: "98fb98", 2049 | paleturquoise: "afeeee", 2050 | palevioletred: "db7093", 2051 | papayawhip: "ffefd5", 2052 | peachpuff: "ffdab9", 2053 | peru: "cd853f", 2054 | pink: "ffc0cb", 2055 | plum: "dda0dd", 2056 | powderblue: "b0e0e6", 2057 | purple: "800080", 2058 | rebeccapurple: "663399", 2059 | red: "f00", 2060 | rosybrown: "bc8f8f", 2061 | royalblue: "4169e1", 2062 | saddlebrown: "8b4513", 2063 | salmon: "fa8072", 2064 | sandybrown: "f4a460", 2065 | seagreen: "2e8b57", 2066 | seashell: "fff5ee", 2067 | sienna: "a0522d", 2068 | silver: "c0c0c0", 2069 | skyblue: "87ceeb", 2070 | slateblue: "6a5acd", 2071 | slategray: "708090", 2072 | slategrey: "708090", 2073 | snow: "fffafa", 2074 | springgreen: "00ff7f", 2075 | steelblue: "4682b4", 2076 | tan: "d2b48c", 2077 | teal: "008080", 2078 | thistle: "d8bfd8", 2079 | tomato: "ff6347", 2080 | turquoise: "40e0d0", 2081 | violet: "ee82ee", 2082 | wheat: "f5deb3", 2083 | white: "fff", 2084 | whitesmoke: "f5f5f5", 2085 | yellow: "ff0", 2086 | yellowgreen: "9acd32" 2087 | }; 2088 | 2089 | // Make it easy to access colors via `hexNames[hex]` 2090 | var hexNames = tinycolor.hexNames = flip(names); 2091 | 2092 | 2093 | // Utilities 2094 | // --------- 2095 | 2096 | // `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }` 2097 | function flip(o) { 2098 | var flipped = { }; 2099 | for (var i in o) { 2100 | if (o.hasOwnProperty(i)) { 2101 | flipped[o[i]] = i; 2102 | } 2103 | } 2104 | return flipped; 2105 | } 2106 | 2107 | // Return a valid alpha value [0,1] with all invalid values being set to 1 2108 | function boundAlpha(a) { 2109 | a = parseFloat(a); 2110 | 2111 | if (isNaN(a) || a < 0 || a > 1) { 2112 | a = 1; 2113 | } 2114 | 2115 | return a; 2116 | } 2117 | 2118 | // Take input from [0, n] and return it as [0, 1] 2119 | function bound01(n, max) { 2120 | if (isOnePointZero(n)) { n = "100%"; } 2121 | 2122 | var processPercent = isPercentage(n); 2123 | n = mathMin(max, mathMax(0, parseFloat(n))); 2124 | 2125 | // Automatically convert percentage into number 2126 | if (processPercent) { 2127 | n = parseInt(n * max, 10) / 100; 2128 | } 2129 | 2130 | // Handle floating point rounding errors 2131 | if ((math.abs(n - max) < 0.000001)) { 2132 | return 1; 2133 | } 2134 | 2135 | // Convert into [0, 1] range if it isn't already 2136 | return (n % max) / parseFloat(max); 2137 | } 2138 | 2139 | // Force a number between 0 and 1 2140 | function clamp01(val) { 2141 | return mathMin(1, mathMax(0, val)); 2142 | } 2143 | 2144 | // Parse a base-16 hex value into a base-10 integer 2145 | function parseIntFromHex(val) { 2146 | return parseInt(val, 16); 2147 | } 2148 | 2149 | // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1 2150 | // 2151 | function isOnePointZero(n) { 2152 | return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1; 2153 | } 2154 | 2155 | // Check to see if string passed in is a percentage 2156 | function isPercentage(n) { 2157 | return typeof n === "string" && n.indexOf('%') != -1; 2158 | } 2159 | 2160 | // Force a hex value to have 2 characters 2161 | function pad2(c) { 2162 | return c.length == 1 ? '0' + c : '' + c; 2163 | } 2164 | 2165 | // Replace a decimal with it's percentage value 2166 | function convertToPercentage(n) { 2167 | if (n <= 1) { 2168 | n = (n * 100) + "%"; 2169 | } 2170 | 2171 | return n; 2172 | } 2173 | 2174 | // Converts a decimal to a hex value 2175 | function convertDecimalToHex(d) { 2176 | return Math.round(parseFloat(d) * 255).toString(16); 2177 | } 2178 | // Converts a hex value to a decimal 2179 | function convertHexToDecimal(h) { 2180 | return (parseIntFromHex(h) / 255); 2181 | } 2182 | 2183 | var matchers = (function() { 2184 | 2185 | // 2186 | var CSS_INTEGER = "[-\\+]?\\d+%?"; 2187 | 2188 | // 2189 | var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?"; 2190 | 2191 | // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome. 2192 | var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")"; 2193 | 2194 | // Actual matching. 2195 | // Parentheses and commas are optional, but not required. 2196 | // Whitespace can take the place of commas or opening paren 2197 | var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; 2198 | var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; 2199 | 2200 | return { 2201 | rgb: new RegExp("rgb" + PERMISSIVE_MATCH3), 2202 | rgba: new RegExp("rgba" + PERMISSIVE_MATCH4), 2203 | hsl: new RegExp("hsl" + PERMISSIVE_MATCH3), 2204 | hsla: new RegExp("hsla" + PERMISSIVE_MATCH4), 2205 | hsv: new RegExp("hsv" + PERMISSIVE_MATCH3), 2206 | hsva: new RegExp("hsva" + PERMISSIVE_MATCH4), 2207 | hex3: /^([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, 2208 | hex6: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/, 2209 | hex8: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/ 2210 | }; 2211 | })(); 2212 | 2213 | // `stringInputToObject` 2214 | // Permissive string parsing. Take in a number of formats, and output an object 2215 | // based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}` 2216 | function stringInputToObject(color) { 2217 | 2218 | color = color.replace(trimLeft,'').replace(trimRight, '').toLowerCase(); 2219 | var named = false; 2220 | if (names[color]) { 2221 | color = names[color]; 2222 | named = true; 2223 | } 2224 | else if (color == 'transparent') { 2225 | return { r: 0, g: 0, b: 0, a: 0, format: "name" }; 2226 | } 2227 | 2228 | // Try to match string input using regular expressions. 2229 | // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360] 2230 | // Just return an object and let the conversion functions handle that. 2231 | // This way the result will be the same whether the tinycolor is initialized with string or object. 2232 | var match; 2233 | if ((match = matchers.rgb.exec(color))) { 2234 | return { r: match[1], g: match[2], b: match[3] }; 2235 | } 2236 | if ((match = matchers.rgba.exec(color))) { 2237 | return { r: match[1], g: match[2], b: match[3], a: match[4] }; 2238 | } 2239 | if ((match = matchers.hsl.exec(color))) { 2240 | return { h: match[1], s: match[2], l: match[3] }; 2241 | } 2242 | if ((match = matchers.hsla.exec(color))) { 2243 | return { h: match[1], s: match[2], l: match[3], a: match[4] }; 2244 | } 2245 | if ((match = matchers.hsv.exec(color))) { 2246 | return { h: match[1], s: match[2], v: match[3] }; 2247 | } 2248 | if ((match = matchers.hsva.exec(color))) { 2249 | return { h: match[1], s: match[2], v: match[3], a: match[4] }; 2250 | } 2251 | if ((match = matchers.hex8.exec(color))) { 2252 | return { 2253 | a: convertHexToDecimal(match[1]), 2254 | r: parseIntFromHex(match[2]), 2255 | g: parseIntFromHex(match[3]), 2256 | b: parseIntFromHex(match[4]), 2257 | format: named ? "name" : "hex8" 2258 | }; 2259 | } 2260 | if ((match = matchers.hex6.exec(color))) { 2261 | return { 2262 | r: parseIntFromHex(match[1]), 2263 | g: parseIntFromHex(match[2]), 2264 | b: parseIntFromHex(match[3]), 2265 | format: named ? "name" : "hex" 2266 | }; 2267 | } 2268 | if ((match = matchers.hex3.exec(color))) { 2269 | return { 2270 | r: parseIntFromHex(match[1] + '' + match[1]), 2271 | g: parseIntFromHex(match[2] + '' + match[2]), 2272 | b: parseIntFromHex(match[3] + '' + match[3]), 2273 | format: named ? "name" : "hex" 2274 | }; 2275 | } 2276 | 2277 | return false; 2278 | } 2279 | 2280 | window.tinycolor = tinycolor; 2281 | })(); 2282 | 2283 | $(function () { 2284 | if ($.fn.spectrum.load) { 2285 | $.fn.spectrum.processNativeColorInputs(); 2286 | } 2287 | }); 2288 | 2289 | }); 2290 | -------------------------------------------------------------------------------- /js/templates/deftheme-panel.handlebars: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |

    Generated Theme: {{name}}

    5 |
    6 |
    7 |

    8 |

    After saving you can install the theme in Emacs by doing this:

    9 |
    10 | M-x package-install-file 11 |
    Package file name: {path}/{{name}}-theme.el 12 |
    Where {path} is the location you saved {{name}}-theme.el 13 |
    14 | 15 |

    Then you can activate it:

    16 |
    17 | M-x load-theme 18 |
    Load custom theme: {{name}} 19 |
    20 |

    Generated Code

    21 |
    {{{generated}}}
    22 |
    23 |
    24 |
    25 | -------------------------------------------------------------------------------- /js/templates/deftheme.handlebars: -------------------------------------------------------------------------------- 1 | ;;; {{name}}-theme.el --- {{name}} 2 | ;;; Version: 1.0 3 | ;;; Commentary: 4 | ;;; A theme called {{name}} 5 | ;;; Code: 6 | 7 | (deftheme {{name}} "DOCSTRING for {{name}}") 8 | (custom-theme-set-faces '{{name}} 9 | '(default ((t (:foreground "{{foreground}}" :background "{{background}}" )))) 10 | '(cursor ((t (:background "{{cursor}}" )))) 11 | '(fringe ((t (:background "{{border}}" )))) 12 | '(mode-line ((t (:foreground "{{modelinefg}}" :background "{{modelinebg}}" )))) 13 | '(region ((t (:background "{{region}}" )))) 14 | '(secondary-selection ((t (:background "{{secondary}}" )))) 15 | '(font-lock-builtin-face ((t (:foreground "{{builtin}}" )))) 16 | '(font-lock-comment-face ((t (:foreground "{{comment}}" )))) 17 | '(font-lock-function-name-face ((t (:foreground "{{function}}" )))) 18 | '(font-lock-keyword-face ((t (:foreground "{{keyword}}" )))) 19 | '(font-lock-string-face ((t (:foreground "{{string}}" )))) 20 | '(font-lock-type-face ((t (:foreground "{{type}}" )))) 21 | '(font-lock-constant-face ((t (:foreground "{{constant}}" )))) 22 | '(font-lock-variable-name-face ((t (:foreground "{{variable}}" )))) 23 | '(minibuffer-prompt ((t (:foreground "{{prompt}}" :bold t )))) 24 | '(font-lock-warning-face ((t (:foreground "red" :bold t )))) 25 | ) 26 | 27 | ;;;###autoload 28 | (and load-file-name 29 | (boundp 'custom-theme-load-path) 30 | (add-to-list 'custom-theme-load-path 31 | (file-name-as-directory 32 | (file-name-directory load-file-name)))) 33 | ;; Automatically add this theme to the load path 34 | 35 | (provide-theme '{{name}}) 36 | 37 | ;;; {{name}}-theme.el ends here 38 | -------------------------------------------------------------------------------- /js/templates/face-list.handlebars: -------------------------------------------------------------------------------- 1 | {{#each this}} 2 |
  • 3 |
    4 | 5 | 6 |
    7 |
  • 8 | {{/each}} 9 | -------------------------------------------------------------------------------- /js/templates/python.handlebars: -------------------------------------------------------------------------------- 1 |
     2 | 
     3 | {{{cm}}}# Python code sample{{{sx}}}
     4 | {{{kw}}}import{{{sx}}} os, re
     5 | {{{kw}}}from{{{sx}}} sys {{{kw}}}import{{{sx}}} {{{bi}}}exit{{{sx}}}
     6 | 
     7 | {{{co}}}SOMECONST{{{sx}}} = None
     8 | {{{va}}}somevariable{{{sx}}} = {{{bi}}}min{{{sx}}}({{{bi}}}sum{{{sx}}}(1,2), {{{bi}}}max{{{sx}}}({{{bi}}}range{{{sx}}}(10)))
     9 | 
    10 | {{{kw}}}def{{{sx}}} {{{fn}}}some_callable{{{sx}}}(argument1, argument2=False):
    11 |     {{{st}}}""" Doc-string for some_callable function. """{{{sx}}}
    12 |     {{{kw}}}if{{{sx}}} {{{kw}}}not{{{sx}}} argument1:
    13 |         {{{kw}}}return{{{sx}}} {{{co}}}None{{{sx}}}
    14 |     {{{kw}}}try{{{sx}}}:
    15 |         argument1.do_stuff()
    16 |     {{{kw}}}except{{{sx}}} ({{{ty}}}AttributeError{{{sx}}}): {{{cm}}}# some comments{{{sx}}}
    17 |         {{{kw}}}if{{{sx}}} {{{kw}}}not{{{sx}}} can_fail:
    18 |             {{{kw}}}raise{{{sx}}}
    19 |         {{{kw}}}return{{{sx}}} argument1{{{sx}}}
    20 | 
    21 | {{{kw}}}class{{{sx}}} {{{ty}}}RegexPattern{{{sx}}}({{{bi}}}object{{{sx}}}):
    22 |     {{{kw}}}def{{{sx}}} {{{fn}}}__init__{{{sx}}}({{{kw}}}self{{{sx}}}, regex, name):
    23 |         {{{kw}}}self{{{sx}}}.v = {{{st}}}"0.2.24"{{{sx}}}
    24 | {{{rg}}}        {{{kw}}}self{{{sx}}}.regex = re.compile(regex)  {{{sx}}}{{{cu}}} {{{sx}}}
    25 |         {{{kw}}}self{{{sx}}}.name = name
    26 | 
    27 |     {{{kw}}}def{{{sx}}} {{{fn}}}__repr__{{{sx}}}({{{kw}}}self{{{sx}}}):
    28 |         {{{kw}}}return{{{sx}}} {{{st}}}'string <%s with %s> formatting'{{{sx}}} % ({{{kw}}}self{{{sx}}}.name, {{{kw}}}self{{{sx}}}.regex)
    29 | 
    30 |     {{{kw}}}def{{{sx}}} {{{fn}}}version{{{sx}}}({{{kw}}}self{{{sx}}}):
    31 |         {{{kw}}}return{{{sx}}} {{{st}}}'Version: %s'{{{sx}}} % ({{{kw}}}self{{{sx}}}.v)
    32 | 
    33 |     {{{kw}}}def{{{sx}}} {{{fn}}}resolve{{{sx}}}({{{kw}}}self{{{sx}}}, path):
    34 |         match = {{{kw}}}self{{{sx}}}.regex.search(path)
    35 |         {{{kw}}}if{{{sx}}} match:
    36 |             {{{cm}}}# Even more comments.
    37 |             # I love writing comments.{{{sx}}}
    38 |             {{{kw}}}if{{{sx}}} kwargs:
    39 |                 args = ()
    40 |             {{{kw}}}else{{{sx}}}:
    41 |                 args = match.groups()
    42 | {{{ss}}}            {{{kw}}}return{{{sx}}} args, kwargs{{{sx}}} {{{cm}}}# secondary selection{{{sx}}}
    43 | 
    44 | 
    45 |
    46 |
    47 | --:**-   project.py     Bot 10% L20     (Python)-------------------------
    48 | 
    49 |
    50 |
    I-search:
    51 | -------------------------------------------------------------------------------- /js/templates/theme-selector.handlebars: -------------------------------------------------------------------------------- 1 |
    2 | 6 | 13 |
    14 | -------------------------------------------------------------------------------- /js/templates/user-themes.handlebars: -------------------------------------------------------------------------------- 1 |
    2 | 6 | 15 |
    16 | -------------------------------------------------------------------------------- /js/themes.js: -------------------------------------------------------------------------------- 1 | const App = {}; 2 | 3 | App.liveTheme = {}; 4 | 5 | const generateDeftheme = name => { 6 | return $.get('./js/templates/deftheme.handlebars').then(t => { 7 | var compiled, o; 8 | o = _.extend({ 9 | name: name 10 | }, App.liveTheme); 11 | compiled = Handlebars.compile(t); 12 | return compiled(o); 13 | }); 14 | }; 15 | 16 | const themeGenerator = () => { 17 | var name; 18 | name = $('#theme-name').val(); 19 | if (!name) { 20 | name = prompt("Generate theme", "untitled"); 21 | $('#theme-name').val(name); 22 | } 23 | return generateDeftheme(name).then(generated => { 24 | App.generatedTheme = generated; 25 | App.generatedThemeName = name + "-theme.el"; 26 | return $.get('./js/templates/deftheme-panel.handlebars', file => { 27 | var c, compiled, ctx; 28 | compiled = Handlebars.compile(file); 29 | ctx = { 30 | generated: generated, 31 | name: name 32 | }; 33 | c = compiled(ctx); 34 | $('#theme-generated').html(c); 35 | window.location.hash = "theme-generated"; 36 | }); 37 | }); 38 | }; 39 | 40 | const saveCurrentTheme = () => { 41 | var downloadLink, textBlob; 42 | if (!(App.generatedTheme && App.generatedThemeName)) { 43 | return; 44 | } 45 | textBlob = new Blob([App.generatedTheme], { 46 | type: 'text/plain' 47 | }); 48 | downloadLink = document.createElement("a"); 49 | downloadLink.download = App.generatedThemeName; 50 | if (window.webkitURL !== null) { 51 | downloadLink.href = window.webkitURL.createObjectURL(textBlob); 52 | } else { 53 | downloadLink.href = window.URL.createObjectURL(textBlob); 54 | downloadLink.onclick = (e) => { 55 | document.body.removeChild(e.target); 56 | }; 57 | downloadLink.style.display = "none"; 58 | document.body.appendChild(downloadLink); 59 | } 60 | downloadLink.click(); 61 | }; 62 | 63 | App.codeSpans = { 64 | kw: '', 65 | cm: '', 66 | bi: '', 67 | ty: '', 68 | va: '', 69 | co: '', 70 | st: '', 71 | fn: '', 72 | rg: '', 73 | ss: '', 74 | cu: '', 75 | sx: '' 76 | }; 77 | 78 | App.faceTable = { 79 | background: { 80 | id: "rbg", 81 | title: "Background", 82 | rx: /background-color +\. +"([^"]*)"/, 83 | rx24: /default +\(\(t +\(\(t +\(.*:background +"([^"]*)"/, 84 | el: ['.default', 'background-color'] 85 | }, 86 | foreground: { 87 | id: "rfg", 88 | title: "Foreground", 89 | rx: /foreground-color \. +"([^"]*)"/, 90 | rx24: /default +\(\(t +\(\(t +\(.*:foreground +"([^"]*)"/, 91 | el: ['.default', 'color'] 92 | }, 93 | keyword: { 94 | id: "rkw", 95 | title: "Keywords", 96 | rx: /font-lock-keyword-face +\(\(t +\(.*:foreground +"([^"]*)"/, 97 | el: ['.keyword', 'color'] 98 | }, 99 | comment: { 100 | id: "rcmt", 101 | title: "Comments", 102 | rx: /font-lock-comment-face +\(\(t +\(.*:foreground +"([^"]*)"/, 103 | el: ['.comment', 'color'] 104 | }, 105 | builtin: { 106 | id: "rbt", 107 | title: "Builtins", 108 | rx: /font-lock-builtin-face +\(\(t +\(.*:foreground +"([^"]*)"/, 109 | el: ['.builtin', 'color'] 110 | }, 111 | type: { 112 | id: "rtp", 113 | title: "Class/Type", 114 | rx: /font-lock-type-face +\(\(t +\(.*:foreground +"([^"]*)"/, 115 | el: ['.type', 'color'] 116 | }, 117 | variable: { 118 | id: "rvar", 119 | title: "Variables", 120 | rx: /font-lock-variable-name-face +\(\(t +\(.*:foreground +"([^"]*)"/, 121 | el: ['.variable', 'color'] 122 | }, 123 | constant: { 124 | id: "rconst", 125 | title: "Constants", 126 | rx: /font-lock-constant-face +\(\(t +\(.*:foreground +"([^"]*)"/, 127 | el: ['.constant', 'color'] 128 | }, 129 | string: { 130 | id: "rstr", 131 | title: "Strings", 132 | rx: /font-lock-string-face +\(\(t +\(.*:foreground +"([^"]*)"/, 133 | el: ['.string', 'color'] 134 | }, 135 | "function": { 136 | id: "rfun", 137 | title: "Functions", 138 | rx: /font-lock-function-name-face +\(\(t +\(.*:foreground +"([^"]*)"/, 139 | el: ['.function', 'color'] 140 | }, 141 | cursor: { 142 | id: "rcr", 143 | title: "Cursor", 144 | rx: /cursor-color +\. +"([^"]*)"/, 145 | rx24: /cursor +\(\(t +\(\(t +\(.*:background +"([^"]*)"/, 146 | el: ['.cursor', 'background-color'] 147 | }, 148 | region: { 149 | id: "reg", 150 | title: "Region", 151 | rx: /region +\(\(t +\(.*:background +"([^"]*)"/, 152 | el: ['.region', 'background-color'] 153 | }, 154 | secondary: { 155 | id: "rss", 156 | title: "Secondary Selection", 157 | rx: /secondary-selection +\(\(t +\(.*:background +"([^"]*)"/, 158 | el: ['.secondary-selection', 'background-color'] 159 | }, 160 | border: { 161 | id: "rbd", 162 | title: "Fringe", 163 | rx: /border-color +\. +"([^"]*)"/, 164 | rx24: /fringe +\(\(t +\(\(t +\(.*:background +"([^"]*)"/, 165 | el: ['.fringe', 'border-color'] 166 | }, 167 | modelinebg: { 168 | id: "modbg", 169 | title: "Modeline bg", 170 | rx: /mode-line +\(\(t +\(.*:background +"([^"]*)"/, 171 | el: ['.modeline', 'background-color'] 172 | }, 173 | modelinefg: { 174 | id: "modfg", 175 | title: "Modeline fg", 176 | rx: /mode-line +\(\(t +\(.*:foreground +"([^"]*)"/, 177 | el: ['.modeline', 'color'] 178 | }, 179 | prompt: { 180 | id: "pmp", 181 | title: "Minibuffer", 182 | rx: /minibuffer-prompt +\(\(t +\(.*:foreground +"([^"]*)"/, 183 | el: ['.prompt', 'color'] 184 | } 185 | }; 186 | 187 | const getFaceList = t => { 188 | return _.keys(t).map(k => ({ 189 | name: k, 190 | id: t[k].id, 191 | title: t[k].title 192 | })); 193 | }; 194 | 195 | 196 | const masterKeys = () => { 197 | return _.keys(App.faceTable); 198 | }; 199 | 200 | const elp = k => { 201 | return App.faceTable[k].el[1]; 202 | }; 203 | 204 | const getColor = k => { 205 | return tinycolor($(App.faceTable[k].el[0]).css(elp(k))).toHexString(); 206 | }; 207 | 208 | const setColor = (k, col) => { 209 | $(App.faceTable[k].el[0]).css(elp(k), col); 210 | $("input[name=" + k + "]").spectrum("set", col); 211 | $("input[name=" + k + "]").val(col); 212 | return App.liveTheme[k] = col; 213 | }; 214 | 215 | const setTheme = (themeJson, name) => { 216 | if (name == null) { 217 | name = null; 218 | } 219 | 220 | if (!themeJson) { 221 | return; 222 | } 223 | 224 | let o = JSON.parse(themeJson); 225 | _.each(_.keys(o), k => setColor(k, o[k])); 226 | 227 | if (name) { 228 | $('#theme-name').val(name); 229 | } 230 | }; 231 | 232 | let userThemes = {}; 233 | 234 | const updateUserThemes = () => { 235 | $('#user-themes').empty(); 236 | _.each(_.keys(userThemes), k => { delete userThemes[k]; }); 237 | _.each(_.keys(localStorage), t => { 238 | if (localStorage.getItem(t)) { 239 | userThemes[t] = localStorage.getItem(t); 240 | } 241 | }); 242 | if (_.keys(userThemes).length > 0) { 243 | $.get('./js/templates/user-themes.handlebars', file => { 244 | var template; 245 | template = Handlebars.compile(file); 246 | $('#user-themes').html(template({ userThemes: userThemes })); 247 | }); 248 | } 249 | }; 250 | 251 | const saveToLocalStorage = () => { 252 | var name; 253 | name = $('#theme-name').val(); 254 | if (!name) { 255 | name = prompt("Save theme", "untitled"); 256 | } 257 | if (!name) { 258 | return; 259 | } 260 | $('#theme-name').val(name); 261 | if (localStorage.getItem(name)) { 262 | this.undoTheme = localStorage.getItem(name); 263 | } 264 | localStorage.setItem(name, JSON.stringify(App.liveTheme)); 265 | updateUserThemes(); 266 | }; 267 | 268 | const removeTheme = name => { 269 | if (confirm("Remove theme " + name)) { 270 | localStorage.removeItem(name); 271 | updateUserThemes(); 272 | } 273 | }; 274 | 275 | const closeThemeBox = () => { 276 | $('#theme-generated').hide(); 277 | $('#theme-generated .msg').remove(); 278 | $('#generate').removeAttr('disabled'); 279 | }; 280 | 281 | $(() => { 282 | $('[data-toggle=tooltip]').tooltip({ 283 | placement: 'top' 284 | }); 285 | $.get('./js/templates/theme-selector.handlebars', file => { 286 | var template; 287 | template = Handlebars.compile(file); 288 | $('#theme-selector').html(template({ 289 | themes: themes 290 | })); 291 | }); 292 | $.get('./js/templates/face-list.handlebars', file => { 293 | var list, template; 294 | template = Handlebars.compile(file); 295 | list = getFaceList(App.faceTable); 296 | $('#face-list').html(template(list)); 297 | $('input.els').spectrum({ 298 | clickoutFiresChange: true, 299 | showInitial: true, 300 | showInput: true, 301 | preferredFormat: "hex", 302 | chooseText: "Set", 303 | cancelText: "Reset", 304 | change: color => { 305 | console.log("Change event from spectrum", color); 306 | }, 307 | move: function(color) { 308 | return setColor(this.name, color.toHexString()); 309 | } 310 | }); 311 | }).then(() => { 312 | $.get('./js/templates/python.handlebars', file => { 313 | var template; 314 | template = Handlebars.compile(file); 315 | $('#code-sample').html(template(App.codeSpans)); 316 | }).then( () => { 317 | setTheme(darkTheme); 318 | }); 319 | }); 320 | 321 | if (_.keys(localStorage).length > 0) { 322 | updateUserThemes(); 323 | } 324 | 325 | $(document).on('click', '#generate', themeGenerator); 326 | }); 327 | 328 | const lightTheme = `{ 329 | "foreground": "#242121", 330 | "builtin": "#738aa1", 331 | "comment": "#7d827d", 332 | "keyword": "#105163", 333 | "variable": "#ac8d4b", 334 | "constant": "#456b48", 335 | "string": "#4c7685", 336 | "type": "#659915", 337 | "function": "#375a0d", 338 | "region": "#cccccc", 339 | "secondary": "#cddbec", 340 | "border": "#eef0f0", 341 | "background": "#ffffff", 342 | "modelinefg": "#ffffff", 343 | "modelinebg": "#6f8784", 344 | "cursor": "#000000", 345 | "prompt": "#7299ff" 346 | }`; 347 | 348 | const darkTheme = `{ 349 | "foreground": "#fdf4c1", 350 | "builtin": "#fe8019", 351 | "comment": "#7c6f64", 352 | "keyword": "#fb4934", 353 | "variable": "#83a598", 354 | "constant": "#d3869b", 355 | "string": "#b8bb26", 356 | "type": "#d3869b", 357 | "function": "#b8bb26", 358 | "region": "#504945", 359 | "secondary": "#3e3834", 360 | "border": "#282828", 361 | "background": "#282828", 362 | "modelinefg": "#282828", 363 | "modelinebg": "#7c6f64", 364 | "cursor": "#fdf4c1", 365 | "prompt": "#b8bb26" 366 | }`; 367 | 368 | const darkBooth = `{ 369 | "foreground": "#fdf4c1", 370 | "builtin": "#fe8019", 371 | "comment": "#7c6f64", 372 | "keyword": "#dd6f48", 373 | "variable": "#83a598", 374 | "constant": "#bbaa97", 375 | "string": "#429489", 376 | "type": "#66999d", 377 | "function": "#a99865", 378 | "region": "#504945", 379 | "secondary": "#3e3834", 380 | "border": "#282828", 381 | "background": "#282828", 382 | "modelinefg": "#ece09f", 383 | "modelinebg": "#1e1c1a", 384 | "cursor": "#fdf4c1", 385 | "prompt": "#61acbb" 386 | }`; 387 | 388 | let themes = { 389 | "Dark": darkTheme, 390 | "Light": lightTheme, 391 | "DarkBooth": darkBooth 392 | }; 393 | -------------------------------------------------------------------------------- /tools/getEmacsColors.el: -------------------------------------------------------------------------------- 1 | ;; Quickly grab all the named colors from Emacs 2 | ;; this is intended to be run with `M-x load-file` 3 | (defun color-hex ( i ) 4 | (format "%02X" (floor (* 255 (/ (float i) (float 65535) ))))) 5 | 6 | (progn 7 | (insert "var emacsColors = [\n") 8 | (setq beg (point)) 9 | (loop for c in (defined-colors) 10 | if (numberp (nth 0 (color-values c))) 11 | do 12 | (insert " { name: \"" c "\", ") 13 | (insert "color: \"#" 14 | (color-hex (nth 0 (color-values c))) 15 | (color-hex (nth 1 (color-values c))) 16 | (color-hex (nth 2 (color-values c)))) 17 | (insert "\" },\n")) 18 | 19 | (align-regexp beg (point) "color:") 20 | (backward-delete-char 2 ) 21 | (insert "\n];")) 22 | --------------------------------------------------------------------------------