Completely revamped the way the main page and the notes on it are loaded by
the client. Previously, the main page would load as mostly blank, then the client would immediately issue two async json calls to load the user and notebook data, including startup notes. Now, the main page loads with the note data actually as part of the page. If JavaScript is off, then you see all the notes displayed, including startup notes and any designated note. If JavaScript is on, then those "static" notes are instantly hidden and their contents are loaded into iframes for editing/display. The real upshot is that Luminotes in read-only mode is now more useful when JavaScript is off, and actually displays notes and their contents. This is very useful for search engine indexing. Updated all Python unit tests. Still have to get to JavaScript unit tests, what few their are.
This commit is contained in:
parent
3e0dbe0509
commit
613ee8a217
|
@ -46,13 +46,15 @@ class Notebooks( object ):
|
|||
self.__users = users
|
||||
|
||||
@expose( view = Main_page )
|
||||
@grab_user_id
|
||||
@validate(
|
||||
notebook_id = Valid_id(),
|
||||
note_id = Valid_id(),
|
||||
parent_id = Valid_id(),
|
||||
revision = Valid_revision(),
|
||||
user_id = Valid_id( none_okay = True ),
|
||||
)
|
||||
def default( self, notebook_id, note_id = None, parent_id = None, revision = None ):
|
||||
def default( self, notebook_id, note_id = None, parent_id = None, revision = None, user_id = None ):
|
||||
"""
|
||||
Provide the information necessary to display the page for a particular notebook. If a
|
||||
particular note id is given without a revision, then the most recent version of that note is
|
||||
|
@ -69,26 +71,18 @@ class Notebooks( object ):
|
|||
@rtype: unicode
|
||||
@return: rendered HTML page
|
||||
"""
|
||||
return dict(
|
||||
notebook_id = notebook_id,
|
||||
note_id = note_id,
|
||||
parent_id = parent_id,
|
||||
revision = revision,
|
||||
)
|
||||
result = self.__users.current( user_id )
|
||||
result.update( self.contents( notebook_id, note_id, revision, user_id ) )
|
||||
result[ "parent_id" ] = parent_id
|
||||
if revision:
|
||||
result[ "note_read_write" ] = False
|
||||
|
||||
return result
|
||||
|
||||
@expose( view = Json )
|
||||
@strongly_expire
|
||||
@grab_user_id
|
||||
@validate(
|
||||
notebook_id = Valid_id(),
|
||||
note_id = Valid_id( none_okay = True ),
|
||||
revision = Valid_revision( none_okay = True ),
|
||||
user_id = Valid_id( none_okay = True ),
|
||||
)
|
||||
def contents( self, notebook_id, note_id = None, revision = None, user_id = None ):
|
||||
"""
|
||||
Return the information on particular notebook, including the contents of its startup notes.
|
||||
Optionally include the contents of a single requested note as well.
|
||||
Return the startup notes for the given notebook. Optionally include a single requested note as
|
||||
well.
|
||||
|
||||
@type notebook_id: unicode
|
||||
@param notebook_id: id of notebook to return
|
||||
|
@ -97,9 +91,13 @@ class Notebooks( object ):
|
|||
@type revision: unicode or NoneType
|
||||
@param revision: revision timestamp of the provided note (optional)
|
||||
@type user_id: unicode or NoneType
|
||||
@param user_id: id of current logged-in user (if any), determined by @grab_user_id
|
||||
@rtype: json dict
|
||||
@return: { 'notebook': notebookdict, 'note': notedict or None }
|
||||
@param user_id: id of current logged-in user (if any)
|
||||
@rtype: dict
|
||||
@return: {
|
||||
'notebook': notebook,
|
||||
'startup_notes': notelist,
|
||||
'note': note or None,
|
||||
}
|
||||
@raise Access_error: the current user doesn't have access to the given notebook or note
|
||||
@raise Validation_error: one of the arguments is invalid
|
||||
"""
|
||||
|
@ -108,17 +106,18 @@ class Notebooks( object ):
|
|||
|
||||
notebook = self.__database.load( Notebook, notebook_id )
|
||||
|
||||
if notebook is None:
|
||||
raise Access_error()
|
||||
|
||||
if not self.__users.check_access( user_id, notebook_id, read_write = True ):
|
||||
notebook.read_write = False
|
||||
|
||||
if notebook is None:
|
||||
note = None
|
||||
elif note_id == u"blank":
|
||||
note = Note.create( note_id )
|
||||
else:
|
||||
if note_id:
|
||||
note = self.__database.load( Note, note_id, revision )
|
||||
if note and note.notebook_id != notebook_id:
|
||||
raise Access_error()
|
||||
else:
|
||||
note = None
|
||||
|
||||
startup_notes = self.__database.select_many( Note, notebook.sql_load_startup_notes() )
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import cherrypy
|
||||
|
||||
from Expose import expose
|
||||
from Expire import strongly_expire
|
||||
from Validate import validate
|
||||
from Notebooks import Notebooks
|
||||
from Users import Users
|
||||
from Users import Users, grab_user_id
|
||||
from Database import Valid_id
|
||||
from model.Note import Note
|
||||
from view.Main_page import Main_page
|
||||
|
@ -52,7 +53,12 @@ class Root( object ):
|
|||
)
|
||||
|
||||
@expose( view = Main_page )
|
||||
def index( self ):
|
||||
@strongly_expire
|
||||
@grab_user_id
|
||||
@validate(
|
||||
user_id = Valid_id( none_okay = True ),
|
||||
)
|
||||
def index( self, user_id ):
|
||||
"""
|
||||
Provide the information necessary to display the web site's front page, potentially performing
|
||||
a redirect to the https version of the page.
|
||||
|
@ -64,7 +70,11 @@ class Root( object ):
|
|||
if cherrypy.session.get( "user_id" ) and https_url and cherrypy.request.remote_addr != https_proxy_ip:
|
||||
return dict( redirect = https_url )
|
||||
|
||||
return dict()
|
||||
result = self.__users.current( user_id )
|
||||
first_notebook_id = result[ u"notebooks" ][ 0 ].object_id
|
||||
result.update( self.__notebooks.contents( first_notebook_id, user_id = user_id ) )
|
||||
|
||||
return result
|
||||
|
||||
# TODO: move this method to controller.Notebooks, and maybe give it a more sensible name
|
||||
@expose( view = Json )
|
||||
|
|
|
@ -68,6 +68,17 @@ class Password_reset_error( Exception ):
|
|||
)
|
||||
|
||||
|
||||
class Access_error( Exception ):
|
||||
def __init__( self, message ):
|
||||
Exception.__init__( self, message )
|
||||
self.__message = message
|
||||
|
||||
def to_dict( self ):
|
||||
return dict(
|
||||
error = self.__message
|
||||
)
|
||||
|
||||
|
||||
def grab_user_id( function ):
|
||||
"""
|
||||
A decorator to grab the current logged in user id from the cherrypy session and pass it as a
|
||||
|
@ -329,29 +340,19 @@ class Users( object ):
|
|||
deauthenticated = True,
|
||||
)
|
||||
|
||||
@expose( view = Json )
|
||||
@strongly_expire
|
||||
@grab_user_id
|
||||
@validate(
|
||||
include_startup_notes = Valid_bool(),
|
||||
user_id = Valid_id( none_okay = True ),
|
||||
)
|
||||
def current( self, include_startup_notes, user_id ):
|
||||
def current( self, user_id ):
|
||||
"""
|
||||
Return information on the currently logged-in user. If not logged in, default to the anonymous
|
||||
user.
|
||||
|
||||
@type include_startup_notes: bool
|
||||
@param include_startup_notes: True to return startup notes for the first notebook
|
||||
@type user_id: unicode
|
||||
@param user_id: id of current logged-in user (if any), determined by @grab_user_id
|
||||
@param user_id: id of current logged-in user (if any)
|
||||
@rtype: json dict
|
||||
@return: {
|
||||
'user': userdict or None,
|
||||
'notebooks': notebooksdict,
|
||||
'startup_notes': noteslist,
|
||||
'http_url': url,
|
||||
'user': user or None,
|
||||
'notebooks': notebookslist,
|
||||
'login_url': url,
|
||||
'logout_url': url,
|
||||
'rate_plan': rateplandict,
|
||||
}
|
||||
@raise Validation_error: one of the arguments is invalid
|
||||
|
@ -364,37 +365,27 @@ class Users( object ):
|
|||
user = anonymous
|
||||
|
||||
if not user or not anonymous:
|
||||
return dict(
|
||||
user = None,
|
||||
notebooks = None,
|
||||
http_url = u"",
|
||||
)
|
||||
raise Access_error( u"Sorry, you don't have access to do that." )
|
||||
|
||||
# in addition to this user's own notebooks, add to that list the anonymous user's notebooks
|
||||
login_url = None
|
||||
notebooks = self.__database.select_many( Notebook, anonymous.sql_load_notebooks() )
|
||||
|
||||
if user_id:
|
||||
if user_id and user_id != anonymous.object_id:
|
||||
notebooks += self.__database.select_many( Notebook, user.sql_load_notebooks() )
|
||||
# if the user is not logged in, return a login URL
|
||||
else:
|
||||
if len( notebooks ) > 0:
|
||||
if len( notebooks ) > 0 and notebooks[ 0 ]:
|
||||
main_notebook = notebooks[ 0 ]
|
||||
login_note = self.__database.select_one( Note, main_notebook.sql_load_note_by_title( u"login" ) )
|
||||
if login_note:
|
||||
login_url = "%s/notebooks/%s?note_id=%s" % ( self.__https_url, main_notebook.object_id, login_note.object_id )
|
||||
|
||||
if include_startup_notes and len( notebooks ) > 0:
|
||||
startup_notes = self.__database.select_many( Note, notebooks[ 0 ].sql_load_startup_notes() )
|
||||
else:
|
||||
startup_notes = []
|
||||
|
||||
return dict(
|
||||
user = user,
|
||||
notebooks = notebooks,
|
||||
startup_notes = startup_notes,
|
||||
http_url = self.__http_url,
|
||||
login_url = login_url,
|
||||
logout_url = self.__https_url + u"/",
|
||||
rate_plan = ( user.rate_plan < len( self.__rate_plans ) ) and self.__rate_plans[ user.rate_plan ] or {},
|
||||
)
|
||||
|
||||
|
@ -452,7 +443,7 @@ class Users( object ):
|
|||
# check if the given user has access to this notebook
|
||||
user = self.__database.load( User, user_id )
|
||||
|
||||
if user and self.__database.select_one( bool, user.sql_has_access( notebook_id ) ):
|
||||
if user and self.__database.select_one( bool, user.sql_has_access( notebook_id, read_write ) ):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
@ -551,12 +542,18 @@ class Users( object ):
|
|||
if len( matching_users ) == 0:
|
||||
raise Password_reset_error( u"There are no Luminotes users with the email address %s" % password_reset.email_address )
|
||||
|
||||
return dict(
|
||||
result = self.current( anonymous.object_id )
|
||||
result[ "notebook" ] = main_notebook
|
||||
result[ "startup_notes" ] = self.__database.select_many( Note, main_notebook.sql_load_startup_notes() )
|
||||
result[ "note_read_write" ] = False
|
||||
result[ "note" ] = Note.create(
|
||||
object_id = u"password_reset",
|
||||
contents = unicode( Redeem_reset_note( password_reset_id, matching_users ) ),
|
||||
notebook_id = main_notebook.object_id,
|
||||
note_id = u"blank",
|
||||
note_contents = unicode( Redeem_reset_note( password_reset_id, matching_users ) ),
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
@expose( view = Json )
|
||||
def reset_password( self, password_reset_id, reset_button, **new_passwords ):
|
||||
"""
|
||||
|
|
|
@ -6,9 +6,11 @@ class Stub_database( object ):
|
|||
# map of object id to list of saved objects (presumably in increasing order of revisions)
|
||||
self.objects = {}
|
||||
self.user_notebook = {} # map of user_id to ( notebook_id, read_write )
|
||||
self.last_saved_obj = None
|
||||
self.__next_id = 0
|
||||
|
||||
def save( self, obj, commit = False ):
|
||||
self.last_saved_obj = obj
|
||||
if obj.object_id in self.objects:
|
||||
self.objects[ obj.object_id ].append( copy( obj ) )
|
||||
else:
|
||||
|
|
|
@ -29,7 +29,7 @@ class Test_controller( object ):
|
|||
notebooks = []
|
||||
notebook_tuples = database.user_notebook.get( self.object_id )
|
||||
|
||||
if not notebook_tuples: return None
|
||||
if not notebook_tuples: return []
|
||||
|
||||
for notebook_tuple in notebook_tuples:
|
||||
( notebook_id, read_write ) = notebook_tuple
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import cherrypy
|
||||
import cgi
|
||||
from nose.tools import raises
|
||||
from urllib import quote
|
||||
from Test_controller import Test_controller
|
||||
from model.Notebook import Notebook
|
||||
from model.Note import Note
|
||||
from model.User import User
|
||||
from controller.Notebooks import Access_error
|
||||
|
||||
|
||||
class Test_notebooks( Test_controller ):
|
||||
|
@ -53,53 +55,118 @@ class Test_notebooks( Test_controller ):
|
|||
self.database.save( self.anonymous, commit = False )
|
||||
self.database.execute( self.user.sql_save_notebook( self.anon_notebook.object_id, read_write = False ) )
|
||||
|
||||
def test_default( self ):
|
||||
result = self.http_get( "/notebooks/%s" % self.notebook.object_id )
|
||||
def test_default_without_login( self ):
|
||||
result = self.http_get(
|
||||
"/notebooks/%s" % self.notebook.object_id,
|
||||
)
|
||||
|
||||
assert result.get( u"notebook_id" ) == self.notebook.object_id
|
||||
assert u"access" in result[ u"error" ]
|
||||
user = self.database.load( User, self.user.object_id )
|
||||
assert user.storage_bytes == 0
|
||||
|
||||
def test_default( self ):
|
||||
self.login()
|
||||
|
||||
result = self.http_get(
|
||||
"/notebooks/%s" % self.notebook.object_id,
|
||||
session_id = self.session_id,
|
||||
)
|
||||
|
||||
assert result.get( u"user" ).object_id == self.user.object_id
|
||||
assert len( result.get( u"notebooks" ) ) == 3
|
||||
assert result.get( u"login_url" ) is None
|
||||
assert result.get( u"logout_url" )
|
||||
assert result.get( u"rate_plan" )
|
||||
assert result.get( u"notebook" ).object_id == self.notebook.object_id
|
||||
assert len( result.get( u"startup_notes" ) ) == 1
|
||||
assert result.get( u"note" ) is None
|
||||
assert result.get( u"parent_id" ) == None
|
||||
assert result.get( u"note_read_write" ) in ( None, True )
|
||||
|
||||
user = self.database.load( User, self.user.object_id )
|
||||
assert user.storage_bytes == 0
|
||||
|
||||
def test_default_with_note( self ):
|
||||
result = self.http_get( "/notebooks/%s?note_id=%s" % ( self.notebook.object_id, self.note.object_id ) )
|
||||
self.login()
|
||||
|
||||
result = self.http_get(
|
||||
"/notebooks/%s?note_id=%s" % ( self.notebook.object_id, self.note.object_id ),
|
||||
session_id = self.session_id,
|
||||
)
|
||||
|
||||
assert result.get( u"notebook_id" ) == self.notebook.object_id
|
||||
assert result.get( u"note_id" ) == self.note.object_id
|
||||
assert result.get( u"user" ).object_id == self.user.object_id
|
||||
assert len( result.get( u"notebooks" ) ) == 3
|
||||
assert result.get( u"login_url" ) is None
|
||||
assert result.get( u"logout_url" )
|
||||
assert result.get( u"rate_plan" )
|
||||
assert result.get( u"notebook" ).object_id == self.notebook.object_id
|
||||
assert len( result.get( u"startup_notes" ) ) == 1
|
||||
assert result.get( u"note" ).object_id == self.note.object_id
|
||||
assert result.get( u"parent_id" ) == None
|
||||
assert result.get( u"note_read_write" ) in ( None, True )
|
||||
|
||||
user = self.database.load( User, self.user.object_id )
|
||||
assert user.storage_bytes == 0
|
||||
|
||||
def test_default_with_note_and_revision( self ):
|
||||
result = self.http_get( "/notebooks/%s?note_id=%s&revision=%s" % (
|
||||
self.notebook.object_id,
|
||||
self.note.object_id,
|
||||
quote( unicode( self.note.revision ) ),
|
||||
) )
|
||||
self.login()
|
||||
|
||||
result = self.http_get(
|
||||
"/notebooks/%s?note_id=%s&revision=%s" % (
|
||||
self.notebook.object_id,
|
||||
self.note.object_id,
|
||||
quote( unicode( self.note.revision ) ),
|
||||
),
|
||||
session_id = self.session_id,
|
||||
)
|
||||
|
||||
assert result.get( u"notebook_id" ) == self.notebook.object_id
|
||||
assert result.get( u"note_id" ) == self.note.object_id
|
||||
assert result.get( u"revision" ) == unicode( self.note.revision )
|
||||
assert result.get( u"user" ).object_id == self.user.object_id
|
||||
assert len( result.get( u"notebooks" ) ) == 3
|
||||
assert result.get( u"login_url" ) is None
|
||||
assert result.get( u"logout_url" )
|
||||
assert result.get( u"rate_plan" )
|
||||
assert result.get( u"notebook" ).object_id == self.notebook.object_id
|
||||
assert len( result.get( u"startup_notes" ) ) == 1
|
||||
assert result.get( u"note" ).object_id == self.note.object_id
|
||||
assert result.get( u"note" ).revision == self.note.revision
|
||||
assert result.get( u"parent_id" ) == None
|
||||
assert result.get( u"note_read_write" ) == False
|
||||
|
||||
user = self.database.load( User, self.user.object_id )
|
||||
assert user.storage_bytes == 0
|
||||
|
||||
def test_default_with_parent( self ):
|
||||
parent_id = "foo"
|
||||
result = self.http_get( "/notebooks/%s?parent_id=%s" % ( self.notebook.object_id, parent_id ) )
|
||||
self.login()
|
||||
|
||||
parent_id = u"foo"
|
||||
result = self.http_get(
|
||||
"/notebooks/%s?parent_id=%s" % ( self.notebook.object_id, parent_id ),
|
||||
session_id = self.session_id,
|
||||
)
|
||||
|
||||
assert result.get( u"notebook_id" ) == self.notebook.object_id
|
||||
assert result.get( u"user" ).object_id == self.user.object_id
|
||||
assert len( result.get( u"notebooks" ) ) == 3
|
||||
assert result.get( u"login_url" ) is None
|
||||
assert result.get( u"logout_url" )
|
||||
assert result.get( u"rate_plan" )
|
||||
assert result.get( u"notebook" ).object_id == self.notebook.object_id
|
||||
assert len( result.get( u"startup_notes" ) ) == 1
|
||||
assert result.get( u"note" ) is None
|
||||
assert result.get( u"parent_id" ) == parent_id
|
||||
assert result.get( u"note_read_write" ) in ( None, True )
|
||||
|
||||
user = self.database.load( User, self.user.object_id )
|
||||
assert user.storage_bytes == 0
|
||||
|
||||
def test_contents( self ):
|
||||
self.login()
|
||||
|
||||
result = self.http_get(
|
||||
"/notebooks/contents?notebook_id=%s" % self.notebook.object_id,
|
||||
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" ]
|
||||
startup_notes = result[ "startup_notes" ]
|
||||
assert result[ "note" ] == None
|
||||
|
||||
assert notebook.object_id == self.notebook.object_id
|
||||
assert notebook.read_write == True
|
||||
|
@ -109,11 +176,10 @@ class Test_notebooks( Test_controller ):
|
|||
assert user.storage_bytes == 0
|
||||
|
||||
def test_contents_with_note( self ):
|
||||
self.login()
|
||||
|
||||
result = self.http_get(
|
||||
"/notebooks/contents?notebook_id=%s¬e_id=%s" % ( self.notebook.object_id, self.note.object_id ),
|
||||
session_id = self.session_id,
|
||||
result = cherrypy.root.notebooks.contents(
|
||||
notebook_id = self.notebook.object_id,
|
||||
note_id = self.note.object_id,
|
||||
user_id = self.user.object_id,
|
||||
)
|
||||
|
||||
notebook = result[ "notebook" ]
|
||||
|
@ -131,16 +197,13 @@ class Test_notebooks( Test_controller ):
|
|||
assert user.storage_bytes == 0
|
||||
|
||||
def test_contents_with_note_and_revision( self ):
|
||||
self.login()
|
||||
|
||||
result = self.http_get(
|
||||
"/notebooks/contents?notebook_id=%s¬e_id=%s&revision=%s" % (
|
||||
self.notebook.object_id,
|
||||
self.note.object_id,
|
||||
quote( unicode( self.note.revision ) ),
|
||||
),
|
||||
session_id = self.session_id,
|
||||
result = cherrypy.root.notebooks.contents(
|
||||
notebook_id = self.notebook.object_id,
|
||||
note_id = self.note.object_id,
|
||||
revision = unicode( self.note.revision ),
|
||||
user_id = self.user.object_id,
|
||||
)
|
||||
self.login()
|
||||
|
||||
notebook = result[ "notebook" ]
|
||||
startup_notes = result[ "startup_notes" ]
|
||||
|
@ -153,54 +216,39 @@ class Test_notebooks( Test_controller ):
|
|||
note = result[ "note" ]
|
||||
|
||||
assert note.object_id == self.note.object_id
|
||||
assert note.revision == self.note.revision
|
||||
user = self.database.load( User, self.user.object_id )
|
||||
assert user.storage_bytes == 0
|
||||
|
||||
def test_contents_with_blank_note( self ):
|
||||
self.login()
|
||||
|
||||
result = self.http_get(
|
||||
"/notebooks/contents?notebook_id=%s¬e_id=blank" % self.notebook.object_id ,
|
||||
session_id = self.session_id,
|
||||
@raises( Access_error )
|
||||
def test_contents_without_user_id( self ):
|
||||
result = cherrypy.root.notebooks.contents(
|
||||
notebook_id = self.notebook.object_id,
|
||||
)
|
||||
|
||||
notebook = result[ "notebook" ]
|
||||
startup_notes = result[ "startup_notes" ]
|
||||
|
||||
assert notebook.object_id == self.notebook.object_id
|
||||
assert notebook.read_write == True
|
||||
assert len( startup_notes ) == 1
|
||||
assert startup_notes[ 0 ].object_id == self.note.object_id
|
||||
|
||||
note = result[ "note" ]
|
||||
|
||||
assert note.object_id == u"blank"
|
||||
assert note.contents == None
|
||||
assert note.title == None
|
||||
assert note.deleted_from_id == None
|
||||
user = self.database.load( User, self.user.object_id )
|
||||
assert user.storage_bytes == 0
|
||||
|
||||
def test_contents_without_login( self ):
|
||||
result = self.http_get(
|
||||
"/notebooks/contents?notebook_id=%s" % self.notebook.object_id,
|
||||
session_id = self.session_id,
|
||||
@raises( Access_error )
|
||||
def test_contents_with_incorrect_user_id( self ):
|
||||
result = cherrypy.root.notebooks.contents(
|
||||
notebook_id = self.notebook.object_id,
|
||||
user_id = self.anonymous.object_id,
|
||||
)
|
||||
|
||||
assert result.get( "error" )
|
||||
user = self.database.load( User, self.user.object_id )
|
||||
assert user.storage_bytes == 0
|
||||
@raises( Access_error )
|
||||
def test_contents_with_unknown_notebook_id( self ):
|
||||
result = cherrypy.root.notebooks.contents(
|
||||
notebook_id = self.unknown_notebook_id,
|
||||
user_id = self.user.object_id,
|
||||
)
|
||||
|
||||
def test_contents_with_read_only_notebook( self ):
|
||||
self.login()
|
||||
|
||||
result = self.http_get(
|
||||
"/notebooks/contents?notebook_id=%s" % self.anon_notebook.object_id,
|
||||
session_id = self.session_id,
|
||||
result = cherrypy.root.notebooks.contents(
|
||||
notebook_id = self.anon_notebook.object_id,
|
||||
user_id = self.user.object_id,
|
||||
)
|
||||
|
||||
notebook = result[ "notebook" ]
|
||||
startup_notes = result[ "startup_notes" ]
|
||||
assert result[ "note" ] == None
|
||||
|
||||
assert notebook.object_id == self.anon_notebook.object_id
|
||||
assert notebook.read_write == False
|
||||
|
|
|
@ -2,6 +2,7 @@ import re
|
|||
import cherrypy
|
||||
import smtplib
|
||||
from pytz import utc
|
||||
from nose.tools import raises
|
||||
from datetime import datetime, timedelta
|
||||
from nose.tools import raises
|
||||
from Test_controller import Test_controller
|
||||
|
@ -10,6 +11,7 @@ from model.User import User
|
|||
from model.Notebook import Notebook
|
||||
from model.Note import Note
|
||||
from model.Password_reset import Password_reset
|
||||
from controller.Users import Access_error
|
||||
|
||||
|
||||
class Test_users( Test_controller ):
|
||||
|
@ -80,7 +82,7 @@ class Test_users( Test_controller ):
|
|||
|
||||
assert result[ u"redirect" ].startswith( u"/notebooks/" )
|
||||
|
||||
def test_current_after_signup( self, include_startup_notes = False ):
|
||||
def test_current_after_signup( self ):
|
||||
result = self.http_post( "/users/signup", dict(
|
||||
username = self.new_username,
|
||||
password = self.new_password,
|
||||
|
@ -92,12 +94,14 @@ class Test_users( Test_controller ):
|
|||
|
||||
new_notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ]
|
||||
|
||||
result = self.http_get(
|
||||
"/users/current?include_startup_notes=%s" % include_startup_notes,
|
||||
session_id = session_id,
|
||||
)
|
||||
user = self.database.last_saved_obj
|
||||
assert isinstance( user, User )
|
||||
result = cherrypy.root.users.current( user.object_id )
|
||||
|
||||
assert result[ u"user" ].object_id == user.object_id
|
||||
assert result[ u"user" ].username == self.new_username
|
||||
assert result[ u"user" ].email_address == self.new_email_address
|
||||
|
||||
notebooks = result[ u"notebooks" ]
|
||||
notebook = notebooks[ 0 ]
|
||||
assert notebook.object_id == self.anon_notebook.object_id
|
||||
|
@ -120,22 +124,13 @@ class Test_users( Test_controller ):
|
|||
assert notebook.trash_id == None
|
||||
assert notebook.read_write == True
|
||||
|
||||
startup_notes = result[ "startup_notes" ]
|
||||
if include_startup_notes:
|
||||
assert len( startup_notes ) == 1
|
||||
assert startup_notes[ 0 ].object_id == self.startup_note.object_id
|
||||
assert startup_notes[ 0 ].title == self.startup_note.title
|
||||
assert startup_notes[ 0 ].contents == self.startup_note.contents
|
||||
else:
|
||||
assert startup_notes == []
|
||||
assert result.get( u"login_url" ) is None
|
||||
assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/"
|
||||
|
||||
rate_plan = result[ u"rate_plan" ]
|
||||
assert rate_plan[ u"name" ] == u"super"
|
||||
assert rate_plan[ u"storage_quota_bytes" ] == 1337
|
||||
|
||||
def test_current_with_startup_notes_after_signup( self ):
|
||||
self.test_current_after_signup( include_startup_notes = True )
|
||||
|
||||
def test_signup_with_different_passwords( self ):
|
||||
result = self.http_post( "/users/signup", dict(
|
||||
username = self.new_username,
|
||||
|
@ -152,18 +147,20 @@ class Test_users( Test_controller ):
|
|||
|
||||
assert result[ u"redirect" ].startswith( u"/notebooks/" )
|
||||
|
||||
def test_current_after_demo( self, include_startup_notes = False ):
|
||||
def test_current_after_demo( self ):
|
||||
result = self.http_post( "/users/demo", dict() )
|
||||
session_id = result[ u"session_id" ]
|
||||
|
||||
new_notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ]
|
||||
|
||||
result = self.http_get(
|
||||
"/users/current?include_startup_notes=%s" % include_startup_notes,
|
||||
session_id = session_id,
|
||||
)
|
||||
user = self.database.last_saved_obj
|
||||
assert isinstance( user, User )
|
||||
result = cherrypy.root.users.current( user.object_id )
|
||||
|
||||
assert result[ u"user" ].object_id == user.object_id
|
||||
assert result[ u"user" ].username is None
|
||||
assert result[ u"user" ].email_address is None
|
||||
|
||||
assert result[ u"user" ].username == None
|
||||
notebooks = result[ u"notebooks" ]
|
||||
assert len( notebooks ) == 3
|
||||
notebook = notebooks[ 0 ]
|
||||
|
@ -187,34 +184,25 @@ class Test_users( Test_controller ):
|
|||
assert notebook.trash_id == None
|
||||
assert notebook.read_write == True
|
||||
|
||||
startup_notes = result[ "startup_notes" ]
|
||||
if include_startup_notes:
|
||||
assert len( startup_notes ) == 1
|
||||
assert startup_notes[ 0 ].object_id == self.startup_note.object_id
|
||||
assert startup_notes[ 0 ].title == self.startup_note.title
|
||||
assert startup_notes[ 0 ].contents == self.startup_note.contents
|
||||
else:
|
||||
assert startup_notes == []
|
||||
assert result.get( u"login_url" ) is None
|
||||
assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/"
|
||||
|
||||
rate_plan = result[ u"rate_plan" ]
|
||||
assert rate_plan[ u"name" ] == u"super"
|
||||
assert rate_plan[ u"storage_quota_bytes" ] == 1337
|
||||
|
||||
def test_current_with_startup_notes_after_demo( self ):
|
||||
self.test_current_after_demo( include_startup_notes = True )
|
||||
|
||||
def test_current_after_demo_twice( self, include_startup_notes = False ):
|
||||
def test_current_after_demo_twice( self ):
|
||||
result = self.http_post( "/users/demo", dict() )
|
||||
session_id = result[ u"session_id" ]
|
||||
|
||||
new_notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ]
|
||||
|
||||
result = self.http_get(
|
||||
"/users/current?include_startup_notes=%s" % include_startup_notes,
|
||||
session_id = session_id,
|
||||
)
|
||||
user = self.database.last_saved_obj
|
||||
assert isinstance( user, User )
|
||||
result = cherrypy.root.users.current( user.object_id )
|
||||
|
||||
user_id = result[ u"user" ].object_id
|
||||
assert user_id == user.object_id
|
||||
|
||||
# request a demo for a second time
|
||||
result = self.http_post( "/users/demo", dict(), session_id = session_id )
|
||||
|
@ -224,10 +212,7 @@ class Test_users( Test_controller ):
|
|||
|
||||
assert notebook_id_again == new_notebook_id
|
||||
|
||||
result = self.http_get(
|
||||
"/users/current?include_startup_notes=%s" % include_startup_notes,
|
||||
session_id = session_id,
|
||||
)
|
||||
result = cherrypy.root.users.current( user_id )
|
||||
|
||||
user_id_again = result[ u"user" ].object_id
|
||||
|
||||
|
@ -235,9 +220,6 @@ class Test_users( Test_controller ):
|
|||
# should just use the same guest user with the same notebook
|
||||
assert user_id_again == user_id
|
||||
|
||||
def test_current_with_startup_notes_after_demo_twice( self ):
|
||||
self.test_current_after_demo_twice( include_startup_notes = True )
|
||||
|
||||
def test_login( self ):
|
||||
result = self.http_post( "/users/login", dict(
|
||||
username = self.username,
|
||||
|
@ -270,18 +252,8 @@ class Test_users( Test_controller ):
|
|||
|
||||
assert result[ u"redirect" ] == self.settings[ u"global" ].get( u"luminotes.http_url" ) + u"/"
|
||||
|
||||
def test_current_after_login( self, include_startup_notes = False ):
|
||||
result = self.http_post( "/users/login", dict(
|
||||
username = self.username,
|
||||
password = self.password,
|
||||
login_button = u"login",
|
||||
) )
|
||||
session_id = result[ u"session_id" ]
|
||||
|
||||
result = self.http_get(
|
||||
"/users/current?include_startup_notes=%s" % include_startup_notes,
|
||||
session_id = session_id,
|
||||
)
|
||||
def test_current( self ):
|
||||
result = cherrypy.root.users.current( self.user.object_id )
|
||||
|
||||
assert result[ u"user" ]
|
||||
assert result[ u"user" ].object_id == self.user.object_id
|
||||
|
@ -293,32 +265,22 @@ class Test_users( Test_controller ):
|
|||
assert result[ u"notebooks" ][ 1 ].read_write == True
|
||||
assert result[ u"notebooks" ][ 2 ].object_id == self.notebooks[ 1 ].object_id
|
||||
assert result[ u"notebooks" ][ 2 ].read_write == True
|
||||
assert result[ u"http_url" ] == self.settings[ u"global" ].get( u"luminotes.http_url" )
|
||||
assert result[ u"login_url" ] == None
|
||||
assert result[ u"login_url" ] is None
|
||||
assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/"
|
||||
|
||||
startup_notes = result[ "startup_notes" ]
|
||||
if include_startup_notes:
|
||||
assert len( startup_notes ) == 1
|
||||
assert startup_notes[ 0 ].object_id == self.startup_note.object_id
|
||||
assert startup_notes[ 0 ].title == self.startup_note.title
|
||||
assert startup_notes[ 0 ].contents == self.startup_note.contents
|
||||
else:
|
||||
assert startup_notes == []
|
||||
rate_plan = result[ u"rate_plan" ]
|
||||
assert rate_plan
|
||||
assert rate_plan[ u"name" ] == u"super"
|
||||
assert rate_plan[ u"storage_quota_bytes" ] == 1337
|
||||
|
||||
def test_current_with_startup_notes_after_login( self ):
|
||||
self.test_current_after_login( include_startup_notes = True )
|
||||
|
||||
def test_current_without_login( self, include_startup_notes = False ):
|
||||
result = self.http_get(
|
||||
"/users/current?include_startup_notes=%s" % include_startup_notes,
|
||||
)
|
||||
def test_current_anonymous( self ):
|
||||
result = cherrypy.root.users.current( self.anonymous.object_id )
|
||||
|
||||
assert result[ u"user" ].username == "anonymous"
|
||||
assert len( result[ u"notebooks" ] ) == 1
|
||||
assert result[ u"notebooks" ][ 0 ].object_id == self.anon_notebook.object_id
|
||||
assert result[ u"notebooks" ][ 0 ].name == self.anon_notebook.name
|
||||
assert result[ u"notebooks" ][ 0 ].read_write == False
|
||||
assert result[ u"http_url" ] == self.settings[ u"global" ].get( u"luminotes.http_url" )
|
||||
|
||||
login_note = self.database.select_one( Note, self.anon_notebook.sql_load_note_by_title( u"login" ) )
|
||||
assert result[ u"login_url" ] == u"%s/notebooks/%s?note_id=%s" % (
|
||||
|
@ -326,18 +288,12 @@ class Test_users( Test_controller ):
|
|||
self.anon_notebook.object_id,
|
||||
login_note.object_id,
|
||||
)
|
||||
assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/"
|
||||
|
||||
startup_notes = result[ "startup_notes" ]
|
||||
if include_startup_notes:
|
||||
assert len( startup_notes ) == 1
|
||||
assert startup_notes[ 0 ].object_id == self.startup_note.object_id
|
||||
assert startup_notes[ 0 ].title == self.startup_note.title
|
||||
assert startup_notes[ 0 ].contents == self.startup_note.contents
|
||||
else:
|
||||
assert startup_notes == []
|
||||
|
||||
def test_current_with_startup_notes_without_login( self ):
|
||||
self.test_current_without_login( include_startup_notes = True )
|
||||
rate_plan = result[ u"rate_plan" ]
|
||||
assert rate_plan
|
||||
assert rate_plan[ u"name" ] == u"super"
|
||||
assert rate_plan[ u"storage_quota_bytes" ] == 1337
|
||||
|
||||
def test_update_storage( self ):
|
||||
previous_revision = self.user.revision
|
||||
|
@ -409,11 +365,36 @@ class Test_users( Test_controller ):
|
|||
|
||||
result = self.http_get( "/users/redeem_reset/%s" % password_reset_id )
|
||||
|
||||
assert result[ u"notebook_id" ] == self.anon_notebook.object_id
|
||||
assert result[ u"note_id" ]
|
||||
assert u"password reset" in result[ u"note_contents" ]
|
||||
assert self.user.username in result[ u"note_contents" ]
|
||||
assert self.user2.username in result[ u"note_contents" ]
|
||||
assert result[ u"user" ].username == "anonymous"
|
||||
assert len( result[ u"notebooks" ] ) == 1
|
||||
assert result[ u"notebooks" ][ 0 ].object_id == self.anon_notebook.object_id
|
||||
assert result[ u"notebooks" ][ 0 ].name == self.anon_notebook.name
|
||||
assert result[ u"notebooks" ][ 0 ].read_write == False
|
||||
|
||||
login_note = self.database.select_one( Note, self.anon_notebook.sql_load_note_by_title( u"login" ) )
|
||||
assert result[ u"login_url" ] == u"%s/notebooks/%s?note_id=%s" % (
|
||||
self.settings[ u"global" ][ u"luminotes.https_url" ],
|
||||
self.anon_notebook.object_id,
|
||||
login_note.object_id,
|
||||
)
|
||||
assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/"
|
||||
|
||||
rate_plan = result[ u"rate_plan" ]
|
||||
assert rate_plan
|
||||
assert rate_plan[ u"name" ] == u"super"
|
||||
assert rate_plan[ u"storage_quota_bytes" ] == 1337
|
||||
|
||||
assert result[ u"notebook" ].object_id == self.anon_notebook.object_id
|
||||
assert len( result[ u"startup_notes" ] ) == 1
|
||||
assert result[ u"startup_notes" ][ 0 ].object_id == self.startup_note.object_id
|
||||
assert result[ u"startup_notes" ][ 0 ].title == self.startup_note.title
|
||||
assert result[ u"startup_notes" ][ 0 ].contents == self.startup_note.contents
|
||||
assert result[ u"note_read_write" ] is False
|
||||
assert result[ u"note" ].title == u"complete your password reset"
|
||||
assert result[ u"note" ].notebook_id == self.anon_notebook.object_id
|
||||
assert u"password reset" in result[ u"note" ].contents
|
||||
assert self.user.username in result[ u"note" ].contents
|
||||
assert self.user2.username in result[ u"note" ].contents
|
||||
|
||||
def test_redeem_reset_unknown( self ):
|
||||
password_reset_id = u"unknownresetid"
|
||||
|
|
|
@ -13,14 +13,6 @@ a:hover {
|
|||
color: #ff6600;
|
||||
}
|
||||
|
||||
noscript {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
noscript h3 {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
ul li {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
@ -206,6 +198,16 @@ ol li {
|
|||
-webkit-border-radius: 0.5em 0 0 0;
|
||||
}
|
||||
|
||||
#static_notes {
|
||||
text-align: left;
|
||||
margin: 1em;
|
||||
line-height: 140%;
|
||||
}
|
||||
|
||||
#static_notes h3 {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
#notes {
|
||||
text-align: left;
|
||||
margin-top: 1em;
|
||||
|
@ -405,3 +407,18 @@ ol li {
|
|||
-moz-border-radius: 0.5em;
|
||||
-webkit-border-radius: 0.5em;
|
||||
}
|
||||
|
||||
.button {
|
||||
border-style: outset;
|
||||
border-width: 0px;
|
||||
background-color: #d0e0f0;
|
||||
font-size: 100%;
|
||||
outline: none;
|
||||
-moz-border-radius: 0.5em;
|
||||
-webkit-border-radius: 0.5em;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: #ffcc66;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
<h3>Luminotes requires JavaScript</h3>
|
||||
|
||||
So if you'd like to check out this site any further, please enable JavaScript
|
||||
in your web browser, and then reload this page. Sorry for the inconvenience.
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
function Wiki( invoker ) {
|
||||
this.next_id = null;
|
||||
this.focused_editor = null;
|
||||
|
@ -5,104 +6,52 @@ function Wiki( invoker ) {
|
|||
this.notebook = null;
|
||||
this.notebook_id = getElement( "notebook_id" ).value;
|
||||
this.parent_id = getElement( "parent_id" ).value; // id of the notebook containing this one
|
||||
this.read_write = false;
|
||||
this.startup_notes = new Array(); // map of startup notes: note id to bool
|
||||
this.open_editors = new Array(); // map of open notes: note title to editor
|
||||
this.all_notes_editor = null; // editor for display of list of all notes
|
||||
this.search_results_editor = null; // editor for display of search results
|
||||
this.invoker = invoker;
|
||||
this.rate_plan = null;
|
||||
this.rate_plan = evalJSON( getElement( "rate_plan" ).value );
|
||||
this.storage_usage_high = false;
|
||||
|
||||
// grab the current notebook from the list of available notebooks
|
||||
var notebooks = evalJSON( getElement( "notebooks" ).value );
|
||||
for ( var i in notebooks ) {
|
||||
if ( notebooks[ i ].object_id == this.notebook_id ) {
|
||||
this.notebook = notebooks[ i ]
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// populate the wiki with startup notes
|
||||
this.populate(
|
||||
evalJSON( getElement( "startup_notes" ).value || "null" ),
|
||||
evalJSON( getElement( "note" ).value || "null" ),
|
||||
evalJSON( getElement( "note_read_write" ).value || "true" )
|
||||
);
|
||||
|
||||
this.display_storage_usage( evalJSON( getElement( "storage_bytes" ).value || "0" ) );
|
||||
|
||||
connect( this.invoker, "error_message", this, "display_error" );
|
||||
connect( this.invoker, "message", this, "display_message" );
|
||||
connect( "search_form", "onsubmit", this, "search" );
|
||||
connect( "html", "onclick", this, "background_clicked" );
|
||||
|
||||
// get info on the requested notebook (if any)
|
||||
var self = this;
|
||||
if ( this.notebook_id ) {
|
||||
this.invoker.invoke(
|
||||
"/notebooks/contents", "GET", {
|
||||
"notebook_id": this.notebook_id,
|
||||
"note_id": getElement( "note_id" ).value,
|
||||
"revision": getElement( "revision" ).value
|
||||
},
|
||||
function( result ) { self.populate( result ); }
|
||||
);
|
||||
var include_startup_notes = false;
|
||||
} else {
|
||||
var include_startup_notes = true;
|
||||
var logout_link = getElement( "logout_link" );
|
||||
if ( logout_link ) {
|
||||
connect( "logout_link", "onclick", function ( event ) {
|
||||
self.save_editor( null, true );
|
||||
self.invoker.invoke( "/users/logout", "POST" );
|
||||
event.stop();
|
||||
} );
|
||||
}
|
||||
|
||||
// get info on the current user (logged-in or anonymous)
|
||||
this.invoker.invoke( "/users/current", "GET", {
|
||||
"include_startup_notes": include_startup_notes
|
||||
},
|
||||
function( result ) { self.display_user( result ); }
|
||||
);
|
||||
}
|
||||
|
||||
Wiki.prototype.update_next_id = function ( result ) {
|
||||
this.next_id = result.next_id;
|
||||
}
|
||||
|
||||
Wiki.prototype.display_user = function ( result ) {
|
||||
// if no notebook id was requested, then just display the user's default notebook
|
||||
if ( !this.notebook_id ) {
|
||||
this.notebook_id = result.notebooks[ 0 ].object_id;
|
||||
this.populate( { "notebook" : result.notebooks[ 0 ], "startup_notes": result.startup_notes } );
|
||||
}
|
||||
|
||||
var user_span = createDOM( "span" );
|
||||
replaceChildNodes( "user_area", user_span );
|
||||
|
||||
// if not logged in, display a login link
|
||||
if ( result.user.username == "anonymous" && result.login_url ) {
|
||||
appendChildNodes( user_span, createDOM( "a", { "href": result.login_url, "id": "login_link" }, "login" ) );
|
||||
return;
|
||||
}
|
||||
|
||||
// display links for current notebook and a list of all notebooks that the user has access to
|
||||
var notebooks_span = createDOM( "span" );
|
||||
replaceChildNodes( "notebooks_area", notebooks_span );
|
||||
|
||||
appendChildNodes( notebooks_span, createDOM( "h4", "notebooks" ) );
|
||||
|
||||
for ( var i in result.notebooks ) {
|
||||
var notebook = result.notebooks[ i ];
|
||||
|
||||
if ( notebook.name == "Luminotes" || notebook.name == "trash" )
|
||||
continue;
|
||||
|
||||
var div_class = "link_area_item";
|
||||
if ( notebook.object_id == this.notebook_id )
|
||||
div_class += " current_notebook_name";
|
||||
|
||||
appendChildNodes( notebooks_span, createDOM( "div", {
|
||||
"class": div_class
|
||||
}, createDOM( "a", {
|
||||
"href": "/notebooks/" + notebook.object_id,
|
||||
"id": "notebook_" + notebook.object_id
|
||||
}, notebook.name ) ) );
|
||||
}
|
||||
|
||||
this.rate_plan = result.rate_plan;
|
||||
this.display_storage_usage( result.user.storage_bytes );
|
||||
|
||||
// display the name of the logged in user and a logout link
|
||||
appendChildNodes( user_span, "logged in as " + ( result.user.username || "a guest" ) );
|
||||
appendChildNodes( user_span, " | " );
|
||||
appendChildNodes( user_span, createDOM( "a", { "href": result.http_url + "/", "id": "logout_link" }, "logout" ) );
|
||||
|
||||
var self = this;
|
||||
connect( "logout_link", "onclick", function ( event ) {
|
||||
self.save_editor( null, true );
|
||||
self.invoker.invoke( "/users/logout", "POST" );
|
||||
event.stop();
|
||||
} );
|
||||
}
|
||||
|
||||
Wiki.prototype.display_storage_usage = function( storage_bytes ) {
|
||||
if ( !storage_bytes )
|
||||
return;
|
||||
|
@ -113,7 +62,10 @@ Wiki.prototype.display_storage_usage = function( storage_bytes ) {
|
|||
return Math.round( storage_bytes / MEGABYTE );
|
||||
}
|
||||
|
||||
var quota_bytes = this.rate_plan.storage_quota_bytes || 0;
|
||||
var quota_bytes = this.rate_plan.storage_quota_bytes;
|
||||
if ( !quota_bytes )
|
||||
return;
|
||||
|
||||
var usage_percent = Math.round( storage_bytes / quota_bytes * 100.0 );
|
||||
|
||||
if ( usage_percent > 90 ) {
|
||||
|
@ -136,68 +88,49 @@ Wiki.prototype.display_storage_usage = function( storage_bytes ) {
|
|||
);
|
||||
}
|
||||
|
||||
Wiki.prototype.populate = function ( result ) {
|
||||
this.notebook = result.notebook;
|
||||
Wiki.prototype.populate = function ( startup_notes, note, note_read_write ) {
|
||||
// create an editor for each startup note in the received notebook, focusing the first one
|
||||
var focus = true;
|
||||
for ( var i in startup_notes ) {
|
||||
var startup_note = startup_notes[ i ];
|
||||
this.startup_notes[ startup_note.object_id ] = true;
|
||||
|
||||
// don't actually create an editor if a particular note was provided in the result
|
||||
if ( !note ) {
|
||||
var editor = this.create_editor(
|
||||
startup_note.object_id,
|
||||
// grab this note's contents from the static <noscript> area
|
||||
getElement( "static_note_" + startup_note.object_id ).innerHTML,
|
||||
startup_note.deleted_from_id,
|
||||
startup_note.revision,
|
||||
this.notebook.read_write, false, focus
|
||||
);
|
||||
|
||||
this.open_editors[ startup_note.title ] = editor;
|
||||
focus = false;
|
||||
}
|
||||
}
|
||||
|
||||
// if one particular note was provided, then just display an editor for that note
|
||||
if ( note )
|
||||
this.create_editor(
|
||||
note.object_id,
|
||||
getElement( "static_note_" + note.object_id ).innerHTML,
|
||||
note.deleted_from_id,
|
||||
note.revision,
|
||||
this.notebook.read_write && note_read_write, false, true
|
||||
);
|
||||
|
||||
if ( startup_notes.length == 0 && !note )
|
||||
this.display_empty_message();
|
||||
|
||||
var self = this;
|
||||
|
||||
var header_area = getElement( "notebook_header_area" );
|
||||
replaceChildNodes( header_area, createDOM( "b", {}, this.notebook.name ) );
|
||||
|
||||
if ( this.parent_id ) {
|
||||
appendChildNodes( header_area, createDOM( "span", {}, ": " ) );
|
||||
var empty_trash_link = createDOM( "a", { "href": location.href }, "empty trash" );
|
||||
appendChildNodes( header_area, empty_trash_link );
|
||||
connect( empty_trash_link, "onclick", function ( event ) { try{ self.delete_all_editors( event ); } catch(e){ alert(e); } } );
|
||||
|
||||
appendChildNodes( header_area, createDOM( "span", {}, " | " ) );
|
||||
appendChildNodes( header_area, createDOM( "a", { "href": "/notebooks/" + this.parent_id }, "return to notebook" ) );
|
||||
}
|
||||
|
||||
var span = createDOM( "span" );
|
||||
replaceChildNodes( "this_notebook_area", span );
|
||||
|
||||
appendChildNodes( span, createDOM( "h4", "this notebook" ) );
|
||||
if ( !this.parent_id ) {
|
||||
appendChildNodes( span, createDOM( "div", { "class": "link_area_item" },
|
||||
createDOM( "a", { "href": location.href, "id": "all_notes_link", "title": "View a list of all notes in this notebook." }, "all notes" )
|
||||
) );
|
||||
}
|
||||
if ( this.notebook.name != "Luminotes" ) {
|
||||
appendChildNodes( span, createDOM( "div", { "class": "link_area_item" },
|
||||
createDOM( "a", { "href": "/notebooks/download_html/" + this.notebook.object_id, "id": "download_html_link", "title": "Download a stand-alone copy of the entire wiki notebook." }, "download as html" )
|
||||
) );
|
||||
}
|
||||
var empty_trash_link = getElement( "empty_trash_link" );
|
||||
if ( empty_trash_link )
|
||||
connect( empty_trash_link, "onclick", function ( event ) { self.delete_all_editors( event ); } );
|
||||
|
||||
if ( this.notebook.read_write ) {
|
||||
this.read_write = true;
|
||||
removeElementClass( "toolbar", "undisplayed" );
|
||||
|
||||
if ( this.notebook.trash_id ) {
|
||||
appendChildNodes( span, createDOM( "div", { "class": "link_area_item" },
|
||||
createDOM( "a", {
|
||||
"href": "/notebooks/" + this.notebook.trash_id + "?parent_id=" + this.notebook.object_id,
|
||||
"id": "trash_link",
|
||||
"title": "Look here for notes you've deleted."
|
||||
}, "trash" )
|
||||
) );
|
||||
} else if ( this.notebook.name == "trash" ) {
|
||||
appendChildNodes( span, createDOM( "div", { "class": "link_area_item current_trash_notebook_name" },
|
||||
createDOM( "a", {
|
||||
"href": location.href,
|
||||
"id": "trash_link",
|
||||
"title": "Look here for notes you've deleted."
|
||||
}, "trash" )
|
||||
) );
|
||||
|
||||
var header_area = getElement( "notebook_header_area" )
|
||||
removeElementClass( header_area, "current_notebook_name" );
|
||||
addElementClass( header_area, "current_trash_notebook_name" );
|
||||
|
||||
var border = getElement( "notebook_border" )
|
||||
removeElementClass( border, "current_notebook_name" );
|
||||
addElementClass( border, "current_trash_notebook_name" );
|
||||
}
|
||||
|
||||
connect( window, "onunload", function ( event ) { self.editor_focused( null, true ); } );
|
||||
connect( "bold", "onclick", function ( event ) { self.toggle_button( event, "bold" ); } );
|
||||
connect( "italic", "onclick", function ( event ) { self.toggle_button( event, "italic" ); } );
|
||||
|
@ -215,51 +148,20 @@ Wiki.prototype.populate = function ( result ) {
|
|||
);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
if ( !this.parent_id ) {
|
||||
connect( "all_notes_link", "onclick", function ( event ) {
|
||||
var all_notes_link = getElement( "all_notes_link" );
|
||||
if ( all_notes_link ) {
|
||||
connect( all_notes_link, "onclick", function ( event ) {
|
||||
self.load_editor( "all notes", "null" );
|
||||
event.stop();
|
||||
} );
|
||||
}
|
||||
|
||||
if ( this.notebook.name != "Luminotes" ) {
|
||||
connect( "download_html_link", "onclick", function ( event ) {
|
||||
var download_html_link = getElement( "download_html_link" );
|
||||
if ( download_html_link ) {
|
||||
connect( download_html_link, "onclick", function ( event ) {
|
||||
self.save_editor( null, true );
|
||||
} );
|
||||
}
|
||||
|
||||
// create an editor for each startup note in the received notebook, focusing the first one
|
||||
var focus = true;
|
||||
for ( var i in result.startup_notes ) {
|
||||
var note = result.startup_notes[ i ];
|
||||
if ( !note ) continue;
|
||||
this.startup_notes[ note.object_id ] = true;
|
||||
|
||||
// don't actually create an editor if a particular note was provided in the result
|
||||
if ( !result.note ) {
|
||||
var editor = this.create_editor( note.object_id, note.contents, note.deleted_from_id, note.revision, this.read_write, false, focus );
|
||||
this.open_editors[ note.title ] = editor;
|
||||
focus = false;
|
||||
}
|
||||
}
|
||||
|
||||
// if one particular note was provided, then just display an editor for that note
|
||||
var read_write = this.read_write;
|
||||
var revision_element = getElement( "revision" );
|
||||
if ( revision_element && revision_element.value ) read_write = false;
|
||||
|
||||
if ( result.note )
|
||||
this.create_editor(
|
||||
result.note.object_id,
|
||||
result.note.contents || getElement( "note_contents" ).value,
|
||||
result.note.deleted_from_id,
|
||||
result.note.revision,
|
||||
read_write, false, true
|
||||
);
|
||||
|
||||
if ( result.startup_notes.length == 0 && !result.note )
|
||||
this.display_empty_message();
|
||||
}
|
||||
|
||||
Wiki.prototype.background_clicked = function ( event ) {
|
||||
|
@ -288,7 +190,7 @@ Wiki.prototype.create_blank_editor = function ( event ) {
|
|||
}
|
||||
}
|
||||
|
||||
var editor = this.create_editor( undefined, undefined, undefined, undefined, this.read_write, true, true );
|
||||
var editor = this.create_editor( undefined, undefined, undefined, undefined, this.notebook.read_write, true, true );
|
||||
this.blank_editor_id = editor.id;
|
||||
}
|
||||
|
||||
|
@ -472,7 +374,7 @@ Wiki.prototype.parse_loaded_editor = function ( result, note_title, requested_re
|
|||
if ( requested_revision )
|
||||
var read_write = false; // show previous revisions as read-only
|
||||
else
|
||||
var read_write = this.read_write;
|
||||
var read_write = this.notebook.read_write;
|
||||
|
||||
var editor = this.create_editor( id, note_text, deleted_from_id, actual_revision, read_write, true, false );
|
||||
id = editor.id;
|
||||
|
@ -485,7 +387,7 @@ Wiki.prototype.parse_loaded_editor = function ( result, note_title, requested_re
|
|||
Wiki.prototype.create_editor = function ( id, note_text, deleted_from_id, revision, read_write, highlight, focus ) {
|
||||
var self = this;
|
||||
if ( isUndefinedOrNull( id ) ) {
|
||||
if ( this.read_write ) {
|
||||
if ( this.notebook.read_write ) {
|
||||
id = this.next_id;
|
||||
this.invoker.invoke( "/next_id", "POST", null,
|
||||
function( result ) { self.update_next_id( result ); }
|
||||
|
@ -496,7 +398,7 @@ Wiki.prototype.create_editor = function ( id, note_text, deleted_from_id, revisi
|
|||
}
|
||||
|
||||
// for read-only notes within read-write notebooks, tack the revision timestamp onto the start of the note text
|
||||
if ( !read_write && this.read_write && revision ) {
|
||||
if ( !read_write && this.notebook.read_write && revision ) {
|
||||
var short_revision = this.brief_revision( revision );
|
||||
note_text = "<p>Previous revision from " + short_revision + "</p>" + note_text;
|
||||
}
|
||||
|
@ -504,7 +406,7 @@ Wiki.prototype.create_editor = function ( id, note_text, deleted_from_id, revisi
|
|||
var startup = this.startup_notes[ id ];
|
||||
var editor = new Editor( id, this.notebook_id, note_text, deleted_from_id, revision, read_write, startup, highlight, focus );
|
||||
|
||||
if ( this.read_write ) {
|
||||
if ( this.notebook.read_write ) {
|
||||
connect( editor, "state_changed", this, "editor_state_changed" );
|
||||
connect( editor, "title_changed", this, "editor_title_changed" );
|
||||
connect( editor, "key_pressed", this, "editor_key_pressed" );
|
||||
|
@ -698,7 +600,7 @@ Wiki.prototype.hide_editor = function ( event, editor ) {
|
|||
|
||||
if ( editor ) {
|
||||
// before hiding an editor, save it
|
||||
if ( this.read_write )
|
||||
if ( this.notebook.read_write )
|
||||
this.save_editor( editor );
|
||||
|
||||
editor.shutdown();
|
||||
|
@ -724,7 +626,7 @@ Wiki.prototype.delete_editor = function ( event, editor ) {
|
|||
this.save_editor( editor, true );
|
||||
|
||||
var self = this;
|
||||
if ( this.read_write && editor.read_write ) {
|
||||
if ( this.notebook.read_write && editor.read_write ) {
|
||||
this.invoker.invoke( "/notebooks/delete_note", "POST", {
|
||||
"notebook_id": this.notebook_id,
|
||||
"note_id": editor.id
|
||||
|
@ -771,7 +673,7 @@ Wiki.prototype.undelete_editor_via_trash = function ( event, editor ) {
|
|||
|
||||
this.save_editor( editor, true );
|
||||
|
||||
if ( this.read_write && editor.read_write ) {
|
||||
if ( this.notebook.read_write && editor.read_write ) {
|
||||
var self = this;
|
||||
this.invoker.invoke( "/notebooks/undelete_note", "POST", {
|
||||
"notebook_id": editor.deleted_from_id,
|
||||
|
@ -794,7 +696,7 @@ Wiki.prototype.undelete_editor_via_undo = function( event, editor ) {
|
|||
this.clear_pulldowns();
|
||||
|
||||
if ( editor ) {
|
||||
if ( this.read_write && editor.read_write ) {
|
||||
if ( this.notebook.read_write && editor.read_write ) {
|
||||
var self = this;
|
||||
this.invoker.invoke( "/notebooks/undelete_note", "POST", {
|
||||
"notebook_id": this.notebook_id,
|
||||
|
@ -908,7 +810,7 @@ Wiki.prototype.display_search_results = function ( result ) {
|
|||
}
|
||||
|
||||
// otherwise, create an editor for the one note
|
||||
this.create_editor( note.object_id, note.contents, note.deleted_from_id, note.revision, this.read_write, true, true );
|
||||
this.create_editor( note.object_id, note.contents, note.deleted_from_id, note.revision, this.notebook.read_write, true, true );
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1077,7 +979,7 @@ Wiki.prototype.delete_all_editors = function ( event ) {
|
|||
|
||||
this.startup_notes = new Array();
|
||||
|
||||
if ( this.read_write ) {
|
||||
if ( this.notebook.read_write ) {
|
||||
var self = this;
|
||||
this.invoker.invoke( "/notebooks/delete_all_notes", "POST", {
|
||||
"notebook_id": this.notebook_id
|
||||
|
@ -1291,7 +1193,7 @@ function Changes_pulldown( wiki, notebook_id, invoker, editor ) {
|
|||
revisions_list.reverse();
|
||||
|
||||
var self = this;
|
||||
for ( var i = 0; i < revisions_list.length; ++i ) {
|
||||
for ( var i = 0; i < revisions_list.length - 1; ++i ) { // -1 to skip the oldest revision
|
||||
var revision = revisions_list[ i ];
|
||||
var short_revision = this.wiki.brief_revision( revision );
|
||||
var href = "/notebooks/" + this.notebook_id + "?" + queryString(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from Page import Page
|
||||
from Tags import Div, H2, P, A, Ul, Li, Strong
|
||||
from Tags import Div, H2, P, A, Ul, Li, Strong, Noscript
|
||||
|
||||
|
||||
class Error_page( Page ):
|
||||
|
@ -20,6 +20,16 @@ class Error_page( Page ):
|
|||
title,
|
||||
Div(
|
||||
H2( title ),
|
||||
Noscript(
|
||||
P(
|
||||
Strong(
|
||||
u"""
|
||||
Please enable JavaScript in your web browser. JavaScript is necessary for many Luminotes
|
||||
features to work properly.
|
||||
""",
|
||||
),
|
||||
),
|
||||
),
|
||||
P(
|
||||
u"Something went wrong! If you care, please",
|
||||
A( "let me know about it.", href = "mailto:%s" % support_email ),
|
||||
|
@ -34,12 +44,6 @@ class Error_page( Page ):
|
|||
P(
|
||||
u"Thanks!",
|
||||
),
|
||||
P(
|
||||
Strong( u"P.S." ),
|
||||
u"""
|
||||
If JavaScript isn't enabled in your browser, please enable it.
|
||||
""",
|
||||
),
|
||||
class_ = u"error_box",
|
||||
),
|
||||
include_js = False,
|
||||
|
|
12
view/Json.py
12
view/Json.py
|
@ -5,11 +5,21 @@ from datetime import datetime, date
|
|||
|
||||
|
||||
class Json( JSONEncoder ):
|
||||
def __init__( self, **kwargs ):
|
||||
def __init__( self, *args, **kwargs ):
|
||||
JSONEncoder.__init__( self )
|
||||
|
||||
if args and kwargs:
|
||||
raise ValueError( "Please provide either args or kwargs, not both." )
|
||||
|
||||
self.__args = args
|
||||
self.__kwargs = kwargs
|
||||
|
||||
def __str__( self ):
|
||||
if self.__args:
|
||||
if len( self.__args ) == 1:
|
||||
return self.encode( self.__args[ 0 ] )
|
||||
return self.encode( self.__args )
|
||||
|
||||
return self.encode( self.__kwargs )
|
||||
|
||||
def default( self, obj ):
|
||||
|
|
|
@ -1,17 +1,73 @@
|
|||
from Tags import Div, H3, A
|
||||
from Tags import Div, Span, H4, A
|
||||
|
||||
|
||||
class Link_area( Div ):
|
||||
def __init__( self, notebook_id ):
|
||||
def __init__( self, notebooks, notebook, parent_id ):
|
||||
Div.__init__(
|
||||
self,
|
||||
Div(
|
||||
H4( u"this notebook" ),
|
||||
( parent_id is None ) and Div(
|
||||
A(
|
||||
u"all notes",
|
||||
href = u"/notebooks/%s" % notebook.object_id,
|
||||
id = u"all_notes_link",
|
||||
title = u"View a list of all notes in this notebook.",
|
||||
),
|
||||
class_ = u"link_area_item",
|
||||
) or None,
|
||||
|
||||
( notebook.name != u"Luminotes" ) and Div(
|
||||
A(
|
||||
u"download as html",
|
||||
href = u"/notebooks/download_html/%s" % notebook.object_id,
|
||||
id = u"download_html_link",
|
||||
title = u"Download a stand-alone copy of the entire wiki notebook.",
|
||||
),
|
||||
class_ = u"link_area_item",
|
||||
) or None,
|
||||
|
||||
notebook.read_write and Span(
|
||||
notebook.trash_id and Div(
|
||||
A(
|
||||
u"trash",
|
||||
href = u"/notebooks/%s?parent_id=%s" % ( notebook.trash_id, notebook.object_id ),
|
||||
id = u"trash_link",
|
||||
title = u"Look here for notes you've deleted.",
|
||||
),
|
||||
class_ = u"link_area_item",
|
||||
) or None,
|
||||
|
||||
( notebook.name == u"trash" ) and Div(
|
||||
A(
|
||||
u"trash",
|
||||
href = u"#",
|
||||
id = u"trash_link",
|
||||
title = u"Look here for notes you've deleted.",
|
||||
),
|
||||
class_ = u"link_area_item current_trash_notebook_name",
|
||||
) or None,
|
||||
) or None,
|
||||
|
||||
id = u"this_notebook_area",
|
||||
),
|
||||
|
||||
Div(
|
||||
[ Span(
|
||||
( index == 0 ) and H4( u"notebooks" ) or None,
|
||||
Div(
|
||||
A(
|
||||
nb.name,
|
||||
href = u"/notebooks/%s" % nb.object_id,
|
||||
id = u"notebook_%s" % nb.object_id,
|
||||
),
|
||||
class_ = ( nb.object_id == notebook.object_id ) and u"link_area_item current_notebook_name" or u"link_area_item",
|
||||
) ) for ( index, nb ) in enumerate( notebooks ) if nb.name not in ( u"Luminotes", u"trash" ) ],
|
||||
id = u"notebooks_area",
|
||||
),
|
||||
|
||||
Div(
|
||||
id = u"storage_usage_area",
|
||||
),
|
||||
id = u"link_area",
|
||||
)
|
||||
|
|
|
@ -1,43 +1,86 @@
|
|||
from cgi import escape
|
||||
from Page import Page
|
||||
from Tags import Input, Div, Noscript, H2, H4, A, Br
|
||||
from Tags import Input, Div, Span, H2, H4, A, Br, Strong, Script
|
||||
from Search_form import Search_form
|
||||
from User_area import User_area
|
||||
from Link_area import Link_area
|
||||
from Toolbar import Toolbar
|
||||
from Json import Json
|
||||
|
||||
|
||||
class Main_page( Page ):
|
||||
def __init__( self, notebook_id = None, note_id = None, parent_id = None, revision = None, note_contents = None ):
|
||||
title = None
|
||||
note_contents = note_contents and escape( note_contents, quote = True ) or ""
|
||||
def __init__(
|
||||
self,
|
||||
user,
|
||||
rate_plan,
|
||||
notebooks,
|
||||
notebook,
|
||||
parent_id = None,
|
||||
login_url = None,
|
||||
logout_url = None,
|
||||
startup_notes = None,
|
||||
note = None,
|
||||
note_read_write = True,
|
||||
):
|
||||
startup_note_ids = [ startup_note.object_id for startup_note in startup_notes ]
|
||||
|
||||
static_notes = Div(
|
||||
note and Div(
|
||||
note.contents,
|
||||
id = "static_note_%s" % note.object_id,
|
||||
) or
|
||||
[ Div(
|
||||
startup_note.contents,
|
||||
id = "static_note_%s" % startup_note.object_id
|
||||
) for startup_note in startup_notes ],
|
||||
id = "static_notes",
|
||||
)
|
||||
|
||||
# Since the contents of these notes are included in the static_notes section below, don't
|
||||
# include them again in the hidden fields here. Accomplish this by making custom dicts for
|
||||
# sending to the client.
|
||||
startup_note_dicts = [ {
|
||||
u"object_id" : startup_note.object_id,
|
||||
u"revision" : startup_note.revision,
|
||||
u"deleted_from_id" : startup_note.deleted_from_id,
|
||||
} for startup_note in startup_notes ]
|
||||
|
||||
if note:
|
||||
note_dict = {
|
||||
u"object_id" : note.object_id,
|
||||
u"revision" : note.revision,
|
||||
u"deleted_from_id" : note.deleted_from_id,
|
||||
}
|
||||
|
||||
def json( string ):
|
||||
return escape( unicode( Json( string ) ), quote = True )
|
||||
|
||||
title = None
|
||||
Page.__init__(
|
||||
self,
|
||||
title,
|
||||
Input( type = u"hidden", name = u"notebook_id", id = u"notebook_id", value = notebook_id or "" ),
|
||||
Input( type = u"hidden", name = u"note_id", id = u"note_id", value = note_id or "" ),
|
||||
Input( type = u"hidden", name = u"storage_bytes", id = u"storage_bytes", value = user.storage_bytes ),
|
||||
Input( type = u"hidden", name = u"rate_plan", id = u"rate_plan", value = json( rate_plan ) ),
|
||||
Input( type = u"hidden", name = u"notebooks", id = u"notebooks", value = json( notebooks ) ),
|
||||
Input( type = u"hidden", name = u"notebook_id", id = u"notebook_id", value = notebook.object_id ),
|
||||
Input( type = u"hidden", name = u"parent_id", id = u"parent_id", value = parent_id or "" ),
|
||||
Input( type = u"hidden", name = u"revision", id = u"revision", value = revision or "" ),
|
||||
Input( type = u"hidden", name = u"note_contents", id = u"note_contents", value = note_contents ),
|
||||
Input( type = u"hidden", name = u"startup_notes", id = u"startup_notes", value = json( startup_note_dicts ) ),
|
||||
Input( type = u"hidden", name = u"note", id = u"note", value = note and json( note_dict ) or "" ),
|
||||
Input( type = u"hidden", name = u"note_read_write", id = u"note_read_write", value = json( note_read_write ) ),
|
||||
Div(
|
||||
id = u"status_area",
|
||||
),
|
||||
Div(
|
||||
Div(
|
||||
Br(),
|
||||
Toolbar(),
|
||||
Toolbar( hide_toolbar = not notebook.read_write ),
|
||||
id = u"toolbar_area",
|
||||
),
|
||||
Div(
|
||||
Link_area( notebook_id ),
|
||||
id = u"link_area",
|
||||
),
|
||||
Link_area( notebooks, notebook, parent_id ),
|
||||
Div(
|
||||
Div(
|
||||
Div(
|
||||
Div(
|
||||
id = u"user_area",
|
||||
),
|
||||
User_area( user, login_url, logout_url ),
|
||||
Div(
|
||||
Search_form(),
|
||||
id = u"search_area",
|
||||
|
@ -52,23 +95,34 @@ class Main_page( Page ):
|
|||
id = u"top_area",
|
||||
),
|
||||
Div(
|
||||
Strong( notebook.name ),
|
||||
parent_id and Span(
|
||||
u" | ",
|
||||
A( u"empty trash", href = u"/notebooks/%s" % notebook.object_id, id = u"empty_trash_link" ),
|
||||
u" | ",
|
||||
A( u"return to notebook", href = u"/notebooks/%s" % parent_id ),
|
||||
) or None,
|
||||
id = u"notebook_header_area",
|
||||
class_ = u"current_notebook_name",
|
||||
class_ = ( notebook.name == u"trash" ) and u"current_trash_notebook_name" or u"current_notebook_name",
|
||||
),
|
||||
Div(
|
||||
Div(
|
||||
Div(
|
||||
id = u"notes",
|
||||
),
|
||||
static_notes,
|
||||
# Sort of simulate the <noscript> tag by hiding the static version of the notes.
|
||||
# This code won't be executed if JavaScript is disabled. I'm not actually using
|
||||
# <noscript> because I want to be able to programmatically read the hidden static
|
||||
# notes when JavaScript is enabled.
|
||||
Script(
|
||||
u"document.getElementById( 'static_notes' ).style.display = 'none';",
|
||||
type = u"text/javascript",
|
||||
),
|
||||
id = u"notebook_background",
|
||||
),
|
||||
id = u"notebook_border",
|
||||
class_ = u"current_notebook_name",
|
||||
),
|
||||
Noscript(
|
||||
Div( file( u"static/html/about.html" ).read() ),
|
||||
Div( file( u"static/html/features.html" ).read().replace( u"href=", u"disabled=" ) ),
|
||||
Div( file( u"static/html/no javascript.html" ).read() ),
|
||||
class_ = ( notebook.name == u"trash" ) and u"current_trash_notebook_name" or u"current_notebook_name",
|
||||
),
|
||||
id = u"center_area",
|
||||
),
|
||||
|
|
|
@ -2,7 +2,7 @@ from Tags import P, Div, A, Input, Span, Br
|
|||
|
||||
|
||||
class Toolbar( Div ):
|
||||
def __init__( self ):
|
||||
def __init__( self, hide_toolbar = False ):
|
||||
Div.__init__(
|
||||
self,
|
||||
Div(
|
||||
|
@ -47,5 +47,5 @@ class Toolbar( Div ):
|
|||
class_ = u"button_wrapper",
|
||||
),
|
||||
id = u"toolbar",
|
||||
class_ = u"undisplayed", # start out as hidden, and then shown in the browser if the current notebook is read-write
|
||||
class_ = hide_toolbar and u"undisplayed" or None,
|
||||
)
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
from Tags import Div, H4, A
|
||||
|
||||
|
||||
class User_area( Div ):
|
||||
def __init__( self, user, login_url, logout_url ):
|
||||
Div.__init__(
|
||||
self,
|
||||
( login_url and user.username == u"anonymous" ) and Div(
|
||||
A(
|
||||
u"login",
|
||||
href = login_url,
|
||||
id = u"login_link",
|
||||
),
|
||||
) or Div(
|
||||
u"logged in as %s" % ( user.username or u"a guest" ),
|
||||
" | ",
|
||||
A(
|
||||
u"logout",
|
||||
href = logout_url,
|
||||
id = u"logout_link",
|
||||
),
|
||||
),
|
||||
id = u"user_area",
|
||||
)
|
Reference in New Issue