* 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 )
|
||||
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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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¬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 ):
|
||||
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 ):
|
||||
|
|
|
@ -24,9 +24,10 @@ 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 ):
|
||||
def __set_contents( self, contents, new_revision = True ):
|
||||
if new_revision:
|
||||
self.update_revision()
|
||||
self.__contents = contents
|
||||
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -27,6 +27,10 @@ h3 {
|
|||
-webkit-border-radius: 0.5em;
|
||||
}
|
||||
|
||||
.small_text {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
ul li {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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, "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 ); } );
|
||||
}
|
||||
|
||||
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" ); } );
|
||||
|
@ -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" );
|
||||
|
|
|
@ -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 ] );
|
||||
}
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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",
|
||||
),
|
||||
|
|
Reference in New Issue