![Dan Helfman](/assets/img/avatar_default.png)
repro: 1. delete a note 2. go to the trash 3. click undelete, and then *very quickly*, click the name of the notebook 4. when the notebook loads, it won't have the undeleted note 5. go back to the trash. the deleted note will be there, with non-trash note buttons this seems to be a race between undelete_editor_via_trash() and save_editor(), which is invoked upon page close. solution: don't allow save_editor() to call save_note() if editor.closed is set
2180 lines
71 KiB
JavaScript
2180 lines
71 KiB
JavaScript
function Wiki( invoker ) {
|
|
this.next_id = null;
|
|
this.focused_editor = null;
|
|
this.blank_editor_id = null;
|
|
this.notebook = null;
|
|
this.notebook_id = getElement( "notebook_id" ).value;
|
|
this.parent_id = getElement( "parent_id" ).value; // id of the notebook containing this one
|
|
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.all_notes_editor = null; // editor for display of list of all notes
|
|
this.search_results_editor = null; // editor for display of search results
|
|
this.invoker = invoker;
|
|
this.rate_plan = evalJSON( getElement( "rate_plan" ).value );
|
|
this.storage_usage_high = false;
|
|
this.invites = evalJSON( getElement( "invites" ).value );
|
|
this.invite_id = getElement( "invite_id" ).value;
|
|
this.after_login = getElement( "after_login" ).value;
|
|
|
|
var total_notes_count_node = getElement( "total_notes_count" );
|
|
if ( total_notes_count_node )
|
|
this.total_notes_count = parseInt( scrapeText( total_notes_count_node ) );
|
|
else
|
|
this.total_notes_count = null;
|
|
|
|
// grab the current notebook from the list of available notebooks
|
|
this.notebooks = evalJSON( getElement( "notebooks" ).value );
|
|
for ( var i in this.notebooks ) {
|
|
if ( this.notebooks[ i ].object_id == this.notebook_id ) {
|
|
this.notebook = this.notebooks[ i ]
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( this.notebook && this.notebook.read_write ) {
|
|
unsupported_agent = null;
|
|
if ( /Safari/.test( navigator.userAgent ) )
|
|
unsupported_agent = "Safari";
|
|
if ( /Opera/.test( navigator.userAgent ) )
|
|
unsupported_agent = "Opera";
|
|
|
|
if ( unsupported_agent )
|
|
alert( "Luminotes does not currently support the " + unsupported_agent + " web browser for editing. If possible, please use Firefox or Internet Explorer instead. " + unsupported_agent + " support will be added in a future release. Sorry for the inconvenience." );
|
|
}
|
|
|
|
var deleted_id = getElement( "deleted_id" ).value;
|
|
var skip_empty_message = deleted_id ? true : false;
|
|
|
|
// populate the wiki with startup notes
|
|
this.populate(
|
|
evalJSON( getElement( "startup_notes" ).value || "null" ),
|
|
evalJSON( getElement( "current_notes" ).value || "null" ),
|
|
evalJSON( getElement( "note_read_write" ).value || "true" ),
|
|
skip_empty_message
|
|
);
|
|
|
|
this.display_storage_usage( evalJSON( getElement( "storage_bytes" ).value || "0" ) );
|
|
|
|
connect( this.invoker, "error_message", this, "display_error" );
|
|
connect( this.invoker, "message", this, "display_message" );
|
|
connect( "search_form", "onsubmit", this, "search" );
|
|
connect( "html", "onclick", this, "background_clicked" );
|
|
connect( "html", "onkeydown", this, "key_pressed" );
|
|
connect( window, "onresize", this, "resize_editors" );
|
|
|
|
var blank_note_stub = getElement( "blank_note_stub" );
|
|
if ( blank_note_stub ) {
|
|
connect( blank_note_stub, "onmouseover", function ( event ) {
|
|
addElementClass( blank_note_stub, "blank_note_stub_border" );
|
|
removeElementClass( blank_note_stub, "blank_note_stub_hidden_border" );
|
|
} );
|
|
connect( blank_note_stub, "onmouseout", function ( event ) {
|
|
addElementClass( blank_note_stub, "blank_note_stub_hidden_border" );
|
|
removeElementClass( blank_note_stub, "blank_note_stub_border" );
|
|
} );
|
|
connect( blank_note_stub, "onclick", this, "create_blank_editor" );
|
|
}
|
|
|
|
var self = this;
|
|
var logout_link = getElement( "logout_link" );
|
|
if ( logout_link ) {
|
|
connect( "logout_link", "onclick", function ( event ) {
|
|
self.save_editor( null, true );
|
|
self.invoker.invoke( "/users/logout", "POST" );
|
|
event.stop();
|
|
} );
|
|
}
|
|
|
|
var rename = evalJSON( getElement( "rename" ).value );
|
|
if ( rename && this.notebook.read_write )
|
|
this.start_notebook_rename();
|
|
|
|
// if a notebook was just deleted, show a message with an undo button
|
|
if ( deleted_id && this.notebook.read_write ) {
|
|
var undo_button = createDOM( "input", {
|
|
"type": "button",
|
|
"class": "message_button",
|
|
"value": "undo",
|
|
"title": "undo deletion"
|
|
} );
|
|
var trash_link = createDOM( "a", {
|
|
"href": "/notebooks/" + this.notebook.trash_id + "?parent_id=" + this.notebook.object_id
|
|
}, "trash" );
|
|
var message_div = this.display_message( "The notebook has been moved to the", [ trash_link, ". ", undo_button ], "notes_top" );
|
|
var self = this;
|
|
connect( undo_button, "onclick", function ( event ) { self.undelete_notebook( event, deleted_id ); } );
|
|
}
|
|
}
|
|
|
|
Wiki.prototype.update_next_id = function ( result ) {
|
|
this.next_id = result.next_id;
|
|
}
|
|
|
|
Wiki.prototype.display_storage_usage = function( storage_bytes ) {
|
|
if ( !storage_bytes )
|
|
return;
|
|
|
|
// display the user's current storage usage
|
|
var MEGABYTE = 1024 * 1024;
|
|
function bytes_to_megabytes( storage_bytes ) {
|
|
return Math.round( storage_bytes / MEGABYTE );
|
|
}
|
|
|
|
var quota_bytes = this.rate_plan.storage_quota_bytes;
|
|
if ( !quota_bytes )
|
|
return;
|
|
|
|
var usage_percent = Math.round( storage_bytes / quota_bytes * 100.0 );
|
|
|
|
if ( usage_percent > 90 ) {
|
|
var storage_usage_class = "storage_usage_high";
|
|
if ( this.storage_usage_high == false )
|
|
this.display_message(
|
|
"You are currently using " +
|
|
usage_percent +
|
|
"% of your available storage space. Please delete some notes, empty the trash, or",
|
|
[ createDOM( "a", { "href": "/upgrade" }, "upgrade" ), " your account." ]
|
|
);
|
|
this.storage_usage_high = true;
|
|
} else if ( usage_percent > 75 ) {
|
|
var storage_usage_class = "storage_usage_medium";
|
|
this.storage_usage_high = false;
|
|
} else {
|
|
var storage_usage_class = "storage_usage_low";
|
|
this.storage_usage_high = false;
|
|
}
|
|
|
|
replaceChildNodes(
|
|
"storage_usage_area",
|
|
createDOM( "div", { "class": storage_usage_class },
|
|
bytes_to_megabytes( storage_bytes ) + " MB (" + usage_percent + "%) of " + bytes_to_megabytes( quota_bytes ) + " MB" )
|
|
);
|
|
}
|
|
|
|
Wiki.prototype.populate = function ( startup_notes, current_notes, note_read_write, skip_empty_message ) {
|
|
var self = this;
|
|
|
|
// if this is the trash and the user has owner-level access, then display a list of all deleted notebooks
|
|
if ( this.notebook.owner && this.notebook.name == "trash" ) {
|
|
var heading_shown = false;
|
|
var deleted_notebooks = getElement( "deleted_notebooks" );
|
|
|
|
for ( var i in this.notebooks ) {
|
|
var notebook = this.notebooks[ i ];
|
|
if ( !notebook.deleted )
|
|
continue;
|
|
|
|
if ( !heading_shown ) {
|
|
appendChildNodes( deleted_notebooks, createDOM( "h4", {}, "deleted notebooks" ) );
|
|
heading_shown = true;
|
|
}
|
|
|
|
delete_button = createDOM( "input", {
|
|
"type": "button",
|
|
"class": "note_button",
|
|
"id": "delete_notebook_" + notebook.object_id,
|
|
"value": "delete forever",
|
|
"title": "delete notebook"
|
|
} );
|
|
function connect_delete( notebook_id ) {
|
|
connect( delete_button, "onclick", function ( event ) { self.delete_notebook_forever( event, notebook_id ); } );
|
|
}
|
|
connect_delete( notebook.object_id );
|
|
|
|
undelete_button = createDOM( "input", {
|
|
"type": "button",
|
|
"class": "note_button",
|
|
"id": "undelete_notebook_" + notebook.object_id,
|
|
"value": "undelete",
|
|
"title": "undelete notebook"
|
|
} );
|
|
function connect_undelete( notebook_id ) {
|
|
connect( undelete_button, "onclick", function ( event ) { self.undelete_notebook( event, notebook_id ); } );
|
|
}
|
|
connect_undelete( notebook.object_id );
|
|
|
|
appendChildNodes( deleted_notebooks, createDOM( "div",
|
|
{ "id": "deleted_notebook_" + notebook.object_id, "class": "deleted_notebook_item" },
|
|
createDOM( "span", {}, delete_button ),
|
|
createDOM( "span", {}, undelete_button ),
|
|
createDOM( "span", {}, notebook.name )
|
|
) );
|
|
}
|
|
}
|
|
|
|
// create an editor for each startup note in the received notebook, focusing the first one
|
|
var focus = true;
|
|
for ( var i in startup_notes ) {
|
|
var startup_note = startup_notes[ i ];
|
|
this.startup_notes[ startup_note.object_id ] = true;
|
|
|
|
// don't actually create an editor if a particular list of notes was provided in the result
|
|
if ( current_notes.length == 0 ) {
|
|
var editor = this.create_editor(
|
|
startup_note.object_id,
|
|
// grab this note's contents from the static notes area
|
|
getElement( "static_note_" + startup_note.object_id ).innerHTML,
|
|
startup_note.deleted_from_id,
|
|
startup_note.revision,
|
|
startup_note.creation,
|
|
this.notebook.read_write, false, focus
|
|
);
|
|
|
|
this.open_editors[ startup_note.title ] = editor;
|
|
focus = false;
|
|
}
|
|
}
|
|
|
|
// if particular notes were provided, then display editors for them
|
|
var focus = true;
|
|
for ( var i in current_notes ) {
|
|
var note = current_notes[ i ];
|
|
|
|
this.create_editor(
|
|
note.object_id,
|
|
getElement( "static_note_" + note.object_id ).innerHTML,
|
|
note.deleted_from_id,
|
|
note.revision,
|
|
note.creation,
|
|
this.notebook.read_write && note_read_write, false, focus
|
|
);
|
|
focus = false;
|
|
}
|
|
|
|
if ( startup_notes.length == 0 && current_notes.length == 0 && !skip_empty_message )
|
|
this.display_empty_message();
|
|
|
|
var empty_trash_link = getElement( "empty_trash_link" );
|
|
if ( empty_trash_link )
|
|
connect( empty_trash_link, "onclick", function ( event ) { self.delete_all_editors( event ); } );
|
|
|
|
if ( this.notebook.read_write ) {
|
|
connect( window, "onunload", function ( event ) { self.editor_focused( null, true ); } );
|
|
connect( "newNote", "onclick", this, "create_blank_editor" );
|
|
connect( "createLink", "onclick", this, "toggle_link_button" );
|
|
connect( "bold", "onclick", function ( event ) { self.toggle_button( event, "bold" ); } );
|
|
connect( "italic", "onclick", function ( event ) { self.toggle_button( event, "italic" ); } );
|
|
connect( "underline", "onclick", function ( event ) { self.toggle_button( event, "underline" ); } );
|
|
connect( "title", "onclick", function ( event ) { self.toggle_button( event, "title" ); } );
|
|
connect( "insertUnorderedList", "onclick", function ( event ) { self.toggle_button( event, "insertUnorderedList" ); } );
|
|
connect( "insertOrderedList", "onclick", function ( event ) { self.toggle_button( event, "insertOrderedList" ); } );
|
|
|
|
this.make_image_button( "newNote", "new_note", true );
|
|
this.make_image_button( "createLink", "link" );
|
|
this.make_image_button( "bold" );
|
|
this.make_image_button( "italic" );
|
|
this.make_image_button( "underline" );
|
|
this.make_image_button( "title" );
|
|
this.make_image_button( "insertUnorderedList", "bullet_list" );
|
|
this.make_image_button( "insertOrderedList", "numbered_list" );
|
|
|
|
// grab the next available object id
|
|
this.invoker.invoke( "/next_id", "POST", null,
|
|
function( result ) { self.update_next_id( result ); }
|
|
);
|
|
}
|
|
|
|
var all_notes_link = getElement( "all_notes_link" );
|
|
if ( all_notes_link ) {
|
|
connect( all_notes_link, "onclick", function ( event ) {
|
|
self.load_editor( "all notes", "null", null, null, getElement( "notes_top" ) );
|
|
event.stop();
|
|
} );
|
|
}
|
|
|
|
var download_html_link = getElement( "download_html_link" );
|
|
if ( download_html_link ) {
|
|
connect( download_html_link, "onclick", function ( event ) {
|
|
self.save_editor( null, true );
|
|
} );
|
|
}
|
|
|
|
var add_notebook_link = getElement( "add_notebook_link" );
|
|
if ( add_notebook_link ) {
|
|
connect( add_notebook_link, "onclick", function ( event ) {
|
|
self.invoker.invoke( "/notebooks/create", "POST" );
|
|
event.stop();
|
|
} );
|
|
}
|
|
|
|
var rename_notebook_link = getElement( "rename_notebook_link" );
|
|
if ( rename_notebook_link ) {
|
|
connect( rename_notebook_link, "onclick", function ( event ) {
|
|
self.start_notebook_rename();
|
|
event.stop();
|
|
} );
|
|
}
|
|
|
|
var notebook_header_name = getElement( "notebook_header_name" );
|
|
if ( notebook_header_name ) {
|
|
connect( notebook_header_name, "onclick", function ( event ) {
|
|
self.start_notebook_rename();
|
|
event.stop();
|
|
} );
|
|
}
|
|
|
|
var delete_notebook_link = getElement( "delete_notebook_link" );
|
|
if ( delete_notebook_link ) {
|
|
connect( delete_notebook_link, "onclick", function ( event ) {
|
|
self.delete_notebook();
|
|
event.stop();
|
|
} );
|
|
}
|
|
|
|
var share_notebook_link = getElement( "share_notebook_link" );
|
|
if ( share_notebook_link ) {
|
|
connect( share_notebook_link, "onclick", function ( event ) {
|
|
self.load_editor( "share this notebook", "null", null, null, getElement( "notes_top" ) );
|
|
event.stop();
|
|
} );
|
|
}
|
|
}
|
|
|
|
Wiki.prototype.background_clicked = function ( event ) {
|
|
if ( !hasElementClass( event.target(), "pulldown_checkbox" ) )
|
|
this.clear_pulldowns();
|
|
}
|
|
|
|
Wiki.prototype.create_blank_editor = function ( event ) {
|
|
if ( event ) event.stop();
|
|
|
|
this.clear_messages();
|
|
this.clear_pulldowns();
|
|
|
|
// if we're within the trash, don't allow new note creation
|
|
if ( this.notebook.name == "trash" ) {
|
|
this.display_error( "You can't create notes in the trash." );
|
|
return;
|
|
}
|
|
|
|
// if there is already a blank editor, then highlight it and bail
|
|
if ( this.blank_editor_id != null ) {
|
|
var blank_iframe_id = "note_" + this.blank_editor_id;
|
|
var iframe = getElement( blank_iframe_id );
|
|
if ( iframe && iframe.editor.empty() ) {
|
|
iframe.editor.highlight();
|
|
return;
|
|
}
|
|
}
|
|
|
|
var editor = this.create_editor( undefined, undefined, undefined, undefined, undefined, this.notebook.read_write, true, true );
|
|
this.increment_total_notes_count();
|
|
this.blank_editor_id = editor.id;
|
|
|
|
this.add_all_notes_link( editor.id, "" );
|
|
}
|
|
|
|
Wiki.prototype.load_editor = function ( note_title, note_id, revision, link, position_after ) {
|
|
if ( this.notebook.name == "trash" && !revision ) {
|
|
this.display_message( "If you'd like to use this note, try undeleting it first.", undefined, position_after );
|
|
return;
|
|
}
|
|
|
|
// if a link is given with an open link pulldown, then ignore the note title given and use the
|
|
// one from the pulldown instead
|
|
if ( link ) {
|
|
var pulldown = link.pulldown;
|
|
var pulldown_title = undefined;
|
|
if ( pulldown ) {
|
|
pulldown_title = strip( pulldown.title_field.value );
|
|
if ( pulldown_title )
|
|
note_title = pulldown_title;
|
|
}
|
|
|
|
if ( link.target )
|
|
link.removeAttribute( "target" );
|
|
}
|
|
|
|
// 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 ) {
|
|
if ( revision )
|
|
var iframe = getElement( "note_" + note_id + " " + revision );
|
|
else
|
|
var iframe = getElement( "note_" + note_id );
|
|
|
|
if ( iframe ) {
|
|
iframe.editor.highlight();
|
|
if ( link )
|
|
link.href = "/notebooks/" + this.notebook_id + "?note_id=" + note_id;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// if there's not a valid destination note id, then load by title instead of by id
|
|
var self = this;
|
|
if ( pulldown_title || note_id == undefined || note_id == "new" || note_id == "null" ) {
|
|
// if the note_title corresponds to a "magic" note's title, then dynamically highlight or create the note
|
|
if ( note_title == "all notes" ) {
|
|
this.invoker.invoke(
|
|
"/notebooks/all_notes", "GET", { "notebook_id": this.notebook.object_id },
|
|
function( result ) { self.display_all_notes_list( result ); }
|
|
);
|
|
return;
|
|
}
|
|
if ( note_title == "search results" ) {
|
|
var editor = this.open_editors[ note_title ];
|
|
if ( editor ) {
|
|
editor.highlight();
|
|
return;
|
|
}
|
|
|
|
this.display_search_results();
|
|
return;
|
|
}
|
|
if ( note_title == "share this notebook" ) {
|
|
var editor = this.open_editors[ note_title ];
|
|
if ( editor ) {
|
|
editor.highlight();
|
|
return;
|
|
}
|
|
|
|
this.share_notebook();
|
|
return;
|
|
}
|
|
|
|
// but if the note corresponding to the link's title is already open, highlight it and bail
|
|
if ( !revision ) {
|
|
var editor = this.open_editors[ note_title ];
|
|
if ( editor ) {
|
|
editor.highlight();
|
|
if ( link )
|
|
link.href = "/notebooks/" + this.notebook_id + "?note_id=" + editor.id;
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.invoker.invoke(
|
|
"/notebooks/load_note_by_title", "GET", {
|
|
"notebook_id": this.notebook_id,
|
|
"note_title": note_title,
|
|
"revision": revision
|
|
},
|
|
function ( result ) { self.parse_loaded_editor( result, note_title, revision, link, position_after ); }
|
|
);
|
|
return;
|
|
}
|
|
|
|
this.invoker.invoke(
|
|
"/notebooks/load_note", "GET", {
|
|
"notebook_id": this.notebook_id,
|
|
"note_id": note_id,
|
|
"revision": revision
|
|
},
|
|
function ( result ) { self.parse_loaded_editor( result, note_title, revision, link, position_after ); }
|
|
);
|
|
}
|
|
|
|
Wiki.prototype.resolve_link = function ( note_title, link, callback ) {
|
|
// if the title looks like a URL, then make it a link to an external site
|
|
if ( /^\w+:\/\//.test( note_title ) )
|
|
var title_looks_like_url = true;
|
|
else
|
|
var title_looks_like_url = false;
|
|
|
|
if ( link && link.target )
|
|
link.removeAttribute( "target" );
|
|
|
|
if ( note_title == "all notes" || note_title == "search results" || note_title == "share this notebook" ) {
|
|
link.href = "/notebooks/" + this.notebook_id + "?" + queryString(
|
|
[ "title", "note_id" ],
|
|
[ note_title, "null" ]
|
|
);
|
|
if ( callback ) {
|
|
if ( note_title == "all notes" )
|
|
callback( "list of all notes in this notebook" );
|
|
if ( note_title == "search results" )
|
|
callback( "current search results" );
|
|
else
|
|
callback( "share this notebook with others" );
|
|
}
|
|
return;
|
|
}
|
|
|
|
var id = parse_query( link ).note_id;
|
|
|
|
// if the link already has a valid-looking id, it's already resolved, so bail
|
|
if ( !callback && id != undefined && 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 ) {
|
|
if ( link )
|
|
link.href = "/notebooks/" + this.notebook_id + "?note_id=" + editor.id;
|
|
if ( callback )
|
|
callback( editor.summarize() );
|
|
return;
|
|
}
|
|
|
|
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 if ( title_looks_like_url ) {
|
|
link.target = "_new";
|
|
link.href = note_title;
|
|
callback( "web link" );
|
|
return;
|
|
} 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/lookup_note_id", "GET", {
|
|
"notebook_id": this.notebook_id,
|
|
"note_title": note_title
|
|
},
|
|
function ( result ) {
|
|
if ( result && result.note_id ) {
|
|
link.href = "/notebooks/" + self.notebook_id + "?note_id=" + result.note_id;
|
|
} else if ( title_looks_like_url ) {
|
|
link.target = "_new";
|
|
link.href = note_title;
|
|
} else {
|
|
link.href = "/notebooks/" + self.notebook_id + "?" + queryString(
|
|
[ "title", "note_id" ],
|
|
[ note_title, "null" ]
|
|
);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
Wiki.prototype.parse_loaded_editor = function ( result, note_title, requested_revision, link, position_after ) {
|
|
if ( result.note_id_in_trash ) {
|
|
var undelete_button = createDOM( "input", {
|
|
"type": "button",
|
|
"class": "message_button",
|
|
"value": "undelete",
|
|
"title": "undelete note"
|
|
} );
|
|
var trash_link = createDOM( "a", {
|
|
"href": "/notebooks/" + this.notebook.trash_id + "?parent_id=" + this.notebook.object_id
|
|
}, "trash" );
|
|
var message_div = this.display_message( "That note is in the", [ trash_link, ". ", undelete_button ], position_after )
|
|
var self = this;
|
|
connect( undelete_button, "onclick", function ( event ) { self.undelete_editor_via_undelete( event, result.note_id_in_trash, message_div ); } );
|
|
return;
|
|
}
|
|
|
|
if ( result.note ) {
|
|
var id = result.note.object_id;
|
|
if ( requested_revision )
|
|
id += " " + requested_revision;
|
|
var actual_revision = result.note.revision;
|
|
var actual_creation = result.note.creation;
|
|
var note_text = result.note.contents;
|
|
var deleted_from_id = result.note.deleted;
|
|
} else {
|
|
// if the title looks like a URL, then make it a link to an external site
|
|
if ( /^\w+:\/\//.test( note_title ) ) {
|
|
link.target = "_new";
|
|
link.href = note_title;
|
|
window.open( link.href );
|
|
return;
|
|
}
|
|
|
|
var id = null;
|
|
var note_text = "<h3>" + note_title;
|
|
var deleted_from_id = null;
|
|
var actual_revision = null;
|
|
var actual_creation = null;
|
|
this.increment_total_notes_count();
|
|
}
|
|
|
|
if ( requested_revision )
|
|
var read_write = false; // show previous revisions as read-only
|
|
else
|
|
var read_write = this.notebook.read_write;
|
|
|
|
var editor = this.create_editor( id, note_text, deleted_from_id, actual_revision, actual_creation, read_write, true, false, position_after );
|
|
id = editor.id;
|
|
|
|
// if a link that launched this editor was provided, update it with the created note's id
|
|
if ( link && id )
|
|
link.href = "/notebooks/" + this.notebook_id + "?note_id=" + id;
|
|
}
|
|
|
|
Wiki.prototype.create_editor = function ( id, note_text, deleted_from_id, revision, creation, read_write, highlight, focus, position_after ) {
|
|
var self = this;
|
|
if ( isUndefinedOrNull( id ) ) {
|
|
if ( this.notebook.read_write ) {
|
|
id = this.next_id;
|
|
this.invoker.invoke( "/next_id", "POST", null,
|
|
function( result ) { self.update_next_id( result ); }
|
|
);
|
|
} else {
|
|
id = 0;
|
|
}
|
|
}
|
|
|
|
// for read-only notes within read-write notebooks, tack the revision timestamp onto the start of the note text
|
|
if ( !read_write && this.notebook.read_write && revision ) {
|
|
var short_revision = this.brief_revision( revision );
|
|
note_text = "<p>Previous revision from " + short_revision + "</p>" + note_text;
|
|
}
|
|
|
|
if ( !read_write && creation ) {
|
|
var short_creation = this.brief_revision( creation );
|
|
note_text = '<p>' + short_creation + ' | <a href="/blog?note_id=' + id + '" target="_top">permalink</a></p>' + note_text;
|
|
}
|
|
|
|
var startup = this.startup_notes[ id ];
|
|
var editor = new Editor( id, this.notebook_id, note_text, deleted_from_id, revision, read_write, startup, highlight, focus, position_after );
|
|
|
|
if ( this.notebook.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 ) } );
|
|
connect( editor, "changes_clicked", function ( event ) { self.toggle_editor_changes( event, editor ) } );
|
|
connect( editor, "options_clicked", function ( event ) { self.toggle_editor_options( event, editor ) } );
|
|
connect( editor, "focused", this, "editor_focused" );
|
|
}
|
|
|
|
connect( editor, "load_editor", this, "load_editor" );
|
|
connect( editor, "hide_clicked", function ( event ) { self.hide_editor( event, editor ) } );
|
|
connect( editor, "invites_updated", function ( invites ) { self.invites = invites; self.share_notebook(); } );
|
|
connect( editor, "submit_form", function ( url, form, callback ) {
|
|
var args = {}
|
|
if ( url == "/users/signup" || url == "/users/login" ) {
|
|
args[ "invite_id" ] = self.invite_id;
|
|
if ( url == "/users/login" )
|
|
args[ "after_login" ] = self.after_login;
|
|
}
|
|
|
|
self.invoker.invoke( url, "POST", args, callback, form );
|
|
} );
|
|
connect( editor, "revoke_invite", function ( invite_id, callback ) {
|
|
self.invoker.invoke( "/users/revoke_invite", "POST", {
|
|
"notebook_id": self.notebook_id,
|
|
"invite_id": invite_id
|
|
}, callback );
|
|
} );
|
|
|
|
this.clear_pulldowns();
|
|
|
|
return editor;
|
|
}
|
|
|
|
Wiki.prototype.resize_editors = function () {
|
|
var iframes = getElementsByTagAndClassName( "iframe", "note_frame" );
|
|
for ( var i in iframes ) {
|
|
var editor = iframes[ i ].editor;
|
|
editor.resize();
|
|
}
|
|
}
|
|
|
|
Wiki.prototype.editor_state_changed = function ( editor ) {
|
|
this.update_toolbar();
|
|
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 && !editor.empty() ) {
|
|
this.open_editors[ new_title ] = editor;
|
|
this.add_all_notes_link( editor.id, new_title );
|
|
}
|
|
}
|
|
|
|
Wiki.prototype.display_link_pulldown = function ( editor, link ) {
|
|
if ( !editor.read_write ) {
|
|
this.clear_pulldowns();
|
|
return;
|
|
}
|
|
|
|
if ( !link )
|
|
link = editor.find_link_at_cursor();
|
|
|
|
// if there's no link at the current cursor location, or there is a link but it was just started,
|
|
// bail
|
|
if ( !link || link == editor.link_started ) {
|
|
this.clear_pulldowns();
|
|
return;
|
|
}
|
|
|
|
var pulldown = link.pulldown;
|
|
if ( pulldown )
|
|
pulldown.update_position();
|
|
|
|
// if the cursor is now on a link, display a link pulldown if there isn't already one open
|
|
if ( link_title( link ).length > 0 ) {
|
|
if ( !pulldown ) {
|
|
this.clear_pulldowns();
|
|
new Link_pulldown( this, this.notebook_id, this.invoker, editor, link );
|
|
}
|
|
}
|
|
}
|
|
|
|
Wiki.prototype.editor_focused = function ( editor, fire_and_forget ) {
|
|
if ( editor )
|
|
addElementClass( editor.iframe, "focused_note_frame" );
|
|
|
|
if ( this.focused_editor && this.focused_editor != editor ) {
|
|
this.clear_pulldowns();
|
|
removeElementClass( this.focused_editor.iframe, "focused_note_frame" );
|
|
|
|
// if the formerly focused editor is completely empty, then remove it as the user leaves it and switches to this editor
|
|
if ( this.focused_editor.id == this.blank_editor_id && this.focused_editor.empty() ) {
|
|
this.remove_all_notes_link( this.focused_editor.id );
|
|
this.focused_editor.shutdown();
|
|
this.decrement_total_notes_count();
|
|
this.display_empty_message();
|
|
} else {
|
|
// when switching editors, save the one being left
|
|
this.save_editor( null, fire_and_forget );
|
|
}
|
|
}
|
|
|
|
this.focused_editor = editor;
|
|
}
|
|
|
|
Wiki.prototype.key_pressed = function ( event ) {
|
|
if ( !this.notebook.read_write )
|
|
return;
|
|
|
|
var code = event.key().code;
|
|
if ( event.modifier().ctrl ) {
|
|
// ctrl-n: new note
|
|
if ( code == 78 )
|
|
this.create_blank_editor( event );
|
|
}
|
|
}
|
|
|
|
Wiki.prototype.editor_key_pressed = function ( editor, event ) {
|
|
var code = event.key().code;
|
|
if ( event.modifier().ctrl ) {
|
|
// ctrl-backtick: alert with frame HTML contents (temporary for debugging)
|
|
if ( code == 192 || code == 96 ) {
|
|
alert( editor.contents() );
|
|
event.stop();
|
|
// ctrl-b: bold
|
|
} else if ( code == 66 ) {
|
|
this.toggle_button( event, "bold" );
|
|
// ctrl-i: italic
|
|
} else if ( code == 73 ) {
|
|
this.toggle_button( event, "italic" );
|
|
// ctrl-u: underline
|
|
} else if ( code == 85 ) {
|
|
this.toggle_button( event, "underline" );
|
|
// ctrl-t: title
|
|
} else if ( code == 84 ) {
|
|
this.toggle_button( event, "title" );
|
|
// ctrl-period: unordered list
|
|
} else if ( code == 190 ) {
|
|
this.toggle_button( event, "insertUnorderedList" );
|
|
// ctrl-1: ordered list
|
|
} else if ( code == 49 ) {
|
|
this.toggle_button( event, "insertOrderedList" );
|
|
// ctrl-l: link
|
|
} else if ( code == 76 ) {
|
|
this.toggle_link_button( event );
|
|
// ctrl-n: new note
|
|
} else if ( code == 78 ) {
|
|
this.create_blank_editor( event );
|
|
// ctrl-h: hide note
|
|
} else if ( code == 72 ) {
|
|
if ( !editor.deleted_from_id )
|
|
this.hide_editor( event );
|
|
// ctrl-d: delete note
|
|
} else if ( code == 68 ) {
|
|
this.delete_editor( event );
|
|
}
|
|
// IE: hitting space or tab while making a link shouldn't end the link
|
|
} else if ( ( code == 32 || code == 9 ) && editor.document.selection && editor.state_enabled( "a" ) ) {
|
|
var range = editor.document.selection.createRange();
|
|
var text = range.parentElement().firstChild;
|
|
text.nodeValue += " ";
|
|
event.stop();
|
|
}
|
|
}
|
|
|
|
IMAGE_DIR = "/static/images/";
|
|
|
|
Wiki.prototype.make_image_button = function ( name, filename_prefix, handle_mouse_up_and_down ) {
|
|
var button = getElement( name );
|
|
|
|
if ( !filename_prefix )
|
|
filename_prefix = name;
|
|
|
|
button.filename_prefix = filename_prefix;
|
|
|
|
connect( button, "onmouseover", function ( event ) {
|
|
if ( /_down/.test( button.src ) )
|
|
button.src = IMAGE_DIR + filename_prefix + "_button_down_hover.png";
|
|
else
|
|
button.src = IMAGE_DIR + filename_prefix + "_button_hover.png";
|
|
} );
|
|
|
|
connect( button, "onmouseout", function ( event ) {
|
|
if ( /_down/.test( button.src ) )
|
|
button.src = IMAGE_DIR + filename_prefix + "_button_down.png";
|
|
else
|
|
button.src = IMAGE_DIR + filename_prefix + "_button.png";
|
|
} );
|
|
|
|
if ( handle_mouse_up_and_down ) {
|
|
connect( button, "onmousedown", function ( event ) {
|
|
if ( /_hover/.test( button.src ) )
|
|
button.src = IMAGE_DIR + filename_prefix + "_button_down_hover.png";
|
|
else
|
|
button.src = IMAGE_DIR + filename_prefix + "_button_down.png";
|
|
} );
|
|
connect( button, "onmouseup", function ( event ) {
|
|
if ( /_hover/.test( button.src ) )
|
|
button.src = IMAGE_DIR + filename_prefix + "_button_hover.png";
|
|
else
|
|
button.src = IMAGE_DIR + filename_prefix + "_button.png";
|
|
} );
|
|
}
|
|
}
|
|
|
|
Wiki.prototype.down_image_button = function ( name ) {
|
|
var button = getElement( name );
|
|
|
|
if ( /_down/.test( button.src ) )
|
|
return;
|
|
|
|
if ( /_hover/.test( button.src ) )
|
|
button.src = IMAGE_DIR + button.filename_prefix + "_button_down_hover.png";
|
|
else
|
|
button.src = IMAGE_DIR + button.filename_prefix + "_button_down.png";
|
|
}
|
|
|
|
Wiki.prototype.up_image_button = function ( name ) {
|
|
var button = getElement( name );
|
|
|
|
if ( !/_down/.test( button.src ) )
|
|
return;
|
|
|
|
if ( /_hover/.test( button.src ) )
|
|
button.src = IMAGE_DIR + button.filename_prefix + "_button_hover.png";
|
|
else
|
|
button.src = IMAGE_DIR + button.filename_prefix + "_button.png";
|
|
}
|
|
|
|
Wiki.prototype.toggle_image_button = function ( name ) {
|
|
var button = getElement( name );
|
|
|
|
if ( /_down/.test( button.src ) ) {
|
|
if ( /_hover/.test( button.src ) )
|
|
button.src = IMAGE_DIR + button.filename_prefix + "_button_hover.png";
|
|
else
|
|
button.src = IMAGE_DIR + button.filename_prefix + "_button.png";
|
|
return false;
|
|
} else {
|
|
if ( /_hover/.test( button.src ) )
|
|
button.src = IMAGE_DIR + button.filename_prefix + "_button_down_hover.png";
|
|
else
|
|
button.src = IMAGE_DIR + button.filename_prefix + "_button_down.png";
|
|
return true;
|
|
}
|
|
}
|
|
|
|
Wiki.prototype.toggle_button = function ( event, button_id ) {
|
|
this.clear_messages();
|
|
this.clear_pulldowns();
|
|
|
|
if ( this.focused_editor && this.focused_editor.read_write ) {
|
|
this.focused_editor.focus();
|
|
if ( button_id == "title" )
|
|
this.focused_editor.exec_command( "h3" );
|
|
else
|
|
this.focused_editor.exec_command( button_id );
|
|
this.focused_editor.resize();
|
|
this.toggle_image_button( button_id );
|
|
}
|
|
|
|
event.stop();
|
|
}
|
|
|
|
Wiki.prototype.update_button = function ( button_id, state_name, node_names ) {
|
|
if ( this.focused_editor.state_enabled( state_name, node_names ) )
|
|
this.down_image_button( button_id );
|
|
else
|
|
this.up_image_button( button_id );
|
|
}
|
|
|
|
Wiki.prototype.update_toolbar = function() {
|
|
if ( !this.focused_editor )
|
|
return;
|
|
|
|
var node_names = this.focused_editor.current_node_names();
|
|
this.update_button( "bold", "b", node_names );
|
|
this.update_button( "italic", "i", node_names );
|
|
this.update_button( "underline", "u", node_names );
|
|
this.update_button( "title", "h3", node_names );
|
|
this.update_button( "insertUnorderedList", "ul", node_names );
|
|
this.update_button( "insertOrderedList", "ol", node_names );
|
|
this.update_button( "createLink", "a", node_names );
|
|
}
|
|
|
|
Wiki.prototype.toggle_link_button = function ( event ) {
|
|
this.clear_messages();
|
|
this.clear_pulldowns();
|
|
var link = null;
|
|
|
|
if ( this.focused_editor && this.focused_editor.read_write ) {
|
|
this.focused_editor.focus();
|
|
if ( this.toggle_image_button( "createLink" ) )
|
|
link = this.focused_editor.start_link();
|
|
else
|
|
link = this.focused_editor.end_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();
|
|
}
|
|
|
|
Wiki.prototype.hide_editor = function ( event, editor ) {
|
|
this.clear_messages();
|
|
this.clear_pulldowns();
|
|
|
|
if ( editor == this.focused_editor )
|
|
this.focused_editor = null;
|
|
|
|
if ( !editor ) {
|
|
editor = this.focused_editor;
|
|
this.focused_editor = null;
|
|
}
|
|
|
|
if ( editor ) {
|
|
// if the editor to hide is completely empty, then simply remove it
|
|
if ( editor.id == this.blank_editor_id && editor.empty() ) {
|
|
this.remove_all_notes_link( editor.id );
|
|
editor.shutdown();
|
|
this.decrement_total_notes_count();
|
|
} else {
|
|
// before hiding an editor, save it
|
|
if ( this.notebook.read_write && editor.read_write )
|
|
this.save_editor( editor );
|
|
|
|
editor.shutdown();
|
|
Highlight( "all_notes_link" );
|
|
}
|
|
|
|
this.display_empty_message();
|
|
}
|
|
|
|
event.stop();
|
|
}
|
|
|
|
Wiki.prototype.delete_editor = function ( event, editor ) {
|
|
this.clear_messages();
|
|
this.clear_pulldowns();
|
|
|
|
if ( !editor ) {
|
|
editor = this.focused_editor;
|
|
this.focused_editor = null;
|
|
}
|
|
|
|
if ( editor ) {
|
|
if ( this.startup_notes[ editor.id ] )
|
|
delete this.startup_notes[ editor.id ];
|
|
|
|
this.save_editor( editor, true );
|
|
|
|
var self = this;
|
|
if ( this.notebook.read_write && editor.read_write ) {
|
|
this.invoker.invoke( "/notebooks/delete_note", "POST", {
|
|
"notebook_id": this.notebook_id,
|
|
"note_id": editor.id
|
|
}, function ( result ) { self.display_storage_usage( result.storage_bytes ); } );
|
|
}
|
|
|
|
if ( editor == this.focused_editor )
|
|
this.focused_editor = null;
|
|
|
|
if ( this.notebook.trash_id && !( editor.id == this.blank_editor_id && editor.empty() ) ) {
|
|
var undo_button = createDOM( "input", {
|
|
"type": "button",
|
|
"class": "message_button",
|
|
"value": "undo",
|
|
"title": "undo deletion"
|
|
} );
|
|
var trash_link = createDOM( "a", {
|
|
"href": "/notebooks/" + this.notebook.trash_id + "?parent_id=" + this.notebook.object_id
|
|
}, "trash" );
|
|
var message_div = this.display_message( "The note has been moved to the", [ trash_link, ". ", undo_button ], editor.iframe );
|
|
var self = this;
|
|
connect( undo_button, "onclick", function ( event ) { self.undelete_editor_via_undo( event, editor, message_div ); } );
|
|
}
|
|
|
|
this.remove_all_notes_link( editor.id );
|
|
|
|
editor.shutdown();
|
|
this.decrement_total_notes_count();
|
|
this.display_empty_message();
|
|
}
|
|
|
|
event.stop();
|
|
}
|
|
|
|
Wiki.prototype.undelete_editor_via_trash = function ( event, editor ) {
|
|
this.clear_messages();
|
|
this.clear_pulldowns();
|
|
|
|
if ( !editor ) {
|
|
editor = this.focused_editor;
|
|
this.focused_editor = null;
|
|
}
|
|
|
|
if ( editor ) {
|
|
if ( this.startup_notes[ editor.id ] )
|
|
delete this.startup_notes[ editor.id ];
|
|
|
|
this.save_editor( editor, true );
|
|
|
|
if ( this.notebook.read_write && editor.read_write ) {
|
|
var self = this;
|
|
this.invoker.invoke( "/notebooks/undelete_note", "POST", {
|
|
"notebook_id": editor.deleted_from_id,
|
|
"note_id": editor.id
|
|
}, function ( result ) { self.display_storage_usage( result.storage_bytes ); } );
|
|
}
|
|
|
|
if ( editor == this.focused_editor )
|
|
this.focused_editor = null;
|
|
|
|
this.remove_all_notes_link( editor.id );
|
|
|
|
editor.shutdown();
|
|
this.decrement_total_notes_count();
|
|
this.display_empty_message();
|
|
}
|
|
|
|
event.stop();
|
|
}
|
|
|
|
Wiki.prototype.undelete_editor_via_undo = function( event, editor, position_after ) {
|
|
if ( editor ) {
|
|
if ( this.notebook.read_write && editor.read_write ) {
|
|
var self = this;
|
|
this.invoker.invoke( "/notebooks/undelete_note", "POST", {
|
|
"notebook_id": this.notebook_id,
|
|
"note_id": editor.id
|
|
}, function ( result ) { self.display_storage_usage( result.storage_bytes ); } );
|
|
}
|
|
|
|
this.startup_notes[ editor.id ] = true;
|
|
this.increment_total_notes_count();
|
|
this.load_editor( "Note not found.", editor.id, null, null, position_after );
|
|
}
|
|
|
|
event.stop();
|
|
}
|
|
|
|
Wiki.prototype.undelete_editor_via_undelete = function( event, note_id, position_after ) {
|
|
if ( this.notebook.read_write ) {
|
|
var self = this;
|
|
this.invoker.invoke( "/notebooks/undelete_note", "POST", {
|
|
"notebook_id": this.notebook_id,
|
|
"note_id": note_id
|
|
}, function ( result ) { self.display_storage_usage( result.storage_bytes ); } );
|
|
}
|
|
|
|
this.startup_notes[ note_id ] = true;
|
|
this.increment_total_notes_count();
|
|
this.load_editor( "Note not found.", note_id, null, null, position_after );
|
|
|
|
event.stop();
|
|
}
|
|
|
|
Wiki.prototype.undelete_notebook = function( event, notebook_id ) {
|
|
this.invoker.invoke( "/notebooks/undelete", "POST", {
|
|
"notebook_id": notebook_id
|
|
} );
|
|
|
|
event.stop();
|
|
}
|
|
|
|
Wiki.prototype.compare_versions = function( event, editor, previous_revision ) {
|
|
this.clear_pulldowns();
|
|
|
|
// display the two revisions for comparison by the user
|
|
this.load_editor( editor.title, editor.id, previous_revision, null, editor.closed ? null : editor.iframe );
|
|
this.load_editor( editor.title, editor.id, null, null, editor.closed ? null : editor.iframe );
|
|
}
|
|
|
|
Wiki.prototype.save_editor = function ( editor, fire_and_forget ) {
|
|
if ( !editor )
|
|
editor = this.focused_editor;
|
|
|
|
var self = this;
|
|
if ( editor && editor.read_write && !( editor.id == this.blank_editor_id && editor.empty() ) && !editor.closed ) {
|
|
this.invoker.invoke( "/notebooks/save_note", "POST", {
|
|
"notebook_id": this.notebook_id,
|
|
"note_id": editor.id,
|
|
"contents": editor.contents(),
|
|
"startup": editor.startup,
|
|
"previous_revision": editor.revision ? editor.revision : "None"
|
|
}, function ( result ) {
|
|
self.update_editor_revisions( result, editor );
|
|
self.display_storage_usage( result.storage_bytes );
|
|
}, null, fire_and_forget );
|
|
}
|
|
}
|
|
|
|
Wiki.prototype.update_editor_revisions = function ( result, editor ) {
|
|
// if there's not a newly saved revision, then the contents are unchanged, so bail
|
|
if ( !result.new_revision )
|
|
return;
|
|
|
|
var client_previous_revision = editor.revision;
|
|
editor.revision = result.new_revision.revision;
|
|
|
|
// if the server's idea of the previous revision doesn't match the client's, then someone has
|
|
// gone behind our back and saved the editor's note from another window
|
|
if ( result.previous_revision.revision != client_previous_revision ) {
|
|
var compare_button = createDOM( "input", {
|
|
"type": "button",
|
|
"class": "message_button",
|
|
"value": "compare versions",
|
|
"title": "compare your version with the modified version"
|
|
} );
|
|
this.display_error(
|
|
'Your changes to the note titled "' + editor.title +
|
|
'" have overwritten changes made in another window by ' + result.previous_revision.username + '.',
|
|
[ compare_button ], editor.iframe
|
|
);
|
|
|
|
var self = this;
|
|
connect( compare_button, "onclick", function ( event ) {
|
|
self.compare_versions( event, editor, result.previous_revision.revision );
|
|
} );
|
|
|
|
if ( !editor.user_revisions || editor.user_revisions.length == 0 )
|
|
return;
|
|
editor.user_revisions.push( result.previous_revision );
|
|
}
|
|
|
|
// add the new revision to the editor's revisions list
|
|
if ( !editor.user_revisions || editor.user_revisions.length == 0 )
|
|
return;
|
|
editor.user_revisions.push( result.new_revision );
|
|
}
|
|
|
|
Wiki.prototype.search = function ( event ) {
|
|
this.clear_messages();
|
|
this.clear_pulldowns();
|
|
|
|
var self = this;
|
|
this.invoker.invoke( "/notebooks/search", "GET", {
|
|
"notebook_id": this.notebook_id
|
|
},
|
|
function( result ) { self.display_search_results( result ); },
|
|
"search_form"
|
|
);
|
|
|
|
event.stop();
|
|
}
|
|
|
|
Wiki.prototype.display_search_results = function ( result ) {
|
|
// if there are no search results, indicate that and bail
|
|
if ( !result || result.notes.length == 0 ) {
|
|
this.display_message( "No matching notes.", undefined, getElement( "notes_top" ) );
|
|
return;
|
|
}
|
|
|
|
// otherwise, there are multiple search results, so create a "magic" search results note. but
|
|
// first close any open search results notes
|
|
if ( this.search_results_editor )
|
|
this.search_results_editor.shutdown();
|
|
|
|
var list = createDOM( "span", {} );
|
|
for ( var i in result.notes ) {
|
|
var note = result.notes[ i ]
|
|
if ( !note.title ) continue;
|
|
|
|
if ( note.contents.length == 0 ) {
|
|
var summary = "empty note";
|
|
} else {
|
|
var summary = note.summary;
|
|
|
|
// if the summary appears not to end with a complete sentence, add "..."
|
|
if ( !/[?!.]\s*$/.test( summary ) )
|
|
summary = summary + " <b>...</b>";
|
|
}
|
|
|
|
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" ),
|
|
summary_span
|
|
)
|
|
);
|
|
}
|
|
|
|
this.search_results_editor = this.create_editor( "search_results", "<h3>search results</h3>" + list.innerHTML, undefined, undefined, undefined, false, true, true, getElement( "notes_top" ) );
|
|
}
|
|
|
|
Wiki.prototype.display_all_notes_list = function ( result ) {
|
|
this.clear_messages();
|
|
this.clear_pulldowns();
|
|
|
|
if ( this.display_empty_message( true ) == true )
|
|
return;
|
|
|
|
if ( this.all_notes_editor )
|
|
this.all_notes_editor.shutdown();
|
|
|
|
// build up a list of all notes in this notebook, one link per note
|
|
var list = createDOM( "ul", { "id": "notes_list" } );
|
|
if ( this.focused_editor )
|
|
appendChildNodes( list, this.create_all_notes_link( this.focused_editor.id, this.focused_editor.title || "untitled note" ) );
|
|
|
|
for ( var i in result.notes ) {
|
|
var note_tuple = result.notes[ i ]
|
|
var note_id = note_tuple[ 0 ];
|
|
var note_title = note_tuple[ 1 ];
|
|
if ( this.focused_editor && note_id == this.focused_editor.id )
|
|
continue;
|
|
if ( !note_title )
|
|
note_title = "untitled note";
|
|
|
|
appendChildNodes( list, this.create_all_notes_link( note_id, note_title ) );
|
|
}
|
|
var list_holder = createDOM( "div", {}, list );
|
|
|
|
this.all_notes_editor = this.create_editor( "all_notes", "<h3>all notes</h3>" + list_holder.innerHTML, undefined, undefined, undefined, false, true, true, getElement( "notes_top" ) );
|
|
}
|
|
|
|
Wiki.prototype.share_notebook = function () {
|
|
this.clear_pulldowns();
|
|
|
|
var share_notebook_frame = getElement( "note_share_notebook" );
|
|
if ( share_notebook_frame )
|
|
share_notebook_frame.editor.shutdown();
|
|
|
|
var collaborators_link = createDOM( "a",
|
|
{ "href": "#", "id": "collaborators_link", "class": "radio_link", "title": "Collaborators may view and edit this notebook." },
|
|
"collaborators"
|
|
);
|
|
var viewers_link = createDOM( "a",
|
|
{ "href": "#", "id": "viewers_link", "class": "radio_link", "title": "Viewers may only view this notebook." },
|
|
"viewers"
|
|
);
|
|
var owners_link = createDOM( "a",
|
|
{ "href": "#", "id": "owners_link", "class": "radio_link", "title": "Owners may view, edit, rename, delete, and invite people to this notebook." },
|
|
"owners"
|
|
);
|
|
|
|
var collaborators_radio = createDOM( "input",
|
|
{ "type": "radio", "id": "collaborators_radio", "name": "access", "value": "collaborator", "checked": "true" }
|
|
);
|
|
var viewers_radio = createDOM( "input",
|
|
{ "type": "radio", "id": "viewers_radio", "name": "access", "value": "viewer" }
|
|
);
|
|
var owners_radio = createDOM( "input",
|
|
{ "type": "radio", "id": "owners_radio", "name": "access", "value": "owner" }
|
|
)
|
|
|
|
if ( this.rate_plan.notebook_collaboration ) {
|
|
var access_area = createDOM( "p", { "id": "access_choices" },
|
|
createDOM( "p", {}, "Invite these people as:" ),
|
|
createDOM( "table" , { "id": "access_table" },
|
|
createDOM( "tr", {},
|
|
createDOM( "td", {}, collaborators_radio, collaborators_link ),
|
|
createDOM( "td", {}, viewers_radio, viewers_link ),
|
|
createDOM( "td", {}, owners_radio, owners_link )
|
|
)
|
|
)
|
|
);
|
|
} else {
|
|
var access_area = createDOM( "p", {},
|
|
createDOM( "b", {}, "Note: " ),
|
|
"These people will only be able to ", createDOM( "i", "view" ), " your notebook. ",
|
|
"If you'd like them to be able to ", createDOM( "i", "edit" ),
|
|
" your notebook as well, please ",
|
|
createDOM( "a", { "href": "/upgrade", "target": "_new" }, "upgrade" ),
|
|
" your account.",
|
|
createDOM( "input", { "type": "hidden", "name": "access", "value": "viewer" } )
|
|
);
|
|
}
|
|
|
|
var invite_area = createDOM( "p", { "id": "invite_area" } );
|
|
this.display_invites( invite_area );
|
|
|
|
var div = createDOM( "div", {},
|
|
createDOM( "form", { "id": "invite_form" },
|
|
createDOM( "input", { "type": "hidden", "name": "notebook_id", "value": this.notebook_id } ),
|
|
createDOM( "p", {},
|
|
createDOM( "b", {}, "people to invite" ),
|
|
createDOM( "br", {} ),
|
|
createDOM( "textarea",
|
|
{ "name": "email_addresses", "class": "textarea_field", "cols": "40", "rows": "4", "wrap": "off" }
|
|
)
|
|
),
|
|
createDOM( "p", {}, "Please separate email addresses with commas, spaces, or the enter key." ),
|
|
access_area,
|
|
createDOM( "p", {},
|
|
createDOM( "input",
|
|
{ "type": "submit", "name": "invite_button", "id": "invite_button", "class": "button", "value": "send invites" }
|
|
)
|
|
),
|
|
invite_area
|
|
),
|
|
createDOM( "div", {},
|
|
createDOM(
|
|
"a", { "href": "/notebooks/" + this.notebook_id + "?preview=viewer", "target": "_new" },
|
|
"Preview this notebook as a viewer."
|
|
)
|
|
),
|
|
this.rate_plan.notebook_collaboration ? createDOM( "div", {},
|
|
createDOM(
|
|
"a", { "href": "/notebooks/" + this.notebook_id + "?preview=collaborator", "target": "_new" },
|
|
"Preview this notebook as a collaborator."
|
|
)
|
|
) : null
|
|
);
|
|
|
|
this.create_editor( "share_notebook", "<h3>share this notebook</h3>" + div.innerHTML, undefined, undefined, undefined, false, true, true, getElement( "notes_top" ) );
|
|
}
|
|
|
|
Wiki.prototype.display_invites = function ( invite_area ) {
|
|
if ( !this.invites || this.invites.length == 0 )
|
|
return;
|
|
|
|
var collaborators = createDOM( "div", { "id": "collaborators" } );
|
|
var viewers = createDOM( "div", { "id": "viewers" } );
|
|
var owners = createDOM( "div", { "id": "owners" } );
|
|
var self = this;
|
|
|
|
var addresses = new Array();
|
|
|
|
for ( var i in this.invites ) {
|
|
var invite = this.invites[ i ];
|
|
|
|
// if there are multiple invites for a given email address, only display those that are
|
|
// redeemed
|
|
if ( addresses[ invite.email_address ] == true && !invite.redeemed_user_id )
|
|
continue;
|
|
|
|
var revoke_button = createDOM( "input", {
|
|
"type": "button",
|
|
"id": "revoke_" + invite.object_id,
|
|
"class": "revoke_button",
|
|
"value": " x ",
|
|
"title": "revoke this person's notebook access"
|
|
} );
|
|
|
|
var add_invite_to = null;
|
|
if ( invite.owner ) {
|
|
add_invite_to = owners;
|
|
} else {
|
|
if ( invite.read_write )
|
|
add_invite_to = collaborators;
|
|
else
|
|
add_invite_to = viewers;
|
|
}
|
|
|
|
appendChildNodes(
|
|
add_invite_to, createDOM( "div", { "class": "invite" },
|
|
invite.email_address, " ",
|
|
( invite.redeemed_username ? "(" + invite.redeemed_username + ")" : "" ),
|
|
" ", revoke_button )
|
|
);
|
|
|
|
addresses[ invite.email_address ] = true;
|
|
}
|
|
|
|
var div = createDOM( "div" );
|
|
|
|
if ( collaborators.childNodes.length > 0 ) {
|
|
var p = createDOM( "p" );
|
|
appendChildNodes( p, createDOM( "b", {}, "collaborators" ) );
|
|
appendChildNodes( p, collaborators );
|
|
appendChildNodes( div, p );
|
|
}
|
|
if ( viewers.childNodes.length > 0 ) {
|
|
var p = createDOM( "p" );
|
|
appendChildNodes( p, createDOM( "b", {}, "viewers" ) );
|
|
appendChildNodes( p, viewers );
|
|
appendChildNodes( div, p );
|
|
}
|
|
if ( owners.childNodes.length > 0 ) {
|
|
var p = createDOM( "p" );
|
|
appendChildNodes( p, createDOM( "b", {}, "owners" ) );
|
|
appendChildNodes( p, owners );
|
|
appendChildNodes( div, p );
|
|
}
|
|
|
|
replaceChildNodes( invite_area, div );
|
|
}
|
|
|
|
Wiki.prototype.display_message = function ( text, nodes, position_after ) {
|
|
this.clear_messages();
|
|
this.clear_pulldowns();
|
|
|
|
var inner_div = DIV( { "class": "message_inner" }, text + " " );
|
|
for ( var i in nodes )
|
|
appendChildNodes( inner_div, nodes[ i ] );
|
|
|
|
var ok_button = createDOM( "input", {
|
|
"type": "button",
|
|
"class": "message_button",
|
|
"value": "ok",
|
|
"title": "dismiss this message"
|
|
} );
|
|
appendChildNodes( inner_div, ok_button );
|
|
connect( ok_button, "onclick", this.clear_messages );
|
|
|
|
var div = DIV( { "class": "message" }, inner_div );
|
|
div.nodes = nodes;
|
|
|
|
if ( position_after )
|
|
insertSiblingNodesAfter( position_after, div )
|
|
else if ( this.focused_editor )
|
|
insertSiblingNodesAfter( this.focused_editor.iframe, div )
|
|
else
|
|
appendChildNodes( "notes", div );
|
|
|
|
return div;
|
|
}
|
|
|
|
Wiki.prototype.display_error = function ( text, nodes, position_after ) {
|
|
this.clear_messages();
|
|
this.clear_pulldowns();
|
|
|
|
// remove all empty editors, some of which might exist due to a problem reaching the server
|
|
var iframes = getElementsByTagAndClassName( "iframe", "note_frame" );
|
|
for ( var i in iframes ) {
|
|
var editor = iframes[ i ].editor;
|
|
if ( editor.empty() )
|
|
editor.shutdown();
|
|
}
|
|
|
|
var inner_div = DIV( { "class": "error_inner" }, text + " " );
|
|
for ( var i in nodes )
|
|
appendChildNodes( inner_div, nodes[ i ] );
|
|
|
|
ok_button = createDOM( "input", {
|
|
"type": "button",
|
|
"class": "message_button",
|
|
"value": "ok",
|
|
"title": "dismiss this message"
|
|
} );
|
|
appendChildNodes( inner_div, ok_button );
|
|
connect( ok_button, "onclick", this.clear_messages );
|
|
|
|
var div = DIV( { "class": "error" }, inner_div );
|
|
div.nodes = nodes;
|
|
|
|
if ( position_after )
|
|
insertSiblingNodesAfter( position_after, div )
|
|
else if ( this.focused_editor )
|
|
insertSiblingNodesAfter( this.focused_editor.iframe, div )
|
|
else
|
|
appendChildNodes( "notes", div );
|
|
|
|
return div;
|
|
}
|
|
|
|
Wiki.prototype.clear_messages = function () {
|
|
var results = getElementsByTagAndClassName( "div", "message" );
|
|
|
|
for ( var i in results ) {
|
|
var result = results[ i ];
|
|
blindUp( result, options = { "duration": 0.5, afterFinish: function () {
|
|
try {
|
|
for ( var j in result.nodes )
|
|
disconnectAll( result.nodes[ j ] );
|
|
removeElement( result );
|
|
} catch ( e ) { }
|
|
} } );
|
|
}
|
|
|
|
var results = getElementsByTagAndClassName( "div", "error" );
|
|
|
|
for ( var i in results ) {
|
|
var result = results[ i ];
|
|
blindUp( result, options = { "duration": 0.5, afterFinish: function () {
|
|
try {
|
|
removeElement( result );
|
|
} catch ( e ) { }
|
|
} } );
|
|
}
|
|
}
|
|
|
|
Wiki.prototype.clear_pulldowns = function () {
|
|
var results = getElementsByTagAndClassName( "div", "pulldown" );
|
|
|
|
for ( var i in results ) {
|
|
var result = results[ i ];
|
|
|
|
// close the pulldown if it's been open at least a quarter second
|
|
if ( new Date() - result.pulldown.init_time >= 250 )
|
|
result.pulldown.shutdown();
|
|
}
|
|
}
|
|
|
|
Wiki.prototype.delete_all_editors = function ( event ) {
|
|
this.clear_messages();
|
|
this.clear_pulldowns();
|
|
|
|
this.startup_notes = new Array();
|
|
|
|
if ( this.notebook.read_write ) {
|
|
var self = this;
|
|
this.invoker.invoke( "/notebooks/delete_all_notes", "POST", {
|
|
"notebook_id": this.notebook_id
|
|
}, function ( result ) { self.display_storage_usage( result.storage_bytes ); } );
|
|
}
|
|
|
|
this.focused_editor = null;
|
|
|
|
var iframes = getElementsByTagAndClassName( "iframe", "note_frame" );
|
|
for ( var i in iframes ) {
|
|
var editor = iframes[ i ].editor;
|
|
editor.shutdown();
|
|
}
|
|
|
|
this.zero_total_notes_count();
|
|
this.display_empty_message( true );
|
|
|
|
event.stop();
|
|
}
|
|
|
|
Wiki.prototype.display_empty_message = function ( replace_messages ) {
|
|
var iframes = getElementsByTagAndClassName( "iframe", "note_frame" );
|
|
|
|
if ( !replace_messages ) {
|
|
// if there are any messages already open, bail
|
|
var messages = getElementsByTagAndClassName( "div", "message" );
|
|
if ( messages.length > 0 ) return false;
|
|
|
|
// if there are any errors open, bail
|
|
var errors = getElementsByTagAndClassName( "div", "error" );
|
|
if ( errors.length > 0 ) return false;
|
|
}
|
|
|
|
// if there are any open editors, bail
|
|
for ( var i in iframes ) {
|
|
var iframe = iframes[ i ];
|
|
if ( iframe.editor.closed == false )
|
|
return false;
|
|
}
|
|
|
|
if ( !this.total_notes_count ) {
|
|
if ( this.parent_id )
|
|
this.display_message( "There are no notes in the trash." )
|
|
else
|
|
this.display_message( "This notebook is empty." );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
DATE_PATTERN = /(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d).(\d+)[+-](\d\d:?\d\d)/;
|
|
|
|
Wiki.prototype.brief_revision = function ( revision ) {
|
|
var matches = DATE_PATTERN.exec( revision );
|
|
|
|
return new Date( Date.UTC(
|
|
matches[ 1 ], // year
|
|
matches[ 2 ] - 1, // month (zero-based)
|
|
matches[ 3 ], // day
|
|
matches[ 4 ], // hour
|
|
matches[ 5 ], // minute
|
|
matches[ 6 ], // second
|
|
matches[ 7 ] * 0.001 // milliseconds
|
|
) ).toLocaleString();
|
|
}
|
|
|
|
Wiki.prototype.increment_total_notes_count = function () {
|
|
if ( this.total_notes_count == null ) return;
|
|
this.total_notes_count += 1;
|
|
replaceChildNodes( "total_notes_count", this.total_notes_count );
|
|
}
|
|
|
|
Wiki.prototype.decrement_total_notes_count = function () {
|
|
if ( this.total_notes_count == null ) return;
|
|
this.total_notes_count -= 1;
|
|
replaceChildNodes( "total_notes_count", this.total_notes_count );
|
|
}
|
|
|
|
Wiki.prototype.zero_total_notes_count = function () {
|
|
if ( this.total_notes_count == null ) return;
|
|
this.total_notes_count = 0;
|
|
replaceChildNodes( "total_notes_count", this.total_notes_count );
|
|
}
|
|
|
|
Wiki.prototype.remove_all_notes_link = function ( note_id ) {
|
|
if ( !this.all_notes_editor ) return;
|
|
|
|
withDocument( this.all_notes_editor.document, function () {
|
|
var note_link = getElement( "note_link_" + note_id );
|
|
if ( note_link )
|
|
removeElement( note_link );
|
|
} );
|
|
|
|
this.all_notes_editor.resize();
|
|
}
|
|
|
|
Wiki.prototype.add_all_notes_link = function ( note_id, note_title ) {
|
|
if ( !this.all_notes_editor ) return;
|
|
if ( note_title == "all notes" || note_title == "search results" || note_title == "share this notebook" ) return;
|
|
|
|
if ( !note_title || note_title.length == 0 )
|
|
note_title = "untitled note";
|
|
|
|
var self = this;
|
|
withDocument( this.all_notes_editor.document, function () {
|
|
// if the note link already exists, update its title and bail
|
|
var note_link = getElement( "note_link_" + note_id );
|
|
if ( note_link ) {
|
|
replaceChildNodes( note_link.firstChild, note_title );
|
|
self.all_notes_editor.resize();
|
|
return;
|
|
}
|
|
|
|
var notes_list = getElement( "notes_list" );
|
|
if ( !notes_list ) return;
|
|
var first_note_link = notes_list.firstChild;
|
|
var new_note_link = self.create_all_notes_link( note_id, note_title );
|
|
|
|
if ( first_note_link )
|
|
insertSiblingNodesBefore( first_note_link, new_note_link );
|
|
else
|
|
appendChildNodes( notes_list, new_note_link );
|
|
} );
|
|
|
|
this.all_notes_editor.resize();
|
|
}
|
|
|
|
Wiki.prototype.create_all_notes_link = function ( note_id, note_title ) {
|
|
return createDOM( "li", { "id": "note_link_" + note_id },
|
|
createDOM( "a", { "href": "/notebooks/" + this.notebook_id + "?note_id=" + note_id }, note_title )
|
|
);
|
|
}
|
|
|
|
Wiki.prototype.start_notebook_rename = function () {
|
|
this.clear_pulldowns();
|
|
|
|
// if a renaming is already in progress, end the renaming instead of starting one
|
|
var notebook_name_field = getElement( "notebook_name_field" );
|
|
if ( notebook_name_field ) {
|
|
this.end_notebook_rename();
|
|
return;
|
|
}
|
|
|
|
var div = createDOM( "div" );
|
|
div.innerHTML = this.notebook.name;
|
|
var notebook_name = scrapeText( div );
|
|
|
|
notebook_name_field = createDOM(
|
|
"input", {
|
|
"type": "text",
|
|
"value": notebook_name,
|
|
"id": "notebook_name_field",
|
|
"name": "notebook_name_field",
|
|
"size": "30",
|
|
"maxlength": "100",
|
|
"class": "text_field"
|
|
}
|
|
);
|
|
|
|
var ok_button = createDOM(
|
|
"input", {
|
|
"type": "button",
|
|
"class": "message_button",
|
|
"value": "ok",
|
|
"title": "dismiss this message"
|
|
}
|
|
);
|
|
|
|
var rename_form = createDOM(
|
|
"form", { "id": "rename_form" }, notebook_name_field, ok_button
|
|
);
|
|
|
|
replaceChildNodes( "notebook_header_area", rename_form );
|
|
|
|
var self = this;
|
|
connect( rename_form, "onsubmit", function ( event ) {
|
|
self.end_notebook_rename();
|
|
event.stop();
|
|
} );
|
|
connect( ok_button, "onclick", function ( event ) {
|
|
self.end_notebook_rename();
|
|
event.stop();
|
|
} );
|
|
|
|
notebook_name_field.focus();
|
|
notebook_name_field.select();
|
|
}
|
|
|
|
Wiki.prototype.end_notebook_rename = function () {
|
|
var new_notebook_name = getElement( "notebook_name_field" ).value;
|
|
|
|
// if the new name is blank or reserved, don't actually rename the notebook
|
|
if ( /^\s*$/.test( new_notebook_name ) )
|
|
new_notebook_name = this.notebook.name;
|
|
|
|
if ( /^\s*Luminotes/.test( new_notebook_name ) || /^\s*trash\s*$/.test( new_notebook_name ) ) {
|
|
new_notebook_name = this.notebook.name;
|
|
this.display_error( "That notebook name is not available. Please try a different one." );
|
|
}
|
|
|
|
// rename the notebook in the header
|
|
var notebook_header_name = createDOM(
|
|
"span",
|
|
{ "id": "notebook_header_name" },
|
|
createDOM( "strong", {}, new_notebook_name )
|
|
);
|
|
replaceChildNodes( "notebook_header_area", notebook_header_name );
|
|
|
|
var self = this;
|
|
connect( notebook_header_name, "onclick", function ( event ) {
|
|
self.start_notebook_rename();
|
|
event.stop();
|
|
} );
|
|
|
|
// rename the notebook link on the right side of the page
|
|
replaceChildNodes(
|
|
"notebook_" + this.notebook.object_id,
|
|
document.createTextNode( new_notebook_name )
|
|
);
|
|
|
|
// if the name has changed, then send the new name to the server
|
|
if ( new_notebook_name == this.notebook.name )
|
|
return;
|
|
|
|
this.notebook.name = new_notebook_name;
|
|
this.invoker.invoke( "/notebooks/rename", "POST", {
|
|
"notebook_id": this.notebook_id,
|
|
"name": new_notebook_name
|
|
} );
|
|
}
|
|
|
|
Wiki.prototype.delete_notebook = function () {
|
|
if ( this.focused_editor )
|
|
this.save_editor( this.focused_editor, true );
|
|
|
|
this.invoker.invoke( "/notebooks/delete", "POST", {
|
|
"notebook_id": this.notebook_id
|
|
} );
|
|
}
|
|
|
|
Wiki.prototype.delete_notebook_forever = function ( event, notebook_id ) {
|
|
var deleted_notebook_node = getElement( "deleted_notebook_" + notebook_id );
|
|
if ( !deleted_notebook_node ) return;
|
|
|
|
for ( var i in deleted_notebook_node.childNodes ) {
|
|
var child = deleted_notebook_node.childNodes[ i ];
|
|
disconnectAll( child );
|
|
}
|
|
|
|
removeElement( deleted_notebook_node );
|
|
|
|
var items = getElementsByTagAndClassName( "div", "deleted_notebook_item" );
|
|
if ( items.length == 0 )
|
|
removeElement( "deleted_notebooks" );
|
|
|
|
var self = this;
|
|
this.invoker.invoke( "/notebooks/delete_forever", "POST", {
|
|
"notebook_id": notebook_id
|
|
}, function ( result ) { self.display_storage_usage( result.storage_bytes ); } );
|
|
|
|
event.stop();
|
|
}
|
|
|
|
Wiki.prototype.toggle_editor_changes = function ( event, editor ) {
|
|
// if the pulldown is already open, then just close it
|
|
var pulldown_id = "changes_" + editor.id;
|
|
var existing_div = getElement( pulldown_id );
|
|
if ( existing_div ) {
|
|
existing_div.pulldown.shutdown();
|
|
return;
|
|
}
|
|
|
|
event.stop();
|
|
|
|
// if there's already a cached revision list, or the editor doesn't have a revision yet, then
|
|
// display the changes pulldown and bail
|
|
if ( ( editor.user_revisions && editor.user_revisions.length > 0 ) || !editor.revision ) {
|
|
new Changes_pulldown( this, this.notebook_id, this.invoker, editor );
|
|
return;
|
|
}
|
|
|
|
// otherwise, load the revision list for this note from the server
|
|
var self = this;
|
|
this.invoker.invoke(
|
|
"/notebooks/load_note_revisions", "GET", {
|
|
"notebook_id": this.notebook_id,
|
|
"note_id": editor.id
|
|
},
|
|
function ( result ) {
|
|
editor.user_revisions = result.revisions;
|
|
new Changes_pulldown( self, self.notebook_id, self.invoker, editor );
|
|
}
|
|
);
|
|
}
|
|
|
|
Wiki.prototype.toggle_editor_options = function ( event, editor ) {
|
|
// if the pulldown is already open, then just close it
|
|
var pulldown_id = "options_" + editor.id;
|
|
var existing_div = getElement( pulldown_id );
|
|
if ( existing_div ) {
|
|
existing_div.pulldown.shutdown();
|
|
return;
|
|
}
|
|
|
|
new Options_pulldown( this, this.notebook_id, this.invoker, editor );
|
|
event.stop();
|
|
}
|
|
|
|
connect( window, "onload", function ( event ) { new Wiki( new Invoker() ); } );
|
|
|
|
|
|
function Pulldown( wiki, notebook_id, pulldown_id, anchor, relative_to ) {
|
|
this.wiki = wiki;
|
|
this.notebook_id = notebook_id;
|
|
this.div = createDOM( "div", { "id": pulldown_id, "class": "pulldown" } );
|
|
this.div.pulldown = this;
|
|
this.init_time = new Date();
|
|
this.anchor = anchor;
|
|
this.relative_to = relative_to;
|
|
|
|
addElementClass( this.div, "invisible" );
|
|
|
|
appendChildNodes( document.body, this.div );
|
|
var position = calculate_position( anchor, relative_to );
|
|
setElementPosition( this.div, position );
|
|
|
|
removeElementClass( this.div, "invisible" );
|
|
}
|
|
|
|
function calculate_position( anchor, relative_to ) {
|
|
// position the pulldown under the anchor
|
|
var position = getElementPosition( anchor );
|
|
|
|
if ( relative_to ) {
|
|
var relative_pos = getElementPosition( relative_to );
|
|
if ( relative_pos ) {
|
|
position.x += relative_pos.x;
|
|
position.y += relative_pos.y;
|
|
|
|
// Work around an IE "feature" in which an element within an iframe changes its absolute
|
|
// position based on how far the page is scrolled. The if is necessary to prevent this
|
|
// workaround from screwing positions up in sane browsers like Firefox.
|
|
if ( getStyle( "content", "position" ) == "absolute" )
|
|
position.y -= getElement( "html" ).scrollTop;
|
|
|
|
}
|
|
}
|
|
|
|
var anchor_dimensions = getElementDimensions( anchor );
|
|
|
|
// if the anchor has no height, move the position down a bit by an arbitrary amount
|
|
if ( anchor_dimensions.h == 0 )
|
|
position.y += 8;
|
|
else
|
|
position.y += anchor_dimensions.h + 4;
|
|
|
|
return position;
|
|
}
|
|
|
|
Pulldown.prototype.update_position = function () {
|
|
var position = calculate_position( this.anchor, this.relative_to );
|
|
setElementPosition( this.div, position );
|
|
}
|
|
|
|
Pulldown.prototype.shutdown = function () {
|
|
removeElement( this.div );
|
|
}
|
|
|
|
|
|
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;
|
|
this.startup_checkbox = createDOM( "input", { "type": "checkbox", "class": "pulldown_checkbox" } );
|
|
this.startup_toggle = createDOM( "a", { "href": "#", "class": "pulldown_link", "title": "Display this note whenever the notebook is loaded." },
|
|
"show on startup"
|
|
);
|
|
|
|
appendChildNodes( this.div, this.startup_checkbox );
|
|
appendChildNodes( this.div, this.startup_toggle );
|
|
this.startup_checkbox.checked = editor.startup;
|
|
|
|
var self = this;
|
|
connect( this.startup_checkbox, "onclick", function ( event ) { self.startup_clicked( event ); } );
|
|
connect( this.startup_toggle, "onclick", function ( event ) { self.startup_clicked( event ); event.stop(); } );
|
|
}
|
|
|
|
Options_pulldown.prototype = new function () { this.prototype = Pulldown.prototype; };
|
|
Options_pulldown.prototype.constructor = Options_pulldown;
|
|
|
|
Options_pulldown.prototype.startup_clicked = function ( event ) {
|
|
if ( event.target() != this.startup_checkbox )
|
|
this.startup_checkbox.checked = this.startup_checkbox.checked ? false : true;
|
|
this.editor.startup = this.startup_checkbox.checked;
|
|
|
|
// 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( 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();
|
|
|
|
if ( !editor.user_revisions || editor.user_revisions.length == 0 ) {
|
|
appendChildNodes( this.div, createDOM( "span", "This note has no previous changes." ) );
|
|
return;
|
|
}
|
|
|
|
// display list of revision timestamps in reverse chronological order
|
|
var user_revisions = clone( editor.user_revisions );
|
|
user_revisions.reverse();
|
|
|
|
var self = this;
|
|
for ( var i = 0; i < user_revisions.length - 1; ++i ) { // -1 to skip the oldest revision
|
|
var user_revision = user_revisions[ i ];
|
|
var short_revision = this.wiki.brief_revision( user_revision.revision );
|
|
var href = "/notebooks/" + this.notebook_id + "?" + queryString(
|
|
[ "note_id", "revision" ],
|
|
[ this.editor.id, user_revision.revision ]
|
|
);
|
|
|
|
var link = createDOM(
|
|
"a",
|
|
{ "href": href, "class": "pulldown_link" },
|
|
short_revision + ( user_revision.username ? " by " + user_revision.username : "" )
|
|
);
|
|
|
|
this.links.push( link );
|
|
link.revision = user_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 = new function () { this.prototype = Pulldown.prototype; };
|
|
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.", note_id, revision, null, this.editor.iframe );
|
|
event.stop();
|
|
}
|
|
|
|
Changes_pulldown.prototype.shutdown = function () {
|
|
Pulldown.prototype.shutdown.call( this );
|
|
|
|
for ( var i in this.links )
|
|
disconnectAll( this.links[ i ] );
|
|
}
|
|
|
|
|
|
function Link_pulldown( wiki, notebook_id, invoker, editor, link ) {
|
|
link.pulldown = this;
|
|
this.link = link;
|
|
|
|
Pulldown.call( this, wiki, notebook_id, "link_" + editor.id, link, editor.iframe );
|
|
|
|
this.invoker = invoker;
|
|
this.editor = editor;
|
|
this.title_field = createDOM( "input", { "class": "text_field", "size": "30", "maxlength": "256" } );
|
|
this.note_summary = createDOM( "span", {} );
|
|
this.previous_title = "";
|
|
|
|
var self = this;
|
|
connect( this.title_field, "onclick", function ( event ) { self.title_field_clicked( event ); } );
|
|
connect( this.title_field, "onfocus", function ( event ) { self.title_field_focused( event ); } );
|
|
connect( this.title_field, "onchange", function ( event ) { self.title_field_changed( event ); } );
|
|
connect( this.title_field, "onblur", function ( event ) { self.title_field_changed( event ); } );
|
|
connect( this.title_field, "onkeydown", function ( event ) { self.title_field_key_pressed( event ); } );
|
|
|
|
appendChildNodes( this.div, createDOM( "span", { "class": "field_label" }, "links to: " ) );
|
|
appendChildNodes( this.div, this.title_field );
|
|
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_summary, "web link" );
|
|
return;
|
|
}
|
|
|
|
var query = parse_query( link );
|
|
var title = link_title( link, query );
|
|
var id = query.note_id;
|
|
|
|
// if the note has no destination note id set, try loading the note from the server by title
|
|
if ( ( id == undefined || id == "new" || id == "null" ) && title.length > 0 ) {
|
|
if ( title == "all notes" ) {
|
|
this.title_field.value = title;
|
|
this.display_summary( title, "list of all notes in this notebook" );
|
|
return;
|
|
}
|
|
|
|
if ( title == "search results" ) {
|
|
this.title_field.value = title;
|
|
this.display_summary( title, "current search results" );
|
|
return;
|
|
}
|
|
|
|
if ( title == "share this notebook" ) {
|
|
this.title_field.value = title;
|
|
this.display_summary( title, "share this notebook with others" );
|
|
return;
|
|
}
|
|
|
|
this.invoker.invoke(
|
|
"/notebooks/load_note_by_title", "GET", {
|
|
"notebook_id": this.notebook_id,
|
|
"note_title": title,
|
|
"summarize": true
|
|
},
|
|
function ( result ) {
|
|
// if the user has already started typing something, don't overwrite it
|
|
if ( self.title_field.value.length != 0 )
|
|
return;
|
|
if ( result.note ) {
|
|
self.title_field.value = result.note.title;
|
|
self.display_summary( result.note.title, result.note.summary );
|
|
} else {
|
|
self.title_field.value = title;
|
|
replaceChildNodes( self.note_summary, "empty note" );
|
|
}
|
|
}
|
|
);
|
|
return;
|
|
}
|
|
|
|
// 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 summary of its contents
|
|
var iframe = getElement( "note_" + id );
|
|
if ( iframe ) {
|
|
this.title_field.value = iframe.editor.title;
|
|
this.display_summary( iframe.editor.title, iframe.editor.summarize() );
|
|
return;
|
|
}
|
|
|
|
// 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,
|
|
"summarize": true
|
|
},
|
|
function ( result ) {
|
|
// if the user has already started typing something, don't overwrite it
|
|
if ( self.title_field.value.length != 0 )
|
|
return;
|
|
if ( result.note ) {
|
|
self.title_field.value = result.note.title;
|
|
self.display_summary( result.note.title, result.note.summary );
|
|
} else {
|
|
self.title_field.value = title;
|
|
replaceChildNodes( self.note_summary, "empty note" );
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
Link_pulldown.prototype = new function () { this.prototype = Pulldown.prototype; };
|
|
Link_pulldown.prototype.constructor = Link_pulldown;
|
|
|
|
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 ) {
|
|
event.stop();
|
|
}
|
|
|
|
Link_pulldown.prototype.title_field_focused = function ( event ) {
|
|
this.title_field.select();
|
|
}
|
|
|
|
Link_pulldown.prototype.title_field_changed = function ( event ) {
|
|
// if the title is actually unchanged, then bail
|
|
if ( this.title_field.value == this.previous_title )
|
|
return;
|
|
|
|
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 ( summary ) {
|
|
self.display_summary( title, summary );
|
|
} );
|
|
}
|
|
|
|
Link_pulldown.prototype.title_field_key_pressed = function ( event ) {
|
|
// if enter is pressed, consider the title field altered. this is necessary because IE neglects
|
|
// to issue an onchange event when enter is pressed in an input field
|
|
if ( event.key().code == 13 ) {
|
|
this.title_field_changed();
|
|
event.stop();
|
|
}
|
|
}
|
|
|
|
Link_pulldown.prototype.update_position = function ( anchor, relative_to ) {
|
|
Pulldown.prototype.update_position.call( this, anchor, relative_to );
|
|
}
|
|
|
|
Link_pulldown.prototype.shutdown = function () {
|
|
Pulldown.prototype.shutdown.call( this );
|
|
|
|
disconnectAll( this.title_field );
|
|
if ( this.link )
|
|
this.link.pulldown = null;
|
|
}
|