witten
/
luminotes
Archived
1
0
Fork 0

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:
Dan Helfman 2007-10-16 21:37:12 +00:00
parent 3e0dbe0509
commit 613ee8a217
16 changed files with 558 additions and 458 deletions

View File

@ -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() )

View File

@ -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 )

View File

@ -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 ):
"""

View File

@ -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:

View File

@ -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

View File

@ -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&note_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&note_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&note_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

View File

@ -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"

View File

@ -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;
}

View File

@ -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.

View File

@ -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(

View File

@ -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,

View File

@ -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 ):

View File

@ -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",
)

View File

@ -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",
),

View File

@ -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,
)

24
view/User_area.py Normal file
View File

@ -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",
)