diff --git a/controller/Notebooks.py b/controller/Notebooks.py index 97e31c4..a07a62a 100644 --- a/controller/Notebooks.py +++ b/controller/Notebooks.py @@ -1,3 +1,4 @@ +import re import cherrypy from datetime import datetime from Expose import expose @@ -29,6 +30,8 @@ class Access_error( Exception ): class Notebooks( object ): + WHITESPACE_PATTERN = re.compile( u"\s+" ) + """ Controller for dealing with notebooks and their notes, corresponding to the "/notebooks" URL. """ @@ -161,9 +164,10 @@ class Notebooks( object ): notebook_id = Valid_id(), note_id = Valid_id(), revision = Valid_revision(), + summarize = Valid_bool(), user_id = Valid_id( none_okay = True ), ) - def load_note( self, notebook_id, note_id, revision = None, user_id = None ): + def load_note( self, notebook_id, note_id, revision = None, summarize = False, user_id = None ): """ Return the information on a particular note by its id. @@ -173,6 +177,9 @@ class Notebooks( object ): @param note_id: id of note to return @type revision: unicode or NoneType @param revision: revision timestamp of the note (optional) + @type summarize: bool or NoneType + @param summarize: True to return a summary of the note's contents, False to return full text + (optional, defaults to False) @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 @@ -196,7 +203,7 @@ class Notebooks( object ): if notebook and note.notebook_id == notebook.trash_id: if revision: return dict( - note = note, + note = summarize and self.summarize_note( note ) or note, ) return dict( @@ -207,7 +214,7 @@ class Notebooks( object ): raise Access_error() return dict( - note = note, + note = summarize and self.summarize_note( note ) or note, ) @expose( view = Json ) @@ -216,9 +223,10 @@ class Notebooks( object ): @validate( notebook_id = Valid_id(), note_title = Valid_string( min = 1, max = 500 ), + summarize = Valid_bool(), user_id = Valid_id( none_okay = True ), ) - def load_note_by_title( self, notebook_id, note_title, user_id ): + def load_note_by_title( self, notebook_id, note_title, summarize = False, user_id = None ): """ Return the information on a particular note by its title. @@ -226,6 +234,9 @@ class Notebooks( object ): @param notebook_id: id of notebook the note is in @type note_title: unicode @param note_title: title of the note to return + @type summarize: bool or NoneType + @param summarize: True to return a summary of the note's contents, False to return full text + (optional, defaults to False) @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 @@ -244,9 +255,59 @@ class Notebooks( object ): note = self.__database.select_one( Note, notebook.sql_load_note_by_title( note_title ) ) return dict( - note = note, + note = summarize and self.summarize_note( note ) or note, ) + def summarize_note( self, note ): + """ + Create a truncated note summary for the given note, and then return the note with its summary + set. + + @type note: model.Note or NoneType + @param note: note to summarize, or None + @rtype: model.Note or NoneType + @return: note with its summary member set, or None if no note was provided + """ + MAX_SUMMARY_LENGTH = 40 + word_count = 10 + + if note is None: + return None + + if note.contents is None: + return note + + # remove all HTML from the contents and also remove the title + summary = Html_nuker().nuke( note.contents ).strip() + if note.title and summary.startswith( note.title ): + summary = summary[ len( note.title ) : ] + + # split the summary on whitespace + words = self.WHITESPACE_PATTERN.split( summary ) + + def first_words( words, word_count ): + return u" ".join( words[ : word_count ] ) + + # find a summary less than MAX_SUMMARY_LENGTH and, if possible, truncated on a word boundary + truncated = False + summary = first_words( words, word_count ) + + while len( summary ) > MAX_SUMMARY_LENGTH: + word_count -= 1 + summary = first_words( words, word_count ) + + # if the first word is just ridiculously long, truncate it without finding a word boundary + if word_count == 1: + summary = summary[ : MAX_SUMMARY_LENGTH ] + truncated = True + break + + if truncated or word_count < len( words ): + summary += " ..." + + note.summary = summary + return note + @expose( view = Json ) @strongly_expire @grab_user_id @@ -607,8 +668,8 @@ class Notebooks( object ): """ Search the notes within a particular notebook for the given search text. Note that the search is case-insensitive, and all HTML tags are ignored. Notes with title matches are generally - ranked higher than matches that are only in the note contents. The returned notes have their - normal contents replaced with summary contents with the search terms highlighted. + ranked higher than matches that are only in the note contents. The returned notes include + content summaries with the search terms highlighted. @type notebook_id: unicode @param notebook_id: id of notebook to search diff --git a/controller/test/Test_notebooks.py b/controller/test/Test_notebooks.py index 568daa1..d7e3985 100644 --- a/controller/test/Test_notebooks.py +++ b/controller/test/Test_notebooks.py @@ -384,6 +384,24 @@ class Test_notebooks( Test_controller ): user = self.database.load( User, self.user.object_id ) assert user.storage_bytes == 0 + def test_load_note_with_summary( self ): + self.login() + + result = self.http_post( "/notebooks/load_note/", dict( + notebook_id = self.notebook.object_id, + note_id = self.note.object_id, + summarize = True, + ), session_id = self.session_id ) + + note = result[ "note" ] + + assert note.object_id == self.note.object_id + assert note.title == self.note.title + assert note.contents == self.note.contents + assert note.summary == u"blah" + user = self.database.load( User, self.user.object_id ) + assert user.storage_bytes == 0 + def test_load_note_by_title( self ): self.login() @@ -435,6 +453,64 @@ class Test_notebooks( Test_controller ): user = self.database.load( User, self.user.object_id ) assert user.storage_bytes == 0 + def test_load_note_by_title_with_summary( self ): + self.login() + + result = self.http_post( "/notebooks/load_note_by_title/", dict( + notebook_id = self.notebook.object_id, + note_title = self.note.title, + summarize = True, + ), session_id = self.session_id ) + + note = result[ "note" ] + + assert note.object_id == self.note.object_id + assert note.title == self.note.title + assert note.contents == self.note.contents + assert note.summary == u"blah" + user = self.database.load( User, self.user.object_id ) + assert user.storage_bytes == 0 + + def test_summarize_note( self ): + note = cherrypy.root.notebooks.summarize_note( self.note ) + + assert note.summary == u"blah" + + def test_summarize_note_truncated_at_word_boundary( self ): + self.note.contents = u"

