Lots more work on the note tree control. Still not done, and some of the new icons suck.
This commit is contained in:
parent
6990920fff
commit
eaaf1b3de5
|
@ -1,4 +1,5 @@
|
||||||
import re
|
import re
|
||||||
|
import cgi
|
||||||
import cherrypy
|
import cherrypy
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from Expose import expose
|
from Expose import expose
|
||||||
|
@ -15,6 +16,7 @@ from model.User_revision import User_revision
|
||||||
from view.Main_page import Main_page
|
from view.Main_page import Main_page
|
||||||
from view.Json import Json
|
from view.Json import Json
|
||||||
from view.Html_file import Html_file
|
from view.Html_file import Html_file
|
||||||
|
from view.Note_tree_area import Note_tree_area
|
||||||
|
|
||||||
|
|
||||||
class Access_error( Exception ):
|
class Access_error( Exception ):
|
||||||
|
@ -33,6 +35,9 @@ class Access_error( Exception ):
|
||||||
|
|
||||||
class Notebooks( object ):
|
class Notebooks( object ):
|
||||||
WHITESPACE_PATTERN = re.compile( u"\s+" )
|
WHITESPACE_PATTERN = re.compile( u"\s+" )
|
||||||
|
LINK_PATTERN = re.compile( u'<a\s+((?:[^>]+\s)?href="([^"]+)"(?:\s+target="([^"]*)")?[^>]*)>([^<]+)</a>', re.IGNORECASE )
|
||||||
|
FILE_PATTERN = re.compile( u'/files/' )
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Controller for dealing with notebooks and their notes, corresponding to the "/notebooks" URL.
|
Controller for dealing with notebooks and their notes, corresponding to the "/notebooks" URL.
|
||||||
"""
|
"""
|
||||||
|
@ -449,6 +454,66 @@ class Notebooks( object ):
|
||||||
revisions = revisions,
|
revisions = revisions,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@expose( view = Json )
|
||||||
|
@strongly_expire
|
||||||
|
@end_transaction
|
||||||
|
@grab_user_id
|
||||||
|
@validate(
|
||||||
|
notebook_id = Valid_id(),
|
||||||
|
note_id = Valid_id(),
|
||||||
|
user_id = Valid_id( none_okay = True ),
|
||||||
|
)
|
||||||
|
def load_note_links( self, notebook_id, note_id, user_id = None ):
|
||||||
|
"""
|
||||||
|
Return a list of HTTP links found within the contents of the given note.
|
||||||
|
|
||||||
|
@type notebook_id: unicode
|
||||||
|
@param notebook_id: id of notebook the note is in
|
||||||
|
@type note_id: unicode
|
||||||
|
@param note_id: id of note in question
|
||||||
|
@type user_id: unicode or NoneType
|
||||||
|
@param user_id: id of current logged-in user (if any), determined by @grab_user_id
|
||||||
|
@rtype: json dict
|
||||||
|
@return: { 'tree_html': html_fragment }
|
||||||
|
@raise Access_error: the current user doesn't have access to the given notebook or note
|
||||||
|
@raise Validation_error: one of the arguments is invalid
|
||||||
|
"""
|
||||||
|
if not self.__users.check_access( user_id, notebook_id ):
|
||||||
|
raise Access_error()
|
||||||
|
|
||||||
|
note = self.__database.load( Note, note_id )
|
||||||
|
items = []
|
||||||
|
|
||||||
|
for match in self.LINK_PATTERN.finditer( note.contents ):
|
||||||
|
( attributes, href, target, title ) = match.groups()
|
||||||
|
|
||||||
|
# if it has a link target, it's a link to an external web site
|
||||||
|
if target:
|
||||||
|
items.append( Note_tree_area.make_item( title, attributes, "note_tree_external_link" ) )
|
||||||
|
continue
|
||||||
|
|
||||||
|
# if it has '/files/' in its path, it's an uploaded file link
|
||||||
|
if self.FILE_PATTERN.search( href ):
|
||||||
|
items.append( Note_tree_area.make_item( title, attributes, "note_tree_file_link" ) )
|
||||||
|
continue
|
||||||
|
|
||||||
|
# if it has a note_id, load that child note and see whether it has any children of its own
|
||||||
|
child_note_ids = cgi.parse_qs( href.split( '?' )[ -1 ] ).get( u"note_id" )
|
||||||
|
|
||||||
|
if child_note_ids:
|
||||||
|
child_note_id = child_note_ids[ 0 ]
|
||||||
|
child_note = self.__database.load( Note, child_note_id )
|
||||||
|
if child_note and self.LINK_PATTERN.search( child_note.contents ):
|
||||||
|
items.append( Note_tree_area.make_item( title, attributes, "note_tree_link", has_children = True ) )
|
||||||
|
continue
|
||||||
|
|
||||||
|
# otherwise, it's childless
|
||||||
|
items.append( Note_tree_area.make_item( title, attributes, "note_tree_link", has_children = False ) )
|
||||||
|
|
||||||
|
return dict(
|
||||||
|
tree_html = unicode( Note_tree_area.make_tree( items ) ),
|
||||||
|
)
|
||||||
|
|
||||||
@expose( view = Json )
|
@expose( view = Json )
|
||||||
@end_transaction
|
@end_transaction
|
||||||
@grab_user_id
|
@grab_user_id
|
||||||
|
|
|
@ -230,6 +230,24 @@ img {
|
||||||
background-image: url(/static/images/arrow_down_hover.png);
|
background-image: url(/static/images/arrow_down_hover.png);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#tree_arrow_hover_preload {
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background-image: url(/static/images/tree_arrow_hover.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
#tree_arrow_down_preload {
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background-image: url(/static/images/tree_arrow_down.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
#tree_arrow_down_hover_preload {
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background-image: url(/static/images/tree_arrow_down_hover.png);
|
||||||
|
}
|
||||||
|
|
||||||
#note_tree_area {
|
#note_tree_area {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: 17em;
|
width: 17em;
|
||||||
|
@ -246,6 +264,8 @@ img {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-left: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#note_tree_area_title {
|
#note_tree_area_title {
|
||||||
|
@ -253,6 +273,26 @@ img {
|
||||||
margin-bottom: 0.25em;
|
margin-bottom: 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.note_tree_link {
|
||||||
|
background: url(/static/images/note_icon.png) left center no-repeat;
|
||||||
|
padding-left: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note_tree_external_link {
|
||||||
|
background: url(/static/images/web_icon.png) left center no-repeat;
|
||||||
|
padding-left: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note_tree_file_link {
|
||||||
|
background: url(/static/images/file_icon.png) left center no-repeat;
|
||||||
|
padding-left: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note_tree_loading {
|
||||||
|
background: url(/static/images/loading.gif) left center no-repeat;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
#link_area {
|
#link_area {
|
||||||
float: right;
|
float: right;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
@ -535,7 +575,8 @@ img {
|
||||||
|
|
||||||
.tree_expander {
|
.tree_expander {
|
||||||
float: left;
|
float: left;
|
||||||
width: 20px;
|
width: 11px;
|
||||||
|
margin-right: 4px;
|
||||||
height: 1.5em;
|
height: 1.5em;
|
||||||
background: url(/static/images/tree_arrow.png) no-repeat center center;
|
background: url(/static/images/tree_arrow.png) no-repeat center center;
|
||||||
}
|
}
|
||||||
|
@ -545,9 +586,23 @@ img {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tree_expander_expanded {
|
||||||
|
float: left;
|
||||||
|
width: 11px;
|
||||||
|
margin-right: 4px;
|
||||||
|
height: 1.5em;
|
||||||
|
background: url(/static/images/tree_arrow_down.png) no-repeat center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree_expander_expanded:hover {
|
||||||
|
background: url(/static/images/tree_arrow_down_hover.png) no-repeat center center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.tree_expander_empty {
|
.tree_expander_empty {
|
||||||
float: left;
|
float: left;
|
||||||
width: 20px;
|
width: 11px;
|
||||||
|
margin-right: 4px;
|
||||||
height: 1.5em;
|
height: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 458 B |
Binary file not shown.
After Width: | Height: | Size: 673 B |
Binary file not shown.
After Width: | Height: | Size: 394 B |
Binary file not shown.
After Width: | Height: | Size: 929 B |
|
@ -2563,15 +2563,24 @@ function Note_tree( wiki, notebook_id, invoker ) {
|
||||||
var links = getElementsByTagAndClassName( "a", "note_tree_link", "note_tree_area" );
|
var links = getElementsByTagAndClassName( "a", "note_tree_link", "note_tree_area" );
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
function connect_expander( note_id ) {
|
||||||
|
connect( "note_tree_expander_" + note_id, "onclick", function ( event ) { self.expand_link( event, note_id ); } );
|
||||||
|
}
|
||||||
|
|
||||||
for ( var i in links ) {
|
for ( var i in links ) {
|
||||||
var link = links[ i ];
|
var link = links[ i ];
|
||||||
// TODO: connect expander as well
|
var query = parse_query( link );
|
||||||
|
var note_id = query[ "note_id" ];
|
||||||
|
|
||||||
|
if ( note_id )
|
||||||
|
connect_expander( note_id );
|
||||||
|
|
||||||
connect( link, "onclick", function ( event ) { self.link_clicked( event ); } );
|
connect( link, "onclick", function ( event ) { self.link_clicked( event ); } );
|
||||||
}
|
}
|
||||||
|
|
||||||
// connect to the wiki note events
|
// connect to the wiki note events
|
||||||
connect( wiki, "note_renamed", function ( editor, new_title ) { self.rename_link( editor, new_title ); } );
|
connect( wiki, "note_renamed", function ( editor, new_title ) { self.rename_link( editor, new_title ); } );
|
||||||
connect( wiki, "note_added", function ( editor ) { self.add_link( editor ); } );
|
connect( wiki, "note_added", function ( editor ) { self.add_root_link( editor ); } );
|
||||||
connect( wiki, "note_removed", function ( id ) { self.remove_link( id ); } );
|
connect( wiki, "note_removed", function ( id ) { self.remove_link( id ); } );
|
||||||
connect( wiki, "note_saved", function ( editor ) { self.update_link( editor ); } );
|
connect( wiki, "note_saved", function ( editor ) { self.update_link( editor ); } );
|
||||||
}
|
}
|
||||||
|
@ -2590,16 +2599,16 @@ Note_tree.prototype.link_clicked = function ( event ) {
|
||||||
|
|
||||||
LINK_PATTERN = /<a\s+([^>]+\s)?href="[^"]+"[^>]*>/;
|
LINK_PATTERN = /<a\s+([^>]+\s)?href="[^"]+"[^>]*>/;
|
||||||
|
|
||||||
Note_tree.prototype.add_link = function ( editor ) {
|
Note_tree.prototype.add_root_link = function ( editor ) {
|
||||||
// for now, only add startup notes to the note tree
|
// for now, only add startup notes to the note tree
|
||||||
if ( !editor.startup )
|
if ( !editor.startup )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// display the tree expander arrow if the given note's editor contains any outgoing links
|
// display the tree expander arrow if the given note's editor contains any outgoing links
|
||||||
if ( LINK_PATTERN.exec( editor.contents() ) )
|
if ( LINK_PATTERN.exec( editor.contents() ) )
|
||||||
var expander = createDOM( "div", { "class": "tree_expander" } );
|
var expander = createDOM( "td", { "class": "tree_expander", "id": "note_tree_expander_" + editor.id } );
|
||||||
else
|
else
|
||||||
var expander = createDOM( "div", { "class": "tree_expander_empty" } );
|
var expander = createDOM( "td", { "class": "tree_expander_empty", "id": "note_tree_expander_" + editor.id } );
|
||||||
|
|
||||||
var link = createDOM( "a", {
|
var link = createDOM( "a", {
|
||||||
"href": "/notebooks/" + this.notebook_id + "?note_id=" + editor.id,
|
"href": "/notebooks/" + this.notebook_id + "?note_id=" + editor.id,
|
||||||
|
@ -2608,26 +2617,26 @@ Note_tree.prototype.add_link = function ( editor ) {
|
||||||
}, editor.title || "untitled note" );
|
}, editor.title || "untitled note" );
|
||||||
|
|
||||||
appendChildNodes( "note_tree_area_holder", createDOM(
|
appendChildNodes( "note_tree_area_holder", createDOM(
|
||||||
"div",
|
"tr",
|
||||||
{ "id": "note_tree_item_" + editor.id, "class": "note_tree_item" },
|
{ "id": "note_tree_item_" + editor.id, "class": "note_tree_item" },
|
||||||
expander,
|
expander,
|
||||||
link
|
createDOM( "td", {}, link )
|
||||||
) );
|
) );
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
// TODO: connect expander as well
|
connect( expander, "onclick", function ( event ) { self.expand_link( event, editor.id ); } );
|
||||||
connect( link, "onclick", function ( event ) { self.link_clicked( event ); } );
|
connect( link, "onclick", function ( event ) { self.link_clicked( event ); } );
|
||||||
}
|
}
|
||||||
|
|
||||||
Note_tree.prototype.remove_link = function ( id ) {
|
Note_tree.prototype.remove_link = function ( note_id ) {
|
||||||
removeElement( "note_tree_item_" + id );
|
removeElement( "note_tree_item_" + note_id );
|
||||||
}
|
}
|
||||||
|
|
||||||
Note_tree.prototype.rename_link = function ( editor, new_title ) {
|
Note_tree.prototype.rename_link = function ( editor, new_title ) {
|
||||||
var link = getElement( "note_tree_link_" + editor.id );
|
var link = getElement( "note_tree_link_" + editor.id );
|
||||||
|
|
||||||
if ( !link ) {
|
if ( !link ) {
|
||||||
this.add_link( editor );
|
this.add_root_link( editor );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2638,7 +2647,7 @@ Note_tree.prototype.update_link = function ( editor ) {
|
||||||
var link = getElement( "note_tree_link_" + editor.id );
|
var link = getElement( "note_tree_link_" + editor.id );
|
||||||
|
|
||||||
if ( !link ) {
|
if ( !link ) {
|
||||||
this.add_link( editor );
|
this.add_root_link( editor );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2646,10 +2655,52 @@ Note_tree.prototype.update_link = function ( editor ) {
|
||||||
this.remove_link( editor.id );
|
this.remove_link( editor.id );
|
||||||
|
|
||||||
// TODO: if link is expanded, update child links (if any)
|
// TODO: if link is expanded, update child links (if any)
|
||||||
|
// TODO: hide/show the link's expander arrow based on the precense of outgoing links
|
||||||
}
|
}
|
||||||
|
|
||||||
Note_tree.prototype.expand_link = function ( id ) {
|
Note_tree.prototype.expand_link = function ( event, note_id ) {
|
||||||
|
// FIXME: use of note_id here is problematic. if a given note is listed in multiple different locations, the id won't be unique in
|
||||||
|
// the DOM
|
||||||
|
var expander = event.target();
|
||||||
|
|
||||||
|
if ( !expander || hasElementClass( expander, "tree_expander_empty" ) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
// if it's collapsed, expand it
|
||||||
|
if ( hasElementClass( expander, "tree_expander" ) ) {
|
||||||
|
var children_area = createDOM( "div", { "id": "note_tree_children_" + note_id },
|
||||||
|
createDOM( "span", { "class": "note_tree_loading" }, "loading..." )
|
||||||
|
);
|
||||||
|
|
||||||
|
swapElementClass( expander, "tree_expander", "tree_expander_expanded" );
|
||||||
|
insertSiblingNodesAfter( "note_tree_link_" + note_id,
|
||||||
|
children_area
|
||||||
|
);
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
this.invoker.invoke(
|
||||||
|
"/notebooks/load_note_links", "GET", {
|
||||||
|
"notebook_id": this.notebook_id,
|
||||||
|
"note_id": note_id
|
||||||
|
},
|
||||||
|
function ( result ) {
|
||||||
|
var span = createDOM( "span" );
|
||||||
|
span.innerHTML = result.tree_html;
|
||||||
|
replaceChildNodes( children_area, span );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it's expanded, collapse it
|
||||||
|
if ( hasElementClass( expander, "tree_expander_expanded" ) ) {
|
||||||
|
swapElementClass( expander, "tree_expander_expanded", "tree_expander" );
|
||||||
|
var children = getElement( "note_tree_children_" + note_id );
|
||||||
|
if ( children )
|
||||||
|
removeElement( children );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Note_tree.prototype.collapse_link = function ( id ) {
|
Note_tree.prototype.collapse_link = function ( event, note_id ) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import re
|
import re
|
||||||
from Tags import Div, Span, H4, A
|
from Tags import Div, Span, H4, A, Table, Tr, Td
|
||||||
|
|
||||||
|
|
||||||
class Note_tree_area( Div ):
|
class Note_tree_area( Div ):
|
||||||
LINK_PATTERN = re.compile( u'<a\s+([^>]+\s)?href="[^"]+"[^>]*>', re.IGNORECASE )
|
LINK_PATTERN = re.compile( u'<a\s+(?:[^>]+\s)?href="[^"]+"[^>]*>', re.IGNORECASE )
|
||||||
|
|
||||||
def __init__( self, toolbar, notebook, root_notes, total_notes_count ):
|
def __init__( self, toolbar, notebook, root_notes, total_notes_count ):
|
||||||
Div.__init__(
|
Div.__init__(
|
||||||
|
@ -17,20 +17,44 @@ class Note_tree_area( Div ):
|
||||||
),
|
),
|
||||||
id = u"note_tree_area_title",
|
id = u"note_tree_area_title",
|
||||||
),
|
),
|
||||||
[ Div(
|
self.make_tree(
|
||||||
self.LINK_PATTERN.search( note.contents ) and \
|
[ self.make_item(
|
||||||
Div( id = u"note_tree_expander_" + note.object_id, class_ = u"tree_expander" ) or
|
title = note.title,
|
||||||
Div( id = u"note_tree_expander_" + note.object_id, class_ = u"tree_expander_empty" ),
|
link_attributes = u"href=/notebooks/%s?note_id=%s" % ( notebook.object_id, note.object_id ),
|
||||||
A(
|
link_class = u"note_tree_link",
|
||||||
note.title or u"untitled note",
|
has_children = self.LINK_PATTERN.search( note.contents ),
|
||||||
href = u"/notebooks/%s?note_id=%s" % ( notebook.object_id, note.object_id ),
|
root_note_id = note.object_id,
|
||||||
id = u"note_tree_link_" + note.object_id,
|
) for note in root_notes ],
|
||||||
class_ = u"note_tree_link",
|
tree_id = u"note_tree_area_holder",
|
||||||
),
|
),
|
||||||
id = u"note_tree_item_" + note.object_id,
|
|
||||||
class_ = u"note_tree_item",
|
|
||||||
) for note in root_notes ],
|
|
||||||
id = u"note_tree_area_holder",
|
|
||||||
),
|
),
|
||||||
|
Span( id = "tree_arrow_hover_preload" ),
|
||||||
|
Span( id = "tree_arrow_down_preload" ),
|
||||||
|
Span( id = "tree_arrow_down_hover_preload" ),
|
||||||
id = u"note_tree_area",
|
id = u"note_tree_area",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def make_item( title, link_attributes, link_class, has_children = False, root_note_id = None ):
|
||||||
|
return Tr(
|
||||||
|
has_children and \
|
||||||
|
Td( id = root_note_id and u"note_tree_expander_" + root_note_id or None, class_ = u"tree_expander" ) or
|
||||||
|
Td( id = root_note_id and u"note_tree_expander_" + root_note_id or None, class_ = u"tree_expander_empty" ),
|
||||||
|
Td(
|
||||||
|
u"<a %s%s class=%s>%s</a>" % (
|
||||||
|
link_attributes,
|
||||||
|
root_note_id and u" id=note_tree_link_" + root_note_id or None,
|
||||||
|
link_class,
|
||||||
|
title or u"untitled note",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
id = root_note_id and u"note_tree_item_" + root_note_id or None,
|
||||||
|
class_ = u"note_tree_item",
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def make_tree( items, tree_id = None ):
|
||||||
|
return Table(
|
||||||
|
items,
|
||||||
|
id = tree_id,
|
||||||
|
)
|
||||||
|
|
Reference in New Issue