Browse Source

Added a toolbar color button for setting text and background colors. Still needs more testing/fixing in IE and WebKit.

Dan Helfman 9 years ago
parent
commit
4e21547da6

+ 1
- 0
NEWS View File

@@ -1,4 +1,5 @@
1 1
 1.6.12: 
2
+ * Added a toolbar color button for setting text and background colors.
2 3
  * Added a "start a new discussion" link to each discussion forum page.
3 4
  * Updated Luminotes Server INSTALL file with instructions for setting the
4 5
    http_url configuration setting.

+ 34
- 1
controller/Html_cleaner.py View File

@@ -19,6 +19,8 @@ class Html_cleaner(HTMLParser):
19 19
   Cleans HTML of any tags not matching a whitelist.
20 20
   """
21 21
   NOTE_LINK_URL_PATTERN = re.compile( '[^"]*/notebooks/\w+\?[^"]*note_id=\w+', re.IGNORECASE )
22
+  COLOR_RGB_PATTERN = re.compile( "^rgb(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*)$" )
23
+  COLOR_HEX_PATTERN = re.compile( "^#\d{6}$" )
22 24
 
23 25
   def __init__( self, require_link_target = False ):
24 26
     HTMLParser.__init__( self, AbstractFormatter( NullWriter() ) )
@@ -110,6 +112,7 @@ class Html_cleaner(HTMLParser):
110 112
       'caption',
111 113
       'col',
112 114
       'colgroup',
115
+      'span',
113 116
     ]
114 117
 
115 118
     # A list of tags that require no closing tag.
@@ -124,7 +127,8 @@ class Html_cleaner(HTMLParser):
124 127
       'p': [ 'align' ],
125 128
       'img': [ 'src', 'alt', 'border', 'title', "class" ],
126 129
       'table': [ 'cellpadding', 'cellspacing', 'border', 'width', 'height' ],
127
-      'font': [ 'color', 'size', 'face' ],
130
+      'font': [ 'size', 'face', 'color' ],
131
+      'span': [ 'style' ],
128 132
       'td': [ 'rowspan', 'colspan', 'width', 'height' ],
129 133
       'th': [ 'rowspan', 'colspan', 'width', 'height' ],
130 134
     }
@@ -168,6 +172,12 @@ class Html_cleaner(HTMLParser):
168 172
         else:
169 173
           bt += ' %s=%s' % \
170 174
              (xssescape(attribute), quoteattr(attrs[attribute]))
175
+        if attribute == 'style':
176
+          if self.style_is_acceptable( attrs[ attribute ] ):
177
+            bt += ' %s="%s"' % (attribute, attrs[attribute])
178
+          else:
179
+            bt += ' %s=%s' % \
180
+               (xssescape(attribute), quoteattr(attrs[attribute]))
171 181
       if tag == "a" and \
172 182
          ( not attrs.get( 'href' ) or not self.NOTE_LINK_URL_PATTERN.search( attrs.get( 'href' ) ) ):
173 183
         if self.require_link_target and not attrs.get( 'target' ):
@@ -209,6 +219,29 @@ class Html_cleaner(HTMLParser):
209 219
 
210 220
     return parsed[0] in self.allowed_schemes
211 221
 
222
+  def style_is_acceptable(self, style):
223
+    pieces = style.split( ";" )
224
+
225
+    for piece in pieces:
226
+      piece = piece.strip()
227
+      if piece == "":
228
+        continue
229
+
230
+      param_and_value = piece.split( ":" )
231
+      if len( param_and_value ) != 2:
232
+        return False
233
+
234
+      ( param, value ) = param_and_value
235
+      value = value.strip()
236
+
237
+      if param.strip().lower() not in ( "color", "background-color" ):
238
+        return False
239
+      if not self.COLOR_RGB_PATTERN.search( value ) and \
240
+         not self.COLOR_HEX_PATTERN.search( value ):
241
+        return False
242
+
243
+    return True
244
+
212 245
   def strip(self, rawstring):
213 246
     """Returns the argument stripped of potentially harmful HTML or JavaScript code"""
214 247
     self.reset()

+ 58
- 4
static/css/style.css View File

@@ -111,11 +111,11 @@ h1 {
111 111
 }
112 112
 
113 113
 #toolbar .bold_large {
114
-  background-position: 440px 0;
114
+  background-position: 480px 0;
115 115
 }
116 116
 
117 117
 #toolbar .bold_small {
118
-  background-position: 220px -2px;
118
+  background-position: 240px -2px;
119 119
 }
120 120
 
121 121
 #toolbar .italic_large {
@@ -142,6 +142,14 @@ h1 {
142 142
   background-position: 60px -2px;
143 143
 }
144 144
 
145
+#toolbar .color_large {
146
+  background-position: 400px 0;
147
+}
148
+
149
+#toolbar .color_small {
150
+  background-position: 200px -2px;
151
+}
152
+
145 153
 #toolbar .font_large {
146 154
   background-position: 360px 0;
147 155
 }
@@ -159,11 +167,11 @@ h1 {
159 167
 }
160 168
 
161 169
 #toolbar .insertUnorderedList_large {
162
-  background-position: 400px 0;
170
+  background-position: 440px 0;
163 171
 }
164 172
 
165 173
 #toolbar .insertUnorderedList_small {
166
-  background-position: 200px -2px;
174
+  background-position: 220px -2px;
167 175
 }
168 176
 
169 177
 #toolbar .insertOrderedList_large {
@@ -848,6 +856,42 @@ h1 {
848 856
   text-decoration: none;
849 857
 }
850 858
 
859
+#color_table {
860
+  border-collapse: collapse;
861
+  border: 1px solid #000000;
862
+  margin-top: 1em;
863
+}
864
+
865
+#color_table td {
866
+  border: 1px solid #000000;
867
+  width: 1.5em;
868
+  height: 1.5em;
869
+  text-align: center;
870
+  vertical-align: middle;
871
+  cursor: pointer;
872
+  padding: 0;
873
+  margin: 0;
874
+}
875
+
876
+#color_table .color_box {
877
+  font-size: 110%;
878
+  outline: none;
879
+  font-weight: bold;
880
+  border: none;
881
+  width: 1.5em;
882
+  height: 1.5em;
883
+  padding: 0;
884
+  margin: 0;
885
+}
886
+
887
+.color_box_dark_selected {
888
+  color: #ffffff;
889
+}
890
+
891
+.color_box_light_selected {
892
+  color: #000000;
893
+}
894
+
851 895
 .selected_mark {
852 896
   vertical-align: top;
853 897
 }
@@ -1154,6 +1198,12 @@ h1 {
1154 1198
 
1155 1199
 .radio_label {
1156 1200
   color: #000000;
1201
+  border: none;
1202
+  outline: none;
1203
+  background-color: transparent;
1204
+  padding: 0;
1205
+  -moz-user-select: none;
1206
+  -webkit-user-select: none;
1157 1207
 }
1158 1208
 
1159 1209
 .radio_label:hover {
@@ -1161,6 +1211,10 @@ h1 {
1161 1211
   cursor: pointer;
1162 1212
 }
1163 1213
 
1214
+.small_button {
1215
+  font-size: 100%;
1216
+}
1217
+
1164 1218
 .hook_action_area {
1165 1219
   -moz-border-radius: 5px;
1166 1220
   -webkit-border-radius: 5px;

BIN
static/images/toolbar/buttons.png View File


BIN
static/images/toolbar/color_button.xcf View File


BIN
static/images/toolbar/small/buttons.png View File


BIN
static/images/toolbar/small/color_button.xcf View File


+ 101
- 5
static/js/Editor.js View File

@@ -485,6 +485,17 @@ Editor.prototype.position_cursor_after = function ( node ) {
485 485
   }
486 486
 }
487 487
 
488
+Editor.prototype.collapse_cursor = function () {
489
+  if ( this.iframe.contentWindow && this.iframe.contentWindow.getSelection ) { // browsers such as Firefox
490
+    var selection = this.iframe.contentWindow.getSelection();
491
+    selection.collapseToEnd();
492
+  } else if ( this.document.selection ) { // browsers such as IE
493
+    var range = this.document.selection.createRange();
494
+    range.collapse( false );
495
+    range.select();
496
+  }
497
+}
498
+
488 499
 Editor.prototype.connect_handlers = function () {
489 500
   if ( this.document && this.document.body ) {
490 501
     // since the browser may subtly tweak the html when it's inserted, save off the browser's version
@@ -551,7 +562,7 @@ Editor.prototype.connect_handlers = function () {
551 562
   }
552 563
 
553 564
   // browsers such as Firefox, but not Opera
554
-  if ( !OPERA && this.iframe && this.iframe.contentDocument && this.edit_enabled ) {
565
+  if ( GECKO && this.iframe && this.iframe.contentDocument && this.edit_enabled ) {
555 566
     this.exec_command( "styleWithCSS", false );
556 567
     this.exec_command( "insertbronreturn", true );
557 568
   }
@@ -918,7 +929,9 @@ Editor.prototype.key_released = function ( event ) {
918 929
   // if ctrl keys are released, bail
919 930
   var code = event.key().code;
920 931
   var CTRL = 17;
921
-  if ( event.modifier().ctrl || code == CTRL )
932
+  var NONE = 0;
933
+
934
+  if ( event.modifier().ctrl || code == CTRL || code == NONE )
922 935
     return;
923 936
 
924 937
   this.cleanup_html( code );
@@ -1432,7 +1445,7 @@ Editor.prototype.contents = function () {
1432 1445
 
1433 1446
 // return true if the given state_name is currently enabled, optionally using a given list of node
1434 1447
 // names
1435
-Editor.prototype.state_enabled = function ( state_name, node_names ) {
1448
+Editor.prototype.state_enabled = function ( state_name, node_names, attribute_name ) {
1436 1449
   if ( !this.edit_enabled )
1437 1450
     return false;
1438 1451
 
@@ -1474,8 +1487,23 @@ Editor.prototype.current_node_names = function () {
1474 1487
   while ( node ) {
1475 1488
     var name = node.nodeName.toLowerCase();
1476 1489
 
1477
-    if ( name == "strong" ) name = "b";
1478
-    if ( name == "em" ) name = "i";
1490
+    if ( name == "body" )
1491
+      break;
1492
+    else if ( name == "strong" ) name = "b";
1493
+    else if ( name == "em" ) name = "i";
1494
+    else if ( name == "font" && node.getAttribute( "face" ) )
1495
+      name = "fontface";
1496
+    else if ( name == "font" && node.getAttribute( "size" ) )
1497
+      name = "fontsize";
1498
+    else if ( name == "font" && node.getAttribute( "color" ) )
1499
+      name = "color";
1500
+    else if ( node.hasAttribute && node.hasAttribute( "style" ) ) {
1501
+      var color = getStyle( node, "color" );
1502
+      var background_color = getStyle( node, "background-color" );
1503
+      if ( ( color && color != "transparent" ) ||
1504
+           ( background_color && background_color != "transparent" ) )
1505
+        name = "color";
1506
+    }
1479 1507
 
1480 1508
     if ( name != "a" || node.href )
1481 1509
       node_names.push( name );
@@ -1486,6 +1514,74 @@ Editor.prototype.current_node_names = function () {
1486 1514
   return node_names;
1487 1515
 }
1488 1516
 
1517
+// return the current effective foreground and background colors as hex code strings
1518
+Editor.prototype.current_colors = function () {
1519
+  var foreground = null;
1520
+  var background = null;
1521
+
1522
+  if ( !this.edit_enabled || !this.iframe || !this.document )
1523
+    return [ null, null ];
1524
+
1525
+  var node;
1526
+  if ( window.getSelection ) { // browsers such as Firefox
1527
+    var selection = this.iframe.contentWindow.getSelection();
1528
+    if ( selection.rangeCount > 0 )
1529
+      var range = selection.getRangeAt( 0 );
1530
+    else
1531
+      var range = this.document.createRange();
1532
+    node = range.endContainer;
1533
+  } else if ( this.document.selection ) { // browsers such as IE
1534
+    var range = this.document.selection.createRange();
1535
+    node = range.parentElement();
1536
+  }
1537
+
1538
+  while ( node ) {
1539
+    var name = node.nodeName.toLowerCase();
1540
+    if ( name == "body" )
1541
+      break;
1542
+
1543
+    if ( node.hasAttribute && node.hasAttribute( "style" ) ) {
1544
+      if ( foreground == null ) {
1545
+        foreground = getStyle( node, "color" )
1546
+        if ( foreground == "transparent" )
1547
+          foreground = null;
1548
+      }
1549
+      if ( background == null ) {
1550
+        background = getStyle( node, "background-color" )
1551
+        if ( background == "transparent" )
1552
+          background = null;
1553
+      }
1554
+    } else if ( name == "font" && node.getAttribute( "color" ) ) {
1555
+      foreground = node.getAttribute( "color" );
1556
+    }
1557
+
1558
+    if ( foreground && background )
1559
+      break;
1560
+
1561
+    node = node.parentNode;
1562
+  }
1563
+
1564
+  return [
1565
+    foreground ? Color.fromString( foreground ).toHexString() : null,
1566
+    background ? Color.fromString( background ).toHexString() : null
1567
+  ];
1568
+}
1569
+
1570
+Editor.prototype.set_foreground_color = function( color_code ) {
1571
+  if ( GECKO ) this.exec_command( "styleWithCSS", true );
1572
+  this.exec_command( "forecolor", Color.fromString( color_code ).toHexString() );
1573
+  if ( GECKO ) this.exec_command( "styleWithCSS", false );
1574
+}
1575
+
1576
+Editor.prototype.set_background_color = function( color_code ) {
1577
+  if ( GECKO ) this.exec_command( "styleWithCSS", true );
1578
+  if ( MSIE )
1579
+    this.exec_command( "backcolor", Color.fromString( color_code ).toHexString() );
1580
+  else
1581
+    this.exec_command( "hilitecolor", Color.fromString( color_code ).toHexString() );
1582
+  if ( GECKO ) this.exec_command( "styleWithCSS", false );
1583
+}
1584
+
1489 1585
 Editor.prototype.shutdown = function( event ) {
1490 1586
   signal( this, "title_changed", this, this.title, null );
1491 1587
   this.closed = true;

+ 287
- 1
static/js/Wiki.js View File

@@ -344,6 +344,7 @@ Wiki.prototype.populate = function ( startup_notes, current_notes, note_read_wri
344 344
     connect( "italic", "onclick", function ( event ) { self.toggle_button( event, "italic" ); } );
345 345
     connect( "underline", "onclick", function ( event ) { self.toggle_button( event, "underline" ); } );
346 346
     connect( "strikethrough", "onclick", function ( event ) { self.toggle_button( event, "strikethrough" ); } );
347
+    connect( "color", "onclick", this, "toggle_color_button" );
347 348
     connect( "font", "onclick", this, "toggle_font_button" );
348 349
     connect( "title", "onclick", function ( event ) { self.toggle_button( event, "title" ); } );
349 350
     connect( "insertUnorderedList", "onclick", function ( event ) { self.toggle_button( event, "insertUnorderedList" ); } );
@@ -357,6 +358,7 @@ Wiki.prototype.populate = function ( startup_notes, current_notes, note_read_wri
357 358
     this.make_image_button( "italic" );
358 359
     this.make_image_button( "underline" );
359 360
     this.make_image_button( "strikethrough" );
361
+    this.make_image_button( "color" );
360 362
     this.make_image_button( "font" );
361 363
     this.make_image_button( "title" );
362 364
     this.make_image_button( "insertUnorderedList" );
@@ -1498,7 +1500,8 @@ Wiki.prototype.update_toolbar = function() {
1498 1500
   this.update_button( "italic", "i", node_names );
1499 1501
   this.update_button( "underline", "u", node_names );
1500 1502
   this.update_button( "strikethrough", "strike", node_names );
1501
-  this.update_button( "font", "font", node_names );
1503
+  this.update_button( "color", "color", node_names );
1504
+  this.update_button( "font", "fontface", node_names );
1502 1505
   this.update_button( "title", "h3", node_names );
1503 1506
   this.update_button( "insertUnorderedList", "ul", node_names );
1504 1507
   this.update_button( "insertOrderedList", "ol", node_names );
@@ -1574,6 +1577,30 @@ Wiki.prototype.toggle_attach_button = function ( event ) {
1574 1577
   event.stop();
1575 1578
 }
1576 1579
 
1580
+Wiki.prototype.toggle_color_button = function ( event ) {
1581
+  if ( this.focused_editor && this.focused_editor.read_write ) {
1582
+    this.focused_editor.focus();
1583
+
1584
+    // if a pulldown is already open, then just close it
1585
+    var existing_div = getElement( "color_pulldown" );
1586
+
1587
+    if ( existing_div ) {
1588
+      this.up_image_button( "color" );
1589
+      existing_div.pulldown.shutdown();
1590
+      existing_div.pulldown = null;
1591
+      return;
1592
+    }
1593
+
1594
+    this.down_image_button( "color" );
1595
+    this.clear_messages();
1596
+    this.clear_pulldowns();
1597
+
1598
+    new Color_pulldown( this, this.notebook.object_id, this.invoker, event.target(), this.focused_editor );
1599
+  }
1600
+
1601
+  event.stop();
1602
+}
1603
+
1577 1604
 Wiki.prototype.toggle_font_button = function ( event ) {
1578 1605
   if ( this.focused_editor && this.focused_editor.read_write ) {
1579 1606
     this.focused_editor.focus();
@@ -4463,6 +4490,265 @@ Suggest_pulldown.prototype.shutdown = function () {
4463 4490
 }
4464 4491
 
4465 4492
 
4493
+NAMED_COLORS = [
4494
+  [ "#000000", "black" ],
4495
+  [ "#333333", "steel gray" ],
4496
+  [ "#696969", "dim gray" ],
4497
+  [ "#808080", "gray" ],
4498
+  [ "#a9a9a9", "dark gray" ],
4499
+  [ "#d3d3d3", "light gray" ],
4500
+  [ "#f5f5f5", "white smoke" ],
4501
+  [ "#ffffff", "white" ],
4502
+
4503
+  [ "#800000", "maroon" ],
4504
+  [ "#8b0000", "dark red" ],
4505
+  [ "#b22222", "fire brick" ],
4506
+  [ "#dc143c", "crimson" ],
4507
+  [ "#ff0000", "red" ],
4508
+  [ "#ff4500", "orange red" ],
4509
+  [ "#ff6347", "tomato" ],
4510
+  [ "#ffa07a", "light salmon" ],
4511
+
4512
+  [ "#8b4513", "saddle brown" ],
4513
+  [ "#a52a2a", "brown" ],
4514
+  [ "#a0522d", "sienna" ],
4515
+  [ "#d2691e", "chocolate" ],
4516
+  [ "#ff8c00", "dark orange" ],
4517
+  [ "#ffa500", "orange" ],
4518
+  [ "#ffd700", "gold" ],
4519
+  [ "#ffff00", "yellow" ],
4520
+
4521
+  [ "#556b2f", "dark olive green" ],
4522
+  [ "#006400", "dark green" ],
4523
+  [ "#008000", "green" ],
4524
+  [ "#2e8b57", "sea green" ],
4525
+  [ "#32cd32", "lime green" ],
4526
+  [ "#00ff00", "lime" ],
4527
+  [ "#7cfc00", "lawn green" ],
4528
+  [ "#98fb98", "pale green" ],
4529
+
4530
+  [ "#008b8b", "dark cyan" ],
4531
+  [ "#20b2aa", "light sea green" ],
4532
+  [ "#00ced1", "dark turquoise" ],
4533
+  [ "#66cdaa", "medium aquamarine" ],
4534
+  [ "#40e0d0", "turquoise" ],
4535
+  [ "#00ffff", "cyan" ],
4536
+  [ "#7fffd4", "aquamarine" ],
4537
+  [ "#afeeee", "pale turquoise" ],
4538
+
4539
+  [ "#191970", "midnight blue" ],
4540
+  [ "#000080", "navy" ],
4541
+  [ "#0000ff", "blue" ],
4542
+  [ "#4169e1", "royal blue" ],
4543
+  [ "#4682b4", "steel blue" ],
4544
+  [ "#6495ed", "cornflower blue" ],
4545
+  [ "#87ceeb", "sky blue" ],
4546
+  [ "#add8e6", "light blue" ],
4547
+
4548
+  [ "#4b0082", "indigo" ],
4549
+  [ "#800080", "purple" ],
4550
+  [ "#9400d3", "dark violet" ],
4551
+  [ "#8a2be2", "blue violet" ],
4552
+  [ "#ba55d3", "medium orchid" ],
4553
+  [ "#da70d6", "orchid" ],
4554
+  [ "#ee82ee", "violet" ],
4555
+  [ "#dda0dd", "plum" ],
4556
+
4557
+  [ "#c71585", "medium violet red" ],
4558
+  [ "#ff1493", "deep pink" ],
4559
+  [ "#db7093", "pale violet red" ],
4560
+  [ "#ff69b4", "hot pink" ],
4561
+  [ "#ffb6c1", "light pink" ],
4562
+  [ "#ffc0cb", "pink" ],
4563
+  [ "#ffdab9", "peach puff" ],
4564
+  [ "#ffe4e1", "misty rose" ],
4565
+]
4566
+
4567
+
4568
+function Color_pulldown( wiki, notebook_id, invoker, anchor, editor ) {
4569
+  anchor.pulldown = this;
4570
+  this.anchor = anchor;
4571
+  this.editor = editor;
4572
+  this.initial_selected_mark = null;
4573
+  this.selected_color_box = null;
4574
+
4575
+  Pulldown.call( this, wiki, notebook_id, "color_pulldown", anchor );
4576
+
4577
+  this.invoker = invoker;
4578
+
4579
+  var DEFAULT_FOREGROUND_CODE = "#000000";
4580
+  var DEFAULT_BACKGROUND_CODE = "#ffffff";
4581
+  var current_colors = editor.current_colors();
4582
+
4583
+  this.foreground_code = current_colors[ 0 ];
4584
+  if ( this.foreground_code == DEFAULT_FOREGROUND_CODE )
4585
+    this.foreground_code = null;
4586
+
4587
+  this.background_code = current_colors[ 1 ];
4588
+  if ( this.background_code == DEFAULT_BACKGROUND_CODE )
4589
+    this.background_code = null;
4590
+
4591
+  var foreground_attributes = { "type": "radio", "id": "foreground_color_radio", "name": "color_type", "value": "foreground" };
4592
+  var background_attributes = { "type": "radio", "id": "background_color_radio", "name": "color_type", "value": "background" };
4593
+
4594
+  if ( this.foreground_code || !this.background_code ) {
4595
+    foreground_attributes[ "checked" ] = true;
4596
+  } else {
4597
+    background_attributes[ "checked" ] = true;
4598
+  }
4599
+
4600
+  this.foreground_radio = createDOM( "input", foreground_attributes );
4601
+
4602
+  // using a button here instead of a <label> to make IE happy: when a <label> is used, clicking
4603
+  // on the label steals focus from the editor iframe and prevents the color from being changed
4604
+  this.foreground_label = createDOM( "input",
4605
+    { "type": "button", "class": "radio_label small_button", "value": "text", "title": "Set the current text color." }
4606
+  );
4607
+
4608
+  this.background_radio = createDOM( "input", background_attributes );
4609
+  this.background_label = createDOM( "input",
4610
+    { "type": "button", "class": "radio_label small_button", "value": "background", "title": "Set the current background color." }
4611
+  );
4612
+
4613
+  var radio_area = createDOM( "div", {},
4614
+    this.foreground_radio, this.foreground_label,
4615
+    " ",
4616
+    this.background_radio, this.background_label
4617
+  );
4618
+
4619
+  var tbody = createDOM( "tbody", {} );
4620
+  this.table = createDOM( "table" , { "id": "color_table" }, tbody );
4621
+  var color_index = 0;
4622
+
4623
+  for ( var i = 0; i < 8; ++i ) {
4624
+    var row_node = createDOM( "tr", {} );
4625
+
4626
+    for ( var j = 0; j < 8; ++j ) {
4627
+      if ( color_index >= NAMED_COLORS.length )
4628
+        break;
4629
+
4630
+      var color_pair = NAMED_COLORS[ color_index ];
4631
+      var color_code = color_pair[ 0 ];
4632
+      var color_name = color_pair[ 1 ];
4633
+
4634
+      var color_box = createDOM( "td", {},
4635
+        createDOM( "input", {
4636
+          "type": "button", "class": "color_box",
4637
+          "id": "color_" + color_code.substring( 1 ),
4638
+          "style": "background-color: " + color_code + ";", "title": color_name
4639
+        } )
4640
+      );
4641
+      appendChildNodes( row_node, color_box );
4642
+
4643
+      ++color_index;
4644
+    }
4645
+
4646
+    appendChildNodes( tbody, row_node );
4647
+  }
4648
+
4649
+  var div = createDOM( "div", {}, radio_area, this.table );
4650
+  appendChildNodes( this.div, div );
4651
+
4652
+  if ( this.foreground_code || !this.background_code ) {
4653
+    this.foreground_code = this.foreground_code || DEFAULT_FOREGROUND_CODE;
4654
+    this.background_code = this.background_code || DEFAULT_BACKGROUND_CODE;
4655
+    this.select_color( this.foreground_code, true );
4656
+  } else {
4657
+    this.foreground_code = this.foreground_code || DEFAULT_FOREGROUND_CODE;
4658
+    this.background_code = this.background_code || DEFAULT_BACKGROUND_CODE;
4659
+    this.select_color( this.background_code, true );
4660
+  }
4661
+
4662
+
4663
+  var self = this;
4664
+  connect( this.table, "onmousedown", function ( event ) { self.color_mouse_pressed( event ); } );
4665
+  connect( this.table, "onmouseup", function ( event ) { self.color_mouse_released( event ); } );
4666
+  connect( this.foreground_radio, "onclick", function ( event ) { self.foreground_radio_clicked( event ); } );
4667
+  connect( this.foreground_label, "onclick", function ( event ) { self.foreground_radio_clicked( event ); } );
4668
+  connect( this.background_radio, "onclick", function ( event ) { self.background_radio_clicked( event ); } );
4669
+  connect( this.background_label, "onclick", function ( event ) { self.background_radio_clicked( event ); } );
4670
+
4671
+  Pulldown.prototype.finish_init.call( this );
4672
+}
4673
+
4674
+Color_pulldown.prototype = new function () { this.prototype = Pulldown.prototype; };
4675
+Color_pulldown.prototype.constructor = Color_pulldown;
4676
+
4677
+Color_pulldown.prototype.color_mouse_pressed = function ( event ) {
4678
+  var color_box = event.target();
4679
+  if ( !hasElementClass( color_box, "color_box" ) )
4680
+    return;
4681
+
4682
+  this.select_color_box( color_box );
4683
+
4684
+  event.stop();
4685
+  this.editor.focus();
4686
+  this.editor.collapse_cursor();
4687
+}
4688
+
4689
+Color_pulldown.prototype.color_mouse_released = function ( event ) {
4690
+  var self = this;
4691
+  setTimeout( function () {
4692
+    self.shutdown();
4693
+  }, 100 );
4694
+
4695
+  event.stop();
4696
+}
4697
+
4698
+Color_pulldown.prototype.select_color = function ( color_code, skip_set ) {
4699
+  var color_box = getElement( "color_" + color_code.substring( 1 ) );
4700
+
4701
+  if ( color_box )
4702
+    this.select_color_box( color_box, color_code, skip_set );
4703
+}
4704
+
4705
+Color_pulldown.prototype.select_color_box = function ( color_box, color_code, skip_set ) {
4706
+  if ( this.selected_color_box ) {
4707
+    this.selected_color_box.value = "";
4708
+    removeElementClass( this.selected_color_box, "color_box_light_selected" );
4709
+    removeElementClass( this.selected_color_box, "color_box_dark_selected" );
4710
+  }
4711
+
4712
+  if ( color_code == undefined || color_code == null )
4713
+    color_code = getStyle( color_box, "background-color" );
4714
+
4715
+  var LIGHT_DARK_THRESHOLD = 0.45;
4716
+
4717
+  if ( Color.fromString( color_code ).asHSL().l >= LIGHT_DARK_THRESHOLD )
4718
+    addElementClass( color_box, "color_box_light_selected" );
4719
+  else
4720
+    addElementClass( color_box, "color_box_dark_selected" );
4721
+
4722
+  color_box.value = "x";
4723
+  this.selected_color_box = color_box;
4724
+
4725
+  if ( skip_set == false || skip_set == undefined || skip_set == null ) {
4726
+    if ( this.background_radio.checked )
4727
+      this.editor.set_background_color( color_code );
4728
+    else
4729
+      this.editor.set_foreground_color( color_code );
4730
+  }
4731
+}
4732
+
4733
+Color_pulldown.prototype.foreground_radio_clicked = function ( event ) {
4734
+  this.foreground_radio.checked = true;
4735
+  this.select_color( this.foreground_code, true );
4736
+}
4737
+
4738
+Color_pulldown.prototype.background_radio_clicked = function ( event ) {
4739
+  this.background_radio.checked = true;
4740
+  this.select_color( this.background_code, true );
4741
+}
4742
+
4743
+Color_pulldown.prototype.shutdown = function () {
4744
+  Pulldown.prototype.shutdown.call( this );
4745
+
4746
+  this.anchor.pulldown = null;
4747
+  disconnectAll( this.table );
4748
+  disconnectAll( this );
4749
+}
4750
+
4751
+
4466 4752
 function Font_pulldown( wiki, notebook_id, invoker, anchor, editor ) {
4467 4753
   anchor.pulldown = this;
4468 4754
   this.anchor = anchor;

+ 0
- 5
tools/tile_images.sh View File

@@ -2,8 +2,3 @@
2 2
 
3 3
 montage -tile x1 -geometry 40x40 -background none static/images/toolbar/*_button.xcf static/images/toolbar/buttons.png
4 4
 montage -tile x1 -geometry 20x20 -background none static/images/toolbar/small/*_button.xcf static/images/toolbar/small/buttons.png
5
-
6
-for theme_dir in static/images/toolbar/themes/* ; do
7
-  montage -tile x1 -geometry 40x40 -background none $theme_dir/*.xcf $theme_dir/buttons.png
8
-  montage -tile x1 -geometry 20x20 -background none $theme_dir/small/*.xcf $theme_dir/small/buttons.png
9
-done

+ 5
- 0
view/Toolbar.py View File

@@ -46,6 +46,11 @@ class Toolbar( Div ):
46 46
             id = u"strikethrough", title = u"strikethrough [ctrl-S]",
47 47
             class_ = "image_button strikethrough_large",
48 48
           ), class_ = u"button_background" ),
49
+          Div( Input(
50
+            type = u"button",
51
+            id = u"color", title = u"text color",
52
+            class_ = "image_button color_large",
53
+          ), class_ = u"button_background" ),
49 54
           Div( Input(
50 55
             type = u"button",
51 56
             id = u"font", title = u"font",