* Started a static upgrade HTML file with rate plans.
* Fixed Invite.sql_update() to have SQL params in proper order. * Fixed bug where email addresses containing "-" were considered invalid. * Made UI for inviting other people to your notebook. * Tweaked the rate plans and added a new one.
This commit is contained in:
parent
a615f65d29
commit
5554b1df17
|
@ -25,16 +25,24 @@ settings = {
|
|||
"luminotes.support_email": "",
|
||||
"luminotes.rate_plans": [
|
||||
{
|
||||
"name": "basic",
|
||||
"name": "free",
|
||||
"storage_quota_bytes": 30 * MEGABYTE,
|
||||
"notebook_sharing": False,
|
||||
},
|
||||
{
|
||||
"name": "basic",
|
||||
"storage_quota_bytes": 250 * MEGABYTE,
|
||||
"notebook_sharing": True,
|
||||
},
|
||||
{
|
||||
"name": "standard",
|
||||
"storage_quota_bytes": 100 * MEGABYTE,
|
||||
"storage_quota_bytes": 500 * MEGABYTE,
|
||||
"notebook_sharing": True,
|
||||
},
|
||||
{
|
||||
"name": "professional",
|
||||
"storage_quota_bytes": 300 * MEGABYTE,
|
||||
"name": "premium",
|
||||
"storage_quota_bytes": 1000 * MEGABYTE,
|
||||
"notebook_sharing": True,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -17,8 +17,8 @@ from view.Redeem_reset_note import Redeem_reset_note
|
|||
|
||||
|
||||
USERNAME_PATTERN = re.compile( "^[a-zA-Z0-9]+$" )
|
||||
EMAIL_ADDRESS_PATTERN = re.compile( "^[\w.+]+@\w+(\.\w+)+$" )
|
||||
EMBEDDED_EMAIL_ADDRESS_PATTERN = re.compile( "(?:^|[\s,<])([\w.+]+@\w+(?:\.\w+)+)(?:[\s,>]|$)" )
|
||||
EMAIL_ADDRESS_PATTERN = re.compile( "^[\w.%+-]+@[\w-]+(\.[\w-]+)+$" )
|
||||
EMBEDDED_EMAIL_ADDRESS_PATTERN = re.compile( "(?:^|[\s,<])([\w.%+-]+@[\w-]+(?:\.[\w-]+)+)(?:[\s,>]|$)" )
|
||||
WHITESPACE_OR_COMMA_PATTERN = re.compile( "[\s,]" )
|
||||
|
||||
|
||||
|
@ -508,9 +508,9 @@ class Users( object ):
|
|||
|
||||
# create an email message with a unique link
|
||||
message = Message.Message()
|
||||
message[ u"from" ] = u"Luminotes support <%s>" % self.__support_email
|
||||
message[ u"to" ] = email_address
|
||||
message[ u"subject" ] = u"Luminotes password reset"
|
||||
message[ u"From" ] = u"Luminotes support <%s>" % self.__support_email
|
||||
message[ u"To" ] = email_address
|
||||
message[ u"Subject" ] = u"Luminotes password reset"
|
||||
message.set_payload(
|
||||
u"Someone has requested a password reset for a Luminotes user with your email\n" +
|
||||
u"address. If this someone is you, please visit the following link for a\n" +
|
||||
|
@ -523,7 +523,7 @@ class Users( object ):
|
|||
# send the message out through localhost's smtp server
|
||||
server = smtplib.SMTP()
|
||||
server.connect()
|
||||
server.sendmail( message[ u"from" ], [ email_address ], message.as_string() )
|
||||
server.sendmail( message[ u"From" ], [ email_address ], message.as_string() )
|
||||
server.quit()
|
||||
|
||||
return dict(
|
||||
|
@ -655,12 +655,11 @@ class Users( object ):
|
|||
@validate(
|
||||
notebook_id = Valid_id(),
|
||||
email_addresses = unicode,
|
||||
read_write = Valid_bool(),
|
||||
owner = Valid_bool(),
|
||||
access = Valid_string(),
|
||||
invite_button = unicode,
|
||||
user_id = Valid_id( none_okay = True ),
|
||||
)
|
||||
def send_invites( self, notebook_id, email_addresses, read_write, owner, invite_button, user_id = None ):
|
||||
def send_invites( self, notebook_id, email_addresses, access, invite_button, user_id = None ):
|
||||
"""
|
||||
Send notebook invitations to the given email addresses.
|
||||
|
||||
|
@ -668,10 +667,8 @@ class Users( object ):
|
|||
@param notebook_id: id of the notebook that the invitation is for
|
||||
@type email_addresses: unicode
|
||||
@param email_addresses: a string containing whitespace- or comma-separated email addresses
|
||||
@type read_write: bool
|
||||
@param read_write: whether the invitation is for read-write access
|
||||
@type owner: bool
|
||||
@param owner: whether the invitation is for owner-level access
|
||||
@type access: unicode
|
||||
@param access: type of access to grant, either "collaborator", "viewer", or "owner"
|
||||
@type invite_button: unicode
|
||||
@param invite_button: ignored
|
||||
@type user_id: unicode
|
||||
|
@ -683,10 +680,22 @@ class Users( object ):
|
|||
@raise Access_error: user_id doesn't have owner-level notebook access to send an invite
|
||||
"""
|
||||
if len( email_addresses ) < 5:
|
||||
raise Invite_error( u"Please enter at least one email valid address." )
|
||||
raise Invite_error( u"Please enter at least one valid email address." )
|
||||
if len( email_addresses ) > 5000:
|
||||
raise Invite_error( u"Please enter fewer email addresses." )
|
||||
|
||||
if access == u"collaborator":
|
||||
read_write = True
|
||||
owner = False
|
||||
elif access == u"viewer":
|
||||
read_write = False
|
||||
owner = False
|
||||
elif access == u"owner":
|
||||
read_write = True
|
||||
owner = True
|
||||
else:
|
||||
raise Access_error()
|
||||
|
||||
if not self.check_access( user_id, notebook_id, read_write = True, owner = True ):
|
||||
raise Access_error()
|
||||
|
||||
|
@ -729,11 +738,11 @@ class Users( object ):
|
|||
# create an email message with a unique invitation link
|
||||
notebook_name = notebook.name.strip().replace( "\n", " " ).replace( "\r", " " )
|
||||
message = Message.Message()
|
||||
message[ u"from" ] = user.email_address or u"Luminotes personal wiki <%s>" % self.__support_email
|
||||
if not user.email_address:
|
||||
message[ u"sender" ] = u"Luminotes personal wiki <%s>" % self.__support_email
|
||||
message[ u"to" ] = email_address
|
||||
message[ u"subject" ] = notebook_name
|
||||
message[ u"From" ] = user.email_address or u"Luminotes personal wiki <%s>" % self.__support_email
|
||||
if user.email_address:
|
||||
message[ u"Sender" ] = u"Luminotes personal wiki <%s>" % self.__support_email
|
||||
message[ u"To" ] = email_address
|
||||
message[ u"Subject" ] = notebook_name
|
||||
message.set_payload(
|
||||
u"I've shared a wiki with you called \"%s\"\n" % notebook_name +
|
||||
u"Please visit the following link to view it online:\n\n" +
|
||||
|
@ -743,7 +752,7 @@ class Users( object ):
|
|||
# send the message out through localhost's smtp server
|
||||
server = smtplib.SMTP()
|
||||
server.connect()
|
||||
server.sendmail( message[ u"from" ], [ email_address ], message.as_string() )
|
||||
server.sendmail( message[ u"From" ], [ email_address ], message.as_string() )
|
||||
server.quit()
|
||||
|
||||
self.__database.commit()
|
||||
|
|
|
@ -23,13 +23,13 @@ class Test_users( Test_controller ):
|
|||
|
||||
self.username = u"mulder"
|
||||
self.password = u"trustno1"
|
||||
self.email_address = u"outthere@example.com"
|
||||
self.email_address = u"out-there@example.com"
|
||||
self.new_username = u"reynolds"
|
||||
self.new_password = u"shiny"
|
||||
self.new_email_address = u"capn@example.com"
|
||||
self.username2 = u"scully"
|
||||
self.password2 = u"trustsome1"
|
||||
self.email_address2 = u"outthere@example.com"
|
||||
self.email_address2 = u"out-there@example.com"
|
||||
self.user = None
|
||||
self.user2 = None
|
||||
self.anonymous = None
|
||||
|
@ -769,8 +769,7 @@ class Test_users( Test_controller ):
|
|||
result = self.http_post( "/users/send_invites", dict(
|
||||
notebook_id = self.notebooks[ 0 ].object_id,
|
||||
email_addresses = email_addresses,
|
||||
read_write = False,
|
||||
owner = False,
|
||||
access = u"viewer",
|
||||
invite_button = u"send invites",
|
||||
), session_id = self.session_id )
|
||||
session_id = result[ u"session_id" ]
|
||||
|
@ -783,7 +782,100 @@ class Test_users( Test_controller ):
|
|||
assert self.email_address in from_address
|
||||
assert to_addresses == email_addresses_list
|
||||
assert self.notebooks[ 0 ].name in message
|
||||
assert self.INVITE_LINK_PATTERN.search( message )
|
||||
matches = self.INVITE_LINK_PATTERN.search( message )
|
||||
invite_id = matches.group( 2 )
|
||||
assert invite_id
|
||||
|
||||
# assert that the invite has the read_write / owner flags set appropriately
|
||||
invite_list = self.database.objects.get( invite_id )
|
||||
assert invite_list
|
||||
assert len( invite_list ) == 1
|
||||
invite = invite_list[ -1 ]
|
||||
assert invite
|
||||
assert invite.read_write is False
|
||||
assert invite.owner is False
|
||||
|
||||
def test_send_invites_collaborator( self ):
|
||||
# trick send_invites() into using a fake SMTP server
|
||||
Stub_smtp.reset()
|
||||
smtplib.SMTP = Stub_smtp
|
||||
self.login()
|
||||
|
||||
self.user.rate_plan = 1
|
||||
self.database.save( self.user )
|
||||
|
||||
email_addresses_list = [ u"foo@example.com" ]
|
||||
email_addresses = email_addresses_list[ 0 ]
|
||||
|
||||
result = self.http_post( "/users/send_invites", dict(
|
||||
notebook_id = self.notebooks[ 0 ].object_id,
|
||||
email_addresses = email_addresses,
|
||||
access = u"collaborator",
|
||||
invite_button = u"send invites",
|
||||
), session_id = self.session_id )
|
||||
session_id = result[ u"session_id" ]
|
||||
|
||||
assert u"An invitation has been sent." in result[ u"message" ]
|
||||
assert smtplib.SMTP.connected == False
|
||||
assert len( smtplib.SMTP.emails ) == 1
|
||||
|
||||
( from_address, to_addresses, message ) = smtplib.SMTP.emails[ 0 ]
|
||||
assert self.email_address in from_address
|
||||
assert to_addresses == email_addresses_list
|
||||
assert self.notebooks[ 0 ].name in message
|
||||
matches = self.INVITE_LINK_PATTERN.search( message )
|
||||
invite_id = matches.group( 2 )
|
||||
assert invite_id
|
||||
|
||||
# assert that the invite has the read_write / owner flags set appropriately
|
||||
invite_list = self.database.objects.get( invite_id )
|
||||
assert invite_list
|
||||
assert len( invite_list ) == 1
|
||||
invite = invite_list[ -1 ]
|
||||
assert invite
|
||||
assert invite.read_write is True
|
||||
assert invite.owner is False
|
||||
|
||||
def test_send_invites_owner( self ):
|
||||
# trick send_invites() into using a fake SMTP server
|
||||
Stub_smtp.reset()
|
||||
smtplib.SMTP = Stub_smtp
|
||||
self.login()
|
||||
|
||||
self.user.rate_plan = 1
|
||||
self.database.save( self.user )
|
||||
|
||||
email_addresses_list = [ u"foo@example.com" ]
|
||||
email_addresses = email_addresses_list[ 0 ]
|
||||
|
||||
result = self.http_post( "/users/send_invites", dict(
|
||||
notebook_id = self.notebooks[ 0 ].object_id,
|
||||
email_addresses = email_addresses,
|
||||
access = u"owner",
|
||||
invite_button = u"send invites",
|
||||
), session_id = self.session_id )
|
||||
session_id = result[ u"session_id" ]
|
||||
|
||||
assert u"An invitation has been sent." in result[ u"message" ]
|
||||
assert smtplib.SMTP.connected == False
|
||||
assert len( smtplib.SMTP.emails ) == 1
|
||||
|
||||
( from_address, to_addresses, message ) = smtplib.SMTP.emails[ 0 ]
|
||||
assert self.email_address in from_address
|
||||
assert to_addresses == email_addresses_list
|
||||
assert self.notebooks[ 0 ].name in message
|
||||
matches = self.INVITE_LINK_PATTERN.search( message )
|
||||
invite_id = matches.group( 2 )
|
||||
assert invite_id
|
||||
|
||||
# assert that the invite has the read_write / owner flags set appropriately
|
||||
invite_list = self.database.objects.get( invite_id )
|
||||
assert invite_list
|
||||
assert len( invite_list ) == 1
|
||||
invite = invite_list[ -1 ]
|
||||
assert invite
|
||||
assert invite.read_write is True
|
||||
assert invite.owner is True
|
||||
|
||||
def test_send_invites_multiple( self ):
|
||||
Stub_smtp.reset()
|
||||
|
@ -800,8 +892,7 @@ class Test_users( Test_controller ):
|
|||
result = self.http_post( "/users/send_invites", dict(
|
||||
notebook_id = self.notebooks[ 0 ].object_id,
|
||||
email_addresses = email_addresses,
|
||||
read_write = False,
|
||||
owner = False,
|
||||
access = u"viewer",
|
||||
invite_button = u"send invites",
|
||||
), session_id = self.session_id )
|
||||
session_id = result[ u"session_id" ]
|
||||
|
@ -834,8 +925,7 @@ class Test_users( Test_controller ):
|
|||
result = self.http_post( "/users/send_invites", dict(
|
||||
notebook_id = self.notebooks[ 0 ].object_id,
|
||||
email_addresses = email_addresses,
|
||||
read_write = False,
|
||||
owner = False,
|
||||
access = u"viewer",
|
||||
invite_button = u"send invites",
|
||||
), session_id = self.session_id )
|
||||
session_id = result[ u"session_id" ]
|
||||
|
@ -868,8 +958,7 @@ class Test_users( Test_controller ):
|
|||
self.http_post( "/users/send_invites", dict(
|
||||
notebook_id = self.notebooks[ 0 ].object_id,
|
||||
email_addresses = email_addresses,
|
||||
read_write = False,
|
||||
owner = False,
|
||||
access = u"viewer",
|
||||
invite_button = u"send invites",
|
||||
), session_id = self.session_id )
|
||||
|
||||
|
@ -881,8 +970,7 @@ class Test_users( Test_controller ):
|
|||
self.http_post( "/users/send_invites", dict(
|
||||
notebook_id = self.notebooks[ 0 ].object_id,
|
||||
email_addresses = email_addresses,
|
||||
read_write = True,
|
||||
owner = True,
|
||||
access = u"owner",
|
||||
invite_button = u"send invites",
|
||||
), session_id = self.session_id )
|
||||
|
||||
|
@ -922,8 +1010,7 @@ class Test_users( Test_controller ):
|
|||
result = self.http_post( "/users/send_invites", dict(
|
||||
notebook_id = self.notebooks[ 0 ].object_id,
|
||||
email_addresses = email_addresses,
|
||||
read_write = False,
|
||||
owner = False,
|
||||
access = u"viewer",
|
||||
invite_button = u"send invites",
|
||||
), session_id = self.session_id )
|
||||
session_id = result[ u"session_id" ]
|
||||
|
@ -951,8 +1038,7 @@ class Test_users( Test_controller ):
|
|||
result = self.http_post( "/users/send_invites", dict(
|
||||
notebook_id = self.notebooks[ 0 ].object_id,
|
||||
email_addresses = email_addresses,
|
||||
read_write = False,
|
||||
owner = False,
|
||||
access = u"viewer",
|
||||
invite_button = u"send invites",
|
||||
), session_id = self.session_id )
|
||||
session_id = result[ u"session_id" ]
|
||||
|
@ -974,8 +1060,7 @@ class Test_users( Test_controller ):
|
|||
result = self.http_post( "/users/send_invites", dict(
|
||||
notebook_id = self.notebooks[ 0 ].object_id,
|
||||
email_addresses = email_addresses,
|
||||
read_write = False,
|
||||
owner = False,
|
||||
access = u"viewer",
|
||||
invite_button = u"send invites",
|
||||
), session_id = self.session_id )
|
||||
session_id = result[ u"session_id" ]
|
||||
|
@ -996,8 +1081,7 @@ class Test_users( Test_controller ):
|
|||
result = self.http_post( "/users/send_invites", dict(
|
||||
notebook_id = self.notebooks[ 0 ].object_id,
|
||||
email_addresses = email_addresses,
|
||||
read_write = False,
|
||||
owner = False,
|
||||
access = u"viewer",
|
||||
invite_button = u"send invites",
|
||||
), session_id = self.session_id )
|
||||
session_id = result[ u"session_id" ]
|
||||
|
@ -1018,8 +1102,7 @@ class Test_users( Test_controller ):
|
|||
result = self.http_post( "/users/send_invites", dict(
|
||||
notebook_id = self.notebooks[ 0 ].object_id,
|
||||
email_addresses = email_addresses,
|
||||
read_write = False,
|
||||
owner = False,
|
||||
access = u"viewer",
|
||||
invite_button = u"send invites",
|
||||
), session_id = self.session_id )
|
||||
session_id = result[ u"session_id" ]
|
||||
|
@ -1041,8 +1124,7 @@ class Test_users( Test_controller ):
|
|||
result = self.http_post( "/users/send_invites", dict(
|
||||
notebook_id = self.notebooks[ 0 ].object_id,
|
||||
email_addresses = email_addresses,
|
||||
read_write = False,
|
||||
owner = False,
|
||||
access = u"viewer",
|
||||
invite_button = u"send invites",
|
||||
), session_id = self.session_id )
|
||||
session_id = result[ u"session_id" ]
|
||||
|
@ -1066,8 +1148,7 @@ class Test_users( Test_controller ):
|
|||
result = self.http_post( "/users/send_invites", dict(
|
||||
notebook_id = self.notebooks[ 0 ].object_id,
|
||||
email_addresses = email_addresses,
|
||||
read_write = False,
|
||||
owner = False,
|
||||
access = u"viewer",
|
||||
invite_button = u"send invites",
|
||||
), session_id = self.session_id )
|
||||
session_id = result[ u"session_id" ]
|
||||
|
@ -1091,8 +1172,7 @@ class Test_users( Test_controller ):
|
|||
result = self.http_post( "/users/send_invites", dict(
|
||||
notebook_id = self.notebooks[ 0 ].object_id,
|
||||
email_addresses = email_addresses,
|
||||
read_write = False,
|
||||
owner = False,
|
||||
access = u"viewer",
|
||||
invite_button = u"send invites",
|
||||
), session_id = self.session_id )
|
||||
session_id = result[ u"session_id" ]
|
||||
|
@ -1111,8 +1191,7 @@ class Test_users( Test_controller ):
|
|||
result = self.http_post( "/users/send_invites", dict(
|
||||
notebook_id = self.notebooks[ 0 ].object_id,
|
||||
email_addresses = email_addresses,
|
||||
read_write = False,
|
||||
owner = False,
|
||||
access = u"viewer",
|
||||
invite_button = u"send invites",
|
||||
), session_id = self.session_id )
|
||||
session_id = result[ u"session_id" ]
|
||||
|
@ -1132,8 +1211,7 @@ class Test_users( Test_controller ):
|
|||
result = self.http_post( "/users/send_invites", dict(
|
||||
notebook_id = unknown_notebook_id,
|
||||
email_addresses = email_addresses,
|
||||
read_write = False,
|
||||
owner = False,
|
||||
access = u"viewer",
|
||||
invite_button = u"send invites",
|
||||
), session_id = self.session_id )
|
||||
session_id = result[ u"session_id" ]
|
||||
|
|
|
@ -86,9 +86,9 @@ class Invite( Persistent ):
|
|||
|
||||
def sql_update( self ):
|
||||
return "update invite set revision = %s, from_user_id = %s, notebook_id = %s, email_address = %s, read_write = %s, owner = %s, redeemed_user_id = %s where id = %s;" % \
|
||||
( quote( self.object_id ), quote( self.revision ), quote( self.__from_user_id ), quote( self.__notebook_id ),
|
||||
( quote( self.revision ), quote( self.__from_user_id ), quote( self.__notebook_id ),
|
||||
quote( self.__email_address ), quote( self.__read_write and "t" or "f" ), quote( self.__owner and "t" or "f" ),
|
||||
quote( self.__redeemed_user_id ) )
|
||||
quote( self.__redeemed_user_id ), quote( self.object_id ) )
|
||||
|
||||
def sql_load_similar( self ):
|
||||
# select unredeemed invitations with the same from_user_id, notebook_id, and email_address as this invitation
|
||||
|
|
|
@ -79,6 +79,13 @@ h3 {
|
|||
border: #999999 1px solid;
|
||||
}
|
||||
|
||||
.textarea_field {
|
||||
margin-top: 0.25em;
|
||||
padding: 0.25em;
|
||||
border: #999999 1px solid;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
@ -99,3 +106,49 @@ ol li {
|
|||
padding-top: 0.5em;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.radio_link {
|
||||
color: #000000;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.radio_link:hover {
|
||||
color: #ff6600;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#access_table td {
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
#upgrade_table {
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #999999;
|
||||
}
|
||||
|
||||
#upgrade_table .plan_name {
|
||||
width: 16%;
|
||||
text-align: center;
|
||||
background-color: #d0e0f0;
|
||||
}
|
||||
|
||||
#upgrade_table .feature_name {
|
||||
width: 36%;
|
||||
text-align: left;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
#upgrade_table .price_text {
|
||||
color: #ff6600;
|
||||
}
|
||||
|
||||
#upgrade_table .month_text {
|
||||
padding-top: 0.5em;
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
#upgrade_table td {
|
||||
text-align: center;
|
||||
background-color: #fafafa;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
<h3>upgrade</h3>
|
||||
|
||||
<table border="1" cellspacing="0" cellpadding="10" id="upgrade_table">
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th class="plan_name">Free</th>
|
||||
<th class="plan_name">Basic<br /><span class="price_text">$5<span class="month_text">/month</span></span></th>
|
||||
<th class="plan_name">Standard<br /><span class="price_text">$9<span class="month_text">/month</span></span></th>
|
||||
<th class="plan_name">Premium<br /><span class="price_text">$14<span class="month_text">/month</span></span></th>
|
||||
<tr>
|
||||
<td class="feature_name">included storage space</td>
|
||||
<td>30 MB</td>
|
||||
<td>250 MB</td>
|
||||
<td>500 MB</td>
|
||||
<td>1000 MB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature_name">unlimited wiki notebooks</td>
|
||||
<td><img src="../../static/images/check.png" width="20" height="17" /></td>
|
||||
<td><img src="../../static/images/check.png" width="20" height="17" /></td>
|
||||
<td><img src="../../static/images/check.png" width="20" height="17" /></td>
|
||||
<td><img src="../../static/images/check.png" width="20" height="17" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature_name">friendly email support</td>
|
||||
<td><img src="../../static/images/check.png" width="20" height="17" /></td>
|
||||
<td><img src="../../static/images/check.png" width="20" height="17" /></td>
|
||||
<td><img src="../../static/images/check.png" width="20" height="17" /></td>
|
||||
<td><img src="../../static/images/check.png" width="20" height="17" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature_name">multi-user collaboration</td>
|
||||
<td> </td>
|
||||
<td><img src="../../static/images/check.png" width="20" height="17" /></td>
|
||||
<td><img src="../../static/images/check.png" width="20" height="17" /></td>
|
||||
<td><img src="../../static/images/check.png" width="20" height="17" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="feature_name">wiki access control</td>
|
||||
<td> </td>
|
||||
<td><img src="../../static/images/check.png" width="20" height="17" /></td>
|
||||
<td><img src="../../static/images/check.png" width="20" height="17" /></td>
|
||||
<td><img src="../../static/images/check.png" width="20" height="17" /></td>
|
||||
</tr>
|
||||
</table>
|
Binary file not shown.
After Width: | Height: | Size: 605 B |
Binary file not shown.
|
@ -136,48 +136,79 @@ Editor.prototype.finish_init = function () {
|
|||
if ( this.read_write ) {
|
||||
connect( this.document, "onkeydown", function ( event ) { self.key_pressed( event ); } );
|
||||
connect( this.document, "onkeyup", function ( event ) { self.key_released( event ); } );
|
||||
connect( this.document, "onblur", function ( event ) { self.blurred( event ); } );
|
||||
connect( this.document, "onfocus", function ( event ) { self.focused( event ); } );
|
||||
connect( this.document.body, "onblur", function ( event ) { self.blurred( event ); } );
|
||||
connect( this.document.body, "onfocus", function ( event ) { self.focused( event ); } );
|
||||
connect( this.iframe.contentWindow, "onblur", function ( event ) { self.blurred( event ); } );
|
||||
connect( this.iframe.contentWindow, "onfocus", function ( event ) { self.focused( event ); } );
|
||||
}
|
||||
connect( this.document, "onblur", function ( event ) { self.blurred( event ); } );
|
||||
connect( this.document, "onfocus", function ( event ) { self.focused( event ); } );
|
||||
connect( this.document.body, "onblur", function ( event ) { self.blurred( event ); } );
|
||||
connect( this.document.body, "onfocus", function ( event ) { self.focused( event ); } );
|
||||
connect( this.iframe.contentWindow, "onblur", function ( event ) { self.blurred( event ); } );
|
||||
connect( this.iframe.contentWindow, "onfocus", function ( event ) { self.focused( event ); } );
|
||||
|
||||
connect( this.document, "onclick", function ( event ) { self.mouse_clicked( event ); } );
|
||||
// special case: don't handle mouse click for share_notebook magic note, so radio button links
|
||||
// work as intended
|
||||
if ( this.id != "share_notebook" )
|
||||
connect( this.document, "onclick", function ( event ) { self.mouse_clicked( event ); } );
|
||||
|
||||
// special-case: connect any submit buttons within the contents of this note
|
||||
var signup_button = withDocument( this.document, function () { return getElement( "signup_button" ); } );
|
||||
if ( signup_button ) {
|
||||
var signup_form = withDocument( this.document, function () { return getElement( "signup_form" ); } );
|
||||
connect( signup_button, "onclick", function ( event ) {
|
||||
signal( self, "submit_form", "/users/signup", signup_form ); event.stop();
|
||||
} );
|
||||
}
|
||||
withDocument( this.document, function () {
|
||||
var signup_button = getElement( "signup_button" );
|
||||
if ( signup_button ) {
|
||||
var signup_form = getElement( "signup_form" );
|
||||
connect( signup_button, "onclick", function ( event ) {
|
||||
signal( self, "submit_form", "/users/signup", signup_form ); event.stop();
|
||||
} );
|
||||
}
|
||||
|
||||
var login_button = withDocument( this.document, function () { return getElement( "login_button" ); } );
|
||||
if ( login_button ) {
|
||||
var login_form = withDocument( this.document, function () { return getElement( "login_form" ); } );
|
||||
connect( login_button, "onclick", function ( event ) {
|
||||
signal( self, "submit_form", "/users/login", login_form ); event.stop();
|
||||
} );
|
||||
}
|
||||
var login_button = getElement( "login_button" );
|
||||
if ( login_button ) {
|
||||
var login_form = getElement( "login_form" );
|
||||
connect( login_button, "onclick", function ( event ) {
|
||||
signal( self, "submit_form", "/users/login", login_form ); event.stop();
|
||||
} );
|
||||
}
|
||||
|
||||
var send_reset_button = withDocument( this.document, function () { return getElement( "send_reset_button" ); } );
|
||||
if ( send_reset_button ) {
|
||||
var send_reset_form = withDocument( this.document, function () { return getElement( "send_reset_form" ); } );
|
||||
connect( send_reset_button, "onclick", function ( event ) {
|
||||
signal( self, "submit_form", "/users/send_reset", send_reset_form ); event.stop();
|
||||
} );
|
||||
}
|
||||
var send_reset_button = getElement( "send_reset_button" );
|
||||
if ( send_reset_button ) {
|
||||
var send_reset_form = getElement( "send_reset_form" );
|
||||
connect( send_reset_button, "onclick", function ( event ) {
|
||||
signal( self, "submit_form", "/users/send_reset", send_reset_form ); event.stop();
|
||||
} );
|
||||
}
|
||||
|
||||
var reset_button = withDocument( this.document, function () { return getElement( "reset_button" ); } );
|
||||
if ( reset_button ) {
|
||||
var reset_form = withDocument( this.document, function () { return getElement( "reset_form" ); } );
|
||||
connect( reset_button, "onclick", function ( event ) {
|
||||
signal( self, "submit_form", "/users/reset_password", reset_form ); event.stop();
|
||||
} );
|
||||
}
|
||||
var reset_button = getElement( "reset_button" );
|
||||
if ( reset_button ) {
|
||||
var reset_form = getElement( "reset_form" );
|
||||
connect( reset_button, "onclick", function ( event ) {
|
||||
signal( self, "submit_form", "/users/reset_password", reset_form ); event.stop();
|
||||
} );
|
||||
}
|
||||
|
||||
var invite_button = getElement( "invite_button" );
|
||||
if ( invite_button ) {
|
||||
var collaborators_radio = getElement( "collaborators_radio" );
|
||||
connect( "collaborators_link", "onclick", function ( event ) {
|
||||
collaborators_radio.checked = true;
|
||||
event.stop();
|
||||
} );
|
||||
|
||||
var viewers_radio = getElement( "viewers_radio" );
|
||||
connect( "viewers_link", "onclick", function ( event ) {
|
||||
viewers_radio.checked = true;
|
||||
event.stop();
|
||||
} );
|
||||
|
||||
var owners_radio = getElement( "owners_radio" );
|
||||
connect( "owners_link", "onclick", function ( event ) {
|
||||
owners_radio.checked = true;
|
||||
event.stop();
|
||||
} );
|
||||
|
||||
var invite_form = getElement( "invite_form" );
|
||||
connect( invite_button, "onclick", function ( event ) {
|
||||
signal( self, "submit_form", "/users/send_invites", invite_form ); event.stop();
|
||||
} );
|
||||
}
|
||||
} );
|
||||
|
||||
// browsers such as Firefox, but not Opera
|
||||
if ( this.iframe.contentDocument && !/Opera/.test( navigator.userAgent ) && this.read_write )
|
||||
|
|
|
@ -146,8 +146,8 @@ Wiki.prototype.display_storage_usage = function( storage_bytes ) {
|
|||
Wiki.prototype.populate = function ( startup_notes, current_notes, note_read_write, skip_empty_message ) {
|
||||
var self = this;
|
||||
|
||||
// if this is the trash, display a list of all deleted notebooks
|
||||
if ( this.notebook.name == "trash" ) {
|
||||
// if this is the trash and the user has owner-level access, then display a list of all deleted notebooks
|
||||
if ( this.notebook.owner && this.notebook.name == "trash" ) {
|
||||
var heading_shown = false;
|
||||
var deleted_notebooks = getElement( "deleted_notebooks" );
|
||||
|
||||
|
@ -312,6 +312,14 @@ Wiki.prototype.populate = function ( startup_notes, current_notes, note_read_wri
|
|||
event.stop();
|
||||
} );
|
||||
}
|
||||
|
||||
var share_notebook_link = getElement( "share_notebook_link" );
|
||||
if ( share_notebook_link ) {
|
||||
connect( share_notebook_link, "onclick", function ( event ) {
|
||||
self.load_editor( "share this notebook", "null", null, null, getElement( "notes_top" ) );
|
||||
event.stop();
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
Wiki.prototype.background_clicked = function ( event ) {
|
||||
|
@ -413,6 +421,16 @@ Wiki.prototype.load_editor = function ( note_title, note_id, revision, link, pos
|
|||
this.display_search_results();
|
||||
return;
|
||||
}
|
||||
if ( note_title == "share this notebook" ) {
|
||||
var editor = this.open_editors[ note_title ];
|
||||
if ( editor ) {
|
||||
editor.highlight();
|
||||
return;
|
||||
}
|
||||
|
||||
this.share_notebook();
|
||||
return;
|
||||
}
|
||||
|
||||
// but if the note corresponding to the link's title is already open, highlight it and bail
|
||||
if ( !revision ) {
|
||||
|
@ -457,7 +475,7 @@ Wiki.prototype.resolve_link = function ( note_title, link, callback ) {
|
|||
if ( link && link.target )
|
||||
link.removeAttribute( "target" );
|
||||
|
||||
if ( note_title == "all notes" || note_title == "search results" ) {
|
||||
if ( note_title == "all notes" || note_title == "search results" || note_title == "share this notebook" ) {
|
||||
link.href = "/notebooks/" + this.notebook_id + "?" + queryString(
|
||||
[ "title", "note_id" ],
|
||||
[ note_title, "null" ]
|
||||
|
@ -465,8 +483,10 @@ Wiki.prototype.resolve_link = function ( note_title, link, callback ) {
|
|||
if ( callback ) {
|
||||
if ( note_title == "all notes" )
|
||||
callback( "list of all notes in this notebook" );
|
||||
else
|
||||
if ( note_title == "search results" )
|
||||
callback( "current search results" );
|
||||
else
|
||||
callback( "share this notebook with others" );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -1220,6 +1240,80 @@ Wiki.prototype.display_all_notes_list = function ( result ) {
|
|||
this.all_notes_editor = this.create_editor( "all_notes", "<h3>all notes</h3>" + list_holder.innerHTML, undefined, undefined, undefined, false, true, true, getElement( "notes_top" ) );
|
||||
}
|
||||
|
||||
Wiki.prototype.share_notebook = function () {
|
||||
this.clear_messages();
|
||||
this.clear_pulldowns();
|
||||
|
||||
var share_notebook_frame = getElement( "note_share_notebook" );
|
||||
if ( share_notebook_frame ) {
|
||||
share_notebook_frame.editor.highlight();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !this.rate_plan.notebook_sharing ) {
|
||||
this.display_message(
|
||||
"If you'd like to share your notebook, please ",
|
||||
[ createDOM( "a", { "href": "/upgrade", "target": "_new" }, "upgrade" ),
|
||||
" your account first." ]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var collaborators_link = createDOM( "a",
|
||||
{ "href": "#", "id": "collaborators_link", "class": "radio_link", "title": "Collaborators may view and edit this notebook." },
|
||||
"collaborators"
|
||||
);
|
||||
var viewers_link = createDOM( "a",
|
||||
{ "href": "#", "id": "viewers_link", "class": "radio_link", "title": "Viewers may only view this notebook." },
|
||||
"viewers"
|
||||
);
|
||||
var owners_link = createDOM( "a",
|
||||
{ "href": "#", "id": "owners_link", "class": "radio_link", "title": "Owners may view, edit, rename, delete, and invite people to this notebook." },
|
||||
"owners"
|
||||
);
|
||||
|
||||
var collaborators_radio = createDOM( "input",
|
||||
{ "type": "radio", "id": "collaborators_radio", "name": "access", "value": "collaborator", "checked": "true" }
|
||||
);
|
||||
var viewers_radio = createDOM( "input",
|
||||
{ "type": "radio", "id": "viewers_radio", "name": "access", "value": "viewer" }
|
||||
);
|
||||
var owners_radio = createDOM( "input",
|
||||
{ "type": "radio", "id": "owners_radio", "name": "access", "value": "owner" }
|
||||
)
|
||||
|
||||
var div = createDOM( "div", {},
|
||||
createDOM( "form", { "id": "invite_form" },
|
||||
createDOM( "input", { "type": "hidden", "name": "notebook_id", "value": this.notebook_id } ),
|
||||
createDOM( "p", {},
|
||||
createDOM( "b", {}, "people to invite" ),
|
||||
createDOM( "br", {} ),
|
||||
createDOM( "textarea",
|
||||
{ "name": "email_addresses", "class": "textarea_field", "cols": "40", "rows": "4", "wrap": "off" }
|
||||
)
|
||||
),
|
||||
createDOM( "p", {}, "Please separate email addresses with commas, spaces, or the enter key." ),
|
||||
createDOM( "p", {},
|
||||
createDOM( "p", {}, "Invite these people as:" ),
|
||||
createDOM( "table" , { "id": "access_table" },
|
||||
createDOM( "tr", {},
|
||||
createDOM( "td", {}, collaborators_radio, collaborators_link ),
|
||||
createDOM( "td", {}, viewers_radio, viewers_link ),
|
||||
createDOM( "td", {}, owners_radio, owners_link )
|
||||
)
|
||||
)
|
||||
),
|
||||
createDOM( "p", {},
|
||||
createDOM( "input",
|
||||
{ "type": "submit", "name": "invite_button", "id": "invite_button", "class": "button", "value": "send invites" }
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
this.create_editor( "share_notebook", "<h3>share this notebook</h3>" + div.innerHTML, undefined, undefined, undefined, false, true, true, getElement( "notes_top" ) );
|
||||
}
|
||||
|
||||
Wiki.prototype.display_message = function ( text, nodes, position_after ) {
|
||||
this.clear_messages();
|
||||
this.clear_pulldowns();
|
||||
|
@ -1432,7 +1526,7 @@ Wiki.prototype.remove_all_notes_link = function ( note_id ) {
|
|||
|
||||
Wiki.prototype.add_all_notes_link = function ( note_id, note_title ) {
|
||||
if ( !this.all_notes_editor ) return;
|
||||
if ( note_title == "all notes" || note_title == "search results" ) return;
|
||||
if ( note_title == "all notes" || note_title == "search results" || note_title == "share this notebook" ) return;
|
||||
|
||||
if ( !note_title || note_title.length == 0 )
|
||||
note_title = "untitled note";
|
||||
|
@ -1709,7 +1803,7 @@ function Options_pulldown( wiki, notebook_id, invoker, editor ) {
|
|||
this.invoker = invoker;
|
||||
this.editor = editor;
|
||||
this.startup_checkbox = createDOM( "input", { "type": "checkbox", "class": "pulldown_checkbox" } );
|
||||
this.startup_toggle = createDOM( "a", { "href": "", "class": "pulldown_link", "title": "Display this note whenever the notebook is loaded." },
|
||||
this.startup_toggle = createDOM( "a", { "href": "#", "class": "pulldown_link", "title": "Display this note whenever the notebook is loaded." },
|
||||
"show on startup"
|
||||
);
|
||||
|
||||
|
@ -1840,6 +1934,12 @@ function Link_pulldown( wiki, notebook_id, invoker, editor, link ) {
|
|||
return;
|
||||
}
|
||||
|
||||
if ( title == "share this notebook" ) {
|
||||
this.title_field.value = title;
|
||||
this.display_summary( title, "share this notebook with others" );
|
||||
return;
|
||||
}
|
||||
|
||||
this.invoker.invoke(
|
||||
"/notebooks/load_note_by_title", "GET", {
|
||||
"notebook_id": this.notebook_id,
|
||||
|
|
|
@ -24,6 +24,7 @@ class Initializer( object ):
|
|||
( u"password reset.html", False ),
|
||||
( u"advanced browser features.html", False ),
|
||||
( u"supported browsers.html", False ),
|
||||
( u"upgrade.html", False ),
|
||||
]
|
||||
|
||||
def __init__( self, database, nuke = False ):
|
||||
|
|
|
@ -24,6 +24,7 @@ class Updater( object ):
|
|||
( u"password reset.html", False ),
|
||||
( u"advanced browser features.html", False ),
|
||||
( u"supported browsers.html", False ),
|
||||
( u"upgrade.html", False ),
|
||||
]
|
||||
|
||||
def __init__( self, database, navigation_note_id = None ):
|
||||
|
|
|
@ -45,7 +45,7 @@ class Link_area( Div ):
|
|||
) or None,
|
||||
|
||||
notebook.read_write and Span(
|
||||
( notebook.name != u"trash" ) and Div(
|
||||
( notebook.owner and notebook.name != u"trash" ) and Div(
|
||||
A(
|
||||
u"rename notebook",
|
||||
href = u"#",
|
||||
|
@ -55,7 +55,7 @@ class Link_area( Div ):
|
|||
class_ = u"link_area_item",
|
||||
) or None,
|
||||
|
||||
( notebook.name != u"trash" ) and Div(
|
||||
( notebook.owner and notebook.name != u"trash" ) and Div(
|
||||
A(
|
||||
u"delete notebook",
|
||||
href = u"#",
|
||||
|
@ -75,6 +75,16 @@ class Link_area( Div ):
|
|||
class_ = u"link_area_item",
|
||||
),
|
||||
|
||||
( notebook.owner ) and Div(
|
||||
A(
|
||||
u"share",
|
||||
href = u"#",
|
||||
id = u"share_notebook_link",
|
||||
title = u"Share this notebook with others.",
|
||||
),
|
||||
class_ = u"link_area_item",
|
||||
) or None,
|
||||
|
||||
notebook.trash_id and Div(
|
||||
A(
|
||||
u"trash",
|
||||
|
|
Reference in New Issue