witten
/
luminotes
Archived
1
0
Fork 0

Can now create and rename notebooks. Still need to implement deletion and some other niceties.

This commit is contained in:
Dan Helfman 2007-11-15 01:30:45 +00:00
parent 0869ae996b
commit b22c784d39
9 changed files with 330 additions and 4 deletions

4
NEWS
View File

@ -1,2 +1,6 @@
1.0.1: November ??, 2007
* Ability to create, rename, delete, and switch between multiple wiki
notebooks in a single account.
1.0.0: November 12, 2007
* Initial release.

View File

@ -8,6 +8,7 @@ from Expire import strongly_expire
from Html_nuker import Html_nuker
from model.Notebook import Notebook
from model.Note import Note
from model.User import User
from view.Main_page import Main_page
from view.Json import Json
from view.Html_file import Html_file
@ -702,6 +703,85 @@ class Notebooks( object ):
notes = startup_notes + other_notes,
)
@expose( view = Json )
@grab_user_id
@validate(
user_id = Valid_id( none_okay = True ),
)
def create( self, user_id ):
"""
Create a new notebook and give it a default name.
@type user_id: unicode or NoneType
@param user_id: id of current logged-in user (if any)
@rtype dict
@return { "redirect": notebookurl }
@raise Access_error: the current user doesn't have access to create a notebook
@raise Validation_error: one of the arguments is invalid
"""
if user_id is None:
raise Access_error()
user = self.__database.load( User, user_id )
# create the notebook along with a trash
trash_id = self.__database.next_id( Notebook, commit = False )
trash = Notebook.create( trash_id, u"trash" )
self.__database.save( trash, commit = False )
notebook_id = self.__database.next_id( Notebook, commit = False )
notebook = Notebook.create( notebook_id, u"new notebook", trash_id )
self.__database.save( notebook, commit = False )
# record the fact that the user has access to their new notebook
self.__database.execute( user.sql_save_notebook( notebook_id, read_write = True ), commit = False )
self.__database.execute( user.sql_save_notebook( trash_id, read_write = True ), commit = False )
self.__database.commit()
return dict(
redirect = u"/notebooks/%s" % notebook_id,
)
@expose( view = Json )
@grab_user_id
@validate(
notebook_id = Valid_id(),
name = Valid_string( min = 1, max = 100 ),
user_id = Valid_id( none_okay = True ),
)
def rename( self, notebook_id, name, user_id ):
"""
Change the name of the given notebook.
@type notebook_id: unicode
@param notebook_id: id of notebook to rename
@type name: unicode
@param name: new name of the notebook
@type user_id: unicode or NoneType
@param user_id: id of current logged-in user (if any)
@rtype dict
@return {}
@raise Access_error: the current user doesn't have access to the given notebook
@raise Validation_error: one of the arguments is invalid
"""
if not self.__users.check_access( user_id, notebook_id, read_write = True ):
raise Access_error()
notebook = self.__database.load( Notebook, notebook_id )
if not notebook:
raise Access_error()
# prevent just anyone from making official Luminotes notebooks
if name.startswith( u"Luminotes" ) and not notebook.name.startswith( u"Luminotes" ):
raise Access_error( "That notebook name is not available. Please try a different one." )
notebook.name = name
self.__database.save( notebook, commit = False )
self.__database.commit()
return dict()
def load_recent_notes( self, notebook_id, start = 0, count = 10, user_id = None ):
"""
Provide the information necessary to display the page for a particular notebook's most recent

View File

@ -108,6 +108,7 @@ class Root( object ):
result = self.__users.current( user_id )
main_notebooks = [ nb for nb in result[ "notebooks" ] if nb.name == u"Luminotes" ]
result.update( self.__notebooks.contents( main_notebooks[ 0 ].object_id, user_id = user_id ) )
return result

View File

@ -1650,6 +1650,94 @@ class Test_notebooks( Test_controller ):
assert result.get( "error" )
def test_create( self ):
self.login()
result = self.http_post( "/notebooks/create", dict(), session_id = self.session_id )
assert result[ u"redirect" ].startswith( u"/notebooks/" )
new_notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ]
notebook = self.database.last_saved_obj
assert isinstance( notebook, Notebook )
assert notebook.object_id == new_notebook_id
assert notebook.name == u"new notebook"
assert notebook.read_write == True
assert notebook.trash_id
def test_contents_after_create( self ):
self.login()
result = self.http_post( "/notebooks/create", dict(), session_id = self.session_id )
new_notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ]
result = cherrypy.root.notebooks.contents(
notebook_id = new_notebook_id,
user_id = self.user.object_id,
)
notebook = result[ "notebook" ]
assert result[ "total_notes_count" ] == 0
assert result[ "startup_notes" ] == []
assert result[ "notes" ] == []
assert notebook.object_id == new_notebook_id
assert notebook.read_write == True
def test_create_without_login( self ):
result = self.http_post( "/notebooks/create", dict() )
assert result[ u"error" ]
def test_rename( self ):
self.login()
new_name = u"renamed notebook"
result = self.http_post( "/notebooks/rename", dict(
notebook_id = self.notebook.object_id,
name = new_name,
), session_id = self.session_id )
assert u"error" not in result
def test_contents_after_rename( self ):
self.login()
new_name = u"renamed notebook"
self.http_post( "/notebooks/rename", dict(
notebook_id = self.notebook.object_id,
name = new_name,
), session_id = self.session_id )
result = cherrypy.root.notebooks.contents(
notebook_id = self.notebook.object_id,
user_id = self.user.object_id,
)
notebook = result[ "notebook" ]
assert notebook.name == new_name
def test_rename_without_login( self ):
new_name = u"renamed notebook"
result = self.http_post( "/notebooks/rename", dict(
notebook_id = self.notebook.object_id,
name = new_name,
) )
assert result[ u"error" ]
def test_rename_with_reserved_name( self ):
self.login()
new_name = u"Luminotes blog"
result = self.http_post( "/notebooks/rename", dict(
notebook_id = self.notebook.object_id,
name = new_name,
), session_id = self.session_id )
assert result[ u"error" ]
def test_recent_notes( self ):
result = cherrypy.root.notebooks.load_recent_notes(
self.notebook.object_id,

View File

@ -64,7 +64,9 @@ class Test_root( Test_controller ):
def test_index( self ):
result = self.http_get( "/" )
assert result
assert result[ u"notebook" ].object_id == self.anon_notebook.object_id
def test_index_after_login( self ):
self.login()
@ -88,6 +90,7 @@ class Test_root( Test_controller ):
assert result
assert result.get( u"redirect" ) is None
assert result[ u"notebook" ].object_id == self.anon_notebook.object_id
def test_default( self ):
result = self.http_get(
@ -98,6 +101,7 @@ class Test_root( Test_controller ):
assert result[ u"notes" ]
assert len( result[ u"notes" ] ) == 1
assert result[ u"notes" ][ 0 ].object_id == self.anon_note.object_id
assert result[ u"notebook" ].object_id == self.anon_notebook.object_id
def test_default_with_unknown_note( self ):
result = self.http_get(
@ -134,6 +138,7 @@ class Test_root( Test_controller ):
assert result
assert u"error" not in result
assert result[ u"notebook" ].object_id == self.blog_notebook.object_id
def test_blog_with_note_id( self ):
result = self.http_get(
@ -142,6 +147,7 @@ class Test_root( Test_controller ):
assert result
assert u"error" not in result
assert result[ u"notebook" ].object_id == self.blog_notebook.object_id
def test_blog_rss( self ):
result = self.http_get(
@ -150,6 +156,7 @@ class Test_root( Test_controller ):
assert result
assert u"error" not in result
assert result[ u"notebook" ].object_id == self.blog_notebook.object_id
def test_guide( self ):
result = self.http_get(
@ -158,6 +165,7 @@ class Test_root( Test_controller ):
assert result
assert u"error" not in result
assert result[ u"notebook" ].object_id == self.guide_notebook.object_id
def test_privacy( self ):
result = self.http_get(
@ -166,6 +174,7 @@ class Test_root( Test_controller ):
assert result
assert u"error" not in result
assert result[ u"notebook" ].object_id == self.privacy_notebook.object_id
def test_next_id( self ):
result = self.http_get( "/next_id" )

View File

@ -266,6 +266,14 @@ ol li {
padding: 0.2em;
}
#notebook_header_name:hover {
color: #ff6600;
}
#rename_form {
margin: 0;
}
#notebook_border {
padding: 0 0 0 0.4em;
}

View File

@ -201,6 +201,30 @@ Wiki.prototype.populate = function ( startup_notes, current_notes, note_read_wri
self.save_editor( null, true );
} );
}
var add_notebook_link = getElement( "add_notebook_link" );
if ( add_notebook_link ) {
connect( add_notebook_link, "onclick", function ( event ) {
self.invoker.invoke( "/notebooks/create", "POST" );
event.stop();
} );
}
var rename_notebook_link = getElement( "rename_notebook_link" );
if ( rename_notebook_link ) {
connect( rename_notebook_link, "onclick", function ( event ) {
self.start_notebook_rename();
event.stop();
} );
}
var notebook_header_name = getElement( "notebook_header_name" );
if ( notebook_header_name ) {
connect( notebook_header_name, "onclick", function ( event ) {
self.start_notebook_rename();
event.stop();
} );
}
}
Wiki.prototype.background_clicked = function ( event ) {
@ -1295,6 +1319,101 @@ Wiki.prototype.create_all_notes_link = function ( note_id, note_title ) {
);
}
Wiki.prototype.start_notebook_rename = function () {
this.clear_messages();
this.clear_pulldowns();
// if a renaming is already in progress, end the renaming instead of starting one
var notebook_name_field = getElement( "notebook_name_field" );
if ( notebook_name_field ) {
this.end_notebook_rename();
return;
}
notebook_name_field = createDOM(
"input", {
"type": "text",
"value": this.notebook.name,
"id": "notebook_name_field",
"name": "notebook_name_field",
"size": "30",
"maxlength": "100",
"class": "text_field"
}
);
var ok_button = createDOM(
"input", {
"type": "button",
"class": "message_button",
"value": "ok",
"title": "dismiss this message"
}
);
var rename_form = createDOM(
"form", { "id": "rename_form" }, notebook_name_field, ok_button
);
replaceChildNodes( "notebook_header_area", rename_form );
var self = this;
connect( rename_form, "onsubmit", function ( event ) {
self.end_notebook_rename();
event.stop();
} );
connect( ok_button, "onclick", function ( event ) {
self.end_notebook_rename();
event.stop();
} );
notebook_name_field.focus();
notebook_name_field.select();
}
Wiki.prototype.end_notebook_rename = function () {
var new_notebook_name = getElement( "notebook_name_field" ).value;
// if the new name is blank or reserved, don't actually rename the notebook
if ( /^\s*$/.test( new_notebook_name ) )
new_notebook_name = this.notebook.name;
if ( /^\s*Luminotes/.test( new_notebook_name ) ) {
new_notebook_name = this.notebook.name;
this.display_error( "That notebook name is not available. Please try a different one." );
}
// rename the notebook in the header
var notebook_header_name = createDOM(
"span",
{ "id": "notebook_header_name" },
createDOM( "strong", {}, new_notebook_name )
);
replaceChildNodes( "notebook_header_area", notebook_header_name );
var self = this;
connect( notebook_header_name, "onclick", function ( event ) {
self.start_notebook_rename();
event.stop();
} );
// rename the notebook link on the right side of the page
replaceChildNodes(
"notebook_" + this.notebook.object_id,
document.createTextNode( new_notebook_name )
);
// if the name has changed, then send the new name to the server
if ( new_notebook_name == this.notebook.name )
return;
this.notebook.name = new_notebook_name;
this.invoker.invoke( "/notebooks/rename", "POST", {
"notebook_id": this.notebook_id,
"name": new_notebook_name
} );
}
Wiki.prototype.toggle_editor_changes = function ( event, editor ) {
// if the pulldown is already open, then just close it
var pulldown_id = "changes_" + editor.id;

View File

@ -13,7 +13,7 @@ class Link_area( Div ):
( parent_id is None ) and Div(
A(
u"all notes",
href = u"/notebooks/%s" % notebook.object_id,
href = u"#",
id = u"all_notes_link",
title = u"View a list of all notes in this notebook.",
),
@ -45,6 +45,16 @@ class Link_area( Div ):
) or None,
notebook.read_write and Span(
( notebook.name != u"trash" ) and Div(
A(
u"rename notebook",
href = u"#",
id = u"rename_notebook_link",
title = u"Change the name of this notebook.",
),
class_ = u"link_area_item",
) or None,
notebook.trash_id and Div(
A(
u"trash",
@ -89,6 +99,15 @@ class Link_area( Div ):
),
class_ = u"link_area_item",
) for nb in linked_notebooks ],
Div(
A(
u"add new notebook",
href = u"#",
id = u"add_notebook_link",
title = u"Create a new wiki notebook.",
),
class_ = u"link_area_item",
),
id = u"notebooks_area"
),

View File

@ -123,9 +123,7 @@ class Main_page( Page ):
),
Rounded_div(
( notebook.name == u"trash" ) and u"trash_notebook" or u"current_notebook",
( len( notes ) > 0 ) and \
A( Strong( notebook.name ), href = notebook_path ) \
or Strong( notebook.name ),
( notebook.name == u"trash" ) and Strong( u"trash" ) or Span( Strong( notebook.name ), id = u"notebook_header_name" ),
parent_id and Span(
u" | ",
A( u"empty trash", href = u"/notebooks/%s" % notebook.object_id, id = u"empty_trash_link" ),