Made upgrade page dynamically generated on the server instead of static html.
This allows things like only displaying subscription buttons if you're logged in.
This commit is contained in:
parent
08cd7057f3
commit
8f00cceb94
2
NEWS
2
NEWS
|
@ -3,6 +3,8 @@
|
||||||
* Feature to preview a notebook as a viewer would see it.
|
* Feature to preview a notebook as a viewer would see it.
|
||||||
* Note revisions list now include username of the user who made that
|
* Note revisions list now include username of the user who made that
|
||||||
revision.
|
revision.
|
||||||
|
* If you go to luminotes.com when you're logged in, you'll be automatically
|
||||||
|
redirected to your first notebook.
|
||||||
* Fixed bug where passwords with special characters broke password hashing.
|
* Fixed bug where passwords with special characters broke password hashing.
|
||||||
* Fixed bug that prevented you from opening a note with a title that looked
|
* Fixed bug that prevented you from opening a note with a title that looked
|
||||||
like an external URL.
|
like an external URL.
|
||||||
|
|
|
@ -28,22 +28,32 @@ settings = {
|
||||||
"name": "free",
|
"name": "free",
|
||||||
"storage_quota_bytes": 30 * MEGABYTE,
|
"storage_quota_bytes": 30 * MEGABYTE,
|
||||||
"notebook_collaboration": False,
|
"notebook_collaboration": False,
|
||||||
|
"fee": None,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "basic",
|
"name": "basic",
|
||||||
"storage_quota_bytes": 250 * MEGABYTE,
|
"storage_quota_bytes": 250 * MEGABYTE,
|
||||||
"notebook_collaboration": True,
|
"notebook_collaboration": True,
|
||||||
|
"fee": 5,
|
||||||
|
"button":
|
||||||
|
"""
|
||||||
|
""",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "standard",
|
"name": "standard",
|
||||||
"storage_quota_bytes": 500 * MEGABYTE,
|
"storage_quota_bytes": 500 * MEGABYTE,
|
||||||
"notebook_collaboration": True,
|
"notebook_collaboration": True,
|
||||||
|
"fee": 9,
|
||||||
|
"button":
|
||||||
|
"""
|
||||||
|
""",
|
||||||
},
|
},
|
||||||
{
|
# {
|
||||||
"name": "premium",
|
# "name": "premium",
|
||||||
"storage_quota_bytes": 2000 * MEGABYTE,
|
# "storage_quota_bytes": 2000 * MEGABYTE,
|
||||||
"notebook_collaboration": True,
|
# "notebook_collaboration": True,
|
||||||
},
|
# "fee": 19,
|
||||||
|
# },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import cherrypy
|
||||||
|
|
||||||
from Expose import expose
|
from Expose import expose
|
||||||
from Expire import strongly_expire
|
from Expire import strongly_expire
|
||||||
from Validate import validate, Valid_int
|
from Validate import validate, Valid_int, Valid_string
|
||||||
from Notebooks import Notebooks
|
from Notebooks import Notebooks
|
||||||
from Users import Users, grab_user_id
|
from Users import Users, grab_user_id
|
||||||
from Database import Valid_id
|
from Database import Valid_id
|
||||||
|
@ -11,6 +11,7 @@ from model.Notebook import Notebook
|
||||||
from model.User import User
|
from model.User import User
|
||||||
from view.Main_page import Main_page
|
from view.Main_page import Main_page
|
||||||
from view.Notebook_rss import Notebook_rss
|
from view.Notebook_rss import Notebook_rss
|
||||||
|
from view.Upgrade_note import Upgrade_note
|
||||||
from view.Json import Json
|
from view.Json import Json
|
||||||
from view.Error_page import Error_page
|
from view.Error_page import Error_page
|
||||||
from view.Not_found_page import Not_found_page
|
from view.Not_found_page import Not_found_page
|
||||||
|
@ -47,9 +48,10 @@ class Root( object ):
|
||||||
@validate(
|
@validate(
|
||||||
note_title = unicode,
|
note_title = unicode,
|
||||||
invite_id = Valid_id( none_okay = True ),
|
invite_id = Valid_id( none_okay = True ),
|
||||||
|
after_login = Valid_string( min = 0, max = 100 ),
|
||||||
user_id = Valid_id( none_okay = True ),
|
user_id = Valid_id( none_okay = True ),
|
||||||
)
|
)
|
||||||
def default( self, note_title, invite_id = None, user_id = None ):
|
def default( self, note_title, invite_id = None, after_login = None, user_id = None ):
|
||||||
"""
|
"""
|
||||||
Convenience method for accessing a note in the main notebook by name rather than by note id.
|
Convenience method for accessing a note in the main notebook by name rather than by note id.
|
||||||
|
|
||||||
|
@ -57,6 +59,8 @@ class Root( object ):
|
||||||
@param note_title: title of the note to return
|
@param note_title: title of the note to return
|
||||||
@type invite_id: unicode
|
@type invite_id: unicode
|
||||||
@param invite_id: id of the invite used to get to this note (optional)
|
@param invite_id: id of the invite used to get to this note (optional)
|
||||||
|
@type after_login: unicode
|
||||||
|
@param after_login: URL to redirect to after login (optional, must start with "/")
|
||||||
@rtype: unicode
|
@rtype: unicode
|
||||||
@return: rendered HTML page
|
@return: rendered HTML page
|
||||||
"""
|
"""
|
||||||
|
@ -85,6 +89,8 @@ class Root( object ):
|
||||||
result.update( self.__notebooks.contents( main_notebook.object_id, user_id = user_id, note_id = note.object_id ) )
|
result.update( self.__notebooks.contents( main_notebook.object_id, user_id = user_id, note_id = note.object_id ) )
|
||||||
if invite_id:
|
if invite_id:
|
||||||
result[ "invite_id" ] = invite_id
|
result[ "invite_id" ] = invite_id
|
||||||
|
if after_login and after_login.startswith( u"/" ):
|
||||||
|
result[ "after_login" ] = after_login
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -144,7 +150,7 @@ class Root( object ):
|
||||||
if user:
|
if user:
|
||||||
first_notebook = self.__database.select_one( Notebook, user.sql_load_notebooks( parents_only = True, undeleted_only = True ) )
|
first_notebook = self.__database.select_one( Notebook, user.sql_load_notebooks( parents_only = True, undeleted_only = True ) )
|
||||||
if first_notebook:
|
if first_notebook:
|
||||||
return dict( redirect = "%s/notebooks/%s" % ( https_url, first_notebook.object_id ) )
|
return dict( redirect = u"%s/notebooks/%s" % ( https_url, first_notebook.object_id ) )
|
||||||
|
|
||||||
# if the user is logged in and not using https, then redirect to the https version of the page (if available)
|
# if the user is logged in and not using https, then redirect to the https version of the page (if available)
|
||||||
if https_url and cherrypy.request.remote_addr != https_proxy_ip:
|
if https_url and cherrypy.request.remote_addr != https_proxy_ip:
|
||||||
|
@ -231,6 +237,40 @@ class Root( object ):
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@expose( view = Main_page )
|
||||||
|
@grab_user_id
|
||||||
|
@validate(
|
||||||
|
user_id = Valid_id( none_okay = True ),
|
||||||
|
)
|
||||||
|
def upgrade( self, user_id = None ):
|
||||||
|
"""
|
||||||
|
Provide the information necessary to display the Luminotes upgrade page.
|
||||||
|
"""
|
||||||
|
anonymous = self.__database.select_one( User, User.sql_load_by_username( u"anonymous" ) )
|
||||||
|
if anonymous:
|
||||||
|
main_notebook = self.__database.select_one( Notebook, anonymous.sql_load_notebooks( undeleted_only = True ) )
|
||||||
|
else:
|
||||||
|
main_notebook = None
|
||||||
|
|
||||||
|
https_url = self.__settings[ u"global" ].get( u"luminotes.https_url" )
|
||||||
|
result = self.__users.current( user_id )
|
||||||
|
result[ "notebook" ] = main_notebook
|
||||||
|
result[ "startup_notes" ] = self.__database.select_many( Note, main_notebook.sql_load_startup_notes() )
|
||||||
|
result[ "total_notes_count" ] = self.__database.select_one( Note, main_notebook.sql_count_notes() )
|
||||||
|
result[ "note_read_write" ] = False
|
||||||
|
result[ "notes" ] = [ Note.create(
|
||||||
|
object_id = u"upgrade",
|
||||||
|
contents = unicode( Upgrade_note(
|
||||||
|
self.__settings[ u"global" ].get( u"luminotes.rate_plans", [] ),
|
||||||
|
https_url,
|
||||||
|
user_id,
|
||||||
|
) ),
|
||||||
|
notebook_id = main_notebook.object_id,
|
||||||
|
) ]
|
||||||
|
result[ "invites" ] = []
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
# TODO: move this method to controller.Notebooks, and maybe give it a more sensible name
|
# TODO: move this method to controller.Notebooks, and maybe give it a more sensible name
|
||||||
@expose( view = Json )
|
@expose( view = Json )
|
||||||
def next_id( self ):
|
def next_id( self ):
|
||||||
|
|
|
@ -331,8 +331,9 @@ class Users( object ):
|
||||||
password = Valid_string( min = 1, max = 30 ),
|
password = Valid_string( min = 1, max = 30 ),
|
||||||
login_button = unicode,
|
login_button = unicode,
|
||||||
invite_id = Valid_id( none_okay = True ),
|
invite_id = Valid_id( none_okay = True ),
|
||||||
|
after_login = Valid_string( min = 0, max = 100 ),
|
||||||
)
|
)
|
||||||
def login( self, username, password, login_button, invite_id = None ):
|
def login( self, username, password, login_button, invite_id = None, after_login = None ):
|
||||||
"""
|
"""
|
||||||
Attempt to authenticate the user. If successful, associate the given user with the current
|
Attempt to authenticate the user. If successful, associate the given user with the current
|
||||||
session.
|
session.
|
||||||
|
@ -343,6 +344,8 @@ class Users( object ):
|
||||||
@param password: the user's password
|
@param password: the user's password
|
||||||
@type invite_id: unicode
|
@type invite_id: unicode
|
||||||
@param invite_id: id of invite to redeem upon login (optional)
|
@param invite_id: id of invite to redeem upon login (optional)
|
||||||
|
@type after_login: unicode
|
||||||
|
@param after_login: URL to redirect to after login (optional, must start with "/")
|
||||||
@rtype: json dict
|
@rtype: json dict
|
||||||
@return: { 'redirect': url, 'authenticated': userdict }
|
@return: { 'redirect': url, 'authenticated': userdict }
|
||||||
@raise Authentication_error: invalid username or password
|
@raise Authentication_error: invalid username or password
|
||||||
|
@ -363,6 +366,9 @@ class Users( object ):
|
||||||
|
|
||||||
self.convert_invite_to_access( invite, user.object_id )
|
self.convert_invite_to_access( invite, user.object_id )
|
||||||
redirect = u"/notebooks/%s" % invite.notebook_id
|
redirect = u"/notebooks/%s" % invite.notebook_id
|
||||||
|
# if there's an after_login URL, redirect to it
|
||||||
|
elif after_login and after_login.startswith( "/" ):
|
||||||
|
redirect = after_login
|
||||||
# otherwise, just redirect to the user's first notebook (if any)
|
# otherwise, just redirect to the user's first notebook (if any)
|
||||||
elif first_notebook:
|
elif first_notebook:
|
||||||
redirect = u"/notebooks/%s" % first_notebook.object_id
|
redirect = u"/notebooks/%s" % first_notebook.object_id
|
||||||
|
|
|
@ -314,10 +314,16 @@ class Test_controller( object ):
|
||||||
{
|
{
|
||||||
u"name": u"super",
|
u"name": u"super",
|
||||||
u"storage_quota_bytes": 1337,
|
u"storage_quota_bytes": 1337,
|
||||||
|
u"notebook_collaboration": True,
|
||||||
|
u"fee": 1.99,
|
||||||
|
u"button": u"[subscribe here user %s!] button",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
u"name": "extra super",
|
u"name": "extra super",
|
||||||
u"storage_quota_bytes": 31337,
|
u"storage_quota_bytes": 31337,
|
||||||
|
u"notebook_collaboration": True,
|
||||||
|
u"fee": 199.99,
|
||||||
|
u"button": u"[or here user %s!] button",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -117,6 +117,36 @@ class Test_root( Test_controller ):
|
||||||
assert result[ u"invite_id" ] == u"whee"
|
assert result[ u"invite_id" ] == u"whee"
|
||||||
assert result[ u"user" ].object_id == self.anonymous.object_id
|
assert result[ u"user" ].object_id == self.anonymous.object_id
|
||||||
|
|
||||||
|
def test_default_with_after_login( self ):
|
||||||
|
after_login = "/foo/bar"
|
||||||
|
|
||||||
|
result = self.http_get(
|
||||||
|
"/my_note?after_login=%s" % after_login,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result
|
||||||
|
assert result[ u"notes" ]
|
||||||
|
assert len( result[ u"notes" ] ) == 1
|
||||||
|
assert result[ u"notes" ][ 0 ].object_id == self.anon_note.object_id
|
||||||
|
assert result[ u"notebook" ].object_id == self.anon_notebook.object_id
|
||||||
|
assert result[ u"after_login" ] == after_login
|
||||||
|
assert result[ u"user" ].object_id == self.anonymous.object_id
|
||||||
|
|
||||||
|
def test_default_with_after_login_with_full_url( self ):
|
||||||
|
after_login = "http://example.com/foo/bar"
|
||||||
|
|
||||||
|
result = self.http_get(
|
||||||
|
"/my_note?after_login=%s" % after_login,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result
|
||||||
|
assert result[ u"notes" ]
|
||||||
|
assert len( result[ u"notes" ] ) == 1
|
||||||
|
assert result[ u"notes" ][ 0 ].object_id == self.anon_note.object_id
|
||||||
|
assert result[ u"notebook" ].object_id == self.anon_notebook.object_id
|
||||||
|
assert result.get( u"after_login" ) is None
|
||||||
|
assert result[ u"user" ].object_id == self.anonymous.object_id
|
||||||
|
|
||||||
def test_default_after_login( self ):
|
def test_default_after_login( self ):
|
||||||
self.login()
|
self.login()
|
||||||
|
|
||||||
|
@ -205,6 +235,72 @@ class Test_root( Test_controller ):
|
||||||
assert u"error" not in result
|
assert u"error" not in result
|
||||||
assert result[ u"notebook" ].object_id == self.privacy_notebook.object_id
|
assert result[ u"notebook" ].object_id == self.privacy_notebook.object_id
|
||||||
|
|
||||||
|
def test_upgrade( self ):
|
||||||
|
result = self.http_get( "/upgrade" )
|
||||||
|
|
||||||
|
assert result[ u"user" ].username == u"anonymous"
|
||||||
|
assert len( result[ u"notebooks" ] ) == 4
|
||||||
|
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"notebooks" ][ 0 ].owner == False
|
||||||
|
|
||||||
|
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" ] ) == 0
|
||||||
|
assert result[ u"note_read_write" ] is False
|
||||||
|
|
||||||
|
assert result[ u"notes" ]
|
||||||
|
assert len( result[ u"notes" ] ) == 1
|
||||||
|
assert result[ u"notes" ][ 0 ].title == u"upgrade your wiki"
|
||||||
|
assert result[ u"notes" ][ 0 ].notebook_id == self.anon_notebook.object_id
|
||||||
|
|
||||||
|
contents = result[ u"notes" ][ 0 ].contents
|
||||||
|
assert u"upgrade" in contents
|
||||||
|
assert u"Super" in contents
|
||||||
|
assert u"Extra super" in contents
|
||||||
|
|
||||||
|
# since the user is not logged in, no subscription buttons should be shown
|
||||||
|
assert u"button" not in contents
|
||||||
|
|
||||||
|
def test_upgrade_after_login( self ):
|
||||||
|
self.login()
|
||||||
|
|
||||||
|
result = self.http_get( "/upgrade", session_id = self.session_id )
|
||||||
|
|
||||||
|
assert result[ u"user" ].username == self.username
|
||||||
|
assert len( result[ u"notebooks" ] ) == 5
|
||||||
|
assert result[ u"notebooks" ][ 0 ].object_id == self.notebook.object_id
|
||||||
|
assert result[ u"notebooks" ][ 0 ].name == self.notebook.name
|
||||||
|
assert result[ u"notebooks" ][ 0 ].read_write == False
|
||||||
|
assert result[ u"notebooks" ][ 0 ].owner == False
|
||||||
|
|
||||||
|
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" ] ) == 0
|
||||||
|
assert result[ u"note_read_write" ] is False
|
||||||
|
|
||||||
|
assert result[ u"notes" ]
|
||||||
|
assert len( result[ u"notes" ] ) == 1
|
||||||
|
assert result[ u"notes" ][ 0 ].title == u"upgrade your wiki"
|
||||||
|
assert result[ u"notes" ][ 0 ].notebook_id == self.anon_notebook.object_id
|
||||||
|
|
||||||
|
contents = result[ u"notes" ][ 0 ].contents
|
||||||
|
assert u"upgrade" in contents
|
||||||
|
assert u"Super" in contents
|
||||||
|
assert u"Extra super" in contents
|
||||||
|
|
||||||
|
# since the user is logged in, subscription buttons should be shown
|
||||||
|
assert u"button" in contents
|
||||||
|
|
||||||
def test_next_id( self ):
|
def test_next_id( self ):
|
||||||
result = self.http_get( "/next_id" )
|
result = self.http_get( "/next_id" )
|
||||||
|
|
||||||
|
|
|
@ -457,6 +457,30 @@ class Test_users( Test_controller ):
|
||||||
assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].object_id )
|
assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].object_id )
|
||||||
assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].trash_id )
|
assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].trash_id )
|
||||||
|
|
||||||
|
def test_current_after_login_with_after_login( self ):
|
||||||
|
after_login = u"/foo/bar"
|
||||||
|
|
||||||
|
result = self.http_post( "/users/login", dict(
|
||||||
|
username = self.username2,
|
||||||
|
password = self.password2,
|
||||||
|
after_login = after_login,
|
||||||
|
login_button = u"login",
|
||||||
|
) )
|
||||||
|
|
||||||
|
assert result[ u"redirect" ] == after_login
|
||||||
|
|
||||||
|
def test_current_after_login_with_after_login_with_full_url( self ):
|
||||||
|
after_login = u"http://this_url/does/not/start/with/a/slash"
|
||||||
|
|
||||||
|
result = self.http_post( "/users/login", dict(
|
||||||
|
username = self.username2,
|
||||||
|
password = self.password2,
|
||||||
|
after_login = after_login,
|
||||||
|
login_button = u"login",
|
||||||
|
) )
|
||||||
|
|
||||||
|
assert result[ u"redirect" ] == u"/"
|
||||||
|
|
||||||
def test_update_storage( self ):
|
def test_update_storage( self ):
|
||||||
previous_revision = self.user.revision
|
previous_revision = self.user.revision
|
||||||
|
|
||||||
|
|
|
@ -138,9 +138,30 @@ ol li {
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#upgrade_table_area {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#upgrade_login_text {
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
#upgrade_table {
|
#upgrade_table {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
border: 1px solid #999999;
|
border: 1px solid #999999;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#upgrade_table th {
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#upgrade_table td {
|
||||||
|
text-align: center;
|
||||||
|
background-color: #fafafa;
|
||||||
|
padding: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#upgrade_table .plan_name {
|
#upgrade_table .plan_name {
|
||||||
|
@ -155,19 +176,41 @@ ol li {
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
#upgrade_table .price_text {
|
#upgrade_table_small {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border: 1px solid #999999;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#upgrade_table_small th {
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#upgrade_table_small td {
|
||||||
|
text-align: center;
|
||||||
|
background-color: #fafafa;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#upgrade_table_small .plan_name {
|
||||||
|
width: 33%;
|
||||||
|
text-align: center;
|
||||||
|
background-color: #d0e0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price_text {
|
||||||
color: #ff6600;
|
color: #ff6600;
|
||||||
}
|
}
|
||||||
|
|
||||||
#upgrade_table .month_text {
|
.month_text {
|
||||||
padding-top: 0.5em;
|
padding-top: 0.5em;
|
||||||
font-size: 75%;
|
font-size: 75%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#upgrade_table td {
|
.subscribe_form {
|
||||||
text-align: center;
|
margin-top: 0.5em;
|
||||||
background-color: #fafafa;
|
margin-bottom: 0;
|
||||||
padding: 0.5em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.thumbnail_left {
|
.thumbnail_left {
|
||||||
|
|
|
@ -16,10 +16,11 @@ whenever you want.</p>
|
||||||
|
|
||||||
<b>Does this cost me anything?</b><br />
|
<b>Does this cost me anything?</b><br />
|
||||||
|
|
||||||
<p>Nope, use of your personal Luminotes wiki is completely free. Soon you will
|
<p>Use of your personal Luminotes wiki is completely free. You also have the
|
||||||
also be able to <a href="/notebooks/%s?note_id=new">upgrade</a> your Luminotes
|
option of <a href="/upgrade" target="_top">upgrading</a> your Luminotes
|
||||||
account to get notebook sharing features and additional storage space. But the
|
account to get notebook sharing features and additional storage space for a
|
||||||
features you're using now will always remain free.</p>
|
reasonable subscription fee. But the features you're using now will always
|
||||||
|
remain free.</p>
|
||||||
|
|
||||||
<b>What does Luminotes run on?</b><br />
|
<b>What does Luminotes run on?</b><br />
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
<a href="/notebooks/%s?note_id=new">faq</a> -
|
<a href="/notebooks/%s?note_id=new">faq</a> -
|
||||||
<a href="/blog" target="_top">blog</a> -
|
<a href="/blog" target="_top">blog</a> -
|
||||||
<a href="/guide" target="_top">user guide</a> -
|
<a href="/guide" target="_top">user guide</a> -
|
||||||
|
<a href="/upgrade" target="_top">pricing</a> -
|
||||||
<a href="/notebooks/%s?note_id=new">meet the team</a> -
|
<a href="/notebooks/%s?note_id=new">meet the team</a> -
|
||||||
<a href="/notebooks/%s?note_id=new">contact info</a> -
|
<a href="/notebooks/%s?note_id=new">contact info</a> -
|
||||||
<a href="/privacy" target="_top">privacy policy</a>
|
<a href="/privacy" target="_top">privacy</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
<h3>upgrade</h3>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
In a few short weeks, you'll be able to upgrade your Luminotes account to get
|
|
||||||
notebook sharing features and additional storage space. Here are some of the
|
|
||||||
features you can look forward to.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>share your notebook</h3>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="/static/images/share.png" target="_new"><img
|
|
||||||
src="/static/images/share_thumb.png" class="thumbnail_right" width="200" height="200" /></a>
|
|
||||||
Most of the time, you want to keep your personal wiki all to yourself. But
|
|
||||||
sometimes you simply need to share your work with friends and colleagues. When
|
|
||||||
you upgrade your Luminotes account, you'll be able to invite specific people
|
|
||||||
to collaborate on your wiki simply by entering their email addresses. You can
|
|
||||||
even give them full editing capbilities, so several people can contribute to
|
|
||||||
your wiki notebook.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>access control</h3>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="/static/images/access.png" target="_new"><img
|
|
||||||
src="/static/images/access_thumb.png" class="thumbnail_left" width="200" height="200" /></a>
|
|
||||||
With an upgraded Luminotes wiki, you'll decide exactly how much access to give
|
|
||||||
people. Collaborators can make changes to your notebook, while viewers can
|
|
||||||
only read your wiki. And owners have the same complete access to your notebook
|
|
||||||
that you do. When you're done collaborating, a single click revokes a user's
|
|
||||||
notebook access.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Your wiki access control works on a per-notebook basis, so you can easily
|
|
||||||
share one notebook with your friends while keeping your other notebooks
|
|
||||||
completely private.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>additional storage space</h3>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
An upgraded Luminotes account gets you more than just notebook sharing
|
|
||||||
features. You'll also be treated to way more room for your personal wiki. That
|
|
||||||
means you'll have more space for your notes and ideas, and you won't have to
|
|
||||||
worry about running out of room anytime soon.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>stay tuned</h3>
|
|
||||||
|
|
||||||
More information about upgrading your Luminotes account will be added as it
|
|
||||||
becomes available. Please consider subscribing to the <a href="/blog"
|
|
||||||
target="_top">Luminotes blog</a> for updates!
|
|
|
@ -13,7 +13,8 @@ function Wiki( invoker ) {
|
||||||
this.rate_plan = evalJSON( getElement( "rate_plan" ).value );
|
this.rate_plan = evalJSON( getElement( "rate_plan" ).value );
|
||||||
this.storage_usage_high = false;
|
this.storage_usage_high = false;
|
||||||
this.invites = evalJSON( getElement( "invites" ).value );
|
this.invites = evalJSON( getElement( "invites" ).value );
|
||||||
this.invite_id = getElement( "invite_id" ).value ;
|
this.invite_id = getElement( "invite_id" ).value;
|
||||||
|
this.after_login = getElement( "after_login" ).value;
|
||||||
|
|
||||||
var total_notes_count_node = getElement( "total_notes_count" );
|
var total_notes_count_node = getElement( "total_notes_count" );
|
||||||
if ( total_notes_count_node )
|
if ( total_notes_count_node )
|
||||||
|
@ -652,8 +653,11 @@ Wiki.prototype.create_editor = function ( id, note_text, deleted_from_id, revisi
|
||||||
connect( editor, "invites_updated", function ( invites ) { self.invites = invites; self.share_notebook(); } );
|
connect( editor, "invites_updated", function ( invites ) { self.invites = invites; self.share_notebook(); } );
|
||||||
connect( editor, "submit_form", function ( url, form, callback ) {
|
connect( editor, "submit_form", function ( url, form, callback ) {
|
||||||
var args = {}
|
var args = {}
|
||||||
if ( url == "/users/signup" || url == "/users/login" )
|
if ( url == "/users/signup" || url == "/users/login" ) {
|
||||||
args[ "invite_id" ] = self.invite_id;
|
args[ "invite_id" ] = self.invite_id;
|
||||||
|
if ( url == "/users/login" )
|
||||||
|
args[ "after_login" ] = self.after_login;
|
||||||
|
}
|
||||||
|
|
||||||
self.invoker.invoke( url, "POST", args, callback, form );
|
self.invoker.invoke( url, "POST", args, callback, form );
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -24,7 +24,6 @@ class Initializer( object ):
|
||||||
( u"password reset.html", False ),
|
( u"password reset.html", False ),
|
||||||
( u"advanced browser features.html", False ),
|
( u"advanced browser features.html", False ),
|
||||||
( u"supported browsers.html", False ),
|
( u"supported browsers.html", False ),
|
||||||
( u"upgrade.html", False ),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__( self, database, nuke = False ):
|
def __init__( self, database, nuke = False ):
|
||||||
|
|
|
@ -24,7 +24,6 @@ class Updater( object ):
|
||||||
( u"password reset.html", False ),
|
( u"password reset.html", False ),
|
||||||
( u"advanced browser features.html", False ),
|
( u"advanced browser features.html", False ),
|
||||||
( u"supported browsers.html", False ),
|
( u"supported browsers.html", False ),
|
||||||
( u"upgrade.html", False ),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__( self, database, navigation_note_id = None ):
|
def __init__( self, database, navigation_note_id = None ):
|
||||||
|
|
|
@ -31,6 +31,7 @@ class Main_page( Page ):
|
||||||
deleted_id = None,
|
deleted_id = None,
|
||||||
invites = None,
|
invites = None,
|
||||||
invite_id = None,
|
invite_id = None,
|
||||||
|
after_login = None,
|
||||||
):
|
):
|
||||||
startup_note_ids = [ startup_note.object_id for startup_note in startup_notes ]
|
startup_note_ids = [ startup_note.object_id for startup_note in startup_notes ]
|
||||||
|
|
||||||
|
@ -102,6 +103,7 @@ class Main_page( Page ):
|
||||||
Input( type = u"hidden", name = u"deleted_id", id = u"deleted_id", value = deleted_id ),
|
Input( type = u"hidden", name = u"deleted_id", id = u"deleted_id", value = deleted_id ),
|
||||||
Input( type = u"hidden", name = u"invites", id = u"invites", value = json( invites ) ),
|
Input( type = u"hidden", name = u"invites", id = u"invites", value = json( invites ) ),
|
||||||
Input( type = u"hidden", name = u"invite_id", id = u"invite_id", value = invite_id ),
|
Input( type = u"hidden", name = u"invite_id", id = u"invite_id", value = invite_id ),
|
||||||
|
Input( type = u"hidden", name = u"after_login", id = u"after_login", value = after_login ),
|
||||||
Div(
|
Div(
|
||||||
id = u"status_area",
|
id = u"status_area",
|
||||||
),
|
),
|
||||||
|
|
|
@ -25,6 +25,7 @@ class Notebook_rss( Rss_channel ):
|
||||||
deleted_id = None,
|
deleted_id = None,
|
||||||
invites = None,
|
invites = None,
|
||||||
invite_id = None,
|
invite_id = None,
|
||||||
|
after_login = None,
|
||||||
):
|
):
|
||||||
if notebook.name == u"Luminotes":
|
if notebook.name == u"Luminotes":
|
||||||
notebook_path = u"/"
|
notebook_path = u"/"
|
||||||
|
|
|
@ -3,8 +3,6 @@ from Tags import Span, H3, P, A
|
||||||
|
|
||||||
class Redeem_invite_note( Span ):
|
class Redeem_invite_note( Span ):
|
||||||
def __init__( self, invite, notebook ):
|
def __init__( self, invite, notebook ):
|
||||||
title = None
|
|
||||||
|
|
||||||
Span.__init__(
|
Span.__init__(
|
||||||
self,
|
self,
|
||||||
H3( notebook.name ),
|
H3( notebook.name ),
|
||||||
|
|
|
@ -3,8 +3,6 @@ from Tags import Span, H3, P, Form, P, Div, Strong, Br, Input
|
||||||
|
|
||||||
class Redeem_reset_note( Span ):
|
class Redeem_reset_note( Span ):
|
||||||
def __init__( self, password_reset_id, users ):
|
def __init__( self, password_reset_id, users ):
|
||||||
title = None
|
|
||||||
|
|
||||||
Span.__init__(
|
Span.__init__(
|
||||||
self,
|
self,
|
||||||
H3( u"complete your password reset" ),
|
H3( u"complete your password reset" ),
|
||||||
|
|
|
@ -0,0 +1,184 @@
|
||||||
|
from Tags import Div, Span, H3, P, A, Table, Tr, Th, Td, Br, Img
|
||||||
|
|
||||||
|
|
||||||
|
class Upgrade_note( Span ):
|
||||||
|
def __init__( self, rate_plans, https_url, user_id ):
|
||||||
|
MEGABYTE = 1024 * 1024
|
||||||
|
|
||||||
|
Span.__init__(
|
||||||
|
self,
|
||||||
|
H3( u"upgrade your wiki" ),
|
||||||
|
P(
|
||||||
|
u"When you",
|
||||||
|
A( u"sign up", href = https_url + u"/sign_up", target = u"_top" ),
|
||||||
|
"""
|
||||||
|
for a free Luminotes account, you get a full-featured
|
||||||
|
personal wiki available wherever you go. And if you upgrade your
|
||||||
|
Luminotes account, you'll also get powerful notebook sharing features
|
||||||
|
so that you and your friends can all collaborate on your wiki notebook.
|
||||||
|
""",
|
||||||
|
),
|
||||||
|
P(
|
||||||
|
Table(
|
||||||
|
self.fee_row( rate_plans, user_id ),
|
||||||
|
Tr(
|
||||||
|
Td( u"included storage space", class_ = u"feature_name" ),
|
||||||
|
[ Td(
|
||||||
|
plan[ u"storage_quota_bytes" ] // MEGABYTE, " MB",
|
||||||
|
) for plan in rate_plans ],
|
||||||
|
),
|
||||||
|
Tr(
|
||||||
|
Td( u"unlimited wiki notebooks", class_ = u"feature_name" ),
|
||||||
|
[ Td(
|
||||||
|
Img( src = u"/static/images/check.png", width = u"20", height = u"17" ),
|
||||||
|
) for plan in rate_plans ],
|
||||||
|
),
|
||||||
|
Tr(
|
||||||
|
Td( u"friendly email support", class_ = u"feature_name" ),
|
||||||
|
[ Td(
|
||||||
|
Img( src = u"/static/images/check.png", width = u"20", height = u"17" ),
|
||||||
|
) for plan in rate_plans ],
|
||||||
|
),
|
||||||
|
Tr(
|
||||||
|
Td( u"multi-user collaboration", class_ = u"feature_name" ),
|
||||||
|
[ Td(
|
||||||
|
plan[ u"notebook_collaboration" ] and
|
||||||
|
Img( src = u"/static/images/check.png", width = u"20", height = u"17" ) or u" ",
|
||||||
|
) for plan in rate_plans ],
|
||||||
|
),
|
||||||
|
Tr(
|
||||||
|
Td( u"wiki access control", class_ = u"feature_name" ),
|
||||||
|
[ Td(
|
||||||
|
plan[ u"notebook_collaboration" ] and
|
||||||
|
Img( src = u"/static/images/check.png", width = u"20", height = u"17" ) or u" ",
|
||||||
|
) for plan in rate_plans ],
|
||||||
|
),
|
||||||
|
border = u"1",
|
||||||
|
id = u"upgrade_table",
|
||||||
|
),
|
||||||
|
( not user_id ) and P(
|
||||||
|
u"To upgrade your Luminotes account, please",
|
||||||
|
A( u"login", href = https_url + u"/login?after_login=/upgrade", target = u"_top" ),
|
||||||
|
u"first!",
|
||||||
|
id = u"upgrade_login_text",
|
||||||
|
) or None,
|
||||||
|
id = u"upgrade_table_area",
|
||||||
|
),
|
||||||
|
|
||||||
|
H3( u"share your notebook" ),
|
||||||
|
P(
|
||||||
|
A(
|
||||||
|
Img(
|
||||||
|
src = u"/static/images/share_thumb.png",
|
||||||
|
class_ = u"thumbnail_right",
|
||||||
|
width = u"200",
|
||||||
|
height = u"200",
|
||||||
|
),
|
||||||
|
href = u"/static/images/share.png",
|
||||||
|
target = u"_new",
|
||||||
|
),
|
||||||
|
u"""
|
||||||
|
Most of the time, you want to keep your personal wiki all to yourself. But
|
||||||
|
sometimes you simply need to share your work with friends and colleagues.
|
||||||
|
""",
|
||||||
|
),
|
||||||
|
P(
|
||||||
|
u"""
|
||||||
|
With an upgraded Luminotes account, you'll be able to invite specific people
|
||||||
|
to collaborate on your wiki simply by entering their email addresses. You can
|
||||||
|
even give them full editing capbilities, so several people can contribute to
|
||||||
|
your wiki notebook. And you can invite as many people as you want to
|
||||||
|
collaborate on your wiki. They only need to sign up for a free Luminotes
|
||||||
|
account to particpate.
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
H3( u"wiki access control" ),
|
||||||
|
P(
|
||||||
|
A(
|
||||||
|
Img(
|
||||||
|
src = u"/static/images/access_thumb.png",
|
||||||
|
class_ = u"thumbnail_left",
|
||||||
|
width = u"200",
|
||||||
|
height = u"200",
|
||||||
|
),
|
||||||
|
href = u"/static/images/access.png",
|
||||||
|
target = u"_new",
|
||||||
|
),
|
||||||
|
u"""
|
||||||
|
With an upgraded Luminotes wiki, you'll decide exactly how much access to give
|
||||||
|
people. Collaborators can make changes to your notebook, while viewers can
|
||||||
|
only read your wiki. And owners have the same complete access to your notebook
|
||||||
|
that you do. When you're done collaborating, a single click revokes a user's
|
||||||
|
notebook access.
|
||||||
|
""",
|
||||||
|
),
|
||||||
|
P(
|
||||||
|
u"""
|
||||||
|
Your wiki access control works on a per-notebook basis, so you can easily
|
||||||
|
share one notebook with your friends while keeping your other notebooks
|
||||||
|
completely private.
|
||||||
|
""",
|
||||||
|
),
|
||||||
|
H3( u"additional storage space" ),
|
||||||
|
P(
|
||||||
|
u"""
|
||||||
|
An upgraded Luminotes account gets you more than just notebook sharing
|
||||||
|
features. You'll also be treated to way more room for your personal wiki. That
|
||||||
|
means you'll have more space for your notes and ideas, and you won't have to
|
||||||
|
worry about running out of room anytime soon.
|
||||||
|
""",
|
||||||
|
),
|
||||||
|
H3( u"no questions asked money-back guarantee" ),
|
||||||
|
P(
|
||||||
|
u"""
|
||||||
|
If you upgrade your Luminotes account and find that it's not meeting your
|
||||||
|
needs, then simply request a refund within 30 days and your money will be
|
||||||
|
returned in full without any questions.
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
P(
|
||||||
|
u"""
|
||||||
|
And no matter how long you've been using an upgraded Luminotes account, you
|
||||||
|
can cancel online anytime. You won't have to send email or talk to anyone in a
|
||||||
|
call center. If you do cancel, you keep all of your wiki notebooks and simply
|
||||||
|
return to a free account.
|
||||||
|
""",
|
||||||
|
),
|
||||||
|
P(
|
||||||
|
Table(
|
||||||
|
self.fee_row( rate_plans, user_id, include_blank = False ),
|
||||||
|
Tr(
|
||||||
|
[ Td(
|
||||||
|
plan[ u"storage_quota_bytes" ] // MEGABYTE, " MB",
|
||||||
|
) for plan in rate_plans ],
|
||||||
|
),
|
||||||
|
border = u"1",
|
||||||
|
id = u"upgrade_table_small",
|
||||||
|
),
|
||||||
|
( not user_id ) and P(
|
||||||
|
u"Please",
|
||||||
|
A( u"login", href = https_url + u"/login?after_login=/upgrade", target = u"_top" ),
|
||||||
|
u"to upgrade your wiki!",
|
||||||
|
id = u"upgrade_login_text",
|
||||||
|
) or None,
|
||||||
|
id = u"upgrade_table_area",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def fee_row( self, rate_plans, user_id, include_blank = True ):
|
||||||
|
return Tr(
|
||||||
|
include_blank and Th( u" " ) or None,
|
||||||
|
[ Th(
|
||||||
|
plan[ u"name" ].capitalize(),
|
||||||
|
plan[ u"fee" ] and Div(
|
||||||
|
Span(
|
||||||
|
u"$%s" % plan[ u"fee" ],
|
||||||
|
Span( u"/month", class_ = u"month_text" ),
|
||||||
|
class_ = u"price_text",
|
||||||
|
separator = u"",
|
||||||
|
),
|
||||||
|
user_id and plan.get( u"button" ) % user_id or None,
|
||||||
|
) or None,
|
||||||
|
class_ = u"plan_name",
|
||||||
|
) for plan in rate_plans ],
|
||||||
|
)
|
Reference in New Issue