witten
/
luminotes
Archived
1
0
Fork 0

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

This commit is contained in:
Dan Helfman 2009-05-14 16:20:43 -07:00
parent 83e20c2c10
commit 4e21547da6
11 changed files with 486 additions and 16 deletions

1
NEWS
View File

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

View File

@ -19,6 +19,8 @@ class Html_cleaner(HTMLParser):
Cleans HTML of any tags not matching a whitelist.
"""
NOTE_LINK_URL_PATTERN = re.compile( '[^"]*/notebooks/\w+\?[^"]*note_id=\w+', re.IGNORECASE )
COLOR_RGB_PATTERN = re.compile( "^rgb(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*)$" )
COLOR_HEX_PATTERN = re.compile( "^#\d{6}$" )
def __init__( self, require_link_target = False ):
HTMLParser.__init__( self, AbstractFormatter( NullWriter() ) )
@ -110,6 +112,7 @@ class Html_cleaner(HTMLParser):
'caption',
'col',
'colgroup',
'span',
]
# A list of tags that require no closing tag.
@ -124,7 +127,8 @@ class Html_cleaner(HTMLParser):
'p': [ 'align' ],
'img': [ 'src', 'alt', 'border', 'title', "class" ],
'table': [ 'cellpadding', 'cellspacing', 'border', 'width', 'height' ],
'font': [ 'color', 'size', 'face' ],
'font': [ 'size', 'face', 'color' ],
'span': [ 'style' ],
'td': [ 'rowspan', 'colspan', 'width', 'height' ],
'th': [ 'rowspan', 'colspan', 'width', 'height' ],
}
@ -168,6 +172,12 @@ class Html_cleaner(HTMLParser):
else:
bt += ' %s=%s' % \
(xssescape(attribute), quoteattr(attrs[attribute]))
if attribute == 'style':
if self.style_is_acceptable( attrs[ attribute ] ):
bt += ' %s="%s"' % (attribute, attrs[attribute])
else:
bt += ' %s=%s' % \
(xssescape(attribute), quoteattr(attrs[attribute]))
if tag == "a" and \
( not attrs.get( 'href' ) or not self.NOTE_LINK_URL_PATTERN.search( attrs.get( 'href' ) ) ):
if self.require_link_target and not attrs.get( 'target' ):
@ -209,6 +219,29 @@ class Html_cleaner(HTMLParser):
return parsed[0] in self.allowed_schemes
def style_is_acceptable(self, style):
pieces = style.split( ";" )
for piece in pieces:
piece = piece.strip()
if piece == "":
continue
param_and_value = piece.split( ":" )
if len( param_and_value ) != 2:
return False
( param, value ) = param_and_value
value = value.strip()
if param.strip().lower() not in ( "color", "background-color" ):
return False
if not self.COLOR_RGB_PATTERN.search( value ) and \
not self.COLOR_HEX_PATTERN.search( value ):
return False
return True
def strip(self, rawstring):
"""Returns the argument stripped of potentially harmful HTML or JavaScript code"""
self.reset()

View File

@ -111,11 +111,11 @@ h1 {
}
#toolbar .bold_large {
background-position: 440px 0;
background-position: 480px 0;
}
#toolbar .bold_small {
background-position: 220px -2px;
background-position: 240px -2px;
}
#toolbar .italic_large {
@ -142,6 +142,14 @@ h1 {
background-position: 60px -2px;
}
#toolbar .color_large {
background-position: 400px 0;
}
#toolbar .color_small {
background-position: 200px -2px;
}
#toolbar .font_large {
background-position: 360px 0;
}
@ -159,11 +167,11 @@ h1 {
}
#toolbar .insertUnorderedList_large {
background-position: 400px 0;
background-position: 440px 0;
}
#toolbar .insertUnorderedList_small {
background-position: 200px -2px;
background-position: 220px -2px;
}
#toolbar .insertOrderedList_large {
@ -848,6 +856,42 @@ h1 {
text-decoration: none;
}
#color_table {
border-collapse: collapse;
border: 1px solid #000000;
margin-top: 1em;
}
#color_table td {
border: 1px solid #000000;
width: 1.5em;
height: 1.5em;
text-align: center;
vertical-align: middle;
cursor: pointer;
padding: 0;
margin: 0;
}
#color_table .color_box {
font-size: 110%;
outline: none;
font-weight: bold;
border: none;
width: 1.5em;
height: 1.5em;
padding: 0;
margin: 0;
}
.color_box_dark_selected {
color: #ffffff;
}
.color_box_light_selected {
color: #000000;
}
.selected_mark {
vertical-align: top;
}
@ -1154,6 +1198,12 @@ h1 {
.radio_label {
color: #000000;
border: none;
outline: none;
background-color: transparent;
padding: 0;
-moz-user-select: none;
-webkit-user-select: none;
}
.radio_label:hover {
@ -1161,6 +1211,10 @@ h1 {
cursor: pointer;
}
.small_button {
font-size: 100%;
}
.hook_action_area {
-moz-border-radius: 5px;
-webkit-border-radius: 5px;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

View File

@ -485,6 +485,17 @@ Editor.prototype.position_cursor_after = function ( node ) {
}
}
Editor.prototype.collapse_cursor = function () {
if ( this.iframe.contentWindow && this.iframe.contentWindow.getSelection ) { // browsers such as Firefox
var selection = this.iframe.contentWindow.getSelection();
selection.collapseToEnd();
} else if ( this.document.selection ) { // browsers such as IE
var range = this.document.selection.createRange();
range.collapse( false );
range.select();
}
}
Editor.prototype.connect_handlers = function () {
if ( this.document && this.document.body ) {
// 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 () {
}
// browsers such as Firefox, but not Opera
if ( !OPERA && this.iframe && this.iframe.contentDocument && this.edit_enabled ) {
if ( GECKO && this.iframe && this.iframe.contentDocument && this.edit_enabled ) {
this.exec_command( "styleWithCSS", false );
this.exec_command( "insertbronreturn", true );
}
@ -918,7 +929,9 @@ Editor.prototype.key_released = function ( event ) {
// if ctrl keys are released, bail
var code = event.key().code;
var CTRL = 17;
if ( event.modifier().ctrl || code == CTRL )
var NONE = 0;
if ( event.modifier().ctrl || code == CTRL || code == NONE )
return;
this.cleanup_html( code );
@ -1432,7 +1445,7 @@ Editor.prototype.contents = function () {
// return true if the given state_name is currently enabled, optionally using a given list of node
// names
Editor.prototype.state_enabled = function ( state_name, node_names ) {
Editor.prototype.state_enabled = function ( state_name, node_names, attribute_name ) {
if ( !this.edit_enabled )
return false;
@ -1474,8 +1487,23 @@ Editor.prototype.current_node_names = function () {
while ( node ) {
var name = node.nodeName.toLowerCase();
if ( name == "strong" ) name = "b";
if ( name == "em" ) name = "i";
if ( name == "body" )
break;
else if ( name == "strong" ) name = "b";
else if ( name == "em" ) name = "i";
else if ( name == "font" && node.getAttribute( "face" ) )
name = "fontface";
else if ( name == "font" && node.getAttribute( "size" ) )
name = "fontsize";
else if ( name == "font" && node.getAttribute( "color" ) )
name = "color";
else if ( node.hasAttribute && node.hasAttribute( "style" ) ) {
var color = getStyle( node, "color" );
var background_color = getStyle( node, "background-color" );
if ( ( color && color != "transparent" ) ||
( background_color && background_color != "transparent" ) )
name = "color";
}
if ( name != "a" || node.href )
node_names.push( name );
@ -1486,6 +1514,74 @@ Editor.prototype.current_node_names = function () {
return node_names;
}
// return the current effective foreground and background colors as hex code strings
Editor.prototype.current_colors = function () {
var foreground = null;
var background = null;
if ( !this.edit_enabled || !this.iframe || !this.document )
return [ null, null ];
var node;
if ( window.getSelection ) { // browsers such as Firefox
var selection = this.iframe.contentWindow.getSelection();
if ( selection.rangeCount > 0 )
var range = selection.getRangeAt( 0 );
else
var range = this.document.createRange();
node = range.endContainer;
} else if ( this.document.selection ) { // browsers such as IE
var range = this.document.selection.createRange();
node = range.parentElement();
}
while ( node ) {
var name = node.nodeName.toLowerCase();
if ( name == "body" )
break;
if ( node.hasAttribute && node.hasAttribute( "style" ) ) {
if ( foreground == null ) {
foreground = getStyle( node, "color" )
if ( foreground == "transparent" )
foreground = null;
}
if ( background == null ) {
background = getStyle( node, "background-color" )
if ( background == "transparent" )
background = null;
}
} else if ( name == "font" && node.getAttribute( "color" ) ) {
foreground = node.getAttribute( "color" );
}
if ( foreground && background )
break;
node = node.parentNode;
}
return [
foreground ? Color.fromString( foreground ).toHexString() : null,
background ? Color.fromString( background ).toHexString() : null
];
}
Editor.prototype.set_foreground_color = function( color_code ) {
if ( GECKO ) this.exec_command( "styleWithCSS", true );
this.exec_command( "forecolor", Color.fromString( color_code ).toHexString() );
if ( GECKO ) this.exec_command( "styleWithCSS", false );
}
Editor.prototype.set_background_color = function( color_code ) {
if ( GECKO ) this.exec_command( "styleWithCSS", true );
if ( MSIE )
this.exec_command( "backcolor", Color.fromString( color_code ).toHexString() );
else
this.exec_command( "hilitecolor", Color.fromString( color_code ).toHexString() );
if ( GECKO ) this.exec_command( "styleWithCSS", false );
}
Editor.prototype.shutdown = function( event ) {
signal( this, "title_changed", this, this.title, null );
this.closed = true;

View File

@ -344,6 +344,7 @@ Wiki.prototype.populate = function ( startup_notes, current_notes, note_read_wri
connect( "italic", "onclick", function ( event ) { self.toggle_button( event, "italic" ); } );
connect( "underline", "onclick", function ( event ) { self.toggle_button( event, "underline" ); } );
connect( "strikethrough", "onclick", function ( event ) { self.toggle_button( event, "strikethrough" ); } );
connect( "color", "onclick", this, "toggle_color_button" );
connect( "font", "onclick", this, "toggle_font_button" );
connect( "title", "onclick", function ( event ) { self.toggle_button( event, "title" ); } );
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
this.make_image_button( "italic" );
this.make_image_button( "underline" );
this.make_image_button( "strikethrough" );
this.make_image_button( "color" );
this.make_image_button( "font" );
this.make_image_button( "title" );
this.make_image_button( "insertUnorderedList" );
@ -1498,7 +1500,8 @@ Wiki.prototype.update_toolbar = function() {
this.update_button( "italic", "i", node_names );
this.update_button( "underline", "u", node_names );
this.update_button( "strikethrough", "strike", node_names );
this.update_button( "font", "font", node_names );
this.update_button( "color", "color", node_names );
this.update_button( "font", "fontface", node_names );
this.update_button( "title", "h3", node_names );
this.update_button( "insertUnorderedList", "ul", node_names );
this.update_button( "insertOrderedList", "ol", node_names );
@ -1574,6 +1577,30 @@ Wiki.prototype.toggle_attach_button = function ( event ) {
event.stop();
}
Wiki.prototype.toggle_color_button = function ( event ) {
if ( this.focused_editor && this.focused_editor.read_write ) {
this.focused_editor.focus();
// if a pulldown is already open, then just close it
var existing_div = getElement( "color_pulldown" );
if ( existing_div ) {
this.up_image_button( "color" );
existing_div.pulldown.shutdown();
existing_div.pulldown = null;
return;
}
this.down_image_button( "color" );
this.clear_messages();
this.clear_pulldowns();
new Color_pulldown( this, this.notebook.object_id, this.invoker, event.target(), this.focused_editor );
}
event.stop();
}
Wiki.prototype.toggle_font_button = function ( event ) {
if ( this.focused_editor && this.focused_editor.read_write ) {
this.focused_editor.focus();
@ -4463,6 +4490,265 @@ Suggest_pulldown.prototype.shutdown = function () {
}
NAMED_COLORS = [
[ "#000000", "black" ],
[ "#333333", "steel gray" ],
[ "#696969", "dim gray" ],
[ "#808080", "gray" ],
[ "#a9a9a9", "dark gray" ],
[ "#d3d3d3", "light gray" ],
[ "#f5f5f5", "white smoke" ],
[ "#ffffff", "white" ],
[ "#800000", "maroon" ],
[ "#8b0000", "dark red" ],
[ "#b22222", "fire brick" ],
[ "#dc143c", "crimson" ],
[ "#ff0000", "red" ],
[ "#ff4500", "orange red" ],
[ "#ff6347", "tomato" ],
[ "#ffa07a", "light salmon" ],
[ "#8b4513", "saddle brown" ],
[ "#a52a2a", "brown" ],
[ "#a0522d", "sienna" ],
[ "#d2691e", "chocolate" ],
[ "#ff8c00", "dark orange" ],
[ "#ffa500", "orange" ],
[ "#ffd700", "gold" ],
[ "#ffff00", "yellow" ],
[ "#556b2f", "dark olive green" ],
[ "#006400", "dark green" ],
[ "#008000", "green" ],
[ "#2e8b57", "sea green" ],
[ "#32cd32", "lime green" ],
[ "#00ff00", "lime" ],
[ "#7cfc00", "lawn green" ],
[ "#98fb98", "pale green" ],
[ "#008b8b", "dark cyan" ],
[ "#20b2aa", "light sea green" ],
[ "#00ced1", "dark turquoise" ],
[ "#66cdaa", "medium aquamarine" ],
[ "#40e0d0", "turquoise" ],
[ "#00ffff", "cyan" ],
[ "#7fffd4", "aquamarine" ],
[ "#afeeee", "pale turquoise" ],
[ "#191970", "midnight blue" ],
[ "#000080", "navy" ],
[ "#0000ff", "blue" ],
[ "#4169e1", "royal blue" ],
[ "#4682b4", "steel blue" ],
[ "#6495ed", "cornflower blue" ],
[ "#87ceeb", "sky blue" ],
[ "#add8e6", "light blue" ],
[ "#4b0082", "indigo" ],
[ "#800080", "purple" ],
[ "#9400d3", "dark violet" ],
[ "#8a2be2", "blue violet" ],
[ "#ba55d3", "medium orchid" ],
[ "#da70d6", "orchid" ],
[ "#ee82ee", "violet" ],
[ "#dda0dd", "plum" ],
[ "#c71585", "medium violet red" ],
[ "#ff1493", "deep pink" ],
[ "#db7093", "pale violet red" ],
[ "#ff69b4", "hot pink" ],
[ "#ffb6c1", "light pink" ],
[ "#ffc0cb", "pink" ],
[ "#ffdab9", "peach puff" ],
[ "#ffe4e1", "misty rose" ],
]
function Color_pulldown( wiki, notebook_id, invoker, anchor, editor ) {
anchor.pulldown = this;
this.anchor = anchor;
this.editor = editor;
this.initial_selected_mark = null;
this.selected_color_box = null;
Pulldown.call( this, wiki, notebook_id, "color_pulldown", anchor );
this.invoker = invoker;
var DEFAULT_FOREGROUND_CODE = "#000000";
var DEFAULT_BACKGROUND_CODE = "#ffffff";
var current_colors = editor.current_colors();
this.foreground_code = current_colors[ 0 ];
if ( this.foreground_code == DEFAULT_FOREGROUND_CODE )
this.foreground_code = null;
this.background_code = current_colors[ 1 ];
if ( this.background_code == DEFAULT_BACKGROUND_CODE )
this.background_code = null;
var foreground_attributes = { "type": "radio", "id": "foreground_color_radio", "name": "color_type", "value": "foreground" };
var background_attributes = { "type": "radio", "id": "background_color_radio", "name": "color_type", "value": "background" };
if ( this.foreground_code || !this.background_code ) {
foreground_attributes[ "checked" ] = true;
} else {
background_attributes[ "checked" ] = true;
}
this.foreground_radio = createDOM( "input", foreground_attributes );
// using a button here instead of a <label> to make IE happy: when a <label> is used, clicking
// on the label steals focus from the editor iframe and prevents the color from being changed
this.foreground_label = createDOM( "input",
{ "type": "button", "class": "radio_label small_button", "value": "text", "title": "Set the current text color." }
);
this.background_radio = createDOM( "input", background_attributes );
this.background_label = createDOM( "input",
{ "type": "button", "class": "radio_label small_button", "value": "background", "title": "Set the current background color." }
);
var radio_area = createDOM( "div", {},
this.foreground_radio, this.foreground_label,
" ",
this.background_radio, this.background_label
);
var tbody = createDOM( "tbody", {} );
this.table = createDOM( "table" , { "id": "color_table" }, tbody );
var color_index = 0;
for ( var i = 0; i < 8; ++i ) {
var row_node = createDOM( "tr", {} );
for ( var j = 0; j < 8; ++j ) {
if ( color_index >= NAMED_COLORS.length )
break;
var color_pair = NAMED_COLORS[ color_index ];
var color_code = color_pair[ 0 ];
var color_name = color_pair[ 1 ];
var color_box = createDOM( "td", {},
createDOM( "input", {
"type": "button", "class": "color_box",
"id": "color_" + color_code.substring( 1 ),
"style": "background-color: " + color_code + ";", "title": color_name
} )
);
appendChildNodes( row_node, color_box );
++color_index;
}
appendChildNodes( tbody, row_node );
}
var div = createDOM( "div", {}, radio_area, this.table );
appendChildNodes( this.div, div );
if ( this.foreground_code || !this.background_code ) {
this.foreground_code = this.foreground_code || DEFAULT_FOREGROUND_CODE;
this.background_code = this.background_code || DEFAULT_BACKGROUND_CODE;
this.select_color( this.foreground_code, true );
} else {
this.foreground_code = this.foreground_code || DEFAULT_FOREGROUND_CODE;
this.background_code = this.background_code || DEFAULT_BACKGROUND_CODE;
this.select_color( this.background_code, true );
}
var self = this;
connect( this.table, "onmousedown", function ( event ) { self.color_mouse_pressed( event ); } );
connect( this.table, "onmouseup", function ( event ) { self.color_mouse_released( event ); } );
connect( this.foreground_radio, "onclick", function ( event ) { self.foreground_radio_clicked( event ); } );
connect( this.foreground_label, "onclick", function ( event ) { self.foreground_radio_clicked( event ); } );
connect( this.background_radio, "onclick", function ( event ) { self.background_radio_clicked( event ); } );
connect( this.background_label, "onclick", function ( event ) { self.background_radio_clicked( event ); } );
Pulldown.prototype.finish_init.call( this );
}
Color_pulldown.prototype = new function () { this.prototype = Pulldown.prototype; };
Color_pulldown.prototype.constructor = Color_pulldown;
Color_pulldown.prototype.color_mouse_pressed = function ( event ) {
var color_box = event.target();
if ( !hasElementClass( color_box, "color_box" ) )
return;
this.select_color_box( color_box );
event.stop();
this.editor.focus();
this.editor.collapse_cursor();
}
Color_pulldown.prototype.color_mouse_released = function ( event ) {
var self = this;
setTimeout( function () {
self.shutdown();
}, 100 );
event.stop();
}
Color_pulldown.prototype.select_color = function ( color_code, skip_set ) {
var color_box = getElement( "color_" + color_code.substring( 1 ) );
if ( color_box )
this.select_color_box( color_box, color_code, skip_set );
}
Color_pulldown.prototype.select_color_box = function ( color_box, color_code, skip_set ) {
if ( this.selected_color_box ) {
this.selected_color_box.value = "";
removeElementClass( this.selected_color_box, "color_box_light_selected" );
removeElementClass( this.selected_color_box, "color_box_dark_selected" );
}
if ( color_code == undefined || color_code == null )
color_code = getStyle( color_box, "background-color" );
var LIGHT_DARK_THRESHOLD = 0.45;
if ( Color.fromString( color_code ).asHSL().l >= LIGHT_DARK_THRESHOLD )
addElementClass( color_box, "color_box_light_selected" );
else
addElementClass( color_box, "color_box_dark_selected" );
color_box.value = "x";
this.selected_color_box = color_box;
if ( skip_set == false || skip_set == undefined || skip_set == null ) {
if ( this.background_radio.checked )
this.editor.set_background_color( color_code );
else
this.editor.set_foreground_color( color_code );
}
}
Color_pulldown.prototype.foreground_radio_clicked = function ( event ) {
this.foreground_radio.checked = true;
this.select_color( this.foreground_code, true );
}
Color_pulldown.prototype.background_radio_clicked = function ( event ) {
this.background_radio.checked = true;
this.select_color( this.background_code, true );
}
Color_pulldown.prototype.shutdown = function () {
Pulldown.prototype.shutdown.call( this );
this.anchor.pulldown = null;
disconnectAll( this.table );
disconnectAll( this );
}
function Font_pulldown( wiki, notebook_id, invoker, anchor, editor ) {
anchor.pulldown = this;
this.anchor = anchor;

View File

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

View File

@ -46,6 +46,11 @@ class Toolbar( Div ):
id = u"strikethrough", title = u"strikethrough [ctrl-S]",
class_ = "image_button strikethrough_large",
), class_ = u"button_background" ),
Div( Input(
type = u"button",
id = u"color", title = u"text color",
class_ = "image_button color_large",
), class_ = u"button_background" ),
Div( Input(
type = u"button",
id = u"font", title = u"font",