the title

" + u"foo bar baz quux " * 10 + note = cherrypy.root.notebooks.summarize_note( self.note ) + + assert note.summary == u"foo bar baz quux foo bar baz quux foo ..." + + def test_summarize_note_truncated_at_character_boundary( self ): + self.note.contents = u"

the title

" + u"foobarbazquux" * 10 + note = cherrypy.root.notebooks.summarize_note( self.note ) + + assert note.summary == u"foobarbazquuxfoobarbazquuxfoobarbazquuxf ..." + + def test_summarize_note_with_short_words( self ): + self.note.contents = u"

the title

" + u"a b c d e f g h i j k l" + note = cherrypy.root.notebooks.summarize_note( self.note ) + + assert note.summary == u"a b c d e f g h i j ..." + + def test_summarize_note_without_title( self ): + self.note.contents = "foo bar baz quux" + note = cherrypy.root.notebooks.summarize_note( self.note ) + + assert note.summary == u"foo bar baz quux" + + def test_summarize_note_without_contents( self ): + self.note.contents = None + note = cherrypy.root.notebooks.summarize_note( self.note ) + + assert note.summary == None + + def test_summarize_note_none( self ): + note = cherrypy.root.notebooks.summarize_note( None ) + + assert note == None + def test_lookup_note_id( self ): self.login() diff --git a/model/Note.py b/model/Note.py index 26789ca..bf76576 100644 --- a/model/Note.py +++ b/model/Note.py @@ -10,7 +10,7 @@ class Note( Persistent ): TITLE_PATTERN = re.compile( u"

