witten
/
luminotes
Archived
1
0
Fork 0

* Can now click on revision timestamps to open up the contents of previous note revisions with a small timestamp at the top.

* Revisions can be opened either in the current page or in a new window/tab.
 * Added ability for a read-write notebook to contain read-only notes. This supports showing read-only revisions.
 * Fixed updatedb.py to properly load the anonymous user.
 * Updated initdb.py and updatedb.py to deadl with new-style /notebooks/notebookid?note_id=noteid wiki links.
 * Made Persistent copy the revisions_list on each revision update so different revisions don't share lists.
 * Prevented Note from updating its revision twice upon construction. Now it's only updated once.
 * Work-around for nasty urlparse() caching bug related to unicode strings that cherrypy barfs on.
 * Added optional revision flag to various controller.Notebooks methods to allow opening of a notebook with a particular note revision displayed.
This commit is contained in:
Dan Helfman 2007-07-31 22:53:57 +00:00
parent b7b88f25a3
commit f23fcdde21
18 changed files with 258 additions and 95 deletions

View File

@ -138,7 +138,7 @@ class Database( object ):
print "error unpickling %s: %s" % ( object_id, pickled )
return None
self.__cache[ unicode( obj.object_id ).encode( "utf8" ) ] = obj
self.__cache[ unicode( obj.revision_id() ).encode( "utf8" ) ] = obj
self.__cache[ unicode( obj.revision_id() ).encode( "utf8" ) ] = copy( obj )
return obj

View File

@ -1,8 +1,8 @@
# originally from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496942
import urlparse
from htmllib import HTMLParser
from cgi import escape
from urlparse import urlparse
from formatter import AbstractFormatter, NullWriter
from htmlentitydefs import entitydefs
from xml.sax.saxutils import quoteattr
@ -120,7 +120,13 @@ class Html_cleaner(HTMLParser):
self.handle_endtag(tag, None)
def url_is_acceptable(self,url):
parsed = urlparse(url)
parsed = urlparse.urlparse(url)
# Work-around a nasty bug. urlparse() caches parsed results and returns them on future calls,
# and if the cache isn't cleared here, then a unicode string gets added to the cache, which
# freaks out cherrypy when it independently calls urlparse() with the same URL later.
urlparse.clear_cache()
return parsed[0] in self.allowed_schemes
def strip(self, rawstring):

View File

@ -39,11 +39,13 @@ class Notebooks( object ):
@validate(
notebook_id = Valid_id(),
note_id = Valid_id(),
revision = Valid_string( min = 19, max = 30 ),
)
def default( self, notebook_id, note_id = None ):
def default( self, notebook_id, note_id = None, revision = None ):
return dict(
notebook_id = notebook_id,
note_id = note_id,
revision = revision,
)
@expose( view = Json )
@ -55,33 +57,10 @@ class Notebooks( object ):
@validate(
notebook_id = Valid_id(),
note_id = Valid_id( none_okay = True ),
revision = Valid_string( min = 0, max = 30 ),
user_id = Valid_id( none_okay = True ),
)
def contents( self, notebook_id, note_id = None, user_id = None ):
self.check_access( notebook_id, user_id, self.__scheduler.thread )
if not ( yield Scheduler.SLEEP ):
raise Access_error()
self.__database.load( notebook_id, self.__scheduler.thread )
notebook = ( yield Scheduler.SLEEP )
yield dict(
notebook = notebook,
note = notebook.lookup_note( note_id ),
)
@expose( view = Json )
@strongly_expire
@wait_for_update
@grab_user_id
@async
@update_client
@validate(
notebook_id = Valid_id(),
note_id = Valid_id(),
user_id = Valid_id( none_okay = True ),
)
def load_note( self, notebook_id, note_id, user_id ):
def contents( self, notebook_id, note_id = None, revision = None, user_id = None ):
self.check_access( notebook_id, user_id, self.__scheduler.thread )
if not ( yield Scheduler.SLEEP ):
raise Access_error()
@ -94,6 +73,44 @@ class Notebooks( object ):
else:
note = notebook.lookup_note( note_id )
if revision:
self.__database.load( note_id, self.__scheduler.thread, revision )
note = ( yield Scheduler.SLEEP )
yield dict(
notebook = notebook,
note = note,
)
@expose( view = Json )
@strongly_expire
@wait_for_update
@grab_user_id
@async
@update_client
@validate(
notebook_id = Valid_id(),
note_id = Valid_id(),
revision = Valid_string( min = 19, max = 30 ),
user_id = Valid_id( none_okay = True ),
)
def load_note( self, notebook_id, note_id, revision = None, user_id = None ):
self.check_access( notebook_id, user_id, self.__scheduler.thread )
if not ( yield Scheduler.SLEEP ):
raise Access_error()
self.__database.load( notebook_id, self.__scheduler.thread )
notebook = ( yield Scheduler.SLEEP )
if notebook is None:
note = None
else:
note = notebook.lookup_note( note_id )
if revision:
self.__database.load( note_id, self.__scheduler.thread, revision )
note = ( yield Scheduler.SLEEP )
yield dict(
note = note,
)
@ -264,7 +281,7 @@ class Notebooks( object ):
yield dict()
@expose( view = Note_page )
@validate( id = Valid_id() )
@validate( id = Valid_string( min = 1, max = 100 ) )
def blank_note( self, id ):
return dict( id = id )
@ -382,7 +399,7 @@ class Notebooks( object ):
self.__database.load( user_id, self.__scheduler.thread )
user = ( yield Scheduler.SLEEP )
if user.has_access( notebook_id ):
if user and user.has_access( notebook_id ):
access = True
yield callback, access

