IMAGE_DIR = "/static/images/";
NOTEBOOK_READ_ONLY = 0;
NOTEBOOK_READ_WRITE = 1;
NOTEBOOK_READ_WRITE_FOR_OWN_NOTES = 2;
function Wiki( invoker ) {
this.next_id = null;
this.focused_editor = null;
this.notebook = evalJSON( getElement( "notebook" ).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: lowercase note title to editor
this.search_results_editor = null; // editor for display of search results
this.invoker = invoker;
this.user = evalJSON( getElement( "user" ).value );
this.rate_plan = evalJSON( getElement( "rate_plan" ).value );
this.yearly = evalJSON( getElement( "yearly" ).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;
this.signup_plan = getElement( "signup_plan" ).value;
this.groups = evalJSON( getElement( "groups" ).value );
this.font_size = null;
this.small_toolbar = false;
this.large_toolbar_bottom = 0;
this.autosaver = null;
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;
if ( getElement( "note_tree_root_table" ) )
this.note_tree = new Note_tree( this, this.notebook.object_id, this.invoker );
this.recent_notes = new Recent_notes( this, this.notebook.object_id, this.invoker );
this.notebooks = evalJSON( getElement( "notebooks" ).value );
this.notebooks_by_id = {};
for ( var i in this.notebooks ) {
var notebook = this.notebooks[ i ];
if ( !this.notebooks_by_id[ notebook.object_id ] )
this.notebooks_by_id[ notebook.object_id ] = notebook;
}
if ( this.notebook && this.notebook.read_write != NOTEBOOK_READ_ONLY ) {
var unsupported_agent = null;
var beta_agent = null;
if ( OPERA )
unsupported_agent = "Opera";
else if ( MSIE6 )
unsupported_agent = "Microsoft Internet Explorer version 6";
if ( unsupported_agent )
this.display_message( "Luminotes does not currently support the " + unsupported_agent + " web browser for editing. If possible, please use a recent version of Firefox, Internet Explorer, Chrome, or Safari instead. Sorry for the inconvenience." );
else if ( beta_agent )
this.display_message( "Luminotes support for your web browser (" + beta_agent + ") is currently in beta. If you encounter any problems, please contact support so that they can be fixed!" );
}
if ( !this.notebook_has_tag( this.notebook, "forum" ) )
this.autosaver = Autosaver( this );
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( this.user.storage_bytes || "0" );
connect( this.invoker, "error_message", this, "display_error" );
connect( this.invoker, "message", this, "display_message" );
connect( "search_form", "onsubmit", this, "search" );
connect( "search_text", "onfocus", this, "search_focused" );
connect( "search_text", "onblur", this, "search_blurred" );
connect( "search_text", "onkeyup", this, "search_key_released" );
connect( "html", "onclick", this, "background_clicked" );
connect( "html", "onkeydown", this, "key_pressed" );
connect( window, "onresize", this, "resize_editor" );
connect( window, "onresize", this, "resize_toolbar" );
connect( document, "onmouseover", this, "detect_font_resize" );
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 top_window = window;
var logout_link = getElement( "logout_link" );
if ( logout_link ) {
connect( "logout_link", "onclick", function ( event ) {
self.save_editor( null, false, function () {
top_window.location = "/users/logout";
} );
event.stop();
} );
}
var rename = evalJSON( getElement( "rename" ).value );
if ( rename && this.notebook.read_write == 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 == 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" );
connect( undo_button, "onclick", function ( event ) { self.undelete_notebook( event, deleted_id ); } );
}
var current_notebook_up = getElement( "current_notebook_up" );
if ( current_notebook_up ) {
connect( current_notebook_up, "onmouseover", function ( event ) { current_notebook_up.src = IMAGE_DIR + "up_arrow_hover.png"; } );
connect( current_notebook_up, "onmouseout", function ( event ) { current_notebook_up.src = IMAGE_DIR + "up_arrow.png"; } );
connect( current_notebook_up, "onclick", function ( event ) {
current_notebook_up.src = IMAGE_DIR + "up_arrow.png";
self.move_current_notebook_up( event );
} );
}
var current_notebook_down = getElement( "current_notebook_down" );
if ( current_notebook_down ) {
connect( current_notebook_down, "onmouseover", function ( event ) { current_notebook_down.src = IMAGE_DIR + "down_arrow_hover.png"; } );
connect( current_notebook_down, "onmouseout", function ( event ) { current_notebook_down.src = IMAGE_DIR + "down_arrow.png"; } );
connect( current_notebook_down, "onclick", function ( event ) {
current_notebook_down.src = IMAGE_DIR + "down_arrow.png";
self.move_current_notebook_down( event );
} );
}
this.resize_toolbar();
}
Wiki.prototype.update_next_id = function ( result ) {
this.next_id = result.next_id;
}
var KILOBYTE = 1024;
var MEGABYTE = 1024 * KILOBYTE;
function bytes_to_megabytes( bytes, choose_units ) {
if ( choose_units ) {
if ( bytes < KILOBYTE )
return bytes + " bytes";
if ( bytes < MEGABYTE )
return Math.round( bytes / KILOBYTE ) + " KB";
}
return Math.round( bytes / MEGABYTE ) + " MB";
}
Wiki.prototype.display_storage_usage = function( storage_bytes ) {
if ( !storage_bytes )
return;
// display the user's current storage usage
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 or files, 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;
}
var storage_usage_area = getElement( "storage_usage_area" );
if ( !storage_usage_area )
return;
replaceChildNodes(
storage_usage_area,
createDOM( "div", { "class": storage_usage_class },
bytes_to_megabytes( storage_bytes ) + " (" + usage_percent + "%) of " + bytes_to_megabytes( quota_bytes ) )
);
}
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 static_note = getElement( "static_note_" + startup_note.object_id );
if ( !static_note ) continue;
var static_contents = getFirstElementByTagAndClassName( "span", "static_note_contents", static_note );
if ( !static_contents ) continue;
var contents_text = static_contents.innerHTML;
var editor = this.create_editor(
startup_note.object_id,
// grab this note's contents from the static notes area
contents_text,
startup_note.deleted_from_id,
startup_note.revision,
startup_note.creation,
this.notebook.read_write, false, focus, null,
startup_note.user_id,
startup_note.username
);
if ( startup_note.title )
this.open_editors[ startup_note.title.toLowerCase() ] = 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 ];
if ( !note_read_write )
var read_write = NOTEBOOK_READ_ONLY;
else
var read_write = this.notebook.read_write;
var static_note = getElement( "static_note_" + note.object_id );
if ( !static_note ) continue;
var static_contents = getFirstElementByTagAndClassName( "span", "static_note_contents", static_note );
if ( !static_contents ) continue;
var contents_text = static_contents.innerHTML;
this.create_editor(
note.object_id,
contents_text,
note.deleted_from_id,
note.revision,
note.creation,
read_write, false, focus, null,
note.user_id,
note.username
);
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 != NOTEBOOK_READ_ONLY ) {
connect( window, "onunload", function ( event ) { self.editor_focused( null, true ); } );
connect( "newNote", "onclick", this, "create_blank_editor" );
connect( "createLink", "onclick", this, "toggle_link_button" );
if ( this.notebook.read_write == NOTEBOOK_READ_WRITE )
connect( "attachFile", "onclick", this, "toggle_attach_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( "strikethrough", "onclick", function ( event ) { self.toggle_button( event, "strikethrough" ); } );
connect( "font", "onclick", this, "toggle_font_button" );
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" );
this.make_image_button( "createLink", "link" );
if ( this.notebook.read_write == NOTEBOOK_READ_WRITE )
this.make_image_button( "attachFile", "attach" );
this.make_image_button( "bold" );
this.make_image_button( "italic" );
this.make_image_button( "underline" );
this.make_image_button( "strikethrough" );
this.make_image_button( "font" );
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 save_button = getElement( "save_button" );
if ( save_button ) {
connect( save_button, "onclick", function ( event ) {
self.save_editor( null );
} );
save_button.disabled = true;
}
var export_link = getElement( "export_link" );
if ( export_link ) {
connect( export_link, "onclick", function ( event ) {
self.save_editor( null );
self.export_clicked();
event.stop();
} );
}
var import_link = getElement( "import_link" );
if ( import_link ) {
connect( import_link, "onclick", function ( event ) {
self.import_clicked();
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, null, getElement( "notes_top" ) );
event.stop();
} );
}
var print_notebook_link = getElement( "print_notebook_link" );
if ( print_notebook_link ) {
connect( print_notebook_link, "onclick", function ( event ) {
self.editor_focused( null, true );
} );
}
var settings_link = getElement( "settings_link" );
if ( settings_link ) {
connect( settings_link, "onclick", function ( event ) {
self.load_editor( "account settings", "null", null, null, null, getElement( "notes_top" ) );
event.stop();
} );
}
var declutter_link = getElement( "declutter_link" );
if ( declutter_link ) {
connect( declutter_link, "onclick", function ( event ) {
self.declutter_clicked();
event.stop();
} );
}
var new_notebook_button = getElement( "new_notebook" );
if ( new_notebook_button ) {
connect( new_notebook_button, "onclick", function ( event ) {
self.invoker.invoke( "/notebooks/create", "POST" );
event.stop();
} );
this.make_image_button( "new_notebook", "new_note", true );
}
var new_note_tree_link_button = getElement( "new_note_tree_link" );
if ( new_note_tree_link_button ) {
connect( new_note_tree_link_button, "onclick", function ( event ) {
self.clear_pulldowns();
if ( self.note_tree )
self.note_tree.start_link_add();
event.stop();
} );
this.make_image_button( "new_note_tree_link", "new_note", true );
}
}
Wiki.prototype.background_clicked = function ( event ) {
if ( !event.target().tagName )
return;
var tag_name = event.target().tagName.toLowerCase();
if ( tag_name == "input" || tag_name == "label" )
return;
this.clear_pulldowns();
}
Wiki.prototype.create_blank_editor = function ( event ) {
if ( event ) event.stop();
if ( this.notebook.read_write == NOTEBOOK_READ_WRITE_FOR_OWN_NOTES &&
( !this.user.username || this.user.username == "anonymous" ) ) {
this.display_message( 'Please login first. No account? Click "sign up" to get a free account.' );
return;
}
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 ( Editor.shared_iframe && Editor.shared_iframe.editor() && Editor.shared_iframe.editor().empty() ) {
Editor.shared_iframe.editor().highlight();
return;
}
var editor = this.create_editor( undefined, undefined, undefined, undefined, undefined, this.notebook.read_write, true, true, null, this.user.object_id, this.user.username );
this.increment_total_notes_count();
signal( this, "note_added", editor );
}
Wiki.prototype.load_editor = function ( note_title, note_id, revision, previous_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_field ) {
pulldown_title = strip( pulldown.title_field.value );
if ( pulldown_title )
note_title = pulldown_title;
// only use the pulldown title if it has changed from the note's original title
if ( pulldown_title == note_title )
pulldown_title = undefined;
}
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 ) {
var editor = editor_by_id( note_id, revision );
if ( editor ) {
editor.highlight();
if ( link )
link.href = "/notebooks/" + this.notebook.object_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" ) {
var lower_note_title = note_title.toLowerCase();
// if the note_title corresponds to a "magic" note's title, then dynamically highlight or create the note
if ( lower_note_title == "search results" ) {
var editor = this.open_editors[ lower_note_title ];
if ( editor ) {
editor.highlight();
return;
}
this.display_search_results();
return;
}
if ( lower_note_title == "share this notebook" ) {
var editor = this.open_editors[ lower_note_title ];
if ( editor ) {
editor.highlight();
return;
}
this.share_notebook();
return;
}
if ( lower_note_title == "account settings" ) {
var editor = this.open_editors[ lower_note_title ];
if ( editor ) {
editor.highlight();
return;
}
this.display_settings();
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[ lower_note_title ];
if ( editor ) {
editor.highlight();
if ( link )
link.href = "/notebooks/" + this.notebook.object_id + "?note_id=" + editor.id;
return;
}
}
// if the notebook is a forum, then instead of opening a new post, display an error message
if ( this.notebook_has_tag( this.notebook, "forum" ) ) {
this.display_message( "No such forum post! (A forum link must point to another post in this discussion or an external web page.)" );
return;
}
this.invoker.invoke(
"/notebooks/load_note_by_title", "GET", {
"notebook_id": this.notebook.object_id,
"note_title": note_title,
"revision": revision
},
function ( result ) { self.parse_loaded_editor( result, note_title, revision, link, position_after ); }
);
return;
}
// if the notebook is a forum, maintain displayed note order by opening an existing note on its own page
if ( this.notebook_has_tag( this.notebook, "forum" ) ) {
window.location = window.location.protocol + '//' + window.location.host + window.location.pathname + '?note_id=' + note_id;
return;
}
this.invoker.invoke(
"/notebooks/load_note", "GET", {
"notebook_id": this.notebook.object_id,
"note_id": note_id,
"revision": revision,
"previous_revision": previous_revision
},
function ( result ) { self.parse_loaded_editor( result, note_title, revision, link, position_after ); }
);
}
Wiki.prototype.resolve_link = function ( note_title, link, force, callback ) {
// if the title looks like a URL, then make it a link to an external site
if ( /^\w+:\/\//.test( note_title ) || /^mailto:/.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 == "search results" || note_title == "share this notebook" || note_title == "account settings" ) {
link.href = "/notebooks/" + this.notebook.object_id + "?" + queryString(
[ "title", "note_id" ],
[ note_title, "null" ]
);
if ( callback ) {
if ( note_title == "search results" )
callback( "current search results" );
else if ( note_title == "share this notebook" )
callback( "share this notebook with others" );
else
callback( "account settings" );
}
return;
}
var id = parse_query( link ).note_id;
// if the link already has a valid-looking id, it's already resolved, so bail
if ( !force && id != undefined && id != "new" && id != "null" ) {
if ( callback )
callback( 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.toLowerCase() ];
if ( editor ) {
if ( link )
link.href = "/notebooks/" + this.notebook.object_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.object_id,
"note_title": note_title,
"summarize": true
},
function ( result ) {
if ( result && result.note ) {
link.href = "/notebooks/" + self.notebook.object_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.object_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.object_id,
"note_title": note_title
},
function ( result ) {
if ( result && result.note_id ) {
link.href = "/notebooks/" + self.notebook.object_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.object_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;
var user_id = result.note.user_id;
var username = result.note.username;
} else {
// if the title looks like a URL, then make it a link to an external site
if ( /^\w+:\/\//.test( note_title ) || /^mailto:/.test( note_title ) ) {
link.target = "_new";
link.href = note_title;
window.open( link.href );
return;
}
var id = null;
var note_text = "
" + note_title;
var deleted_from_id = null;
var actual_revision = null;
var actual_creation = null;
var user_id = null;
var username = 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 self = this;
var editor = this.create_editor( id, note_text, deleted_from_id, actual_revision, actual_creation, read_write, true, true, position_after, user_id, username );
if ( !requested_revision )
connect( editor, "init_complete", function () { signal( self, "note_added", editor ); } );
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.object_id + "?note_id=" + id;
}
Wiki.prototype.create_editor = function ( id, note_text, deleted_from_id, revision, creation, read_write, highlight, focus, position_after, user_id, username ) {
var self = this;
var dirty = false;
var own_notes_only = false;
if ( read_write == NOTEBOOK_READ_ONLY )
read_write = false;
else if ( read_write == NOTEBOOK_READ_WRITE )
read_write = true;
else if ( read_write == NOTEBOOK_READ_WRITE_FOR_OWN_NOTES ) {
own_notes_only = true;
if ( user_id == this.user.object_id && this.user.username && this.user.username != "anonymous" )
read_write = true;
else
read_write = false;
}
if ( isUndefinedOrNull( id ) ) {
if ( this.notebook.read_write ) {
id = this.next_id;
dirty = true;
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 == NOTEBOOK_READ_WRITE && revision ) {
var short_revision = this.brief_revision( revision );
var note_id = id.split( ' ' )[ 0 ];
note_text = '
Previous revision from ' + short_revision + '
' +
'' + note_text;
}
if ( creation && note_text != "" ) {
var note_id = id.split( ' ' )[ 0 ];
note_text = note_text + this.make_byline( username, creation, note_id );
}
var startup = this.startup_notes[ id ];
var editor = new Editor( id, this.notebook.object_id, note_text, deleted_from_id, revision, read_write, startup, highlight, focus, position_after, dirty, own_notes_only );
if ( focus )
this.editor_focused( editor );
else if ( !read_write )
this.editor_focused( null );
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, "tools_clicked", function ( event ) { self.toggle_editor_tools( event, editor ) } );
connect( editor, "focused", this, "editor_focused" );
connect( editor, "mouse_hovered", function ( target ) { self.editor_mouse_hovered( editor, target ) } );
connect( editor, "grabber_pressed", function ( event ) { self.editor_focused( null, false, false ); } );
connect( editor, "moved", function ( editor, position_after, position_before ) {
self.editor_moved( editor, position_after, position_before );
} );
}
connect( editor, "load_editor", this, "load_editor" );
connect( editor, "hide_clicked", function ( event ) { self.hide_editor( event, editor ) } );
connect( editor, "submit_form", function ( form ) { self.submit_form( form ); } );
connect( editor, "button_clicked", function ( editor, button ) { self.editor_button_clicked( editor, button ); } );
this.clear_pulldowns();
return editor;
}
Wiki.prototype.resize_editor = function () {
if ( Editor.shared_iframe && Editor.shared_iframe.editor() )
Editor.shared_iframe.editor().resize();
}
Wiki.prototype.resize_toolbar = function () {
var last_toolbar_button = getElement( "insertOrderedList" );
var current_toolbar_bottom = getElementPosition( last_toolbar_button ).y + getElementDimensions( last_toolbar_button ).h;
var viewport_size = getViewportDimensions();
var VIEWPORT_WIDTH_THRESHOLD = 1000;
// if the toolbar is large and the bottom of the toolbar is outside of the viewport or the
// viewport is too narrow, then make the toolbar smaller
if ( !this.small_toolbar && ( current_toolbar_bottom > viewport_size.h || viewport_size.w < VIEWPORT_WIDTH_THRESHOLD ) ) {
this.large_toolbar_bottom = current_toolbar_bottom;
this.small_toolbar = true;
this.update_toolbar();
// otherwise, if the toolbar is small and making the toolbar large would still fit within the
// viewport and the viewport is wide, then make the toolbar large again
} else if ( this.small_toolbar && this.large_toolbar_bottom <= viewport_size.h && viewport_size.w >= VIEWPORT_WIDTH_THRESHOLD ) {
if ( !this.small_toolbar ) return; // it's already big, so bail
this.small_toolbar = false;
this.update_toolbar();
}
}
Wiki.prototype.detect_font_resize = function () {
if ( !window.getComputedStyle ) return;
var style = window.getComputedStyle( getElement( "content" ), null );
if ( !style ) return;
if ( style.fontSize == this.font_size )
return;
this.font_size = style.fontSize;
this.resize_editor();
}
Wiki.prototype.editor_state_changed = function ( editor, link_clicked ) {
this.update_toolbar();
if ( !link_clicked )
this.display_link_pulldown( editor );
if ( editor.dirty() ) {
var save_button = getElement( "save_button" );
if ( save_button && save_button.disabled ) {
save_button.disabled = false;
save_button.value = "save";
}
}
signal( this, "note_state_changed", editor );
}
Wiki.prototype.editor_title_changed = function ( editor, old_title, new_title ) {
if ( old_title )
delete this.open_editors[ old_title.toLowerCase() ];
if ( new_title != null && !editor.empty() ) {
if ( new_title )
this.open_editors[ new_title.toLowerCase() ] = editor;
signal( this, "note_renamed", editor, new_title );
}
}
Wiki.prototype.display_link_pulldown = function ( editor, link, ephemeral ) {
this.clear_messages();
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, bail
if ( !link ) {
this.clear_pulldowns();
return;
}
var pulldown = link.pulldown;
var query = parse_query( link );
var title = link_title( link, query );
// display a Suggest_pulldown once something is typed for the link title, as long as the link
// doesn't yet have a note_id
if ( !pulldown && title.length > 0 && query.note_id == "new" ) {
this.clear_pulldowns();
var self = this;
var suggest_pulldown = new Suggest_pulldown( this, this.notebook.object_id, this.invoker, link, editor.iframe, title, editor.document );
connect( suggest_pulldown, "suggestion_selected", function ( note ) {
self.update_link_with_suggestion( editor, link, note )
} );
return;
}
// if there is a link but it was just started, bail
if ( link == editor.link_started && !pulldown ) {
this.clear_pulldowns();
return;
}
if ( pulldown ) {
// if a Suggest_pulldown is open for the link, update the pulldown and bail
if ( pulldown.update_suggestions ) {
pulldown.update_suggestions( title );
return;
// otherwise, just update the pulldown's position
} else {
pulldown.update_position();
}
}
var link_contains_image = getElementsByTagAndClassName( "img", null, link );
// 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 || link_contains_image ) {
if ( !pulldown ) {
this.clear_pulldowns();
// display a different pulldown depending on whether the link is a note link or a file link
if ( link.target || !/\/files\//.test( link.href ) )
new Link_pulldown( this, this.notebook.object_id, this.invoker, editor, link, ephemeral );
else {
if ( /\/files\/new$/.test( link.href ) )
new Upload_pulldown( this, this.notebook.object_id, this.invoker, editor, link, null, ephemeral );
else
new File_link_pulldown( this, this.notebook.object_id, this.invoker, editor, link, ephemeral );
}
}
}
}
Wiki.prototype.update_link_with_suggestion = function ( editor, link, note ) {
link.innerHTML = note.title;
// manually position the text cursor at the end of the link title
editor.position_cursor_after( link );
link.href = "/notebooks/" + this.notebook.object_id + "?note_id=" + note.object_id;
link.pulldown.shutdown();
link.pulldown = null;
editor.focus();
editor.end_link();
this.display_link_pulldown( editor, link );
}
Wiki.prototype.editor_focused = function ( editor, synchronous, remove_empty, save_blurred ) {
if ( remove_empty == undefined )
remove_empty = true;
if ( save_blurred == undefined )
save_blurred = true;
if ( this.focused_editor && this.focused_editor != editor ) {
this.focused_editor.blur();
this.clear_pulldowns();
// if there is no focused editor anymore, release the iframe of the previously focused editor
if ( editor == null )
this.focused_editor.release_iframe();
// 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.empty() && remove_empty ) {
signal( this, "note_removed", this.focused_editor.id );
this.focused_editor.shutdown();
this.decrement_total_notes_count();
this.display_empty_message();
} else if ( save_blurred ) {
// when switching editors, save the one being left
this.save_editor( null, null, null, synchronous );
}
}
this.focused_editor = editor;
this.update_toolbar();
}
Wiki.prototype.editor_moved = function ( editor, position_after, position_before ) {
this.save_editor( editor, false, null, null, null, position_after, position_before );
if ( this.note_tree )
this.note_tree.move_link( editor, position_after, position_before );
// reset the revision for each open editor. this is because the server is updating the revisions
// on the server while reordering the ntoes. and we don't want to have a stale idea of what the
// current revision is for a given editor
var divs = getElementsByTagAndClassName( "div", "static_note_div" );
for ( var i in divs ) {
var editor = divs[ i ].editor;
editor.revision = null;
editor.user_revisions = new Array();
}
}
Wiki.prototype.make_byline = function ( username, creation, note_id ) {
if ( username == "anonymous" )
username = "admin";
if ( username )
var by = ' by ' + username;
else
var by = '';
if ( creation )
var timestamp = ' on ' + this.brief_revision( creation );
else
var timestamp = '';
return '
Posted' + by + timestamp +
' | permalink';
}
Wiki.prototype.remove_byline = function ( editor ) {
if ( editor.document && editor.read_write ) {
var byline = getFirstElementByTagAndClassName( "div", "byline", editor.document );
if ( byline ) {
removeElement( byline );
editor.resize();
}
}
}
Wiki.prototype.editor_mouse_hovered = function ( editor, target ) {
var pulldowns = getElementsByTagAndClassName( "div", "pulldown" );
// if the mouse is hovering over a link, and no pulldowns are open, open a link pulldown
if ( target.nodeName == "A" && pulldowns.length == 0 )
this.display_link_pulldown( editor, target, true );
// the mouse is hovering over something else, so clear all ephemeral pulldowns
else
this.clear_pulldowns( true );
}
Wiki.prototype.key_pressed = function ( event ) {
if ( this.notebook.read_write == NOTEBOOK_READ_ONLY )
return;
var code = event.key().code;
if ( event.modifier().ctrl ) {
// ctrl-m: make a new note
if ( code == 77 ) {
this.create_blank_editor( event );
// ctrl-slash: start a search
} else if ( code == 191 ) {
var search_text = getElement( "search_text" );
if ( search_text )
search_text.focus();
// ctrl-space: save the current editor
} else if ( code == 32 ) {
this.save_editor();
}
return;
}
// page up: previous note
if ( code == 33 ) {
event.stop();
this.focus_previous_editor();
// page down: next note
} else if ( code == 34 ) {
event.stop();
this.focus_next_editor();
}
}
Wiki.prototype.editor_key_pressed = function ( editor, event ) {
this.remove_byline( editor );
var code = event.key().code;
if ( event.modifier().ctrl ) {
// ctrl-backtick: message with frame HTML contents (for debugging)
if ( code == 192 || code == 96 ) {
this.display_message( 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-s: strikethrough
} else if ( code == 83 ) {
this.toggle_button( event, "strikethrough" );
// 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-m: make a new note
} else if ( code == 77 ) {
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 );
// ctrl-slash: start a search
} else if ( code == 191 ) {
var search_text = getElement( "search_text" );
if ( search_text )
search_text.focus();
// ctrl-space: save the current editor
} else if ( code == 32 ) {
this.save_editor();
}
// shift-tab: outdent
} else if ( event.modifier().shift && code == 9 ) {
// ignore shift-tab here if a Suggest_pulldown is open
var link = editor.find_link_at_cursor();
if ( link && link.pulldown && link.pulldown.visible && link.pulldown.visible() )
return;
editor.exec_command( "outdent" );
event.stop();
// tab: outdent
} else if ( code == 9 ) {
// ignore tab here if a Suggest_pulldown is open
var link = editor.find_link_at_cursor();
if ( link && link.pulldown && link.pulldown.visible && link.pulldown.visible() )
return;
editor.exec_command( "indent" );
event.stop();
// page up: previous note
} else if ( code == 33 ) {
event.stop();
this.focus_previous_editor();
// page down: next note
} else if ( code == 34 ) {
event.stop();
this.focus_next_editor();
// IE: hitting space while making a link shouldn't end the link
} else if ( code == 32 && editor.document.selection && editor.state_enabled( "a" ) ) {
var range = editor.document.selection.createRange();
range.text = " ";
event.stop();
// IE: hitting backspace while making a link shouldn't end the link
} else if ( code == 8 && editor.document.selection ) {
var range = editor.document.selection.createRange();
range.moveStart( "character", -1 );
if ( range.text != "" ) {
range.text = "";
event.stop();
}
}
}
Wiki.prototype.focus_previous_editor = function () {
if ( !this.focused_editor ) {
var div = getFirstElementByTagAndClassName( "div", "static_note_div" );
if ( !div || !div.editor ) return;
div.editor.highlight();
return;
}
var previous_editor = this.focused_editor.previous_editor();
if ( !previous_editor ) return;
this.editor_focused( null );
previous_editor.highlight();
}
Wiki.prototype.focus_next_editor = function () {
if ( !this.focused_editor ) {
var div = getFirstElementByTagAndClassName( "div", "static_note_div" );
if ( !div || !div.editor ) return;
div.editor.highlight();
return;
}
var next_editor = this.focused_editor.next_editor();
if ( !next_editor ) return;
this.editor_focused( null );
next_editor.highlight();
}
Wiki.prototype.get_toolbar_image_dir = function ( always_small ) {
var toolbar_image_dir = IMAGE_DIR + "toolbar/";
if ( always_small || this.small_toolbar )
toolbar_image_dir += "small/";
return toolbar_image_dir;
}
Wiki.prototype.resize_toolbar_button = function ( button ) {
var SMALL_BUTTON_SIZE = 20;
var LARGE_BUTTON_SIZE = 40;
var button_size = getElementDimensions( button );
if ( this.small_toolbar || button.always_small ) {
if ( button_size.w == SMALL_BUTTON_SIZE ) return false;
setElementDimensions( button, { "w": SMALL_BUTTON_SIZE, "h": SMALL_BUTTON_SIZE } );
} else {
if ( button_size.w == LARGE_BUTTON_SIZE ) return false;
setElementDimensions( button, { "w": LARGE_BUTTON_SIZE, "h": LARGE_BUTTON_SIZE } );
}
return true;
}
Wiki.prototype.make_image_button = function ( name, filename_prefix, always_small ) {
var button = getElement( name );
var toolbar_image_dir = this.get_toolbar_image_dir( always_small );
if ( !filename_prefix )
filename_prefix = name;
button.name = name;
button.filename_prefix = filename_prefix;
button.always_small = always_small;
this.resize_toolbar_button( button );
this.connect_image_button( button );
}
Wiki.prototype.connect_image_button = function ( button, filename_prefix ) {
var self = this;
connect( button, "onmouseover", function ( event ) {
var toolbar_image_dir = self.get_toolbar_image_dir( button.always_small );
if ( /_down/.test( button.src ) )
button.src = toolbar_image_dir + button.filename_prefix + "_button_down_hover.png";
else
button.src = toolbar_image_dir + button.filename_prefix + "_button_hover.png";
} );
connect( button, "onmouseout", function ( event ) {
var toolbar_image_dir = self.get_toolbar_image_dir( button.always_small );
if ( /_down/.test( button.src ) )
button.src = toolbar_image_dir + button.filename_prefix + "_button_down.png";
else
button.src = toolbar_image_dir + button.filename_prefix + "_button.png";
} );
if ( button.name == "newNote" || button.name == "new_notebook" || button.name == "new_note_tree_link" ) {
connect( button, "onmousedown", function ( event ) {
var toolbar_image_dir = self.get_toolbar_image_dir( button.always_small );
if ( /_hover/.test( button.src ) )
button.src = toolbar_image_dir + button.filename_prefix + "_button_down_hover.png";
else
button.src = toolbar_image_dir + button.filename_prefix + "_button_down.png";
} );
connect( button, "onmouseup", function ( event ) {
var toolbar_image_dir = self.get_toolbar_image_dir( button.always_small );
if ( /_hover/.test( button.src ) )
button.src = toolbar_image_dir + button.filename_prefix + "_button_hover.png";
else
button.src = toolbar_image_dir + button.filename_prefix + "_button.png";
} );
}
}
Wiki.prototype.down_image_button = function ( name ) {
var button = getElement( name );
if ( !button || !button.filename_preifx )
return;
var toolbar_image_dir = this.get_toolbar_image_dir( button.always_small );
if ( !this.resize_toolbar_button( button ) && /_down/.test( button.src ) )
return;
if ( /_hover/.test( button.src ) )
button.src = toolbar_image_dir + button.filename_prefix + "_button_down_hover.png";
else
button.src = toolbar_image_dir + button.filename_prefix + "_button_down.png";
}
Wiki.prototype.up_image_button = function ( name ) {
var button = getElement( name );
if ( !button || !button.filename_prefix )
return;
var toolbar_image_dir = this.get_toolbar_image_dir( button.always_small );
if ( !this.resize_toolbar_button( button ) && !/_down/.test( button.src ) )
return;
if ( /_hover/.test( button.src ) )
button.src = toolbar_image_dir + button.filename_prefix + "_button_hover.png";
else
button.src = toolbar_image_dir + button.filename_prefix + "_button.png";
}
Wiki.prototype.toggle_image_button = function ( name ) {
var button = getElement( name );
if ( !button || !button.filename_prefix )
return;
var toolbar_image_dir = this.get_toolbar_image_dir( button.always_small );
if ( /_down/.test( button.src ) ) {
if ( /_hover/.test( button.src ) )
button.src = toolbar_image_dir + button.filename_prefix + "_button_hover.png";
else
button.src = toolbar_image_dir + button.filename_prefix + "_button.png";
this.resize_toolbar_button( button );
return false;
} else {
if ( /_hover/.test( button.src ) )
button.src = toolbar_image_dir + button.filename_prefix + "_button_down_hover.png";
else
button.src = toolbar_image_dir + button.filename_prefix + "_button_down.png";
this.resize_toolbar_button( button );
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 ( state_name && node_names && 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() {
var node_names = null;
var link = null;
// a read-only notebook doesn't have a visible toolbar
if ( this.notebook.read_write == NOTEBOOK_READ_ONLY )
return;
if ( this.focused_editor ) {
node_names = this.focused_editor.current_node_names();
link = this.focused_editor.find_link_at_cursor();
}
this.update_button( "newNote" );
this.update_button( "bold", "b", node_names );
this.update_button( "italic", "i", node_names );
this.update_button( "underline", "u", node_names );
this.update_button( "strikethrough", "strike", node_names );
this.update_button( "font", "font", node_names );
this.update_button( "title", "h3", node_names );
this.update_button( "insertUnorderedList", "ul", node_names );
this.update_button( "insertOrderedList", "ol", node_names );
if ( link ) {
// determine whether the link is a note link or a file link
if ( link.target || !/\/files\//.test( link.href ) ) {
this.down_image_button( "createLink" );
this.up_image_button( "attachFile" );
} else {
this.up_image_button( "createLink" );
this.down_image_button( "attachFile" );
}
} else {
this.up_image_button( "createLink" );
this.up_image_button( "attachFile" );
}
}
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 && link.parentNode != null ) {
var self = this;
this.resolve_link( link_title( link ), link, false, function ( summary ) {
self.display_link_pulldown( self.focused_editor, link );
} );
} else {
this.display_link_pulldown( this.focused_editor );
}
}
event.stop();
}
Wiki.prototype.toggle_attach_button = function ( event ) {
if ( this.focused_editor && this.focused_editor.read_write ) {
this.focused_editor.focus();
if ( this.toggle_image_button( "attachFile" ) )
var link = this.focused_editor.start_file_link();
else
var link = this.focused_editor.end_link();
// if a pulldown is already open, then just close it
var pulldown_id = "upload_" + this.focused_editor.id;
var existing_div = getElement( pulldown_id );
if ( !existing_div ) {
pulldown_id = "file_link_" + this.focused_editor.id;
existing_div = getElement( pulldown_id );
}
if ( existing_div ) {
existing_div.pulldown.shutdown();
existing_div.pulldown = null;
return;
}
this.clear_messages();
this.clear_pulldowns();
new Upload_pulldown( this, this.notebook.object_id, this.invoker, this.focused_editor, link, null );
}
event.stop();
}
Wiki.prototype.toggle_font_button = function ( event ) {
if ( this.focused_editor && this.focused_editor.read_write ) {
this.focused_editor.focus();
// if a pulldown is already open, then just close it
var existing_div = getElement( "font_pulldown" );
if ( existing_div ) {
this.up_image_button( "font" );
existing_div.pulldown.shutdown();
existing_div.pulldown = null;
return;
}
this.down_image_button( "font" );
this.clear_messages();
this.clear_pulldowns();
new Font_pulldown( this, this.notebook.object_id, this.invoker, event.target(), this.focused_editor );
}
event.stop();
}
Wiki.prototype.hide_editor = function ( event, editor ) {
this.clear_messages();
this.clear_pulldowns();
if ( this.focused_editor )
this.editor_focused( null, false, true, false );
if ( !editor ) {
editor = this.focused_editor;
this.focused_editor = null;
}
if ( editor ) {
var id = editor.id;
// if the editor to hide is completely empty, then simply remove it
if ( editor.empty() ) {
signal( this, "note_removed", editor.id );
editor.shutdown();
this.decrement_total_notes_count();
this.display_empty_message();
} else {
// before hiding an editor, save it
if ( this.notebook.read_write != NOTEBOOK_READ_ONLY && editor.read_write ) {
var self = this;
this.save_editor( editor, false, function () {
editor.shutdown();
self.display_empty_message();
} );
} else {
editor.shutdown();
this.display_empty_message();
}
}
}
event.stop();
}
Wiki.prototype.delete_editor = function ( event, editor ) {
this.clear_messages();
this.clear_pulldowns();
if ( this.focused_editor )
this.editor_focused( null, false, true, false );
if ( !editor ) {
editor = this.focused_editor;
this.focused_editor = null;
}
if ( !editor ) {
event.stop();
return;
}
if ( this.startup_notes[ editor.id ] )
delete this.startup_notes[ editor.id ];
var self = this;
this.save_editor( editor, false, function () {
if ( self.notebook.read_write != NOTEBOOK_READ_ONLY && editor.read_write ) {
self.invoker.invoke( "/notebooks/delete_note", "POST", {
"notebook_id": self.notebook.object_id,
"note_id": editor.id
}, function ( result ) { self.display_storage_usage( result.storage_bytes ); } );
}
if ( editor == self.focused_editor )
self.focused_editor = null;
if ( self.notebook.trash_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/" + self.notebook.trash_id + "?parent_id=" + self.notebook.object_id
}, "trash" );
var message_div = self.display_message( "The note has been moved to the", [ trash_link, ". ", undo_button ], editor.holder );
connect( undo_button, "onclick", function ( event ) { self.undelete_editor_via_undo( event, editor, message_div ); } );
}
signal( self, "note_removed", editor.id );
editor.shutdown();
self.decrement_total_notes_count();
self.display_empty_message();
}, false, true );
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 ];
if ( this.notebook.read_write != NOTEBOOK_READ_ONLY && 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;
signal( this, "note_removed", 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 != NOTEBOOK_READ_ONLY && editor.read_write ) {
var self = this;
this.invoker.invoke( "/notebooks/undelete_note", "POST", {
"notebook_id": this.notebook.object_id,
"note_id": editor.id
}, function ( result ) {
self.display_storage_usage( result.storage_bytes );
self.clear_messages();
self.clear_pulldowns();
self.startup_notes[ editor.id ] = true;
self.increment_total_notes_count();
self.load_editor( "Note not found.", editor.id, null, null, null, position_after );
} );
}
}
event.stop();
}
Wiki.prototype.undelete_editor_via_undelete = function( event, note_id, position_after ) {
if ( this.notebook.read_write != NOTEBOOK_READ_ONLY ) {
var self = this;
this.invoker.invoke( "/notebooks/undelete_note", "POST", {
"notebook_id": this.notebook.object_id,
"note_id": note_id
}, function ( result ) {
self.display_storage_usage( result.storage_bytes );
self.clear_messages();
self.clear_pulldowns();
self.startup_notes[ note_id ] = true;
self.increment_total_notes_count();
self.load_editor( "Note not found.", note_id, null, 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 a diff between the two revisions for examination by the user
this.load_editor( editor.title, editor.id, editor.revision, previous_revision, editor.closed ? null : editor.holder );
}
Wiki.prototype.save_editor = function ( editor, fire_and_forget, callback, synchronous, suppress_save_signal, position_after, position_before ) {
if ( !editor )
editor = this.focused_editor;
var self = this;
if ( editor && editor.read_write && !editor.closed &&
( ( !editor.empty() && editor.dirty() ) || position_after || position_before ) ) {
editor.scrape_title();
this.remove_byline( editor );
this.invoker.invoke( "/notebooks/save_note", "POST", {
"notebook_id": this.notebook.object_id,
"note_id": editor.id,
"contents": editor.contents(),
"startup": editor.startup,
"previous_revision": editor.revision ? editor.revision : "None",
"position_after": position_after ? position_after.id : "None",
"position_before": position_before ? position_before.id : "None"
}, function ( result ) {
self.update_editor_revisions( result, editor );
self.display_storage_usage( result.storage_bytes );
editor.mark_clean();
var save_button = getElement( "save_button" );
if ( save_button && self.focused_editor && !self.focused_editor.dirty() ) {
save_button.disabled = true;
save_button.value = "saved";
}
if ( editor.startup )
self.startup_notes[ editor.id ] = true;
else if ( self.startup_notes[ editor.id ] )
delete self.startup_notes[ editor.id ];
// special case to rename a forum notebook when its first note is renamed
if ( result.rank == 0 && self.notebook_has_tag( self.notebook, "forum" ) )
self.end_notebook_rename( editor.title, true );
if ( callback )
callback();
if ( !suppress_save_signal )
signal( self, "note_saved", editor );
}, null, synchronous, fire_and_forget );
} else {
var save_button = getElement( "save_button" );
if ( save_button && this.focused_editor && !this.focused_editor.dirty() ) {
save_button.disabled = true;
save_button.value = "saved";
}
if ( callback )
callback();
}
}
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 && client_previous_revision &&
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 || 'you' ) + '.',
[ compare_button ], editor.holder
);
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.submit_form = function ( form ) {
this.clear_messages();
this.clear_pulldowns();
var self = this;
var args = {}
var url = form.getAttribute( "target" );
var callback = null;
if ( url == "/users/signup" ) {
args[ "invite_id" ] = this.invite_id;
args[ "rate_plan" ] = this.signup_plan;
args[ "yearly" ] = this.yearly;
} else if ( url == "/users/login" ) {
args[ "invite_id" ] = this.invite_id;
args[ "after_login" ] = this.after_login;
} else if ( url == "/users/send_invites" ) {
callback = function ( result ) {
if ( !result.invites ) return;
self.invites = result.invites;
self.share_notebook();
}
} else if ( url == "/users/update_settings" ) {
callback = function ( result ) {
self.user.email_address = result.email_address || "";
self.display_message( "Your account settings have been updated." );
}
} else if ( url == "/users/signup_group_member" ) {
callback = function ( result ) {
var group_id = getFirstElementByTagAndClassName( "input", "group_id", form ).value;
self.invoker.invoke( "/groups/load_users", "GET", {
"group_id": group_id
}, function ( result ) {
self.display_group_settings( result );
} );
}
} else if ( url == "/notebooks/revert_note" ) {
callback = function ( result ) {
var editor = editor_by_id( form.note_id.value );
if ( editor ) {
self.update_editor_revisions( result, editor );
editor.mark_clean();
if ( result.new_revision ) {
if ( editor.document && editor.document.body )
editor.document.body.innerHTML = result.contents;
else if ( editor.div )
editor.div.innerHTML = result.contents;
editor.resize();
editor.scrape_title();
}
editor.highlight();
signal( self, "note_saved", editor );
}
if ( result.new_revision )
self.display_message( "The note has been reverted to an earlier revision.", [],
editor && editor.holder || null );
else
self.display_message( "The note is already at that revision.", [],
editor && editor.holder || null );
self.display_storage_usage( result.storage_bytes );
}
}
this.invoker.invoke( url, "POST", args, callback, form );
}
Wiki.prototype.editor_button_clicked = function ( editor, button ) {
this.clear_messages();
this.clear_pulldowns();
var self = this;
if ( hasElementClass( button, "revoke_button" ) ) {
var invite_id = button.id.split( "_" ).pop();
this.invoker.invoke( "/users/revoke_invite", "POST", {
"notebook_id": this.notebook.object_id,
"invite_id": invite_id
}, function ( result ) {
if ( !result.invites ) return;
self.invites = result.invites;
self.share_notebook();
} );
} else if ( hasElementClass( button, "admin_button" ) ) {
var group_id = button.id.split( "_" ).pop();
this.invoker.invoke( "/groups/load_users", "GET", {
"group_id": group_id
}, function ( result ) {
self.display_group_settings( result );
} );
} else if ( hasElementClass( button, "remove_user_button" ) ) {
var id_pieces = button.id.split( "_" );
var group_id = id_pieces.pop();
var user_id = id_pieces.pop();
this.invoker.invoke( "/users/remove_group", "POST", {
"user_id_to_remove": user_id,
"group_id": group_id
}, function ( result ) {
self.invoker.invoke( "/groups/load_users", "GET", {
"group_id": group_id
}, function ( result ) {
self.display_group_settings( result );
} );
} );
}
}
Wiki.prototype.search = function ( event ) {
this.clear_messages();
this.clear_pulldowns();
var self = this;
this.invoker.invoke( "/notebooks/search", "GET", {
"notebook_id": this.notebook.object_id
},
function( result ) { self.display_search_results( result ); },
"search_form"
);
event.stop();
}
Wiki.prototype.search_focused = function ( event ) {
var search_text = getElement( "search_text" );
if ( search_text.value == 'search' )
search_text.value = '';
}
Wiki.prototype.search_blurred = function ( event ) {
var search_text = getElement( "search_text" );
if ( search_text.value == '' )
search_text.value = 'search';
}
Wiki.prototype.search_key_released = function ( event ) {
var search_text = getElement( "search_text" );
var self = this;
if ( search_text.pulldown ) {
search_text.pulldown.update_suggestions( search_text.value );
} else if ( event.key().code != 13 ) {
search_text.pulldown = new Suggest_pulldown( this.wiki, this.notebook.object_id, this.invoker, search_text, null, search_text.value, search_text );
connect( search_text.pulldown, "suggestion_selected", function ( note ) {
self.load_search_suggestion( note )
} );
}
}
Wiki.prototype.load_search_suggestion = function ( note ) {
var search_text = getElement( "search_text" );
search_text.value = note.title;
this.load_editor( note.title, note.object_id );
if ( search_text.pulldown ) {
search_text.pulldown.shutdown();
search_text.pulldown = null;
}
}
Wiki.prototype.notebook_has_tag = function ( notebook, tag_name, tag_value ) {
// determine whether the given notebook has a tag with tag_name, and (optionally) tag_value
for ( var i in notebook.tags ) {
var tag = notebook.tags[ i ];
if ( tag.name == tag_name ) {
if ( tag_value && tag.value != tag_value )
return false;
return true;
}
}
return false;
}
Wiki.prototype.display_search_results = function ( result ) {
// if there are no search results, indicate that and bail
if ( !result || result.notes.length == 0 ) {
if ( this.search_results_editor )
this.search_results_editor.shutdown();
this.display_message( "No matching notes.", undefined, getElement( "notes_top" ) );
return;
}
// create a "magic" search results note
if ( this.notebook_has_tag( this.notebook, "forum" ) )
var notebook_word = "discussion";
else
var notebook_word = "notebook";
var list = createDOM( "span", {} );
var other_notebooks_section = false;
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 + " ...";
}
var summary_span = createDOM( "span" );
summary_span.setAttribute( "class", "search_results_summary" );
summary_span.innerHTML = summary;
// when a link is clicked for a note from a notebook other than the current one, open it in a
// new window
var link_attributes = { "href": "/notebooks/" + note.notebook_id + "?note_id=" + note.object_id };
if ( note.notebook_id != this.notebook.object_id ) {
link_attributes[ "target" ] = "_new";
if ( !other_notebooks_section ) {
other_notebooks_section = true;
if ( i == 0 )
appendChildNodes( list, createDOM( "p", {}, "No matching notes in this " + notebook_word + "." ) );
appendChildNodes( list, createDOM( "hr" ), createDOM( "h4", {}, "other " + notebook_word + "s" ) );
}
}
var notebook = this.notebooks_by_id[ note.notebook_id ];
appendChildNodes( list,
createDOM( "p", {},
createDOM( "a", link_attributes, note.title ),
other_notebooks_section && notebook && createDOM( "span", { "class": "small_text" }, " (", notebook.name, ")" ) || null,
createDOM( "br" ),
summary_span
)
);
}
this.search_results_editor = this.create_editor( "search_results", "
search results
" + list.innerHTML, undefined, undefined, undefined, false, true, false, 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_label = createDOM( "label",
{ "for": "collaborators_radio", "class": "radio_label", "title": "Collaborators may view and edit this notebook." },
"collaborators"
);
var viewers_label = createDOM( "label",
{ "for": "viewers_radio", "class": "radio_label", "title": "Viewers may only view this notebook." },
"viewers"
);
var owners_label = createDOM( "label",
{ "for": "owners_radio", "class": "radio_label", "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", "class": "radio_table" },
createDOM( "tr", {},
createDOM( "td", {}, collaborators_radio, collaborators_label ),
createDOM( "td", {}, viewers_radio, viewers_label ),
createDOM( "td", {}, owners_radio, owners_label )
)
)
);
} 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", "target": "/users/send_invites" },
createDOM( "input", { "type": "hidden", "name": "notebook_id", "value": this.notebook.object_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.object_id + "?preview=viewer", "target": "_new" },
"Preview this notebook as a viewer."
)
),
this.rate_plan.notebook_collaboration ? createDOM( "div", {},
createDOM(
"a", { "href": "/notebooks/" + this.notebook.object_id + "?preview=collaborator", "target": "_new" },
"Preview this notebook as a collaborator."
)
) : null
);
this.create_editor( "share_notebook", "
share this notebook
" + div.innerHTML, undefined, undefined, undefined, false, true, false, 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 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 indented" },
invite.email_address, " ",
createDOM( "span", { "class": "invite_status" },
invite.redeemed_username ? "(invite accepted by " + invite.redeemed_username + ")" : "(waiting for invite to be accepted)"
),
" ", revoke_button,
!invite.redeemed_username && createDOM( "span", {},
createDOM( "br" ),
createDOM( "span", { "class": "invite_link_area" },
"invite link: " + location.protocol + "//" + location.host + "/i/" + invite.object_id
)
) || ""
)
);
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_settings = function () {
this.clear_pulldowns();
var settings_frame = getElement( "note_settings" );
if ( settings_frame ) {
settings_frame.editor.highlight();
return;
}
var group_list = createDOM( "div" );
var div = createDOM( "div", {},
createDOM( "form", { "id": "settings_form", "target": "/users/update_settings" },
createDOM( "p", {},
createDOM( "b", {}, "email address" ),
createDOM( "br", {} ),
createDOM( "input",
{ "type": "text", "name": "email_address", "id": "email_address", "class": "text_field",
"size": "30", "maxlength": "60", "value": this.user.email_address || "" }
)
),
createDOM( "p", {},
createDOM( "input",
{ "type": "submit", "name": "settings_button", "id": "settings_button", "class": "button", "value": "save settings" }
)
),
createDOM( "p", { "class": "small_text" },
"Your email address will ",
createDOM( "a", { "href": "/privacy", "target": "_new" }, "never be shared" ),
". It will only be used for password resets, contacting you about account problems, and the from address in any invite emails you send."
)
),
createDOM( "p", {},
createDOM( "b", {}, "group membership" ),
createDOM( "br", {} ),
group_list
),
createDOM( "p", {},
createDOM(
"a", { "href": "/pricing", "target": "_top" },
"Upgrade, downgrade, or cancel your account."
)
)
);
for ( var i in this.groups ) {
var group = this.groups[ i ];
var item = createDOM( "div", { "class": "indented" } );
appendChildNodes( group_list, item );
appendChildNodes( item, group.name );
if ( group.admin ) {
appendChildNodes( item, createDOM( "input", {
"id": "admin_" + group.object_id,
"class": "admin_button button",
"type": "button",
"value": "admin",
"title": "administer this group's users"
} ) );
}
}
if ( this.groups.length == 0 ) {
var item = createDOM( "div", { "class": "indented" }, "You're not a member of any groups." );
appendChildNodes( group_list, item );
}
this.create_editor( "settings", "