(.*?)

", flags = re.IGNORECASE ) def __init__( self, object_id, revision = None, title = None, contents = None, notebook_id = None, - startup = None, deleted_from_id = None, rank = None, creation = None ): + startup = None, deleted_from_id = None, rank = None, creation = None, summary = None ): """ Create a new note with the given id and contents. @@ -32,12 +32,15 @@ class Note( Persistent ): @param rank: indicates numeric ordering of this note in relation to other startup notes @type creation: datetime or NoneType @param creation: creation timestamp of the object (optional, defaults to None) + @type summary: unicode or NoneType + @param summary: textual summary of the note's contents (optional, defaults to None) @rtype: Note @return: newly constructed note """ Persistent.__init__( self, object_id, revision ) self.__title = title self.__contents = contents + self.__summary = summary self.__notebook_id = notebook_id self.__startup = startup or False self.__deleted_from_id = deleted_from_id @@ -45,7 +48,7 @@ class Note( Persistent ): self.__creation = creation @staticmethod - def create( object_id, contents = None, notebook_id = None, startup = None, rank = None, creation = None ): + def create( object_id, contents = None, notebook_id = None, startup = None, rank = None, creation = None, summary = None ): """ Convenience constructor for creating a new note. @@ -61,10 +64,12 @@ class Note( Persistent ): @param rank: indicates numeric ordering of this note in relation to other startup notes @type creation: datetime or NoneType @param creation: creation timestamp of the object (optional, defaults to None) + @type summary: unicode or NoneType + @param summary: textual summary of the note's contents (optional, defaults to None) @rtype: Note @return: newly constructed note """ - note = Note( object_id, notebook_id = notebook_id, startup = startup, rank = rank, creation = creation ) + note = Note( object_id, notebook_id = notebook_id, startup = startup, rank = rank, creation = creation, summary = summary ) note.contents = contents return note @@ -86,6 +91,9 @@ class Note( Persistent ): else: self.__title = None + def __set_summary( self, summary ): + self.__summary = summary + def __set_notebook_id( self, notebook_id ): self.__notebook_id = notebook_id self.update_revision() @@ -141,6 +149,7 @@ class Note( Persistent ): d = Persistent.to_dict( self ) d.update( dict( contents = self.__contents, + summary = self.__summary, title = self.__title, deleted_from_id = self.__deleted_from_id, creation = self.__creation, @@ -150,6 +159,7 @@ class Note( Persistent ): title = property( lambda self: self.__title ) contents = property( lambda self: self.__contents, __set_contents ) + summary = property( lambda self: self.__summary, __set_summary ) notebook_id = property( lambda self: self.__notebook_id, __set_notebook_id ) startup = property( lambda self: self.__startup, __set_startup ) deleted_from_id = property( lambda self: self.__deleted_from_id, __set_deleted_from_id ) diff --git a/model/Notebook.py b/model/Notebook.py index 9a95afe..86cae87 100644 --- a/model/Notebook.py +++ b/model/Notebook.py @@ -161,9 +161,10 @@ class Notebook( Persistent ): return \ """ - select id, revision, title, headline( drop_html_tags( contents ), query ), notebook_id, startup, deleted_from_id from ( + select id, revision, title, contents, notebook_id, startup, deleted_from_id, rank, null, + headline( drop_html_tags( contents ), query ) as summary from ( select - id, revision, title, contents, notebook_id, startup, deleted_from_id, query, rank_cd( search, query ) as rank + id, revision, title, contents, notebook_id, startup, deleted_from_id, rank_cd( search, query ) as rank, null, query from note_current, to_tsquery( 'default', %s ) query where diff --git a/model/test/Test_note.py b/model/test/Test_note.py index 2226084..d0920de 100644 --- a/model/test/Test_note.py +++ b/model/test/Test_note.py @@ -8,18 +8,20 @@ class Test_note( object ): self.object_id = u"17" self.title = u"title goes here" self.contents = u"

%s

blah" % self.title + self.summary = None self.notebook_id = u"18" self.startup = False self.rank = 17.5 self.creation = datetime.now() self.delta = timedelta( seconds = 1 ) - self.note = Note.create( self.object_id, self.contents, self.notebook_id, self.startup, self.rank, self.creation ) + self.note = Note.create( self.object_id, self.contents, self.notebook_id, self.startup, self.rank, self.creation, self.summary ) def test_create( self ): assert self.note.object_id == self.object_id assert datetime.now( tz = utc ) - self.note.revision < self.delta assert self.note.contents == self.contents + assert self.note.summary == None assert self.note.title == self.title assert self.note.notebook_id == self.notebook_id assert self.note.startup == self.startup @@ -36,6 +38,7 @@ class Test_note( object ): assert self.note.revision > previous_revision assert self.note.contents == new_contents + assert self.note.summary == None assert self.note.title == new_title assert self.note.notebook_id == self.notebook_id assert self.note.startup == self.startup @@ -53,6 +56,7 @@ class Test_note( object ): # html should be stripped out of the title assert self.note.revision > previous_revision assert self.note.contents == new_contents + assert self.note.summary == None assert self.note.title == new_title assert self.note.notebook_id == self.notebook_id assert self.note.startup == self.startup @@ -70,6 +74,7 @@ class Test_note( object ): # should only use the first title assert self.note.revision > previous_revision assert self.note.contents == new_contents + assert self.note.summary == None assert self.note.title == new_title assert self.note.notebook_id == self.notebook_id assert self.note.startup == self.startup @@ -77,6 +82,22 @@ class Test_note( object ): assert self.note.rank == self.rank assert self.note.creation == self.creation + def test_set_summary( self ): + summary = u"summary goes here..." + original_revision = self.note.revision + + self.note.summary = summary + + assert self.note.revision == original_revision + assert self.note.contents == self.contents + assert self.note.summary == summary + assert self.note.title == self.title + assert self.note.notebook_id == self.notebook_id + assert self.note.startup == self.startup + assert self.note.deleted_from_id == None + assert self.note.rank == self.rank + assert self.note.creation == self.creation + def test_set_notebook_id( self ): previous_revision = self.note.revision self.note.notebook_id = u"54" @@ -111,6 +132,7 @@ class Test_note( object ): assert d.get( "object_id" ) == self.note.object_id assert datetime.now( tz = utc ) - d.get( "revision" ) < self.delta assert d.get( "contents" ) == self.contents + assert d.get( "summary" ) == self.summary assert d.get( "title" ) == self.title assert d.get( "deleted_from_id" ) == None assert d.get( "creation" ) == self.note.creation @@ -121,6 +143,7 @@ class Test_note_blank( Test_note ): self.object_id = u"17" self.title = None self.contents = None + self.summary = None self.notebook_id = None self.startup = False self.rank = None @@ -133,6 +156,7 @@ class Test_note_blank( Test_note ): assert self.note.object_id == self.object_id assert datetime.now( tz = utc ) - self.note.revision < self.delta assert self.note.contents == None + assert self.note.summary == None assert self.note.title == None assert self.note.notebook_id == None assert self.note.startup == False diff --git a/static/js/Editor.js b/static/js/Editor.js index a6b3d1c..2d29719 100644 --- a/static/js/Editor.js +++ b/static/js/Editor.js @@ -383,8 +383,7 @@ Editor.prototype.start_link = function () { } else { this.link_started = null; this.exec_command( "createLink", "/notebooks/" + this.notebook_id + "?note_id=new" ); - var link = this.find_link_at_cursor(); - signal( this, "resolve_link", link_title( link ), link ); + return this.find_link_at_cursor(); } } else if ( this.document.selection ) { // browsers such as IE var range = this.document.selection.createRange(); @@ -400,8 +399,7 @@ Editor.prototype.start_link = function () { } else { this.link_started = null; this.exec_command( "createLink", "/notebooks/" + this.notebook_id + "?note_id=new" ); - var link = this.find_link_at_cursor(); - signal( this, "resolve_link", link_title( link ), link ); + return this.find_link_at_cursor(); } } } @@ -430,7 +428,6 @@ Editor.prototype.end_link = function () { range.pasteHTML( "" ); } - signal( this, "resolve_link", link_title( link ), link ); return link; } @@ -562,6 +559,48 @@ Editor.prototype.shutdown = function( event ) { } } ); } +Editor.prototype.summarize = function () { + var summary = strip( scrapeText( this.document.body ) ); + + // remove the title from the scraped summary text + if ( summary.indexOf( this.title ) == 0 ) + summary = summary.substr( this.title.length ); + + if ( summary.length == 0 ) + return null; + + var MAX_SUMMARY_LENGTH = 40; + var word_count = 10; + + // split the summary on whitespace + var words = summary.split( /\s+/ ); + + function first_words( words, word_count ) { + return words.slice( 0, word_count ).join( " " ); + } + + var truncated = false; + summary = first_words( words, word_count ); + + // find a summary less than MAX_SUMMARY_LENGTH and, if possible, truncated on a word boundary + while ( summary.length > MAX_SUMMARY_LENGTH ) { + word_count -= 1; + summary = first_words( words, word_count ); + + // if the first word is just ridiculously long, truncate it without finding a word boundary + if ( word_count == 1 ) { + summary = summary.substr( 0, MAX_SUMMARY_LENGTH ); + truncated = true; + break; + } + } + + if ( truncated || word_count < words.length ) + summary += " ..."; + + return summary; +} + // convenience function for parsing a link that has an href URL containing a query string function parse_query( link ) { if ( !link || !link.href ) diff --git a/static/js/Wiki.js b/static/js/Wiki.js index 773454e..7f42481 100644 --- a/static/js/Wiki.js +++ b/static/js/Wiki.js @@ -490,22 +490,43 @@ Wiki.prototype.resolve_link = function ( note_title, link, callback ) { } var self = this; + if ( callback ) { + this.invoker.invoke( + "/notebooks/load_note_by_title", "GET", { + "notebook_id": this.notebook_id, + "note_title": note_title, + "summarize": true + }, + function ( result ) { + if ( result && result.note ) { + link.href = "/notebooks/" + self.notebook_id + "?note_id=" + result.note.object_id; + } else { + link.href = "/notebooks/" + self.notebook_id + "?" + queryString( + [ "title", "note_id" ], + [ note_title, "null" ] + ); + } + + callback( ( result && result.note ) ? result.note.summary : null ); + } + ); + return; + } + this.invoker.invoke( - "/notebooks/" + ( callback ? "load_note_by_title" : "lookup_note_id" ), "GET", { + "/notebooks/lookup_note_id", "GET", { "notebook_id": this.notebook_id, "note_title": note_title }, function ( result ) { - if ( result && ( result.note || result.note_id ) ) { - link.href = "/notebooks/" + self.notebook_id + "?note_id=" + ( result.note ? result.note.object_id : result.note_id ); + if ( result && result.note_id ) { + link.href = "/notebooks/" + self.notebook_id + "?note_id=" + result.note_id; } else { link.href = "/notebooks/" + self.notebook_id + "?" + queryString( [ "title", "note_id" ], [ note_title, "null" ] ); } - if ( callback ) - callback( ( result && result.note ) ? result.note.contents : null ); } ); } @@ -596,7 +617,6 @@ Wiki.prototype.create_editor = function ( id, note_text, deleted_from_id, revisi } connect( editor, "load_editor", this, "load_editor" ); - connect( editor, "resolve_link", this, "resolve_link" ); connect( editor, "hide_clicked", function ( event ) { self.hide_editor( event, editor ) } ); connect( editor, "submit_form", function ( url, form ) { self.invoker.invoke( url, "POST", null, null, form ); @@ -857,11 +877,18 @@ Wiki.prototype.toggle_link_button = function ( event ) { if ( this.focused_editor && this.focused_editor.read_write ) { this.focused_editor.focus(); if ( this.toggle_image_button( "createLink" ) ) - this.focused_editor.start_link(); + link = this.focused_editor.start_link(); else link = this.focused_editor.end_link(); - this.display_link_pulldown( this.focused_editor, link ); + if ( link ) { + var self = this; + this.resolve_link( link_title( link ), link, function ( summary ) { + self.display_link_pulldown( self.focused_editor, link ); + } ); + } else { + this.display_link_pulldown( this.focused_editor ); + } } event.stop(); @@ -1124,23 +1151,23 @@ Wiki.prototype.display_search_results = function ( result ) { if ( !note.title ) continue; if ( note.contents.length == 0 ) { - var preview = "empty note"; + var summary = "empty note"; } else { - var preview = note.contents; + var summary = note.summary; - // if the preview appears not to end with a complete sentence, add "..." - if ( !/[?!.]\s*$/.test( preview ) ) - preview = preview + " ..."; + // if the summary appears not to end with a complete sentence, add "..." + if ( !/[?!.]\s*$/.test( summary ) ) + summary = summary + " ..."; } - var preview_span = createDOM( "span" ); - preview_span.innerHTML = preview; + var summary_span = createDOM( "span" ); + summary_span.innerHTML = summary; appendChildNodes( list, createDOM( "p", {}, createDOM( "a", { "href": "/notebooks/" + this.notebook_id + "?note_id=" + note.object_id }, note.title ), createDOM( "br" ), - preview_span + summary_span ) ); } @@ -1756,7 +1783,7 @@ function Link_pulldown( wiki, notebook_id, invoker, editor, link ) { this.invoker = invoker; this.editor = editor; this.title_field = createDOM( "input", { "class": "text_field", "size": "30", "maxlength": "256" } ); - this.note_preview = createDOM( "span", {} ); + this.note_summary = createDOM( "span", {} ); this.previous_title = ""; var self = this; @@ -1768,12 +1795,12 @@ function Link_pulldown( wiki, notebook_id, invoker, editor, link ) { appendChildNodes( this.div, createDOM( "span", { "class": "field_label" }, "links to: " ) ); appendChildNodes( this.div, this.title_field ); - appendChildNodes( this.div, this.note_preview ); + appendChildNodes( this.div, this.note_summary ); // links with targets are considered links to external sites if ( link.target ) { this.title_field.value = link.href; - replaceChildNodes( this.note_preview, "web link" ); + replaceChildNodes( this.note_summary, "web link" ); return; } @@ -1785,20 +1812,21 @@ function Link_pulldown( wiki, notebook_id, invoker, editor, link ) { if ( ( id == undefined || id == "new" || id == "null" ) && title.length > 0 ) { if ( title == "all notes" ) { this.title_field.value = title; - this.display_preview( title, "list of all notes in this notebook" ); + this.display_summary( title, "list of all notes in this notebook" ); return; } if ( title == "search results" ) { this.title_field.value = title; - this.display_preview( title, "current search results" ); + this.display_summary( title, "current search results" ); return; } this.invoker.invoke( "/notebooks/load_note_by_title", "GET", { "notebook_id": this.notebook_id, - "note_title": title + "note_title": title, + "summarize": true }, function ( result ) { // if the user has already started typing something, don't overwrite it @@ -1806,10 +1834,10 @@ function Link_pulldown( wiki, notebook_id, invoker, editor, link ) { return; if ( result.note ) { self.title_field.value = result.note.title; - self.display_preview( result.note.title, result.note.contents ); + self.display_summary( result.note.title, result.note.summary ); } else { self.title_field.value = title; - replaceChildNodes( self.note_preview, "empty note" ); + replaceChildNodes( self.note_summary, "empty note" ); } } ); @@ -1817,20 +1845,21 @@ function Link_pulldown( wiki, notebook_id, invoker, editor, link ) { } // if this link has an actual destination note id set, then see if that note is already open. if - // so, display its title and a preview of its contents + // so, display its title and a summary of its contents var iframe = getElement( "note_" + id ); if ( iframe ) { this.title_field.value = iframe.editor.title; - this.display_preview( iframe.editor.title, iframe.editor.document ); + this.display_summary( iframe.editor.title, iframe.editor.summarize() ); return; } - // otherwise, load the destination note from the server, displaying its title and a preview of + // otherwise, load the destination note from the server, displaying its title and a summary of // its contents this.invoker.invoke( "/notebooks/load_note", "GET", { "notebook_id": this.notebook_id, - "note_id": id + "note_id": id, + "summarize": true }, function ( result ) { // if the user has already started typing something, don't overwrite it @@ -1838,10 +1867,10 @@ function Link_pulldown( wiki, notebook_id, invoker, editor, link ) { return; if ( result.note ) { self.title_field.value = result.note.title; - self.display_preview( result.note.title, result.note.contents ); + self.display_summary( result.note.title, result.note.summary ); } else { self.title_field.value = title; - replaceChildNodes( self.note_preview, "empty note" ); + replaceChildNodes( self.note_summary, "empty note" ); } } ); @@ -1850,33 +1879,13 @@ function Link_pulldown( wiki, notebook_id, invoker, editor, link ) { Link_pulldown.prototype = new function () { this.prototype = Pulldown.prototype; }; Link_pulldown.prototype.constructor = Link_pulldown; -Link_pulldown.prototype.display_preview = function ( title, contents ) { - if ( !contents ) { - replaceChildNodes( this.note_preview, "empty note" ); - return; - } - - // if contents is a DOM node, just scrape its text - if ( contents.nodeType ) { - contents = strip( scrapeText( contents ) ); - // otherwise, assume contents is a string, so put it into a DOM node and then scrape its contents - } else { - var contents_node = createDOM( "span", {} ); - contents_node.innerHTML = contents; - contents = strip( scrapeText( contents_node ) ); - } - - // remove the title from the scraped contents text - if ( contents.indexOf( title ) == 0 ) - contents = contents.substr( title.length ); - - if ( contents.length == 0 ) { - replaceChildNodes( this.note_preview, "empty note" ); - } else { - var max_preview_length = 40; - var preview = contents.substr( 0, max_preview_length ) + ( ( contents.length > max_preview_length ) ? "..." : "" ); - replaceChildNodes( this.note_preview, preview ); - } +Link_pulldown.prototype.display_summary = function ( title, summary ) { + if ( !summary ) + replaceChildNodes( this.note_summary, "empty note" ); + else if ( summary.length == 0 ) + replaceChildNodes( this.note_summary, "empty note" ); + else + replaceChildNodes( this.note_summary, summary ); } Link_pulldown.prototype.title_field_clicked = function ( event ) { @@ -1892,13 +1901,13 @@ Link_pulldown.prototype.title_field_changed = function ( event ) { if ( this.title_field.value == this.previous_title ) return; - replaceChildNodes( this.note_preview, "" ); + replaceChildNodes( this.note_summary, "" ); var title = strip( this.title_field.value ); this.previous_title = title; var self = this; - this.wiki.resolve_link( title, this.link, function ( contents ) { - self.display_preview( title, contents ); + this.wiki.resolve_link( title, this.link, function ( summary ) { + self.display_summary( title, summary ); } ); }