View File

@ -37,6 +37,7 @@ class Test_database( object ):
def test_save_and_load( self ):
def gen():
basic_obj = Some_object( object_id = "5", value = 1 )
original_revision = basic_obj.revision
self.database.save( basic_obj, self.scheduler.thread )
yield Scheduler.SLEEP
@ -45,6 +46,8 @@ class Test_database( object ):
obj = ( yield Scheduler.SLEEP )
assert obj.object_id == basic_obj.object_id
assert obj.revision == original_revision
assert obj.revisions_list == [ original_revision ]
assert obj.value == basic_obj.value
g = gen()
@ -54,7 +57,9 @@ class Test_database( object ):
def test_complex_save_and_load( self ):
def gen():
basic_obj = Some_object( object_id = "7", value = 2 )
basic_original_revision = basic_obj.revision
complex_obj = Some_object( object_id = "6", value = basic_obj )
complex_original_revision = complex_obj.revision
self.database.save( complex_obj, self.scheduler.thread )
yield Scheduler.SLEEP
@ -64,14 +69,20 @@ class Test_database( object ):
if self.clear_cache: self.database.clear_cache()
assert obj.object_id == complex_obj.object_id
assert obj.revision == complex_original_revision
assert obj.revisions_list == [ complex_original_revision ]
assert obj.value.object_id == basic_obj.object_id
assert obj.value.value == basic_obj.value
assert obj.value.revision == basic_original_revision
assert obj.value.revisions_list == [ basic_original_revision ]
self.database.load( basic_obj.object_id, self.scheduler.thread )
obj = ( yield Scheduler.SLEEP )
assert obj.object_id == basic_obj.object_id
assert obj.value == basic_obj.value
assert obj.revision == basic_original_revision
assert obj.revisions_list == [ basic_original_revision ]
g = gen()
self.scheduler.add( g )
@ -80,6 +91,7 @@ class Test_database( object ):
def test_save_and_load_by_secondary( self ):
def gen():
basic_obj = Some_object( object_id = "5", value = 1, secondary_id = u"foo" )
original_revision = basic_obj.revision
self.database.save( basic_obj, self.scheduler.thread )
yield Scheduler.SLEEP
@ -89,6 +101,8 @@ class Test_database( object ):
assert obj.object_id == basic_obj.object_id
assert obj.value == basic_obj.value
assert obj.revision == original_revision
assert obj.revisions_list == [ original_revision ]
g = gen()
self.scheduler.add( g )
@ -97,7 +111,9 @@ class Test_database( object ):
def test_duplicate_save_and_load( self ):
def gen():
basic_obj = Some_object( object_id = "9", value = 3 )
basic_original_revision = basic_obj.revision
complex_obj = Some_object( object_id = "8", value = basic_obj, value2 = basic_obj )
complex_original_revision = complex_obj.revision
self.database.save( complex_obj, self.scheduler.thread )
yield Scheduler.SLEEP
@ -107,10 +123,19 @@ class Test_database( object ):
if self.clear_cache: self.database.clear_cache()
assert obj.object_id == complex_obj.object_id
assert obj.revision == complex_original_revision
assert obj.revisions_list == [ complex_original_revision ]
assert obj.value.object_id == basic_obj.object_id
assert obj.value.value == basic_obj.value
assert obj.value.revision == basic_original_revision
assert obj.value.revisions_list == [ basic_original_revision ]
assert obj.value2.object_id == basic_obj.object_id
assert obj.value2.value == basic_obj.value
assert obj.value2.revision == basic_original_revision
assert obj.value2.revisions_list == [ basic_original_revision ]
assert obj.value == obj.value2
self.database.load( basic_obj.object_id, self.scheduler.thread )
@ -118,6 +143,8 @@ class Test_database( object ):
assert obj.object_id == basic_obj.object_id
assert obj.value == basic_obj.value
assert obj.revision == basic_original_revision
assert obj.revisions_list == [ basic_original_revision ]
g = gen()
self.scheduler.add( g )
@ -143,14 +170,17 @@ class Test_database( object ):
assert obj.object_id == basic_obj.object_id
assert obj.revision == basic_obj.revision
assert obj.revisions_list == [ original_revision, basic_obj.revision ]
assert obj.value == basic_obj.value
self.database.load( basic_obj.object_id, self.scheduler.thread, revision = original_revision )
obj = ( yield Scheduler.SLEEP )
revised = ( yield Scheduler.SLEEP )
assert obj.object_id == basic_obj.object_id
assert obj.revision == original_revision
assert obj.value == 1
assert revised.object_id == basic_obj.object_id
assert revised.value == 1
assert revised.revision == original_revision
assert id( obj.revisions_list ) != id( revised.revisions_list )
assert revised.revisions_list == [ original_revision ]
g = gen()
self.scheduler.add( g )
@ -171,6 +201,7 @@ class Test_database( object ):
def test_reload( self ):
def gen():
basic_obj = Some_object( object_id = "5", value = 1 )
original_revision = basic_obj.revision
self.database.save( basic_obj, self.scheduler.thread )
yield Scheduler.SLEEP
@ -192,6 +223,8 @@ class Test_database( object ):
assert obj.object_id == basic_obj.object_id
assert obj.value == 55
assert obj.revision == original_revision
assert obj.revisions_list == [ original_revision ]
g = gen()
self.scheduler.add( g )
@ -229,6 +262,7 @@ class Test_database( object ):
assert obj.object_id == basic_obj.object_id
assert obj.revision == original_revision
assert obj.revisions_list == [ original_revision ]
assert obj.value == 55
g = gen()

