mirror of
https://github.com/jupyter/notebook.git
synced 2024-12-27 04:20:22 +08:00
Merge pull request #6783 from takluyver/contentsmanager-js
contents.js wrapper for contents service
This commit is contained in:
commit
ef49e32ef0
@ -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):
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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(
|
||||
|
@ -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";
|
||||
Notebook.prototype.rename = function (new_name) {
|
||||
if (!new_name.match(/\.ipynb$/)) {
|
||||
new_name = new_name + ".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);
|
||||
|
||||
var that = this;
|
||||
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.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.delete(this.notebook_name, this.notebook_path);
|
||||
};
|
||||
|
||||
|
||||
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.rename_error = function (xhr, status, error) {
|
||||
Notebook.prototype.rename_error = function (error) {
|
||||
var that = this;
|
||||
var dialog_body = $('<div/>').append(
|
||||
$("<p/>").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
|
||||
* @param {Error} error
|
||||
*/
|
||||
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 = $("<div>");
|
||||
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. " +
|
||||
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;
|
||||
|
285
IPython/html/static/services/contents.js
Normal file
285
IPython/html/static/services/contents.js
Normal file
@ -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};
|
||||
});
|
@ -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();
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -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<len; i++) {
|
||||
model = list[i];
|
||||
model = list.content[i];
|
||||
item = this.new_item(i+offset);
|
||||
this.add_link(model, item);
|
||||
}
|
||||
@ -329,8 +320,9 @@ define([
|
||||
// We use the filename from the parent list_item element's
|
||||
// data because the outer scope's values change as we iterate through the loop.
|
||||
var parent_item = that.parents('div.list_item');
|
||||
var name = parent_item.data('name');
|
||||
var message = 'Are you sure you want to permanently delete the file: ' + name + '?';
|
||||
var name = parent_item.data('nbname');
|
||||
var path = parent_item.data('path');
|
||||
var message = 'Are you sure you want to permanently delete the file: ' + nbname + '?';
|
||||
dialog.modal({
|
||||
title : "Delete file",
|
||||
body : message,
|
||||
@ -338,23 +330,11 @@ define([
|
||||
Delete : {
|
||||
class: "btn-danger",
|
||||
click: function() {
|
||||
var settings = {
|
||||
processData : false,
|
||||
cache : false,
|
||||
type : "DELETE",
|
||||
dataType : "json",
|
||||
success : function (data, status, xhr) {
|
||||
parent_item.remove();
|
||||
},
|
||||
error : utils.log_ajax_error,
|
||||
};
|
||||
var url = utils.url_join_encode(
|
||||
notebooklist.base_url,
|
||||
'api/contents',
|
||||
notebooklist.notebook_path,
|
||||
name
|
||||
);
|
||||
$.ajax(url, settings);
|
||||
notebooklist.contents.delete(name, path, {
|
||||
success: function() {
|
||||
notebooklist.notebook_deleted(path, name);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
Cancel : {}
|
||||
@ -365,6 +345,17 @@ define([
|
||||
item.find(".item_buttons").text("").append(delete_button);
|
||||
};
|
||||
|
||||
NotebookList.prototype.notebook_deleted = function(path, name) {
|
||||
// Remove the deleted notebook.
|
||||
$( ":data(nbname)" ).each(function() {
|
||||
var element = $( this );
|
||||
if (element.data( "nbname" ) == d.name &&
|
||||
element.data( "path" ) == d.path) {
|
||||
element.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
NotebookList.prototype.add_upload_button = function (item, type) {
|
||||
var that = this;
|
||||
@ -487,53 +478,7 @@ define([
|
||||
};
|
||||
|
||||
|
||||
NotebookList.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 : $.proxy(this.new_notebook_failed, this),
|
||||
};
|
||||
var url = utils.url_join_encode(
|
||||
base_url,
|
||||
'api/contents',
|
||||
path
|
||||
);
|
||||
$.ajax(url, settings);
|
||||
};
|
||||
|
||||
|
||||
NotebookList.prototype.new_notebook_failed = function (xhr, status, error) {
|
||||
utils.log_ajax_error(xhr, status, error);
|
||||
var msg;
|
||||
if (xhr.responseJSON && xhr.responseJSON.message) {
|
||||
msg = xhr.responseJSON.message;
|
||||
} else {
|
||||
msg = xhr.statusText;
|
||||
}
|
||||
dialog.modal({
|
||||
title : 'Creating Notebook Failed',
|
||||
body : "The error was: " + msg,
|
||||
buttons : {'OK' : {'class' : 'btn-primary'}}
|
||||
});
|
||||
};
|
||||
|
||||
// Backwards compatability.
|
||||
// Backwards compatability.
|
||||
IPython.NotebookList = NotebookList;
|
||||
|
||||
return {'NotebookList': NotebookList};
|
||||
|
@ -29,7 +29,8 @@
|
||||
highlight: 'components/highlight.js/build/highlight.pack',
|
||||
moment: "components/moment/moment",
|
||||
codemirror: 'components/codemirror',
|
||||
termjs: "components/term.js/src/term"
|
||||
termjs: "components/term.js/src/term",
|
||||
contents: '{{ contents_js_source }}',
|
||||
},
|
||||
shim: {
|
||||
underscore: {
|
||||
|
Loading…
Reference in New Issue
Block a user