diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py
index c64ebd7c5..a52f1d6f9 100644
--- a/IPython/html/base/handlers.py
+++ b/IPython/html/base/handlers.py
@@ -136,6 +136,12 @@ class IPythonHandler(AuthenticatedHandler):
@property
def ws_url(self):
return self.settings.get('websocket_url', '')
+
+ @property
+ def contents_js_source(self):
+ self.log.debug("Using contents: %s", self.settings.get('contents_js_source',
+ 'services/contents'))
+ return self.settings.get('contents_js_source', 'services/contents')
#---------------------------------------------------------------
# Manager objects
@@ -224,7 +230,8 @@ class IPythonHandler(AuthenticatedHandler):
logged_in=self.logged_in,
login_available=self.login_available,
static_url=self.static_url,
- sys_info=sys_info
+ sys_info=sys_info,
+ contents_js_source=self.contents_js_source,
)
def get_json_body(self):
diff --git a/IPython/html/static/base/js/utils.js b/IPython/html/static/base/js/utils.js
index 22261f3e4..16f9a7068 100644
--- a/IPython/html/static/base/js/utils.js
+++ b/IPython/html/static/base/js/utils.js
@@ -563,6 +563,22 @@ define([
);
};
+ /** Error type for wrapped XHR errors. */
+ var XHR_ERROR = 'XhrError';
+
+ /**
+ * Wraps an AJAX error as an Error object.
+ */
+ var wrap_ajax_error = function (jqXHR, status, error) {
+ var wrapped_error = new Error(ajax_error_msg(jqXHR));
+ wrapped_error.name = XHR_ERROR;
+ // provide xhr response
+ wrapped_error.xhr = jqXHR;
+ wrapped_error.xhr_status = status;
+ wrapped_error.xhr_error = error;
+ return wrapped_error;
+ }
+
var utils = {
regex_split : regex_split,
uuid : uuid,
@@ -588,6 +604,8 @@ define([
ajax_error_msg : ajax_error_msg,
log_ajax_error : log_ajax_error,
requireCodeMirrorMode : requireCodeMirrorMode,
+ XHR_ERROR : XHR_ERROR,
+ wrap_ajax_error : wrap_ajax_error
};
// Backwards compatability.
diff --git a/IPython/html/static/notebook/js/main.js b/IPython/html/static/notebook/js/main.js
index 3cf2782e3..486267de9 100644
--- a/IPython/html/static/notebook/js/main.js
+++ b/IPython/html/static/notebook/js/main.js
@@ -5,6 +5,7 @@ require([
'base/js/namespace',
'jquery',
'notebook/js/notebook',
+ 'contents',
'base/js/utils',
'base/js/page',
'notebook/js/layoutmanager',
@@ -27,6 +28,7 @@ require([
IPython,
$,
notebook,
+ contents,
utils,
page,
layoutmanager,
@@ -70,10 +72,14 @@ require([
var save_widget = new savewidget.SaveWidget('span#save_widget', {
events: events,
keyboard_manager: keyboard_manager});
+ var contents = new contents.Contents($.extend({
+ events: events},
+ common_options));
var notebook = new notebook.Notebook('div#notebook', $.extend({
events: events,
keyboard_manager: keyboard_manager,
save_widget: save_widget,
+ contents: contents,
config: user_config},
common_options));
var login_widget = new loginwidget.LoginWidget('span#login_widget', common_options);
@@ -86,6 +92,7 @@ require([
notebook: notebook});
var menubar = new menubar.MenuBar('#menubar', $.extend({
notebook: notebook,
+ contents: contents,
layout_manager: layout_manager,
events: events,
save_widget: save_widget,
@@ -131,6 +138,7 @@ require([
IPython.page = page;
IPython.layout_manager = layout_manager;
IPython.notebook = notebook;
+ IPython.contents = contents;
IPython.pager = pager;
IPython.quick_help = quick_help;
IPython.login_widget = login_widget;
diff --git a/IPython/html/static/notebook/js/menubar.js b/IPython/html/static/notebook/js/menubar.js
index c7e359583..fc6f26523 100644
--- a/IPython/html/static/notebook/js/menubar.js
+++ b/IPython/html/static/notebook/js/menubar.js
@@ -21,6 +21,7 @@ define([
// options: dictionary
// Dictionary of keyword arguments.
// notebook: Notebook instance
+ // contents: ContentManager instance
// layout_manager: LayoutManager instance
// events: $(Events) instance
// save_widget: SaveWidget instance
@@ -32,6 +33,7 @@ define([
this.base_url = options.base_url || utils.get_body_data("baseUrl");
this.selector = selector;
this.notebook = options.notebook;
+ this.contents = options.contents;
this.layout_manager = options.layout_manager;
this.events = options.events;
this.save_widget = options.save_widget;
@@ -85,7 +87,26 @@ define([
// File
var that = this;
this.element.find('#new_notebook').click(function () {
- that.notebook.new_notebook();
+ // Create a new notebook in the same path as the current
+ // notebook's path.
+ that.contents.new(that.notebook.notebook_path, null, {
+ ext: ".ipynb",
+ extra_settings: {async: false}, // So we can open a new window afterwards
+ success: function (data) {
+ window.open(
+ utils.url_join_encode(
+ that.base_url, 'notebooks',
+ data.path, data.name
+ ), '_blank');
+ },
+ error: function(error) {
+ dialog.modal({
+ title : 'Creating Notebook Failed',
+ body : "The error was: " + error.message,
+ buttons : {'OK' : {'class' : 'btn-primary'}}
+ });
+ }
+ });
});
this.element.find('#open_notebook').click(function () {
window.open(utils.url_join_encode(
diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js
index 12c0f8b3c..e1f41c749 100644
--- a/IPython/html/static/notebook/js/notebook.js
+++ b/IPython/html/static/notebook/js/notebook.js
@@ -50,6 +50,7 @@ define([
// Dictionary of keyword arguments.
// events: $(Events) instance
// keyboard_manager: KeyboardManager instance
+ // contents: Contents instance
// save_widget: SaveWidget instance
// config: dictionary
// base_url : string
@@ -61,6 +62,7 @@ define([
this.notebook_name = options.notebook_name;
this.events = options.events;
this.keyboard_manager = options.keyboard_manager;
+ this.contents = options.contents;
this.save_widget = options.save_widget;
this.tooltip = new tooltip.Tooltip(this.events);
this.ws_url = options.ws_url;
@@ -1860,7 +1862,9 @@ define([
}
var data = {
cells: cell_array,
- metadata : this.metadata
+ metadata: this.metadata,
+ nbformat: this.nbformat,
+ nbformat_minor: this.nbformat_minor
};
if (trusted != this.trusted) {
this.trusted = trusted;
@@ -1904,52 +1908,33 @@ define([
*/
Notebook.prototype.save_notebook = function (extra_settings) {
// Create a JSON model to be sent to the server.
- var model = {};
- model.name = this.notebook_name;
- model.path = this.notebook_path;
- model.type = 'notebook';
- model.format = 'json';
- model.content = this.toJSON();
- model.content.nbformat = this.nbformat;
- model.content.nbformat_minor = this.nbformat_minor;
+ var model = {
+ name : this.notebook_name,
+ path : this.notebook_path,
+ type : "notebook",
+ content : this.toJSON()
+ };
// time the ajax call for autosave tuning purposes.
var start = new Date().getTime();
- // We do the call with settings so we can set cache to false.
- var settings = {
- processData : false,
- cache : false,
- type : "PUT",
- data : JSON.stringify(model),
- contentType: 'application/json',
- dataType : "json",
- success : $.proxy(this.save_notebook_success, this, start),
- error : $.proxy(this.save_notebook_error, this)
- };
- if (extra_settings) {
- for (var key in extra_settings) {
- settings[key] = extra_settings[key];
- }
- }
- this.events.trigger('notebook_saving.Notebook');
- var url = utils.url_join_encode(
- this.base_url,
- 'api/contents',
- this.notebook_path,
- this.notebook_name
- );
- $.ajax(url, settings);
+
+ var that = this;
+ this.contents.save(this.notebook_path, this.notebook_name, model, {
+ extra_settings: extra_settings,
+ success: $.proxy(this.save_notebook_success, this, start),
+ error: function (error) {
+ that.events.trigger('notebook_save_failed.Notebook');
+ }
+ });
};
/**
* Success callback for saving a notebook.
*
* @method save_notebook_success
- * @param {Integer} start the time when the save request started
+ * @param {Integer} start Time when the save request start
* @param {Object} data JSON representation of a notebook
- * @param {String} status Description of response status
- * @param {jqXHR} xhr jQuery Ajax object
*/
- Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
+ Notebook.prototype.save_notebook_success = function (start, data) {
this.set_dirty(false);
if (data.message) {
// save succeeded, but validation failed.
@@ -2002,18 +1987,6 @@ define([
}
}
};
-
- /**
- * Failure callback for saving a notebook.
- *
- * @method save_notebook_error
- * @param {jqXHR} xhr jQuery Ajax object
- * @param {String} status Description of response status
- * @param {String} error HTTP error message
- */
- Notebook.prototype.save_notebook_error = function (xhr, status, error) {
- this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
- };
/**
* Explicitly trust the output of this notebook.
@@ -2065,124 +2038,47 @@ define([
});
};
- Notebook.prototype.new_notebook = function(){
- var path = this.notebook_path;
- var base_url = this.base_url;
- var settings = {
- processData : false,
- cache : false,
- type : "POST",
- dataType : "json",
- async : false,
- success : function (data, status, xhr){
- var notebook_name = data.name;
- window.open(
- utils.url_join_encode(
- base_url,
- 'notebooks',
- path,
- notebook_name
- ),
- '_blank'
- );
- },
- error : utils.log_ajax_error,
- };
- var url = utils.url_join_encode(
- base_url,
- 'api/contents',
- path
- );
- $.ajax(url,settings);
- };
-
-
Notebook.prototype.copy_notebook = function(){
- var path = this.notebook_path;
var base_url = this.base_url;
- var settings = {
- processData : false,
- cache : false,
- type : "POST",
- dataType : "json",
- data : JSON.stringify({copy_from : this.notebook_name}),
- async : false,
- success : function (data, status, xhr) {
+ this.contents.copy(this.notebook_path, null, this.notebook_name, {
+ // synchronous so we can open a new window on success
+ extra_settings: {async: false},
+ success: function (data) {
window.open(utils.url_join_encode(
- base_url,
- 'notebooks',
- data.path,
- data.name
+ base_url, 'notebooks', data.path, data.name
), '_blank');
},
- error : utils.log_ajax_error,
- };
- var url = utils.url_join_encode(
- base_url,
- 'api/contents',
- path
- );
- $.ajax(url,settings);
+ error : utils.log_ajax_error
+ });
};
- Notebook.prototype.rename = function (nbname) {
- var that = this;
- if (!nbname.match(/\.ipynb$/)) {
- nbname = nbname + ".ipynb";
- }
- var data = {name: nbname};
- var settings = {
- processData : false,
- cache : false,
- type : "PATCH",
- data : JSON.stringify(data),
- dataType: "json",
- contentType: 'application/json',
- success : $.proxy(that.rename_success, this),
- error : $.proxy(that.rename_error, this)
- };
- this.events.trigger('rename_notebook.Notebook', data);
- var url = utils.url_join_encode(
- this.base_url,
- 'api/contents',
- this.notebook_path,
- this.notebook_name
- );
- $.ajax(url, settings);
- };
+ Notebook.prototype.rename = function (new_name) {
+ if (!new_name.match(/\.ipynb$/)) {
+ new_name = new_name + ".ipynb";
+ }
- Notebook.prototype.delete = function () {
var that = this;
- var settings = {
- processData : false,
- cache : false,
- type : "DELETE",
- dataType: "json",
- error : utils.log_ajax_error,
- };
- var url = utils.url_join_encode(
- this.base_url,
- 'api/contents',
- this.notebook_path,
- this.notebook_name
- );
- $.ajax(url, settings);
+ this.contents.rename(this.notebook_path, this.notebook_name,
+ this.notebook_path, new_name, {
+ success: function (json) {
+ var name = that.notebook_name = json.name;
+ that.session.rename_notebook(name, json.path);
+ that.events.trigger('notebook_renamed.Notebook', json);
+ },
+ error: $.proxy(this.rename_error, this)
+ });
};
-
- Notebook.prototype.rename_success = function (json, status, xhr) {
- var name = this.notebook_name = json.name;
- var path = json.path;
- this.session.rename_notebook(name, path);
- this.events.trigger('notebook_renamed.Notebook', json);
+ Notebook.prototype.delete = function () {
+ this.contents.delete(this.notebook_name, this.notebook_path);
};
- Notebook.prototype.rename_error = function (xhr, status, error) {
+ Notebook.prototype.rename_error = function (error) {
var that = this;
var dialog_body = $('
').append(
$("").text('This notebook name already exists.')
);
- this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
+ this.events.trigger('notebook_rename_failed.Notebook', error);
dialog.modal({
notebook: this,
keyboard_manager: this.keyboard_manager,
@@ -2193,7 +2089,7 @@ define([
"OK": {
class: "btn-primary",
click: function () {
- this.save_widget.rename_notebook({notebook:that});
+ that.save_widget.rename_notebook({notebook:that});
}}
},
open : function (event, ui) {
@@ -2216,26 +2112,13 @@ define([
* @param {String} notebook_name and path A notebook to load
*/
Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
- var that = this;
this.notebook_name = notebook_name;
this.notebook_path = notebook_path;
- // We do the call with settings so we can set cache to false.
- var settings = {
- processData : false,
- cache : false,
- type : "GET",
- dataType : "json",
- success : $.proxy(this.load_notebook_success,this),
- error : $.proxy(this.load_notebook_error,this),
- };
this.events.trigger('notebook_loading.Notebook');
- var url = utils.url_join_encode(
- this.base_url,
- 'api/contents',
- this.notebook_path,
- this.notebook_name
- );
- $.ajax(url, settings);
+ this.contents.load(notebook_path, notebook_name, {
+ success: $.proxy(this.load_notebook_success, this),
+ error: $.proxy(this.load_notebook_error, this)
+ });
};
/**
@@ -2245,10 +2128,8 @@ define([
*
* @method load_notebook_success
* @param {Object} data JSON representation of a notebook
- * @param {String} status Description of response status
- * @param {jqXHR} xhr jQuery Ajax object
*/
- Notebook.prototype.load_notebook_success = function (data, status, xhr) {
+ Notebook.prototype.load_notebook_success = function (data) {
var failed;
try {
this.fromJSON(data);
@@ -2393,20 +2274,18 @@ define([
* Failure callback for loading a notebook from the server.
*
* @method load_notebook_error
- * @param {jqXHR} xhr jQuery Ajax object
- * @param {String} status Description of response status
- * @param {String} error HTTP error message
- */
- Notebook.prototype.load_notebook_error = function (xhr, status, error) {
- this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
- utils.log_ajax_error(xhr, status, error);
- var msg = $("");
- if (xhr.status === 400) {
- msg.text(utils.ajax_error_msg(xhr));
- } else if (xhr.status === 500) {
- msg.text("An unknown error occurred while loading this notebook. " +
+ * @param {Error} error
+ */
+ Notebook.prototype.load_notebook_error = function (error) {
+ this.events.trigger('notebook_load_failed.Notebook', error);
+ var msg;
+ if (error.name = utils.XHR_ERROR && error.xhr.status === 500) {
+ utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
+ msg = "An unknown error occurred while loading this notebook. " +
"This version can load notebook formats " +
- "v" + this.nbformat + " or earlier. See the server log for details.");
+ "v" + this.nbformat + " or earlier. See the server log for details.";
+ } else {
+ msg = error.message;
}
dialog.modal({
notebook: this,
@@ -2459,18 +2338,13 @@ define([
* @method list_checkpoints
*/
Notebook.prototype.list_checkpoints = function () {
- var url = utils.url_join_encode(
- this.base_url,
- 'api/contents',
- this.notebook_path,
- this.notebook_name,
- 'checkpoints'
- );
- $.get(url).done(
- $.proxy(this.list_checkpoints_success, this)
- ).fail(
- $.proxy(this.list_checkpoints_error, this)
- );
+ var that = this;
+ this.contents.list_checkpoints(this.notebook_path, this.notebook_name, {
+ success: $.proxy(this.list_checkpoints_success, this),
+ error: function(error) {
+ that.events.trigger('list_checkpoints_failed.Notebook');
+ }
+ });
};
/**
@@ -2478,10 +2352,8 @@ define([
*
* @method list_checkpoint_success
* @param {Object} data JSON representation of a checkpoint
- * @param {String} status Description of response status
- * @param {jqXHR} xhr jQuery Ajax object
*/
- Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
+ Notebook.prototype.list_checkpoints_success = function (data) {
data = $.parseJSON(data);
this.checkpoints = data;
if (data.length) {
@@ -2492,36 +2364,19 @@ define([
this.events.trigger('checkpoints_listed.Notebook', [data]);
};
- /**
- * Failure callback for listing a checkpoint.
- *
- * @method list_checkpoint_error
- * @param {jqXHR} xhr jQuery Ajax object
- * @param {String} status Description of response status
- * @param {String} error_msg HTTP error message
- */
- Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
- this.events.trigger('list_checkpoints_failed.Notebook');
- };
-
/**
* Create a checkpoint of this notebook on the server from the most recent save.
*
* @method create_checkpoint
*/
Notebook.prototype.create_checkpoint = function () {
- var url = utils.url_join_encode(
- this.base_url,
- 'api/contents',
- this.notebook_path,
- this.notebook_name,
- 'checkpoints'
- );
- $.post(url).done(
- $.proxy(this.create_checkpoint_success, this)
- ).fail(
- $.proxy(this.create_checkpoint_error, this)
- );
+ var that = this;
+ this.contents.create_checkpoint(this.notebook_path, this.notebook_name, {
+ success: $.proxy(this.create_checkpoint_success, this),
+ error: function (error) {
+ that.events.trigger('checkpoint_failed.Notebook');
+ }
+ });
};
/**
@@ -2529,27 +2384,13 @@ define([
*
* @method create_checkpoint_success
* @param {Object} data JSON representation of a checkpoint
- * @param {String} status Description of response status
- * @param {jqXHR} xhr jQuery Ajax object
*/
- Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
+ Notebook.prototype.create_checkpoint_success = function (data) {
data = $.parseJSON(data);
this.add_checkpoint(data);
this.events.trigger('checkpoint_created.Notebook', data);
};
- /**
- * Failure callback for creating a checkpoint.
- *
- * @method create_checkpoint_error
- * @param {jqXHR} xhr jQuery Ajax object
- * @param {String} status Description of response status
- * @param {String} error_msg HTTP error message
- */
- Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
- this.events.trigger('checkpoint_failed.Notebook');
- };
-
Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
var that = this;
checkpoint = checkpoint || this.last_checkpoint;
@@ -2599,46 +2440,26 @@ define([
*/
Notebook.prototype.restore_checkpoint = function (checkpoint) {
this.events.trigger('notebook_restoring.Notebook', checkpoint);
- var url = utils.url_join_encode(
- this.base_url,
- 'api/contents',
- this.notebook_path,
- this.notebook_name,
- 'checkpoints',
- checkpoint
- );
- $.post(url).done(
- $.proxy(this.restore_checkpoint_success, this)
- ).fail(
- $.proxy(this.restore_checkpoint_error, this)
- );
+ var that = this;
+ this.contents.restore_checkpoint(this.notebook_path, this.notebook_name,
+ checkpoint, {
+ success: $.proxy(this.restore_checkpoint_success, this),
+ error: function (error) {
+ that.events.trigger('checkpoint_restore_failed.Notebook');
+ }
+ });
};
/**
* Success callback for restoring a notebook to a checkpoint.
*
* @method restore_checkpoint_success
- * @param {Object} data (ignored, should be empty)
- * @param {String} status Description of response status
- * @param {jqXHR} xhr jQuery Ajax object
*/
- Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
+ Notebook.prototype.restore_checkpoint_success = function () {
this.events.trigger('checkpoint_restored.Notebook');
this.load_notebook(this.notebook_name, this.notebook_path);
};
- /**
- * Failure callback for restoring a notebook to a checkpoint.
- *
- * @method restore_checkpoint_error
- * @param {jqXHR} xhr jQuery Ajax object
- * @param {String} status Description of response status
- * @param {String} error_msg HTTP error message
- */
- Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
- this.events.trigger('checkpoint_restore_failed.Notebook');
- };
-
/**
* Delete a notebook checkpoint.
*
@@ -2647,18 +2468,13 @@ define([
*/
Notebook.prototype.delete_checkpoint = function (checkpoint) {
this.events.trigger('notebook_restoring.Notebook', checkpoint);
- var url = utils.url_join_encode(
- this.base_url,
- 'api/contents',
- this.notebook_path,
- this.notebook_name,
- 'checkpoints',
- checkpoint
- );
- $.ajax(url, {
- type: 'DELETE',
+ var that = this;
+ this.contents.delete_checkpoint(this.notebook_path, this.notebook_name,
+ checkpoint, {
success: $.proxy(this.delete_checkpoint_success, this),
- error: $.proxy(this.delete_checkpoint_error, this)
+ error: function (error) {
+ that.events.trigger('checkpoint_delete_failed.Notebook', error);
+ }
});
};
@@ -2666,27 +2482,12 @@ define([
* Success callback for deleting a notebook checkpoint
*
* @method delete_checkpoint_success
- * @param {Object} data (ignored, should be empty)
- * @param {String} status Description of response status
- * @param {jqXHR} xhr jQuery Ajax object
*/
- Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
- this.events.trigger('checkpoint_deleted.Notebook', data);
+ Notebook.prototype.delete_checkpoint_success = function () {
+ this.events.trigger('checkpoint_deleted.Notebook');
this.load_notebook(this.notebook_name, this.notebook_path);
};
- /**
- * Failure callback for deleting a notebook checkpoint.
- *
- * @method delete_checkpoint_error
- * @param {jqXHR} xhr jQuery Ajax object
- * @param {String} status Description of response status
- * @param {String} error HTTP error message
- */
- Notebook.prototype.delete_checkpoint_error = function (xhr, status, error) {
- this.events.trigger('checkpoint_delete_failed.Notebook', [xhr, status, error]);
- };
-
// For backwards compatability.
IPython.Notebook = Notebook;
diff --git a/IPython/html/static/services/contents.js b/IPython/html/static/services/contents.js
new file mode 100644
index 000000000..3cc5f11e5
--- /dev/null
+++ b/IPython/html/static/services/contents.js
@@ -0,0 +1,285 @@
+// Copyright (c) IPython Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+define([
+ 'base/js/namespace',
+ 'jquery',
+ 'base/js/utils',
+ 'base/js/dialog',
+], function(IPython, $, utils, dialog) {
+ var Contents = function(options) {
+ // Constructor
+ //
+ // A contents handles passing file operations
+ // to the back-end. This includes checkpointing
+ // with the normal file operations.
+ //
+ // Parameters:
+ // options: dictionary
+ // Dictionary of keyword arguments.
+ // base_url: string
+ this.base_url = options.base_url;
+ };
+
+ /** Error type */
+ Contents.DIRECTORY_NOT_EMPTY_ERROR = 'DirectoryNotEmptyError';
+
+ Contents.DirectoryNotEmptyError = function() {
+ // Constructor
+ //
+ // An error representing the result of attempting to delete a non-empty
+ // directory.
+ this.message = 'A directory must be empty before being deleted.';
+ }
+ Contents.DirectoryNotEmptyError.prototype = new Error;
+ Contents.DirectoryNotEmptyError.prototype.name =
+ Contents.DIRECTORY_NOT_EMPTY_ERROR;
+
+
+ Contents.prototype.api_url = function() {
+ var url_parts = [this.base_url, 'api/contents'].concat(
+ Array.prototype.slice.apply(arguments));
+ return utils.url_join_encode.apply(null, url_parts);
+ };
+
+ /**
+ * Creates a basic error handler that wraps a jqXHR error as an Error.
+ *
+ * Takes a callback that accepts an Error, and returns a callback that can
+ * be passed directly to $.ajax, which will wrap the error from jQuery
+ * as an Error, and pass that to the original callback.
+ *
+ * @method create_basic_error_handler
+ * @param{Function} callback
+ * @return{Function}
+ */
+ Contents.prototype.create_basic_error_handler = function(callback) {
+ if (!callback) {
+ return function(xhr, status, error) { };
+ }
+ return function(xhr, status, error) {
+ callback(utils.wrap_ajax_error(xhr, status, error));
+ };
+ }
+
+ /**
+ * File Functions (including notebook operations)
+ */
+
+ /**
+ * Load a file.
+ *
+ * Calls success with file JSON model, or error with error.
+ *
+ * @method load_notebook
+ * @param {String} path
+ * @param {String} name
+ * @param {Function} success
+ * @param {Function} error
+ */
+ Contents.prototype.load = function (path, name, options) {
+ // We do the call with settings so we can set cache to false.
+ var settings = {
+ processData : false,
+ cache : false,
+ type : "GET",
+ dataType : "json",
+ success : options.success,
+ error : this.create_basic_error_handler(options.error)
+ };
+ var url = this.api_url(path, name);
+ $.ajax(url, settings);
+ };
+
+
+ /**
+ * Creates a new notebook file at the specified directory path.
+ *
+ * @method scroll_to_cell
+ * @param {String} path The path to create the new notebook at
+ * @param {String} name Name for new file. Chosen by server if unspecified.
+ * @param {Object} options:
+ * ext: file extension to use if name unspecified
+ */
+ Contents.prototype.new = function(path, name, options) {
+ var method, data;
+ if (name) {
+ method = "PUT";
+ } else {
+ method = "POST";
+ data = JSON.stringify({ext: options.ext || ".ipynb"});
+ }
+
+ var settings = {
+ processData : false,
+ type : method,
+ data: data,
+ dataType : "json",
+ success : options.success || function() {},
+ error : this.create_basic_error_handler(options.error)
+ };
+ if (options.extra_settings) {
+ $.extend(settings, options.extra_settings);
+ }
+ $.ajax(this.api_url(path), settings);
+ };
+
+ Contents.prototype.delete = function(name, path, options) {
+ var error_callback = options.error || function() {};
+ var that = this;
+ var settings = {
+ processData : false,
+ type : "DELETE",
+ dataType : "json",
+ success : options.success || function() {},
+ error : function(xhr, status, error) {
+ // TODO: update IPEP27 to specify errors more precisely, so
+ // that error types can be detected here with certainty.
+ if (xhr.status === 400) {
+ error_callback(new Contents.DirectoryNotEmptyError());
+ }
+ error_callback(utils.wrap_ajax_error(xhr, status, error));
+ }
+ };
+ var url = this.api_url(path, name);
+ $.ajax(url, settings);
+ };
+
+ Contents.prototype.rename = function(path, name, new_path, new_name, options) {
+ var data = {name: new_name, path: new_path};
+ var settings = {
+ processData : false,
+ type : "PATCH",
+ data : JSON.stringify(data),
+ dataType: "json",
+ contentType: 'application/json',
+ success : options.success || function() {},
+ error : this.create_basic_error_handler(options.error)
+ };
+ var url = this.api_url(path, name);
+ $.ajax(url, settings);
+ };
+
+ Contents.prototype.save = function(path, name, model, options) {
+ // We do the call with settings so we can set cache to false.
+ var settings = {
+ processData : false,
+ type : "PUT",
+ data : JSON.stringify(model),
+ contentType: 'application/json',
+ success : options.success || function() {},
+ error : this.create_basic_error_handler(options.error)
+ };
+ if (options.extra_settings) {
+ $.extend(settings, options.extra_settings);
+ }
+ var url = this.api_url(path, name);
+ $.ajax(url, settings);
+ };
+
+ Contents.prototype.copy = function(to_path, to_name, from, options) {
+ var url, method;
+ if (to_name) {
+ url = this.api_url(to_path, to_name);
+ method = "PUT";
+ } else {
+ url = this.api_url(to_path);
+ method = "POST";
+ }
+
+ var settings = {
+ processData : false,
+ type: method,
+ data: JSON.stringify({copy_from: from}),
+ dataType : "json",
+ success: options.success || function() {},
+ error: this.create_basic_error_handler(options.error)
+ };
+ if (options.extra_settings) {
+ $.extend(settings, options.extra_settings);
+ }
+ $.ajax(url, settings);
+ };
+
+ /**
+ * Checkpointing Functions
+ */
+
+ Contents.prototype.create_checkpoint = function(path, name, options) {
+ var url = this.api_url(path, name, 'checkpoints');
+ var settings = {
+ type : "POST",
+ success: options.success || function() {},
+ error : this.create_basic_error_handler(options.error)
+ };
+ $.ajax(url, settings);
+ };
+
+ Contents.prototype.list_checkpoints = function(path, name, options) {
+ var url = this.api_url(path, name, 'checkpoints');
+ var settings = {
+ type : "GET",
+ success: options.success,
+ error : this.create_basic_error_handler(options.error)
+ };
+ $.ajax(url, settings);
+ };
+
+ Contents.prototype.restore_checkpoint = function(path, name, checkpoint_id, options) {
+ var url = this.api_url(path, name, 'checkpoints', checkpoint_id);
+ var settings = {
+ type : "POST",
+ success: options.success || function() {},
+ error : this.create_basic_error_handler(options.error)
+ };
+ $.ajax(url, settings);
+ };
+
+ Contents.prototype.delete_checkpoint = function(path, name, checkpoint_id, options) {
+ var url = this.api_url(path, name, 'checkpoints', checkpoint_id);
+ var settings = {
+ type : "DELETE",
+ success: options.success || function() {},
+ error : this.create_basic_error_handler(options.error)
+ };
+ $.ajax(url, settings);
+ };
+
+ /**
+ * File management functions
+ */
+
+ /**
+ * List notebooks and directories at a given path
+ *
+ * On success, load_callback is called with an array of dictionaries
+ * representing individual files or directories. Each dictionary has
+ * the keys:
+ * type: "notebook" or "directory"
+ * name: the name of the file or directory
+ * created: created date
+ * last_modified: last modified dat
+ * path: the path
+ * @method list_notebooks
+ * @param {String} path The path to list notebooks in
+ * @param {Function} load_callback called with list of notebooks on success
+ * @param {Function} error called with ajax results on error
+ */
+ Contents.prototype.list_contents = function(path, options) {
+ var settings = {
+ processData : false,
+ cache : false,
+ type : "GET",
+ dataType : "json",
+ success : options.success,
+ error : this.create_basic_error_handler(options.error)
+ };
+
+ $.ajax(this.api_url(path), settings);
+ };
+
+
+ IPython.Contents = Contents;
+
+ return {'Contents': Contents};
+});
diff --git a/IPython/html/static/tree/js/main.js b/IPython/html/static/tree/js/main.js
index 1f1975828..2396ad8db 100644
--- a/IPython/html/static/tree/js/main.js
+++ b/IPython/html/static/tree/js/main.js
@@ -7,6 +7,7 @@ require([
'base/js/events',
'base/js/page',
'base/js/utils',
+ 'contents',
'tree/js/notebooklist',
'tree/js/clusterlist',
'tree/js/sessionlist',
@@ -23,6 +24,7 @@ require([
events,
page,
utils,
+ contents,
notebooklist,
clusterlist,
sesssionlist,
@@ -39,7 +41,11 @@ require([
session_list = new sesssionlist.SesssionList($.extend({
events: events},
common_options));
+ contents = new contents.Contents($.extend({
+ events: events},
+ common_options));
notebook_list = new notebooklist.NotebookList('#notebook_list', $.extend({
+ contents: contents,
session_list: session_list},
common_options));
cluster_list = new clusterlist.ClusterList('#cluster_list', common_options);
@@ -54,7 +60,24 @@ require([
login_widget = new loginwidget.LoginWidget('#login_widget', common_options);
$('#new_notebook').click(function (e) {
- notebook_list.new_notebook();
+ contents.new(common_options.notebook_path, null, {
+ ext: ".ipynb",
+ extra_settings: {async: false}, // So we can open a new window afterwards
+ success: function (data) {
+ window.open(
+ utils.url_join_encode(
+ common_options.base_url, 'notebooks',
+ data.path, data.name
+ ), '_blank');
+ },
+ error: function(error) {
+ dialog.modal({
+ title : 'Creating Notebook Failed',
+ body : "The error was: " + error.message,
+ buttons : {'OK' : {'class' : 'btn-primary'}}
+ });
+ }
+ });
});
var interval_id=0;
@@ -118,5 +141,4 @@ require([
if (window.location.hash) {
$("#tabs").find("a[href=" + window.location.hash + "]").click();
}
-
});
diff --git a/IPython/html/static/tree/js/notebooklist.js b/IPython/html/static/tree/js/notebooklist.js
index 7f526d788..b36116720 100644
--- a/IPython/html/static/tree/js/notebooklist.js
+++ b/IPython/html/static/tree/js/notebooklist.js
@@ -20,6 +20,7 @@ define([
// element_name: string
// base_url: string
// notebook_path: string
+ // contents: Contents instance
var that = this;
this.session_list = options.session_list;
// allow code re-use by just changing element_name in kernellist.js
@@ -34,6 +35,7 @@ define([
this.sessions = {};
this.base_url = options.base_url || utils.get_body_data("baseUrl");
this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
+ this.contents = options.contents;
if (this.session_list && this.session_list.events) {
this.session_list.events.on('sessions_loaded.Dashboard',
function(e, d) { that.sessions_loaded(d); });
@@ -139,38 +141,27 @@ define([
};
NotebookList.prototype.load_list = function () {
- var that = this;
- var settings = {
- processData : false,
- cache : false,
- type : "GET",
- dataType : "json",
- success : $.proxy(this.list_loaded, this),
- error : $.proxy( function(xhr, status, error){
- utils.log_ajax_error(xhr, status, error);
- that.list_loaded([], null, null, {msg:"Error connecting to server."});
- },this)
- };
-
- var url = utils.url_join_encode(
- this.base_url,
- 'api',
- 'contents',
- this.notebook_path
- );
- $.ajax(url, settings);
+ var that = this
+ this.contents.list_contents(that.notebook_path, {
+ success: $.proxy(this.draw_notebook_list, this),
+ error: function(error) {
+ that.draw_notebook_list({content: []}, "Server error: " + error.message);
+ }
+ });
};
-
- NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
- var message = 'Notebook list empty.';
- if (param !== undefined && param.msg) {
- message = param.msg;
- }
+ /**
+ * Draw the list of notebooks
+ * @method draw_notebook_list
+ * @param {Array} list An array of dictionaries representing files or
+ * directories.
+ * @param {String} error_msg An error message
+ */
+ NotebookList.prototype.draw_notebook_list = function (list, error_msg) {
+ var message = error_msg || 'Notebook list empty.';
var item = null;
var model = null;
- var list = data.content;
- var len = list.length;
+ var len = list.content.length;
this.clear_list();
var n_uploads = this.element.children('.list_item').length;
if (len === 0) {
@@ -192,7 +183,7 @@ define([
offset += 1;
}
for (var i=0; i