* 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:
parent
b7b88f25a3
commit
f23fcdde21
|
@ -138,7 +138,7 @@ class Database( object ):
|
||||||
print "error unpickling %s: %s" % ( object_id, pickled )
|
print "error unpickling %s: %s" % ( object_id, pickled )
|
||||||
return None
|
return None
|
||||||
self.__cache[ unicode( obj.object_id ).encode( "utf8" ) ] = obj
|
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
|
return obj
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# originally from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496942
|
# originally from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496942
|
||||||
|
|
||||||
|
import urlparse
|
||||||
from htmllib import HTMLParser
|
from htmllib import HTMLParser
|
||||||
from cgi import escape
|
from cgi import escape
|
||||||
from urlparse import urlparse
|
|
||||||
from formatter import AbstractFormatter, NullWriter
|
from formatter import AbstractFormatter, NullWriter
|
||||||
from htmlentitydefs import entitydefs
|
from htmlentitydefs import entitydefs
|
||||||
from xml.sax.saxutils import quoteattr
|
from xml.sax.saxutils import quoteattr
|
||||||
|
@ -120,7 +120,13 @@ class Html_cleaner(HTMLParser):
|
||||||
self.handle_endtag(tag, None)
|
self.handle_endtag(tag, None)
|
||||||
|
|
||||||
def url_is_acceptable(self,url):
|
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
|
return parsed[0] in self.allowed_schemes
|
||||||
|
|
||||||
def strip(self, rawstring):
|
def strip(self, rawstring):
|
||||||
|
|
|
@ -39,11 +39,13 @@ class Notebooks( object ):
|
||||||
@validate(
|
@validate(
|
||||||
notebook_id = Valid_id(),
|
notebook_id = Valid_id(),
|
||||||
note_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(
|
return dict(
|
||||||
notebook_id = notebook_id,
|
notebook_id = notebook_id,
|
||||||
note_id = note_id,
|
note_id = note_id,
|
||||||
|
revision = revision,
|
||||||
)
|
)
|
||||||
|
|
||||||
@expose( view = Json )
|
@expose( view = Json )
|
||||||
|
@ -55,33 +57,10 @@ class Notebooks( object ):
|
||||||
@validate(
|
@validate(
|
||||||
notebook_id = Valid_id(),
|
notebook_id = Valid_id(),
|
||||||
note_id = Valid_id( none_okay = True ),
|
note_id = Valid_id( none_okay = True ),
|
||||||
|
revision = Valid_string( min = 0, max = 30 ),
|
||||||
user_id = Valid_id( none_okay = True ),
|
user_id = Valid_id( none_okay = True ),
|
||||||
)
|
)
|
||||||
def contents( self, notebook_id, note_id = None, user_id = None ):
|
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()
|
|
||||||
|
|
||||||
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 ):
|
|
||||||
self.check_access( notebook_id, user_id, self.__scheduler.thread )
|
self.check_access( notebook_id, user_id, self.__scheduler.thread )
|
||||||
if not ( yield Scheduler.SLEEP ):
|
if not ( yield Scheduler.SLEEP ):
|
||||||
raise Access_error()
|
raise Access_error()
|
||||||
|
@ -94,6 +73,44 @@ class Notebooks( object ):
|
||||||
else:
|
else:
|
||||||
note = notebook.lookup_note( note_id )
|
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(
|
yield dict(
|
||||||
note = note,
|
note = note,
|
||||||
)
|
)
|
||||||
|
@ -264,7 +281,7 @@ class Notebooks( object ):
|
||||||
yield dict()
|
yield dict()
|
||||||
|
|
||||||
@expose( view = Note_page )
|
@expose( view = Note_page )
|
||||||
@validate( id = Valid_id() )
|
@validate( id = Valid_string( min = 1, max = 100 ) )
|
||||||
def blank_note( self, id ):
|
def blank_note( self, id ):
|
||||||
return dict( id = id )
|
return dict( id = id )
|
||||||
|
|
||||||
|
@ -382,7 +399,7 @@ class Notebooks( object ):
|
||||||
self.__database.load( user_id, self.__scheduler.thread )
|
self.__database.load( user_id, self.__scheduler.thread )
|
||||||
user = ( yield Scheduler.SLEEP )
|
user = ( yield Scheduler.SLEEP )
|
||||||
|
|
||||||
if user.has_access( notebook_id ):
|
if user and user.has_access( notebook_id ):
|
||||||
access = True
|
access = True
|
||||||
|
|
||||||
yield callback, access
|
yield callback, access
|
||||||
|
|
|
@ -37,6 +37,7 @@ class Test_database( object ):
|
||||||
def test_save_and_load( self ):
|
def test_save_and_load( self ):
|
||||||
def gen():
|
def gen():
|
||||||
basic_obj = Some_object( object_id = "5", value = 1 )
|
basic_obj = Some_object( object_id = "5", value = 1 )
|
||||||
|
original_revision = basic_obj.revision
|
||||||
|
|
||||||
self.database.save( basic_obj, self.scheduler.thread )
|
self.database.save( basic_obj, self.scheduler.thread )
|
||||||
yield Scheduler.SLEEP
|
yield Scheduler.SLEEP
|
||||||
|
@ -45,6 +46,8 @@ class Test_database( object ):
|
||||||
obj = ( yield Scheduler.SLEEP )
|
obj = ( yield Scheduler.SLEEP )
|
||||||
|
|
||||||
assert obj.object_id == basic_obj.object_id
|
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
|
assert obj.value == basic_obj.value
|
||||||
|
|
||||||
g = gen()
|
g = gen()
|
||||||
|
@ -54,7 +57,9 @@ class Test_database( object ):
|
||||||
def test_complex_save_and_load( self ):
|
def test_complex_save_and_load( self ):
|
||||||
def gen():
|
def gen():
|
||||||
basic_obj = Some_object( object_id = "7", value = 2 )
|
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_obj = Some_object( object_id = "6", value = basic_obj )
|
||||||
|
complex_original_revision = complex_obj.revision
|
||||||
|
|
||||||
self.database.save( complex_obj, self.scheduler.thread )
|
self.database.save( complex_obj, self.scheduler.thread )
|
||||||
yield Scheduler.SLEEP
|
yield Scheduler.SLEEP
|
||||||
|
@ -64,14 +69,20 @@ class Test_database( object ):
|
||||||
if self.clear_cache: self.database.clear_cache()
|
if self.clear_cache: self.database.clear_cache()
|
||||||
|
|
||||||
assert obj.object_id == complex_obj.object_id
|
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.object_id == basic_obj.object_id
|
||||||
assert obj.value.value == basic_obj.value
|
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 )
|
self.database.load( basic_obj.object_id, self.scheduler.thread )
|
||||||
obj = ( yield Scheduler.SLEEP )
|
obj = ( yield Scheduler.SLEEP )
|
||||||
|
|
||||||
assert obj.object_id == basic_obj.object_id
|
assert obj.object_id == basic_obj.object_id
|
||||||
assert obj.value == basic_obj.value
|
assert obj.value == basic_obj.value
|
||||||
|
assert obj.revision == basic_original_revision
|
||||||
|
assert obj.revisions_list == [ basic_original_revision ]
|
||||||
|
|
||||||
g = gen()
|
g = gen()
|
||||||
self.scheduler.add( g )
|
self.scheduler.add( g )
|
||||||
|
@ -80,6 +91,7 @@ class Test_database( object ):
|
||||||
def test_save_and_load_by_secondary( self ):
|
def test_save_and_load_by_secondary( self ):
|
||||||
def gen():
|
def gen():
|
||||||
basic_obj = Some_object( object_id = "5", value = 1, secondary_id = u"foo" )
|
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 )
|
self.database.save( basic_obj, self.scheduler.thread )
|
||||||
yield Scheduler.SLEEP
|
yield Scheduler.SLEEP
|
||||||
|
@ -89,6 +101,8 @@ class Test_database( object ):
|
||||||
|
|
||||||
assert obj.object_id == basic_obj.object_id
|
assert obj.object_id == basic_obj.object_id
|
||||||
assert obj.value == basic_obj.value
|
assert obj.value == basic_obj.value
|
||||||
|
assert obj.revision == original_revision
|
||||||
|
assert obj.revisions_list == [ original_revision ]
|
||||||
|
|
||||||
g = gen()
|
g = gen()
|
||||||
self.scheduler.add( g )
|
self.scheduler.add( g )
|
||||||
|
@ -97,7 +111,9 @@ class Test_database( object ):
|
||||||
def test_duplicate_save_and_load( self ):
|
def test_duplicate_save_and_load( self ):
|
||||||
def gen():
|
def gen():
|
||||||
basic_obj = Some_object( object_id = "9", value = 3 )
|
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_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 )
|
self.database.save( complex_obj, self.scheduler.thread )
|
||||||
yield Scheduler.SLEEP
|
yield Scheduler.SLEEP
|
||||||
|
@ -107,10 +123,19 @@ class Test_database( object ):
|
||||||
if self.clear_cache: self.database.clear_cache()
|
if self.clear_cache: self.database.clear_cache()
|
||||||
|
|
||||||
assert obj.object_id == complex_obj.object_id
|
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.object_id == basic_obj.object_id
|
||||||
assert obj.value.value == basic_obj.value
|
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.object_id == basic_obj.object_id
|
||||||
assert obj.value2.value == basic_obj.value
|
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
|
assert obj.value == obj.value2
|
||||||
|
|
||||||
self.database.load( basic_obj.object_id, self.scheduler.thread )
|
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.object_id == basic_obj.object_id
|
||||||
assert obj.value == basic_obj.value
|
assert obj.value == basic_obj.value
|
||||||
|
assert obj.revision == basic_original_revision
|
||||||
|
assert obj.revisions_list == [ basic_original_revision ]
|
||||||
|
|
||||||
g = gen()
|
g = gen()
|
||||||
self.scheduler.add( g )
|
self.scheduler.add( g )
|
||||||
|
@ -143,14 +170,17 @@ class Test_database( object ):
|
||||||
|
|
||||||
assert obj.object_id == basic_obj.object_id
|
assert obj.object_id == basic_obj.object_id
|
||||||
assert obj.revision == basic_obj.revision
|
assert obj.revision == basic_obj.revision
|
||||||
|
assert obj.revisions_list == [ original_revision, basic_obj.revision ]
|
||||||
assert obj.value == basic_obj.value
|
assert obj.value == basic_obj.value
|
||||||
|
|
||||||
self.database.load( basic_obj.object_id, self.scheduler.thread, revision = original_revision )
|
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 revised.object_id == basic_obj.object_id
|
||||||
assert obj.revision == original_revision
|
assert revised.value == 1
|
||||||
assert obj.value == 1
|
assert revised.revision == original_revision
|
||||||
|
assert id( obj.revisions_list ) != id( revised.revisions_list )
|
||||||
|
assert revised.revisions_list == [ original_revision ]
|
||||||
|
|
||||||
g = gen()
|
g = gen()
|
||||||
self.scheduler.add( g )
|
self.scheduler.add( g )
|
||||||
|
@ -171,6 +201,7 @@ class Test_database( object ):
|
||||||
def test_reload( self ):
|
def test_reload( self ):
|
||||||
def gen():
|
def gen():
|
||||||
basic_obj = Some_object( object_id = "5", value = 1 )
|
basic_obj = Some_object( object_id = "5", value = 1 )
|
||||||
|
original_revision = basic_obj.revision
|
||||||
|
|
||||||
self.database.save( basic_obj, self.scheduler.thread )
|
self.database.save( basic_obj, self.scheduler.thread )
|
||||||
yield Scheduler.SLEEP
|
yield Scheduler.SLEEP
|
||||||
|
@ -192,6 +223,8 @@ class Test_database( object ):
|
||||||
|
|
||||||
assert obj.object_id == basic_obj.object_id
|
assert obj.object_id == basic_obj.object_id
|
||||||
assert obj.value == 55
|
assert obj.value == 55
|
||||||
|
assert obj.revision == original_revision
|
||||||
|
assert obj.revisions_list == [ original_revision ]
|
||||||
|
|
||||||
g = gen()
|
g = gen()
|
||||||
self.scheduler.add( g )
|
self.scheduler.add( g )
|
||||||
|
@ -229,6 +262,7 @@ class Test_database( object ):
|
||||||
|
|
||||||
assert obj.object_id == basic_obj.object_id
|
assert obj.object_id == basic_obj.object_id
|
||||||
assert obj.revision == original_revision
|
assert obj.revision == original_revision
|
||||||
|
assert obj.revisions_list == [ original_revision ]
|
||||||
assert obj.value == 55
|
assert obj.value == 55
|
||||||
|
|
||||||
g = gen()
|
g = gen()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import cherrypy
|
import cherrypy
|
||||||
import cgi
|
import cgi
|
||||||
|
from urllib import quote
|
||||||
from Test_controller import Test_controller
|
from Test_controller import Test_controller
|
||||||
from controller.Scheduler import Scheduler
|
from controller.Scheduler import Scheduler
|
||||||
from model.Notebook import Notebook
|
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"notebook_id" ) == self.notebook.object_id
|
||||||
assert result.get( u"note_id" ) == self.note.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 ):
|
def test_contents( self ):
|
||||||
self.login()
|
self.login()
|
||||||
|
|
||||||
|
@ -100,6 +112,28 @@ class Test_notebooks( Test_controller ):
|
||||||
|
|
||||||
assert note.object_id == self.note.object_id
|
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¬e_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 ):
|
def test_contents_without_login( self ):
|
||||||
result = self.http_get(
|
result = self.http_get(
|
||||||
"/notebooks/contents?notebook_id=%s" % self.notebook.object_id,
|
"/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.title == self.note.title
|
||||||
assert note.contents == self.note.contents
|
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 ):
|
def test_load_note_without_login( self ):
|
||||||
result = self.http_post( "/notebooks/load_note/", dict(
|
result = self.http_post( "/notebooks/load_note/", dict(
|
||||||
notebook_id = self.notebook.object_id,
|
notebook_id = self.notebook.object_id,
|
||||||
|
@ -510,6 +573,7 @@ class Test_notebooks( Test_controller ):
|
||||||
note_id = self.note.object_id,
|
note_id = self.note.object_id,
|
||||||
), session_id = self.session_id )
|
), session_id = self.session_id )
|
||||||
|
|
||||||
|
print result
|
||||||
assert result.get( "note" ) == None
|
assert result.get( "note" ) == None
|
||||||
|
|
||||||
def test_delete_note_without_login( self ):
|
def test_delete_note_without_login( self ):
|
||||||
|
|
|
@ -24,10 +24,11 @@ class Note( Persistent ):
|
||||||
self.__title = None
|
self.__title = None
|
||||||
self.__contents = None or ""
|
self.__contents = None or ""
|
||||||
|
|
||||||
self.__set_contents( contents )
|
self.__set_contents( contents, new_revision = False )
|
||||||
|
|
||||||
def __set_contents( self, contents ):
|
def __set_contents( self, contents, new_revision = True ):
|
||||||
self.update_revision()
|
if new_revision:
|
||||||
|
self.update_revision()
|
||||||
self.__contents = contents
|
self.__contents = contents
|
||||||
|
|
||||||
# parse title out of the beginning of the contents
|
# parse title out of the beginning of the contents
|
||||||
|
|
|
@ -10,7 +10,9 @@ class Persistent( object ):
|
||||||
|
|
||||||
def update_revision( self ):
|
def update_revision( self ):
|
||||||
self.__revision = datetime.now()
|
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 ):
|
def revision_id( self ):
|
||||||
return "%s %s" % ( self.__object_id, self.__revision )
|
return "%s %s" % ( self.__object_id, self.__revision )
|
||||||
|
|
|
@ -27,6 +27,10 @@ h3 {
|
||||||
-webkit-border-radius: 0.5em;
|
-webkit-border-radius: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.small_text {
|
||||||
|
font-size: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
ul li {
|
ul li {
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,5 +33,5 @@ A single search looks through every word in the entire wiki.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>
|
<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>
|
</p>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<h3>login</h3>
|
<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">
|
<form id="login_form">
|
||||||
<p>
|
<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>
|
||||||
|
|
||||||
<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>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<a href="/notes/new">about</a> -
|
<a href="/notebooks/%s?note_id=new">about</a> -
|
||||||
<a href="/notes/new">features</a> -
|
<a href="/notebooks/%s?note_id=new">features</a> -
|
||||||
<a href="/notes/new">take a tour</a> -
|
<a href="/notebooks/%s?note_id=new">take a tour</a> -
|
||||||
<a href="/notes/new">try it out</a> -
|
<a href="/notebooks/%s?note_id=new">try it out</a> -
|
||||||
<a href="/notes/new">login</a>
|
<a href="/notebooks/%s?note_id=new">login</a>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<h3>supported browsers</h3>
|
<h3>supported browsers</h3>
|
||||||
|
|
||||||
<p>
|
<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:
|
so not all browsers will work for editing your wiki. Supported browsers include:
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
|
@ -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" />
|
<input type="submit" name="signup_button" id="signup_button" class="button" value="sign up" />
|
||||||
</p>
|
</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>
|
</form>
|
||||||
|
|
|
@ -126,13 +126,13 @@ Editor.prototype.finish_init = function () {
|
||||||
if ( this.read_write ) {
|
if ( this.read_write ) {
|
||||||
connect( this.document, "onkeydown", function ( event ) { self.key_pressed( event ); } );
|
connect( this.document, "onkeydown", function ( event ) { self.key_pressed( event ); } );
|
||||||
connect( this.document, "onkeyup", function ( event ) { self.key_released( 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, "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
|
// special-case: connect any submit buttons within the contents of this note
|
||||||
var signup_button = withDocument( this.document, function () { return getElement( "signup_button" ); } );
|
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
|
// return true if the specified state is enabled
|
||||||
Editor.prototype.state_enabled = function ( state_name ) {
|
Editor.prototype.state_enabled = function ( state_name ) {
|
||||||
|
if ( !this.read_write ) return false;
|
||||||
|
|
||||||
state_name = state_name.toLowerCase();
|
state_name = state_name.toLowerCase();
|
||||||
var format_block = this.document.queryCommandValue( "formatblock" ).toLowerCase();
|
var format_block = this.document.queryCommandValue( "formatblock" ).toLowerCase();
|
||||||
var heading = ( format_block == "h3" || format_block == "heading 3" );
|
var heading = ( format_block == "h3" || format_block == "heading 3" );
|
||||||
|
|
|
@ -17,7 +17,8 @@ function Wiki() {
|
||||||
this.invoker.invoke(
|
this.invoker.invoke(
|
||||||
"/notebooks/contents", "GET", {
|
"/notebooks/contents", "GET", {
|
||||||
"notebook_id": this.notebook_id,
|
"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 ); }
|
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
|
// don't actually create an editor if a particular note was provided in the result
|
||||||
if ( !result.note ) {
|
if ( !result.note ) {
|
||||||
var focus = ( i == 0 );
|
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
|
// 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 )
|
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 ) {
|
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;
|
var self = this;
|
||||||
|
|
||||||
this.invoker.invoke(
|
this.invoker.invoke(
|
||||||
"/notebooks/load_note", "GET", {
|
"/notebooks/load_note", "GET", {
|
||||||
"notebook_id": this.notebook_id,
|
"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 ) {
|
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 note_text = result.note.contents;
|
||||||
var revisions_list = result.note.revisions_list;
|
var revisions_list = result.note.revisions_list;
|
||||||
} else {
|
} else {
|
||||||
|
@ -198,10 +203,15 @@ Wiki.prototype.parse_loaded_editor = function ( result, insert_after_iframe_id,
|
||||||
var revisions_list = new Array();
|
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_messages();
|
||||||
this.clear_pulldowns();
|
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 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 ) {
|
if ( this.read_write ) {
|
||||||
connect( editor, "state_changed", this, "editor_state_changed" );
|
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_messages();
|
||||||
this.clear_pulldowns();
|
this.clear_pulldowns();
|
||||||
|
|
||||||
if ( this.focused_editor ) {
|
if ( this.focused_editor && this.focused_editor.read_write ) {
|
||||||
this.focused_editor.focus();
|
this.focused_editor.focus();
|
||||||
this.focused_editor.exec_command( state_name || button_id );
|
this.focused_editor.exec_command( state_name || button_id );
|
||||||
this.focused_editor.resize();
|
this.focused_editor.resize();
|
||||||
|
@ -368,7 +384,7 @@ Wiki.prototype.toggle_link_button = function ( event ) {
|
||||||
this.clear_messages();
|
this.clear_messages();
|
||||||
this.clear_pulldowns();
|
this.clear_pulldowns();
|
||||||
|
|
||||||
if ( this.focused_editor ) {
|
if ( this.focused_editor && this.focused_editor.read_write ) {
|
||||||
this.focused_editor.focus();
|
this.focused_editor.focus();
|
||||||
toggleElementClass( "button_down", "createLink" );
|
toggleElementClass( "button_down", "createLink" );
|
||||||
if ( hasElementClass( "createLink", "button_down" ) )
|
if ( hasElementClass( "createLink", "button_down" ) )
|
||||||
|
@ -413,7 +429,7 @@ Wiki.prototype.delete_editor = function ( event, editor ) {
|
||||||
if ( this.startup_notes[ editor.id ] )
|
if ( this.startup_notes[ editor.id ] )
|
||||||
delete 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", {
|
this.invoker.invoke( "/notebooks/delete_note", "POST", {
|
||||||
"notebook_id": this.notebook_id,
|
"notebook_id": this.notebook_id,
|
||||||
"note_id": editor.id
|
"note_id": editor.id
|
||||||
|
@ -434,8 +450,7 @@ Wiki.prototype.save_editor = function ( editor, fire_and_forget ) {
|
||||||
editor = this.focused_editor;
|
editor = this.focused_editor;
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
if ( editor && !editor.empty() ) {
|
if ( editor && editor.read_write && !editor.empty() ) {
|
||||||
// TODO: do something with the result other than just ignoring it
|
|
||||||
this.invoker.invoke( "/notebooks/save_note", "POST", {
|
this.invoker.invoke( "/notebooks/save_note", "POST", {
|
||||||
"notebook_id": this.notebook_id,
|
"notebook_id": this.notebook_id,
|
||||||
"note_id": editor.id,
|
"note_id": editor.id,
|
||||||
|
@ -494,7 +509,7 @@ Wiki.prototype.display_search_results = function ( result ) {
|
||||||
continue;
|
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 ) {
|
Wiki.prototype.toggle_editor_changes = function ( event, editor ) {
|
||||||
// if the pulldown is already open, then just close it
|
// if the pulldown is already open, then just close it
|
||||||
var pulldown_id = "changes_" + editor.id;
|
var pulldown_id = "changes_" + editor.id;
|
||||||
|
@ -539,7 +558,7 @@ Wiki.prototype.toggle_editor_changes = function ( event, editor ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
new Changes_pulldown( this.notebook_id, this.invoker, editor );
|
new Changes_pulldown( this, this.notebook_id, this.invoker, editor );
|
||||||
event.stop();
|
event.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -552,14 +571,15 @@ Wiki.prototype.toggle_editor_options = function ( event, editor ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
new Options_pulldown( this.notebook_id, this.invoker, editor );
|
new Options_pulldown( this, this.notebook_id, this.invoker, editor );
|
||||||
event.stop();
|
event.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
connect( window, "onload", function ( event ) { new Wiki(); } );
|
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.notebook_id = notebook_id;
|
||||||
this.div = createDOM( "div", { "id": pulldown_id, "class": "pulldown" } );
|
this.div = createDOM( "div", { "id": pulldown_id, "class": "pulldown" } );
|
||||||
this.div.pulldown = this;
|
this.div.pulldown = this;
|
||||||
|
@ -584,8 +604,8 @@ Pulldown.prototype.shutdown = function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function Options_pulldown( notebook_id, invoker, editor ) {
|
function Options_pulldown( wiki, notebook_id, invoker, editor ) {
|
||||||
Pulldown.call( this, notebook_id, "options_" + editor.id, editor.options_button );
|
Pulldown.call( this, wiki, notebook_id, "options_" + editor.id, editor.options_button );
|
||||||
|
|
||||||
this.invoker = invoker;
|
this.invoker = invoker;
|
||||||
this.editor = editor;
|
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.startup_checkbox.checked = this.startup_checkbox.checked ? false : true;
|
||||||
this.editor.startup = this.startup_checkbox.checked;
|
this.editor.startup = this.startup_checkbox.checked;
|
||||||
|
|
||||||
// if this note isn't empty, save it along with its startup status
|
// save this note along with its toggled startup state
|
||||||
if ( !this.editor.empty() ) {
|
this.wiki.save_editor( this.editor );
|
||||||
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
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Options_pulldown.prototype.shutdown = function () {
|
Options_pulldown.prototype.shutdown = function () {
|
||||||
Pulldown.prototype.shutdown.call( this );
|
Pulldown.prototype.shutdown.call( this );
|
||||||
|
|
||||||
|
disconnectAll( this.startup_checkbox );
|
||||||
disconnectAll( this.startup_toggle );
|
disconnectAll( this.startup_toggle );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function Changes_pulldown( notebook_id, invoker, editor ) {
|
function Changes_pulldown( wiki, notebook_id, invoker, editor ) {
|
||||||
Pulldown.call( this, notebook_id, "changes_" + editor.id, editor.changes_button );
|
Pulldown.call( this, wiki, notebook_id, "changes_" + editor.id, editor.changes_button );
|
||||||
|
|
||||||
this.invoker = invoker;
|
this.invoker = invoker;
|
||||||
this.editor = editor;
|
this.editor = editor;
|
||||||
|
this.links = new Array();
|
||||||
|
|
||||||
// display list of revision timestamps in reverse chronological order
|
// 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." ) );
|
appendChildNodes( this.div, createDOM( "span", "This note has no previous changes." ) );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -644,18 +659,35 @@ function Changes_pulldown( notebook_id, invoker, editor ) {
|
||||||
var revisions_list = clone( this.editor.revisions_list );
|
var revisions_list = clone( this.editor.revisions_list );
|
||||||
revisions_list.reverse();
|
revisions_list.reverse();
|
||||||
|
|
||||||
|
var self = this;
|
||||||
for ( var i = 0; i < revisions_list.length; ++i ) {
|
for ( var i = 0; i < revisions_list.length; ++i ) {
|
||||||
var revision = revisions_list[ 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(
|
var href = "/notebooks/" + this.notebook_id + "?" + queryString(
|
||||||
[ "note_id", "revision" ],
|
[ "note_id", "revision" ],
|
||||||
[ this.editor.id, revision ]
|
[ this.editor.id, revision ]
|
||||||
);
|
);
|
||||||
// appendChildNodes( this.div, createDOM( "a", { "href": href, "class": "pulldown_link" }, revision ) );
|
var link = createDOM( "a", { "href": href, "class": "pulldown_link" }, short_revision );
|
||||||
appendChildNodes( this.div, createDOM( "span", {}, 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" ) );
|
appendChildNodes( this.div, createDOM( "br" ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Changes_pulldown.prototype = Pulldown;
|
Changes_pulldown.prototype = Pulldown;
|
||||||
Changes_pulldown.prototype.constructor = Changes_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 ] );
|
||||||
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ class Initializer( object ):
|
||||||
|
|
||||||
for ( filename, startup ) in self.ENTRY_FILES:
|
for ( filename, startup ) in self.ENTRY_FILES:
|
||||||
full_filename = os.path.join( self.HTML_PATH, filename )
|
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 )
|
self.database.next_id( self.scheduler.thread )
|
||||||
note_id = ( yield Scheduler.SLEEP )
|
note_id = ( yield Scheduler.SLEEP )
|
||||||
|
|
|
@ -34,7 +34,7 @@ class Initializer( object ):
|
||||||
self.scheduler.wait_for( thread )
|
self.scheduler.wait_for( thread )
|
||||||
|
|
||||||
def update_main_notebook( self ):
|
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 )
|
anonymous = ( yield Scheduler.SLEEP )
|
||||||
main_notebook = anonymous.notebooks[ 0 ]._Read_only_notebook__wrapped
|
main_notebook = anonymous.notebooks[ 0 ]._Read_only_notebook__wrapped
|
||||||
startup_notes = []
|
startup_notes = []
|
||||||
|
@ -42,7 +42,7 @@ class Initializer( object ):
|
||||||
# update all of the notes in the main notebook
|
# update all of the notes in the main notebook
|
||||||
for ( filename, startup ) in self.ENTRY_FILES:
|
for ( filename, startup ) in self.ENTRY_FILES:
|
||||||
full_filename = os.path.join( self.HTML_PATH, filename )
|
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"" )
|
title = filename.replace( u".html", u"" )
|
||||||
note = main_notebook.lookup_note_by_title( title )
|
note = main_notebook.lookup_note_by_title( title )
|
||||||
|
|
|
@ -6,7 +6,7 @@ from Toolbar import Toolbar
|
||||||
|
|
||||||
|
|
||||||
class Main_page( Page ):
|
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
|
title = None
|
||||||
|
|
||||||
Page.__init__(
|
Page.__init__(
|
||||||
|
@ -14,6 +14,7 @@ class Main_page( Page ):
|
||||||
title,
|
title,
|
||||||
Input( type = u"hidden", name = u"notebook_id", id = u"notebook_id", value = notebook_id or "" ),
|
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"note_id", id = u"note_id", value = note_id or "" ),
|
||||||
|
Input( type = u"hidden", name = u"revision", id = u"revision", value = revision or "" ),
|
||||||
Div(
|
Div(
|
||||||
id = u"status_area",
|
id = u"status_area",
|
||||||
),
|
),
|
||||||
|
|
Reference in New Issue