You can now resize embedded images (small, medium, or large).
Fixed a bug that potentially caused link pulldowns to open in the wrong location when the page was scrolled past the top.
This commit is contained in:
parent
9c5d734fad
commit
41a85bb41f
5
NEWS
5
NEWS
|
@ -1,3 +1,8 @@
|
|||
1.4.5: June 18, 2008:
|
||||
* You can now resize embedded images (small, medium, or large).
|
||||
* Fixed a bug that potentially caused link pulldowns to open in the wrong
|
||||
location when the page was scrolled past the top.
|
||||
|
||||
1.4.4: June 17, 2008:
|
||||
* Links to embedded images now show up within the note tree's list of links.
|
||||
* Links to files that have not yet been uploaded (or have been deleted) are
|
||||
|
|
|
@ -333,15 +333,18 @@ class Files( object ):
|
|||
@grab_user_id
|
||||
@validate(
|
||||
file_id = Valid_id(),
|
||||
user_id = Valid_id( none_okay = True ),
|
||||
max_size = Valid_int( min = 10, max = 1000, none_okay = True ),
|
||||
user_id = Valid_id( none_okay = True )
|
||||
)
|
||||
def thumbnail( self, file_id, user_id = None ):
|
||||
def thumbnail( self, file_id, max_size = None, user_id = None ):
|
||||
"""
|
||||
Return a thumbnail for a file that a user has previously uploaded. If a thumbnail cannot be
|
||||
generated for the given file, return a default thumbnail image.
|
||||
|
||||
@type file_id: unicode
|
||||
@param file_id: id of the file to return a thumbnail for
|
||||
@type max_size: int or NoneType
|
||||
@param max_size: maximum thumbnail width or height in pixels (optional, defaults to a small size)
|
||||
@type user_id: unicode or NoneType
|
||||
@param user_id: id of current logged-in user (if any)
|
||||
@rtype: generator
|
||||
|
@ -360,14 +363,17 @@ class Files( object ):
|
|||
|
||||
cherrypy.response.headerMap[ u"Content-Type" ] = u"image/png"
|
||||
|
||||
DEFAULT_MAX_THUMBNAIL_SIZE = 125
|
||||
if not max_size:
|
||||
max_size = DEFAULT_MAX_THUMBNAIL_SIZE
|
||||
|
||||
# attempt to open the file as an image
|
||||
image_buffer = None
|
||||
try:
|
||||
image = Upload_file.open_image( file_id )
|
||||
|
||||
# scale the image down into a thumbnail
|
||||
THUMBNAIL_MAX_SIZE = ( 125, 125 ) # in pixels
|
||||
image.thumbnail( THUMBNAIL_MAX_SIZE, Image.ANTIALIAS )
|
||||
image.thumbnail( ( max_size, max_size ), Image.ANTIALIAS )
|
||||
|
||||
# save the image into a memory buffer
|
||||
image_buffer = StringIO()
|
||||
|
|
|
@ -566,6 +566,82 @@ class Test_files( Test_controller ):
|
|||
assert image
|
||||
assert image.size == ( 125, 50 )
|
||||
|
||||
def test_thumbnail_with_max_size( self ):
|
||||
self.login()
|
||||
|
||||
# make the test image big enough to require scaling down
|
||||
image = Image.open( StringIO( self.IMAGE_DATA ) )
|
||||
image = image.transform( ( 250, 250 ), Image.QUAD, range( 8 ) )
|
||||
|
||||
image_data = StringIO()
|
||||
image.save( image_data, "PNG" )
|
||||
|
||||
self.http_upload(
|
||||
"/files/upload?file_id=%s" % self.file_id,
|
||||
dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
note_id = self.note.object_id,
|
||||
),
|
||||
filename = self.filename,
|
||||
file_data = image_data.getvalue(),
|
||||
content_type = self.content_type,
|
||||
session_id = self.session_id,
|
||||
)
|
||||
|
||||
max_size = 225
|
||||
result = self.http_get(
|
||||
"/files/thumbnail?file_id=%s&max_size=%s" % ( self.file_id, max_size ),
|
||||
session_id = self.session_id,
|
||||
)
|
||||
|
||||
headers = result[ u"headers" ]
|
||||
assert headers
|
||||
assert headers[ u"Content-Type" ] == self.content_type
|
||||
assert u"Content-Disposition" not in headers
|
||||
|
||||
file_data = "".join( result[ u"body" ] )
|
||||
image = Image.open( StringIO( file_data ) )
|
||||
assert image
|
||||
assert image.size == ( max_size, max_size )
|
||||
|
||||
def test_thumbnail_with_max_size_without_scaling( self ):
|
||||
self.login()
|
||||
|
||||
# make the test image big enough to require scaling down
|
||||
image = Image.open( StringIO( self.IMAGE_DATA ) )
|
||||
image = image.transform( ( 250, 250 ), Image.QUAD, range( 8 ) )
|
||||
|
||||
image_data = StringIO()
|
||||
image.save( image_data, "PNG" )
|
||||
|
||||
self.http_upload(
|
||||
"/files/upload?file_id=%s" % self.file_id,
|
||||
dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
note_id = self.note.object_id,
|
||||
),
|
||||
filename = self.filename,
|
||||
file_data = image_data.getvalue(),
|
||||
content_type = self.content_type,
|
||||
session_id = self.session_id,
|
||||
)
|
||||
|
||||
max_size = 300
|
||||
result = self.http_get(
|
||||
"/files/thumbnail?file_id=%s&max_size=%s" % ( self.file_id, max_size ),
|
||||
session_id = self.session_id,
|
||||
)
|
||||
|
||||
headers = result[ u"headers" ]
|
||||
assert headers
|
||||
assert headers[ u"Content-Type" ] == self.content_type
|
||||
assert u"Content-Disposition" not in headers
|
||||
|
||||
file_data = "".join( result[ u"body" ] )
|
||||
image = Image.open( StringIO( file_data ) )
|
||||
assert image
|
||||
assert image.size == ( 250, 250 )
|
||||
|
||||
def test_thumbnail_with_non_image( self ):
|
||||
self.login()
|
||||
|
||||
|
@ -620,6 +696,28 @@ class Test_files( Test_controller ):
|
|||
assert headers
|
||||
assert headers.get( "Location" ) == u"http:///login?after_login=%s" % urllib.quote( path )
|
||||
|
||||
def test_thumbnail_with_invalid_max_size( self ):
|
||||
self.login()
|
||||
|
||||
self.http_upload(
|
||||
"/files/upload?file_id=%s" % self.file_id,
|
||||
dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
note_id = self.note.object_id,
|
||||
),
|
||||
filename = self.filename,
|
||||
file_data = self.file_data, # not a valid image
|
||||
content_type = self.content_type,
|
||||
session_id = self.session_id,
|
||||
)
|
||||
|
||||
result = self.http_get(
|
||||
"/files/thumbnail?file_id=%s&max_size=0" % self.file_id,
|
||||
session_id = self.session_id,
|
||||
)
|
||||
|
||||
assert u"max size" in result[ u"body" ][ 0 ]
|
||||
|
||||
def test_thumbnail_without_access( self ):
|
||||
self.login()
|
||||
|
||||
|
|
|
@ -2524,10 +2524,8 @@ function calculate_position( node, anchor, relative_to ) {
|
|||
position.x += relative_pos.x;
|
||||
position.y += relative_pos.y;
|
||||
|
||||
// Work around an IE "feature" in which an element within an iframe changes its absolute
|
||||
// position based on how far the page is scrolled.
|
||||
if ( /MSIE/.test( navigator.userAgent ) )
|
||||
position.y -= getElement( "html" ).scrollTop;
|
||||
// adjust the vertical position based on how far the page has scrolled
|
||||
position.y -= getElement( "html" ).scrollTop;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2983,6 +2981,12 @@ Upload_pulldown.prototype.shutdown = function () {
|
|||
this.link.pulldown = null;
|
||||
}
|
||||
|
||||
|
||||
SMALL_MAX_IMAGE_SIZE = 125;
|
||||
MEDIUM_MAX_IMAGE_SIZE = 300;
|
||||
LARGE_MAX_IMAGE_SIZE = 500;
|
||||
|
||||
|
||||
function File_link_pulldown( wiki, notebook_id, invoker, editor, link, ephemeral ) {
|
||||
link.pulldown = this;
|
||||
this.link = link;
|
||||
|
@ -3028,6 +3032,9 @@ function File_link_pulldown( wiki, notebook_id, invoker, editor, link, ephemeral
|
|||
// if the link is an image thumbnail link, update the contents of the file link pulldown accordingly
|
||||
var image = getFirstElementByTagAndClassName( "img", null, this.link );
|
||||
var embed_attributes = { "type": "checkbox", "class": "pulldown_checkbox", "id": "embed_checkbox" };
|
||||
var small_size_attributes = { "type": "radio", "id": "small_size_radio", "name": "size", "value": "small" };
|
||||
var medium_size_attributes = { "type": "radio", "id": "medium_size_radio", "name": "size", "value": "medium" };
|
||||
var large_size_attributes = { "type": "radio", "id": "large_size_radio", "name": "size", "value": "large" };
|
||||
var left_justify_attributes = { "type": "radio", "id": "left_justify_radio", "name": "justify", "value": "left" };
|
||||
var center_justify_attributes = { "type": "radio", "id": "center_justify_radio", "name": "justify", "value": "center" };
|
||||
var right_justify_attributes = { "type": "radio", "id": "right_justify_radio", "name": "justify", "value": "right" };
|
||||
|
@ -3036,17 +3043,30 @@ function File_link_pulldown( wiki, notebook_id, invoker, editor, link, ephemeral
|
|||
addElementClass( this.thumbnail_span, "undisplayed" );
|
||||
embed_attributes[ "checked" ] = "true";
|
||||
|
||||
if ( hasElementClass( image, "left_justified" ) )
|
||||
left_justify_attributes[ "checked" ] = "true";
|
||||
else if ( hasElementClass( image, "center_justified" ) )
|
||||
var src = parseQueryString( image.src.split( "?" ).pop() );
|
||||
var max_size = src[ "max_size" ];
|
||||
if ( max_size == LARGE_MAX_IMAGE_SIZE )
|
||||
large_size_attributes[ "checked" ] = "true";
|
||||
else if ( max_size == MEDIUM_MAX_IMAGE_SIZE )
|
||||
medium_size_attributes[ "checked" ] = "true";
|
||||
else
|
||||
small_size_attributes[ "checked" ] = "true";
|
||||
|
||||
if ( hasElementClass( image, "center_justified" ) )
|
||||
center_justify_attributes[ "checked" ] = "true";
|
||||
else if ( hasElementClass( image, "right_justified" ) )
|
||||
right_justify_attributes[ "checked" ] = "true";
|
||||
else
|
||||
left_justify_attributes[ "checked" ] = "true";
|
||||
} else {
|
||||
small_size_attributes[ "checked" ] = "true";
|
||||
left_justify_attributes[ "checked" ] = "true";
|
||||
}
|
||||
|
||||
this.embed_checkbox = createDOM( "input", embed_attributes );
|
||||
this.small_size_radio = createDOM( "input", small_size_attributes );
|
||||
this.medium_size_radio = createDOM( "input", medium_size_attributes );
|
||||
this.large_size_radio = createDOM( "input", large_size_attributes );
|
||||
this.left_justify_radio = createDOM( "input", left_justify_attributes );
|
||||
this.center_justify_radio = createDOM( "input", center_justify_attributes );
|
||||
this.right_justify_radio = createDOM( "input", right_justify_attributes );
|
||||
|
@ -3055,9 +3075,22 @@ function File_link_pulldown( wiki, notebook_id, invoker, editor, link, ephemeral
|
|||
"show image within note"
|
||||
);
|
||||
|
||||
var small_size_label = createDOM( "label",
|
||||
{ "for": "small_size_radio", "class": "radio_label", "title": "Display a small thumbnail of this image." },
|
||||
"small"
|
||||
);
|
||||
var medium_size_label = createDOM( "label",
|
||||
{ "for": "medium_size_radio", "class": "radio_label", "title": "Display a medium thumbnail of this image." },
|
||||
"medium"
|
||||
);
|
||||
var large_size_label = createDOM( "label",
|
||||
{ "for": "large_size_radio", "class": "radio_label", "title": "Display a large thumbnail of this image." },
|
||||
"large"
|
||||
);
|
||||
|
||||
var left_justify_label = createDOM( "label",
|
||||
{ "for": "left_justify_radio", "class": "radio_label", "title": "Left justify this image within the note." },
|
||||
"left justify"
|
||||
"left"
|
||||
);
|
||||
var center_justify_label = createDOM( "label",
|
||||
{ "for": "center_justify_radio", "class": "radio_label", "title": "Center this image horizontally within the note." },
|
||||
|
@ -3065,13 +3098,20 @@ function File_link_pulldown( wiki, notebook_id, invoker, editor, link, ephemeral
|
|||
);
|
||||
var right_justify_label = createDOM( "label",
|
||||
{ "for": "right_justify_radio", "class": "radio_label", "title": "Right justify this image within the note." },
|
||||
"right justify"
|
||||
"right"
|
||||
);
|
||||
|
||||
this.image_justify_area = createDOM( "div", { "class": "undisplayed" },
|
||||
createDOM( "table" , { "id": "justify_table" },
|
||||
this.image_settings_area = createDOM( "div", { "class": "undisplayed" },
|
||||
createDOM( "table" , { "id": "image_settings_table" },
|
||||
createDOM( "tbody", {},
|
||||
createDOM( "tr", {},
|
||||
createDOM( "td", { "class": "field_label" }, "size: " ),
|
||||
createDOM( "td", {}, this.small_size_radio, small_size_label ),
|
||||
createDOM( "td", {}, this.medium_size_radio, medium_size_label ),
|
||||
createDOM( "td", {}, this.large_size_radio, large_size_label )
|
||||
),
|
||||
createDOM( "tr", {},
|
||||
createDOM( "td", { "class": "field_label" }, "position: " ),
|
||||
createDOM( "td", {}, this.left_justify_radio, left_justify_label ),
|
||||
createDOM( "td", {}, this.center_justify_radio, center_justify_label ),
|
||||
createDOM( "td", {}, this.right_justify_radio, right_justify_label )
|
||||
|
@ -3081,7 +3121,7 @@ function File_link_pulldown( wiki, notebook_id, invoker, editor, link, ephemeral
|
|||
);
|
||||
|
||||
if ( image )
|
||||
removeElementClass( this.image_justify_area, "undisplayed" );
|
||||
removeElementClass( this.image_settings_area, "undisplayed" );
|
||||
|
||||
appendChildNodes( this.div, createDOM( "span", { "class": "field_label" }, "filename: " ) );
|
||||
appendChildNodes( this.div, this.filename_field );
|
||||
|
@ -3089,7 +3129,7 @@ function File_link_pulldown( wiki, notebook_id, invoker, editor, link, ephemeral
|
|||
appendChildNodes( this.div, " " );
|
||||
appendChildNodes( this.div, this.delete_button );
|
||||
appendChildNodes( this.div, createDOM( "div", {}, this.embed_checkbox, embed_label ) );
|
||||
appendChildNodes( this.div, this.image_justify_area );
|
||||
appendChildNodes( this.div, this.image_settings_area );
|
||||
|
||||
// get the file's name and size from the server
|
||||
this.invoker.invoke(
|
||||
|
@ -3109,6 +3149,9 @@ function File_link_pulldown( wiki, notebook_id, invoker, editor, link, ephemeral
|
|||
|
||||
connect( this.delete_button, "onclick", function ( event ) { self.delete_button_clicked( event ); } );
|
||||
connect( this.embed_checkbox, "onclick", function ( event ) { self.embed_clicked( event ); } );
|
||||
connect( this.small_size_radio, "onclick", function ( event ) { self.resize_image( event, "small" ); } );
|
||||
connect( this.medium_size_radio, "onclick", function ( event ) { self.resize_image( event, "medium" ); } );
|
||||
connect( this.large_size_radio, "onclick", function ( event ) { self.resize_image( event, "large" ); } );
|
||||
connect( this.left_justify_radio, "onclick", function ( event ) { self.justify_image( event, "left" ); } );
|
||||
connect( this.center_justify_radio, "onclick", function ( event ) { self.justify_image( event, "center" ); } );
|
||||
connect( this.right_justify_radio, "onclick", function ( event ) { self.justify_image( event, "right" ); } );
|
||||
|
@ -3180,17 +3223,18 @@ File_link_pulldown.prototype.delete_button_clicked = function ( event ) {
|
|||
|
||||
File_link_pulldown.prototype.embed_clicked = function ( event ) {
|
||||
if ( this.embed_checkbox.checked ) {
|
||||
var image = createDOM( "img", { "src": "/files/thumbnail?file_id=" + this.file_id, "class": "left_justified" } );
|
||||
var image = createDOM( "img", { "src": "/files/thumbnail?file_id=" + this.file_id + "&max_size=" + SMALL_MAX_IMAGE_SIZE, "class": "left_justified" } );
|
||||
var image_span = createDOM( "span", {}, image );
|
||||
this.link_title = link_title( this.link );
|
||||
this.link.innerHTML = image_span.innerHTML;
|
||||
addElementClass( this.thumbnail_span, "undisplayed" );
|
||||
removeElementClass( this.image_justify_area, "undisplayed" );
|
||||
removeElementClass( this.image_settings_area, "undisplayed" );
|
||||
} else {
|
||||
this.justify_image( "left" );
|
||||
this.left_justify_radio.checked = true;
|
||||
this.small_size_radio.checked = true;
|
||||
removeElementClass( this.thumbnail_span, "undisplayed" );
|
||||
addElementClass( this.image_justify_area, "undisplayed" );
|
||||
addElementClass( this.image_settings_area, "undisplayed" );
|
||||
this.link.innerHTML = this.link_title || this.filename_field.value || this.previous_filename;
|
||||
}
|
||||
|
||||
|
@ -3198,6 +3242,30 @@ File_link_pulldown.prototype.embed_clicked = function ( event ) {
|
|||
this.editor.resize();
|
||||
}
|
||||
|
||||
File_link_pulldown.prototype.resize_image = function ( event, position ) {
|
||||
var image = getFirstElementByTagAndClassName( "img", null, this.link );
|
||||
if ( !image )
|
||||
return;
|
||||
|
||||
if ( position == "large" ) {
|
||||
var max_size = LARGE_MAX_IMAGE_SIZE;
|
||||
} else if ( position == "medium" ) {
|
||||
var max_size = MEDIUM_MAX_IMAGE_SIZE;
|
||||
} else {
|
||||
var max_size = SMALL_MAX_IMAGE_SIZE;
|
||||
}
|
||||
|
||||
// when the newly resized image finishes loading, update the pulldown position and resize the
|
||||
// editor
|
||||
var self = this;
|
||||
connect( image, "onload", function () {
|
||||
self.update_position( self.link, self.editor.iframe );
|
||||
self.editor.resize();
|
||||
} );
|
||||
|
||||
image.setAttribute( "src", "/files/thumbnail?file_id=" + this.file_id + "&max_size=" + max_size );
|
||||
}
|
||||
|
||||
File_link_pulldown.prototype.justify_image = function ( event, position ) {
|
||||
var image = getFirstElementByTagAndClassName( "img", null, this.link );
|
||||
if ( !image )
|
||||
|
|
|
@ -23,7 +23,7 @@ class Toolbar( Div ):
|
|||
) ),
|
||||
Div( Input(
|
||||
type = u"image",
|
||||
id = u"attachFile", title = u"attach file",
|
||||
id = u"attachFile", title = u"attach file or image",
|
||||
src = u"/static/images/toolbar/attach_button.png",
|
||||
width = u"40", height = u"40",
|
||||
class_ = "image_button",
|
||||
|
|
Reference in New Issue