From 3424e6ad12d0528cef39c0860394ad2278b19398 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Thu, 16 Aug 2007 01:29:18 +0000 Subject: [PATCH] Some refactoring of the new link resolving and launching code, because, well, it was just plain broken: * Setting link hrefs is no longer done in quite so many places to prevent conflicts. Still could be more centralized though. * Editor is no longer responsible for tracking note titles. It now issues signals for title changes, and Wiki takes care of tracking titles for all open editors. * A few places in Editor weren't making use of the new link_title() convenience function. Now they are. * They way Wiki was associating link pulldowns with their links was completely broken, because Javascript Arrays can't use DOM objects as keys. At least not if you want them to work. Now just setting a "pulldown" member on each link node directly. * Moved some of the "if you click a link and the target editor is already open, just highlight it" logic out of Editor and into Wiki. (Both load_note() and resolve_link(), unfortunately.) * Made Link_pulldown.title_field_changed just rely on resolve_link() instead of doing all the link resolving itself. --- static/js/Editor.js | 61 +++++----------------- static/js/Wiki.js | 121 ++++++++++++++++++++++++++------------------ 2 files changed, 87 insertions(+), 95 deletions(-) diff --git a/static/js/Editor.js b/static/js/Editor.js index e8a5573..686f87d 100644 --- a/static/js/Editor.js +++ b/static/js/Editor.js @@ -1,5 +1,3 @@ -note_titles = {} // map from note title to the open editor for that note - function Editor( id, notebook_id, note_text, deleted_from, revisions_list, insert_after_iframe_id, read_write, startup, highlight, focus ) { this.id = id; this.notebook_id = notebook_id; @@ -246,24 +244,6 @@ Editor.prototype.resize = function () { setElementDimensions( this.iframe, dimensions ); } -Editor.prototype.resolve_link = function ( link ) { - // in case the link is to ourself, first grab the most recent version of our title - this.scrape_title(); - - var id; - var title = link_title( link ); - var editor = note_titles[ title ]; - // if the link's title corresponds to an open note id, set that as the link's destination - if ( editor ) { - id = editor.id; - link.href = "/notebooks/" + this.notebook_id + "?note_id=" + id; - // otherwise, resolve the link by looking up the link's title on the server - } else { - signal( this, "resolve_link", title, link ); - return; - } -} - Editor.prototype.key_pressed = function ( event ) { signal( this, "key_pressed", this, event ); @@ -304,22 +284,10 @@ Editor.prototype.mouse_clicked = function ( event ) { event.stop(); - // if the note corresponding to the link's id or title is already open, highlight it + // load the note corresponding to the clicked link var query = parse_query( link ); var title = link_title( link, query ); var id = query.note_id; - var iframe = getElement( "note_" + id ); - if ( iframe ) { - iframe.editor.highlight(); - return; - } - var editor = note_titles[ title ]; - if ( editor ) { - editor.highlight(); - return; - } - - // otherwise, load the note for that id signal( this, "load_editor", title, this.iframe.id, id, null, link ); } @@ -329,13 +297,9 @@ Editor.prototype.scrape_title = function () { if ( !heading ) return; var title = scrapeText( heading ); - // delete the previous title (if any) from the note_titles map - if ( this.title ) - delete note_titles[ this.title ]; - - // record the new title in note_titles + // issue a signal that the title has changed and save off the new title + signal( this, "title_changed", this, this.title, title ); this.title = title; - note_titles[ this.title ] = this; } Editor.prototype.focused = function () { @@ -353,6 +317,13 @@ Editor.prototype.empty = function () { return ( scrapeText( this.document.body ).length == 0 ); } +Editor.prototype.contents = function () { + if ( !this.document.body ) + return "" + + return scrapeText( this.document.body ); +} + Editor.prototype.start_link = function () { // get the current selection, which is the link title if ( this.iframe.contentWindow && this.iframe.contentWindow.getSelection ) { // browsers such as Firefox @@ -405,7 +376,7 @@ Editor.prototype.start_link = function () { } else { this.exec_command( "createLink", "/notebooks/" + this.notebook_id + "?note_id=new" ); var link = this.find_link_at_cursor(); - signal( this, "resolve_link", strip( scrapeText( link ) ), link ); + signal( this, "resolve_link", link_title( link ), link ); } } else if ( this.document.selection ) { // browsers such as IE var range = this.document.selection.createRange(); @@ -420,7 +391,7 @@ Editor.prototype.start_link = function () { } else { this.exec_command( "createLink", "/notebooks/" + this.notebook_id + "?note_id=new" ); var link = this.find_link_at_cursor(); - signal( this, "resolve_link", strip( scrapeText( link ) ), link ); + signal( this, "resolve_link", link_title( link ), link ); } } } @@ -448,9 +419,7 @@ Editor.prototype.end_link = function () { range.pasteHTML( "" ); } - var query = parse_query( link ); - var link_title = query.title || strip( scrapeText( link ) ); - signal( this, "resolve_link", link_title, link ); + signal( this, "resolve_link", link_title( link ), link ); } Editor.prototype.find_link_at_cursor = function () { @@ -545,9 +514,7 @@ Editor.prototype.contents = function () { } Editor.prototype.shutdown = function( event ) { - if ( this.title ) - delete note_titles[ this.title ]; - + signal( this, "title_changed", this, this.title, null ); var iframe = this.iframe; var note_controls = this.note_controls; disconnectAll( this ); diff --git a/static/js/Wiki.js b/static/js/Wiki.js index 3bb6fb8..edfd7fb 100644 --- a/static/js/Wiki.js +++ b/static/js/Wiki.js @@ -5,8 +5,8 @@ function Wiki() { this.notebook = null; this.notebook_id = getElement( "notebook_id" ).value; this.read_write = false; - this.startup_notes = new Array(); // map of startup notes: note id to bool - this.link_pulldowns = new Array(); // map of link pulldowns: link object to pulldown + this.startup_notes = new Array(); // map of startup notes: note id to bool + this.open_editors = new Array(); // map of open notes: note title to editor this.invoker = new Invoker(); connect( this.invoker, "error_message", this, "display_error" ); @@ -175,11 +175,10 @@ Wiki.prototype.create_blank_editor = function ( event ) { } Wiki.prototype.load_editor = function ( note_title, from_iframe_id, note_id, revision, link ) { - var self = this; - // if a link is given with an open link pulldown, then ignore the note title given and use the // one from the pulldown instead - var pulldown = this.link_pulldowns[ link ]; + var pulldown = link.pulldown; + var pulldown_title = undefined; if ( pulldown ) { pulldown_title = strip( pulldown.title_field.value ); if ( pulldown_title ) @@ -188,7 +187,27 @@ Wiki.prototype.load_editor = function ( note_title, from_iframe_id, note_id, rev pulldown.title_field.value = note_title; } + // if the note corresponding to the link's id is already open, highlight it and bail, but only if + // we didn't pull a title from an open link pulldown + if ( !pulldown_title ) { + var iframe = getElement( "note_" + note_id ); + if ( iframe ) { + iframe.editor.highlight(); + link.href = "/notebooks/" + this.notebook_id + "?note_id=" + note_id; + return; + } + } + + // if the note corresponding to the link's title is already open, highlight it and bail + var editor = this.open_editors[ note_title ]; + if ( editor ) { + editor.highlight(); + link.href = "/notebooks/" + this.notebook_id + "?note_id=" + editor.id; + return; + } + // if there's not a valid destination note id, then load by title instead of by id + var self = this; if ( note_id == "new" || note_id == "null" ) { this.invoker.invoke( "/notebooks/load_note_by_title", "GET", { @@ -211,25 +230,43 @@ Wiki.prototype.load_editor = function ( note_title, from_iframe_id, note_id, rev ); } -Wiki.prototype.resolve_link = function ( note_title, link ) { - // if the link already has an id, then the link is already resolved so we can just bail - if ( link.href ) { - var id = parse_query( link ).note_id; - if ( id != "new" && id != "null" ) - return; - } +Wiki.prototype.resolve_link = function ( note_title, link, callback ) { + var id = parse_query( link ).note_id; + if ( !id ) return; + + // if the link already has a valid-looking id, it's already resolved, so bail + if ( !callback && id != "new" && id != "null" ) + return; if ( note_title.length == 0 ) return; + // if the note corresponding to the link's title is already open, resolve the link and bail + var editor = this.open_editors[ note_title ]; + if ( editor ) { + link.href = "/notebooks/" + this.notebook_id + "?note_id=" + editor.id; + if ( callback ) + callback( editor.contents() ); + return; + } + var self = this; this.invoker.invoke( - "/notebooks/lookup_note_id", "GET", { + "/notebooks/" + ( callback ? "load_note_by_title" : "lookup_note_id" ), "GET", { "notebook_id": this.notebook_id, "note_title": note_title }, function ( result ) { - link.href = "/notebooks/" + self.notebook_id + "?note_id=" + result.note_id; + if ( result && ( result.note || result.note_id ) ) { + link.href = "/notebooks/" + self.notebook_id + "?note_id=" + ( result.note ? result.note.object_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 ); } ); } @@ -275,16 +312,6 @@ Wiki.prototype.create_editor = function ( id, note_text, deleted_from, revisions } } - // update any matching links in from_iframe_id with the id of this new editor - if ( from_iframe_id ) { - var links = getElementsByTagAndClassName( "a", null, getElement( from_iframe_id ).editor.document ); - for ( var i in links ) { - // a link matches if its contained text is the same as this note's title - if ( link_title( links[ i ] ) == note_title ) - links[ i ].href = "/notebooks/" + this.notebook_id + "?note_id=" + id; - } - } - // 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 ] ); @@ -296,6 +323,7 @@ Wiki.prototype.create_editor = function ( id, note_text, deleted_from, revisions if ( this.read_write ) { connect( editor, "state_changed", this, "editor_state_changed" ); + connect( editor, "title_changed", this, "editor_title_changed" ); connect( editor, "key_pressed", this, "editor_key_pressed" ); connect( editor, "delete_clicked", function ( event ) { self.delete_editor( event, editor ) } ); connect( editor, "undelete_clicked", function ( event ) { self.undelete_editor_via_trash( event, editor ) } ); @@ -319,6 +347,13 @@ Wiki.prototype.editor_state_changed = function ( editor ) { this.display_link_pulldown( editor ); } +Wiki.prototype.editor_title_changed = function ( editor, old_title, new_title ) { + delete this.open_editors[ old_title ]; + + if ( new_title != null ) + this.open_editors[ new_title ] = editor; +} + Wiki.prototype.display_link_pulldown = function ( editor ) { var link = editor.find_link_at_cursor(); @@ -327,7 +362,7 @@ Wiki.prototype.display_link_pulldown = function ( editor ) { return; } - var pulldown = this.link_pulldowns[ link ]; + var pulldown = link.pulldown; if ( pulldown ) pulldown.update_position(); @@ -876,7 +911,7 @@ Changes_pulldown.prototype.shutdown = function () { function Link_pulldown( wiki, notebook_id, invoker, editor, link ) { - wiki.link_pulldowns[ link ] = this; + link.pulldown = this; this.link = link; Pulldown.call( this, wiki, notebook_id, "link_" + editor.id, link, editor.iframe ); @@ -910,8 +945,8 @@ function Link_pulldown( wiki, notebook_id, invoker, editor, link ) { // so, display its title and a preview of its contents var iframe = getElement( "note_" + id ); if ( iframe ) { - self.title_field.value = iframe.editor.title; - self.display_preview( iframe.editor.title, iframe.editor.document ); + this.title_field.value = iframe.editor.title; + this.display_preview( iframe.editor.title, iframe.editor.document ); return; } @@ -938,6 +973,11 @@ Link_pulldown.prototype = Pulldown; 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 ) ); @@ -970,29 +1010,14 @@ Link_pulldown.prototype.title_field_changed = function ( event ) { if ( this.title_field.value == this.previous_title ) return; - var self = this; replaceChildNodes( this.note_preview, "" ); var title = strip( this.title_field.value ); this.previous_title = title; - this.invoker.invoke( - "/notebooks/load_note_by_title", "GET", { - "notebook_id": this.notebook_id, - "note_title": title - }, - function ( result ) { - if ( result.note ) { - self.link.href = "/notebooks/" + self.notebook_id + "?note_id=" + result.note.object_id; - self.display_preview( result.note.title, result.note.contents ); - } else { - self.link.href = "/notebooks/" + self.notebook_id + "?" + queryString( - [ "title", "note_id" ], - [ title, "null" ] - ); - replaceChildNodes( self.note_preview, "empty note" ); - } - } - ); + var self = this; + this.wiki.resolve_link( title, this.link, function ( contents ) { + self.display_preview( title, contents ); + } ); } Link_pulldown.prototype.title_field_key_pressed = function ( event ) { @@ -1013,5 +1038,5 @@ Link_pulldown.prototype.shutdown = function () { disconnectAll( this.title_field ); if ( this.link ) - delete this.wiki.link_pulldowns[ this.link ]; + this.link.pulldown = null; }