View File

@ -1,5 +1,6 @@
import cherrypy
import cgi
from urllib import quote
from Test_controller import Test_controller
from controller.Scheduler import Scheduler
from model.Notebook import Notebook
@ -68,6 +69,17 @@ class Test_notebooks( Test_controller ):
assert result.get( u"notebook_id" ) == self.notebook.object_id
assert result.get( u"note_id" ) == self.note.object_id
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 ) ),
) )
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 )
def test_contents( self ):
self.login()
@ -100,6 +112,28 @@ class Test_notebooks( Test_controller ):
assert note.object_id == self.note.object_id
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,
)
notebook = result[ "notebook" ]
assert notebook.object_id == self.notebook.object_id
assert len( notebook.startup_notes ) == 1
assert notebook.startup_notes[ 0 ] == self.note
note = result[ "note" ]
assert note.object_id == self.note.object_id
def test_contents_without_login( self ):
result = self.http_get(
"/notebooks/contents?notebook_id=%s" % self.notebook.object_id,
@ -122,6 +156,35 @@ class Test_notebooks( Test_controller ):
assert note.title == self.note.title
assert note.contents == self.note.contents
def test_load_note_with_revision( self ):
self.login()
# update the note to generate a new revision
previous_revision = self.note.revision
previous_title = self.note.title
previous_contents = self.note.contents
new_note_contents = u"<h3>new title</h3>new blah"
result = self.http_post( "/notebooks/save_note/", dict(
notebook_id = self.notebook.object_id,
note_id = self.note.object_id,
contents = new_note_contents,
), session_id = self.session_id )
# load the note by the old revision
result = self.http_post( "/notebooks/load_note/", dict(
notebook_id = self.notebook.object_id,
note_id = self.note.object_id,
revision = previous_revision,
), session_id = self.session_id )
note = result[ "note" ]
# assert that we get the previous revision of the note, not the new one
assert note.object_id == self.note.object_id
assert note.revision == previous_revision
assert note.title == previous_title
assert note.contents == previous_contents
def test_load_note_without_login( self ):
result = self.http_post( "/notebooks/load_note/", dict(
notebook_id = self.notebook.object_id,
@ -510,6 +573,7 @@ class Test_notebooks( Test_controller ):
note_id = self.note.object_id,
), session_id = self.session_id )
print result
assert result.get( "note" ) == None
def test_delete_note_without_login( self ):

View File

@ -24,10 +24,11 @@ class Note( Persistent ):
self.__title = None
self.__contents = None or ""
self.__set_contents( contents )
self.__set_contents( contents, new_revision = False )
def __set_contents( self, contents ):
self.update_revision()
def __set_contents( self, contents, new_revision = True ):
if new_revision:
self.update_revision()
self.__contents = contents
# parse title out of the beginning of the contents

View File

@ -10,7 +10,9 @@ class Persistent( object ):
def update_revision( self ):
self.__revision = datetime.now()
self.__revisions_list.append( self.__revision )
# make a new copy of the list to prevent sharing of this list between different revisions
self.__revisions_list = self.__revisions_list + [ self.__revision ]
def revision_id( self ):
return "%s %s" % ( self.__object_id, self.__revision )

View File

@ -27,6 +27,10 @@ h3 {
-webkit-border-radius: 0.5em;
}
.small_text {
font-size: 0.75em;
}
ul li {
margin-top: 0.5em;
}

View File

@ -33,5 +33,5 @@ A single search looks through every word in the entire wiki.</li>
</ul>
<p>
Sound interesting? Then <a href="/notes/new">take a tour</a> or <a href="/notes/new">try it out</a> for yourself!
Sound interesting? Then <a href="/notebooks/%s?note_id=new">take a tour</a> or <a href="/notebooks/%s?note_id=new">try it out</a> for yourself!
</p>

View File

@ -1,6 +1,6 @@
<h3>login</h3>
No account yet? Want to make a wiki? You can <a href="/notes/new">try it out</a> for free.
No account yet? Want to make a wiki? You can <a href="/notebooks/%s?note_id=new">try it out</a> for free.
<form id="login_form">
<p>
@ -18,6 +18,6 @@ No account yet? Want to make a wiki? You can <a href="/notes/new">try it out</a>
</p>
<p>
Forgot? Need your <a href="/notes/new">password reset</a>?
Forgot? Need your <a href="/notebooks/%s?note_id=new">password reset</a>?
</p>
</form>

View File

@ -1,5 +1,5 @@
<a href="/notes/new">about</a> -
<a href="/notes/new">features</a> -
<a href="/notes/new">take a tour</a> -
<a href="/notes/new">try it out</a> -
<a href="/notes/new">login</a>
<a href="/notebooks/%s?note_id=new">about</a> -
<a href="/notebooks/%s?note_id=new">features</a> -
<a href="/notebooks/%s?note_id=new">take a tour</a> -
<a href="/notebooks/%s?note_id=new">try it out</a> -
<a href="/notebooks/%s?note_id=new">login</a>

View File

@ -1,7 +1,7 @@
<h3>supported browsers</h3>
<p>
Luminotes makes use of some <a href="/notes/new">advanced browser features</a>,
Luminotes makes use of some <a href="/notebooks/%s?note_id=new">advanced browser features</a>,
so not all browsers will work for editing your wiki. Supported browsers include:
</p>

View File

@ -35,5 +35,5 @@ dream of giving out your email address.
<input type="submit" name="signup_button" id="signup_button" class="button" value="sign up" />
</p>
Please make sure you're using one of the <a href="/notes/new">supported browsers</a>.
Please make sure you're using one of the <a href="/notebooks/?note_id=new">supported browsers</a>.
</form>

View File

@ -126,13 +126,13 @@ 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.document, "onclick", function ( event ) { self.mouse_clicked( 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 ); } );
// special-case: connect any submit buttons within the contents of this note
var signup_button = withDocument( this.document, function () { return getElement( "signup_button" ); } );
@ -431,6 +431,8 @@ Editor.prototype.focus = function () {
// return true if the specified state is enabled
Editor.prototype.state_enabled = function ( state_name ) {
if ( !this.read_write ) return false;
state_name = state_name.toLowerCase();
var format_block = this.document.queryCommandValue( "formatblock" ).toLowerCase();
var heading = ( format_block == "h3" || format_block == "heading 3" );

View File

@ -17,7 +17,8 @@ function Wiki() {
this.invoker.invoke(
"/notebooks/contents", "GET", {
"notebook_id": this.notebook_id,
"note_id": getElement( "note_id" ).value
"note_id": getElement( "note_id" ).value,
"revision": getElement( "revision" ).value
},
function( result ) { self.populate( result ); }
);
@ -126,13 +127,15 @@ Wiki.prototype.populate = function ( result ) {
// don't actually create an editor if a particular note was provided in the result
if ( !result.note ) {
var focus = ( i == 0 );
this.create_editor( note.object_id, note.contents, note.revisions_list, undefined, undefined, false, focus );
this.create_editor( note.object_id, note.contents, note.revisions_list, undefined, undefined, this.read_write, false, focus );
}
}
// if one particular note was provided, then just display an editor for that note
var read_write = this.read_write;
if ( getElement( "revision" ).value ) read_write = false;
if ( result.note )
this.create_editor( result.note.object_id, result.note.contents, result.note.revisions_list, undefined, undefined, false, true );
this.create_editor( result.note.object_id, result.note.contents, result.note.revisions_list, undefined, undefined, read_write, false, true );
}
Wiki.prototype.background_clicked = function ( event ) {
@ -160,18 +163,19 @@ Wiki.prototype.create_blank_editor = function ( event ) {
}
}
this.blank_editor_id = this.create_editor( undefined, undefined, undefined, undefined, undefined, true, true );
this.blank_editor_id = this.create_editor( undefined, undefined, undefined, undefined, undefined, this.read_write, true, true );
}
Wiki.prototype.load_editor = function ( note_title, insert_after_iframe_id, note_id ) {
Wiki.prototype.load_editor = function ( note_title, insert_after_iframe_id, note_id, revision ) {
var self = this;
this.invoker.invoke(
"/notebooks/load_note", "GET", {
"notebook_id": this.notebook_id,
"note_id": note_id
"note_id": note_id,
"revision": revision
},
function ( result ) { self.parse_loaded_editor( result, insert_after_iframe_id, note_title ); }
function ( result ) { self.parse_loaded_editor( result, insert_after_iframe_id, note_title, revision ); }
);
}
@ -187,9 +191,10 @@ Wiki.prototype.load_editor_by_title = function ( note_title, insert_after_iframe
);
}
Wiki.prototype.parse_loaded_editor = function ( result, insert_after_iframe_id, note_title ) {
Wiki.prototype.parse_loaded_editor = function ( result, insert_after_iframe_id, note_title, revision ) {
if ( result.note ) {
var id = result.note.object_id
var id = result.note.object_id;
if ( revision ) id += " " + revision;
var note_text = result.note.contents;
var revisions_list = result.note.revisions_list;
} else {
@ -198,10 +203,15 @@ Wiki.prototype.parse_loaded_editor = function ( result, insert_after_iframe_id,
var revisions_list = new Array();
}
this.create_editor( id, note_text, revisions_list, insert_after_iframe_id, note_title, true, false );
if ( revision )
var read_write = false; // show previous revisions as read-only
else
var read_write = this.read_write;
this.create_editor( id, note_text, revisions_list, insert_after_iframe_id, note_title, read_write, true, false );
}
Wiki.prototype.create_editor = function ( id, note_text, revisions_list, insert_after_iframe_id, note_title, highlight, focus ) {
Wiki.prototype.create_editor = function ( id, note_text, revisions_list, insert_after_iframe_id, note_title, read_write, highlight, focus ) {
this.clear_messages();
this.clear_pulldowns();
@ -240,8 +250,14 @@ Wiki.prototype.create_editor = function ( id, note_text, revisions_list, insert_
}
}
// 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 && revisions_list && revisions_list.length ) {
var short_revision = this.brief_revision( revisions_list[ revisions_list.length - 1 ] );
note_text = "<p class=\"small_text\">Previous revision from " + short_revision + "</p>" + note_text;
}
var startup = this.startup_notes[ id ];
var editor = new Editor( id, this.notebook_id, note_text, revisions_list, undefined, this.read_write, startup, highlight, focus );
var editor = new Editor( id, this.notebook_id, note_text, revisions_list, undefined, read_write, startup, highlight, focus );
if ( this.read_write ) {
connect( editor, "state_changed", this, "editor_state_changed" );
@ -336,7 +352,7 @@ Wiki.prototype.toggle_button = function ( event, button_id, state_name ) {
this.clear_messages();
this.clear_pulldowns();
if ( this.focused_editor ) {
if ( this.focused_editor && this.focused_editor.read_write ) {
this.focused_editor.focus();
this.focused_editor.exec_command( state_name || button_id );
this.focused_editor.resize();
@ -368,7 +384,7 @@ Wiki.prototype.toggle_link_button = function ( event ) {
this.clear_messages();
this.clear_pulldowns();
if ( this.focused_editor ) {
if ( this.focused_editor && this.focused_editor.read_write ) {
this.focused_editor.focus();
toggleElementClass( "button_down", "createLink" );
if ( hasElementClass( "createLink", "button_down" ) )
@ -413,7 +429,7 @@ Wiki.prototype.delete_editor = function ( event, editor ) {
if ( this.startup_notes[ editor.id ] )
delete this.startup_notes[ editor.id ];
if ( this.read_write ) {
if ( this.read_write && editor.read_write ) {
this.invoker.invoke( "/notebooks/delete_note", "POST", {
"notebook_id": this.notebook_id,
"note_id": editor.id
@ -434,8 +450,7 @@ Wiki.prototype.save_editor = function ( editor, fire_and_forget ) {
editor = this.focused_editor;
var self = this;
if ( editor && !editor.empty() ) {
// TODO: do something with the result other than just ignoring it
if ( editor && editor.read_write && !editor.empty() ) {
this.invoker.invoke( "/notebooks/save_note", "POST", {
"notebook_id": this.notebook_id,
"note_id": editor.id,
@ -494,7 +509,7 @@ Wiki.prototype.display_search_results = function ( result ) {
continue;
}
this.create_editor( note.object_id, note.contents, note.revisions_list, undefined, undefined, false, focus );
this.create_editor( note.object_id, note.contents, note.revisions_list, undefined, undefined, this.read_write, false, focus );
}
}
@ -530,6 +545,10 @@ Wiki.prototype.clear_pulldowns = function () {
}
}
Wiki.prototype.brief_revision = function ( revision ) {
return revision.split( /\.\d/ )[ 0 ]; // strip off seconds from the timestamp
}
Wiki.prototype.toggle_editor_changes = function ( event, editor ) {
// if the pulldown is already open, then just close it
var pulldown_id = "changes_" + editor.id;
@ -539,7 +558,7 @@ Wiki.prototype.toggle_editor_changes = function ( event, editor ) {
return;
}
new Changes_pulldown( this.notebook_id, this.invoker, editor );
new Changes_pulldown( this, this.notebook_id, this.invoker, editor );
event.stop();
}
@ -552,14 +571,15 @@ Wiki.prototype.toggle_editor_options = function ( event, editor ) {
return;
}
new Options_pulldown( this.notebook_id, this.invoker, editor );
new Options_pulldown( this, this.notebook_id, this.invoker, editor );
event.stop();
}
connect( window, "onload", function ( event ) { new Wiki(); } );
function Pulldown( notebook_id, pulldown_id, button ) {
function Pulldown( wiki, notebook_id, pulldown_id, button ) {
this.wiki = wiki;
this.notebook_id = notebook_id;
this.div = createDOM( "div", { "id": pulldown_id, "class": "pulldown" } );
this.div.pulldown = this;
@ -584,8 +604,8 @@ Pulldown.prototype.shutdown = function () {
}
function Options_pulldown( notebook_id, invoker, editor ) {
Pulldown.call( this, notebook_id, "options_" + editor.id, editor.options_button );
function Options_pulldown( wiki, notebook_id, invoker, editor ) {
Pulldown.call( this, wiki, notebook_id, "options_" + editor.id, editor.options_button );
this.invoker = invoker;
this.editor = editor;
@ -611,32 +631,27 @@ Options_pulldown.prototype.startup_clicked = function ( event ) {
this.startup_checkbox.checked = this.startup_checkbox.checked ? false : true;
this.editor.startup = this.startup_checkbox.checked;
// if this note isn't empty, save it along with its startup status
if ( !this.editor.empty() ) {
this.invoker.invoke( "/notebooks/save_note", "POST", {
"notebook_id": this.notebook_id,
"note_id": this.editor.id,
"contents": this.editor.contents(),
"startup": this.editor.startup
} );
}
// save this note along with its toggled startup state
this.wiki.save_editor( this.editor );
}
Options_pulldown.prototype.shutdown = function () {
Pulldown.prototype.shutdown.call( this );
disconnectAll( this.startup_checkbox );
disconnectAll( this.startup_toggle );
}
function Changes_pulldown( notebook_id, invoker, editor ) {
Pulldown.call( this, notebook_id, "changes_" + editor.id, editor.changes_button );
function Changes_pulldown( wiki, notebook_id, invoker, editor ) {
Pulldown.call( this, wiki, notebook_id, "changes_" + editor.id, editor.changes_button );
this.invoker = invoker;
this.editor = editor;
this.links = new Array();
// display list of revision timestamps in reverse chronological order
if ( isUndefinedOrNull( this.editor.revisions_list ) ) {
if ( isUndefinedOrNull( this.editor.revisions_list ) || this.editor.revisions_list.length == 0 ) {
appendChildNodes( this.div, createDOM( "span", "This note has no previous changes." ) );
return;
}
@ -644,18 +659,35 @@ function Changes_pulldown( notebook_id, invoker, editor ) {
var revisions_list = clone( this.editor.revisions_list );
revisions_list.reverse();
var self = this;
for ( var i = 0; i < revisions_list.length; ++i ) {
var revision = revisions_list[ i ];
revision = revision.split( /\.\d/ )[ 0 ]; // strip off seconds from the timestamp
var short_revision = this.wiki.brief_revision( revision );
var href = "/notebooks/" + this.notebook_id + "?" + queryString(
[ "note_id", "revision" ],
[ this.editor.id, revision ]
);
// appendChildNodes( this.div, createDOM( "a", { "href": href, "class": "pulldown_link" }, revision ) );
appendChildNodes( this.div, createDOM( "span", {}, revision ) );
var link = createDOM( "a", { "href": href, "class": "pulldown_link" }, short_revision );
this.links.push( link );
link.revision = revision;
connect( link, "onclick", function ( event ) { self.link_clicked( event, self.editor.id ); } );
appendChildNodes( this.div, link );
appendChildNodes( this.div, createDOM( "br" ) );
}
}
Changes_pulldown.prototype = Pulldown;
Changes_pulldown.prototype.constructor = Changes_pulldown;
Changes_pulldown.prototype.link_clicked = function( event, note_id ) {
var revision = event.target().revision;
this.wiki.load_editor( "Revision not found.", null, note_id, revision );
event.stop();
}
Options_pulldown.prototype.shutdown = function () {
Pulldown.prototype.shutdown.call( this );
for ( var i in this.links )
disconnectAll( this.links[ i ] );
}

View File

@ -52,7 +52,7 @@ class Initializer( object ):
for ( filename, startup ) in self.ENTRY_FILES:
full_filename = os.path.join( self.HTML_PATH, filename )
contents = file( full_filename ).read()
contents = file( full_filename ).read().replace( "%s", main_notebook_id )
self.database.next_id( self.scheduler.thread )
note_id = ( yield Scheduler.SLEEP )

View File

@ -34,7 +34,7 @@ class Initializer( object ):
self.scheduler.wait_for( thread )
def update_main_notebook( self ):
self.database.load( u"anonymous", self.scheduler.thread )
self.database.load( u"User anonymous", self.scheduler.thread )
anonymous = ( yield Scheduler.SLEEP )
main_notebook = anonymous.notebooks[ 0 ]._Read_only_notebook__wrapped
startup_notes = []
@ -42,7 +42,7 @@ class Initializer( object ):
# update all of the notes in the main notebook
for ( filename, startup ) in self.ENTRY_FILES:
full_filename = os.path.join( self.HTML_PATH, filename )
contents = file( full_filename ).read()
contents = file( full_filename ).read().replace( "%s", main_notebook.object_id )
title = filename.replace( u".html", u"" )
note = main_notebook.lookup_note_by_title( title )

View File

@ -6,7 +6,7 @@ from Toolbar import Toolbar
class Main_page( Page ):
def __init__( self, notebook_id = None, note_id = None ):
def __init__( self, notebook_id = None, note_id = None, revision = None ):
title = None
Page.__init__(
@ -14,6 +14,7 @@ class Main_page( Page ):
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"revision", id = u"revision", value = revision or "" ),
Div(
id = u"status_area",
),