diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py
index 70a1b13..14e5487 100644
--- a/IPython/html/base/handlers.py
+++ b/IPython/html/base/handlers.py
@@ -363,7 +363,8 @@ def json_errors(method):
message = e.log_message
self.log.warn(message)
self.set_status(e.status_code)
- self.finish(json.dumps(dict(message=message)))
+ reply = dict(message=message, reason=e.reason)
+ self.finish(json.dumps(reply))
except Exception:
self.log.error("Unhandled error in API request", exc_info=True)
status = 500
@@ -371,7 +372,7 @@ def json_errors(method):
t, value, tb = sys.exc_info()
self.set_status(status)
tb_text = ''.join(traceback.format_exception(t, value, tb))
- reply = dict(message=message, traceback=tb_text)
+ reply = dict(message=message, reason=None, traceback=tb_text)
self.finish(json.dumps(reply))
else:
return result
diff --git a/IPython/html/services/contents/filemanager.py b/IPython/html/services/contents/filemanager.py
index 51e0d85..707ca93 100644
--- a/IPython/html/services/contents/filemanager.py
+++ b/IPython/html/services/contents/filemanager.py
@@ -281,7 +281,7 @@ class FileContentsManager(ContentsManager):
model['content'] = bcontent.decode('utf8')
except UnicodeError as e:
if format == 'text':
- raise web.HTTPError(400, "%s is not UTF-8 encoded" % path)
+ raise web.HTTPError(400, "%s is not UTF-8 encoded" % path, reason='bad format')
else:
model['format'] = 'text'
default_mime = 'text/plain'
@@ -348,14 +348,14 @@ class FileContentsManager(ContentsManager):
if os.path.isdir(os_path):
if type_ not in (None, 'directory'):
raise web.HTTPError(400,
- u'%s is a directory, not a %s' % (path, type_))
+ u'%s is a directory, not a %s' % (path, type_), reason='bad type')
model = self._dir_model(path, content=content)
elif type_ == 'notebook' or (type_ is None and path.endswith('.ipynb')):
model = self._notebook_model(path, content=content)
else:
if type_ == 'directory':
raise web.HTTPError(400,
- u'%s is not a directory')
+ u'%s is not a directory', reason='bad type')
model = self._file_model(path, content=content, format=format)
return model
diff --git a/IPython/html/services/contents/handlers.py b/IPython/html/services/contents/handlers.py
index 121e325..a03a2e2 100644
--- a/IPython/html/services/contents/handlers.py
+++ b/IPython/html/services/contents/handlers.py
@@ -47,6 +47,7 @@ class ContentsHandler(IPythonHandler):
location = self.location_url(model['path'])
self.set_header('Location', location)
self.set_header('Last-Modified', model['last_modified'])
+ self.set_header('Content-Type', 'application/json')
self.finish(json.dumps(model, default=date_default))
@web.authenticated
diff --git a/IPython/html/static/edit/js/editor.js b/IPython/html/static/edit/js/editor.js
index 53320b6..319b472 100644
--- a/IPython/html/static/edit/js/editor.js
+++ b/IPython/html/static/edit/js/editor.js
@@ -6,20 +6,32 @@ define([
'base/js/utils',
'codemirror/lib/codemirror',
'codemirror/mode/meta',
- 'codemirror/addon/search/search'
+ 'codemirror/addon/comment/comment',
+ 'codemirror/addon/dialog/dialog',
+ 'codemirror/addon/edit/closebrackets',
+ 'codemirror/addon/edit/matchbrackets',
+ 'codemirror/addon/search/searchcursor',
+ 'codemirror/addon/search/search',
+ 'codemirror/keymap/emacs',
+ 'codemirror/keymap/sublime',
+ 'codemirror/keymap/vim',
],
function($,
utils,
CodeMirror
) {
+ "use strict";
+
var Editor = function(selector, options) {
+ var that = this;
this.selector = selector;
this.contents = options.contents;
this.events = options.events;
this.base_url = options.base_url;
this.file_path = options.file_path;
-
- this.codemirror = CodeMirror($(this.selector)[0]);
+ this.config = options.config;
+ this.codemirror = new CodeMirror($(this.selector)[0]);
+ this.generation = -1;
// It appears we have to set commands on the CodeMirror class, not the
// instance. I'd like to be wrong, but since there should only be one CM
@@ -27,27 +39,55 @@ function($,
CodeMirror.commands.save = $.proxy(this.save, this);
this.save_enabled = false;
+
+ this.config.loaded.then(function () {
+ // load codemirror config
+ var cfg = that.config.data.Editor || {};
+ var cmopts = $.extend(true, {}, // true = recursive copy
+ Editor.default_codemirror_options,
+ cfg.codemirror_options || {}
+ );
+ that._set_codemirror_options(cmopts);
+ that.events.trigger('config_changed.Editor', {config: that.config});
+ });
+ };
+
+ // default CodeMirror options
+ Editor.default_codemirror_options = {
+ extraKeys: {
+ "Tab" : "indentMore",
+ },
+ indentUnit: 4,
+ theme: "ipython",
+ lineNumbers: true,
+ lineWrapping: true,
};
Editor.prototype.load = function() {
+ /** load the file */
var that = this;
var cm = this.codemirror;
- this.contents.get(this.file_path, {type: 'file', format: 'text'})
+ return this.contents.get(this.file_path, {type: 'file', format: 'text'})
.then(function(model) {
cm.setValue(model.content);
// Setting the file's initial value creates a history entry,
// which we don't want.
cm.clearHistory();
-
- // Find and load the highlighting mode
- utils.requireCodeMirrorMode(model.mimetype, function(spec) {
- var mode = CodeMirror.getMode({}, spec);
- cm.setOption('mode', mode);
- });
+ that._set_mode_for_model(model);
that.save_enabled = true;
+ that.generation = cm.changeGeneration();
+ that.events.trigger("file_loaded.Editor", model);
},
function(error) {
+ that.events.trigger("file_load_failed.Editor", error);
+ if (error.xhr.responseJSON.reason === 'bad format') {
+ window.location = utils.url_path_join(
+ that.base_url,
+ 'files',
+ that.file_path
+ );
+ }
cm.setValue("Error! " + error.message +
"\nSaving disabled.");
that.save_enabled = false;
@@ -55,7 +95,55 @@ function($,
);
};
- Editor.prototype.save = function() {
+ Editor.prototype._set_mode_for_model = function (model) {
+ /** Set the CodeMirror mode based on the file model */
+
+ // Find and load the highlighting mode,
+ // first by mime-type, then by file extension
+ var modeinfo = CodeMirror.findModeByMIME(model.mimetype);
+ if (modeinfo.mode === "null") {
+ // find by mime failed, use find by ext
+ var ext_idx = model.name.lastIndexOf('.');
+
+ if (ext_idx > 0) {
+ // CodeMirror.findModeByExtension wants extension without '.'
+ modeinfo = CodeMirror.findModeByExtension(model.name.slice(ext_idx + 1));
+ }
+ }
+ if (modeinfo) {
+ this.set_codemirror_mode(modeinfo);
+ }
+ };
+
+ Editor.prototype.set_codemirror_mode = function (modeinfo) {
+ /** set the codemirror mode from a modeinfo struct */
+ var that = this;
+ utils.requireCodeMirrorMode(modeinfo, function () {
+ that.codemirror.setOption('mode', modeinfo.mode);
+ that.events.trigger("mode_changed.Editor", modeinfo);
+ });
+ };
+
+ Editor.prototype.get_filename = function () {
+ return utils.url_path_split(this.file_path)[1];
+ };
+
+ Editor.prototype.rename = function (new_name) {
+ /** rename the file */
+ var that = this;
+ var parent = utils.url_path_split(this.file_path)[0];
+ var new_path = utils.url_path_join(parent, new_name);
+ return this.contents.rename(this.file_path, new_path).then(
+ function (model) {
+ that.file_path = model.path;
+ that.events.trigger('file_renamed.Editor', model);
+ that._set_mode_for_model(model);
+ }
+ );
+ };
+
+ Editor.prototype.save = function () {
+ /** save the file */
if (!this.save_enabled) {
console.log("Not saving, save disabled");
return;
@@ -67,10 +155,36 @@ function($,
content: this.codemirror.getValue(),
};
var that = this;
- this.contents.save(this.file_path, model).then(function() {
- that.events.trigger("save_succeeded.TextEditor");
+ // record change generation for isClean
+ this.generation = this.codemirror.changeGeneration();
+ return this.contents.save(this.file_path, model).then(function(data) {
+ that.events.trigger("file_saved.Editor", data);
});
};
+
+ Editor.prototype._set_codemirror_options = function (options) {
+ // update codemirror options from a dict
+ var codemirror = this.codemirror;
+ $.map(options, function (value, opt) {
+ if (value === null) {
+ value = CodeMirror.defaults[opt];
+ }
+ codemirror.setOption(opt, value);
+ });
+ };
+
+ Editor.prototype.update_codemirror_options = function (options) {
+ /** update codemirror options locally and save changes in config */
+ var that = this;
+ this._set_codemirror_options(options);
+ return this.config.update({
+ Editor: {
+ codemirror_options: options
+ }
+ }).then(
+ that.events.trigger('config_changed.Editor', {config: that.config})
+ );
+ };
return {Editor: Editor};
});
diff --git a/IPython/html/static/edit/js/main.js b/IPython/html/static/edit/js/main.js
index ec787d4..2ff2845 100644
--- a/IPython/html/static/edit/js/main.js
+++ b/IPython/html/static/edit/js/main.js
@@ -10,6 +10,7 @@ require([
'services/config',
'edit/js/editor',
'edit/js/menubar',
+ 'edit/js/savewidget',
'edit/js/notificationarea',
'custom/custom',
], function(
@@ -19,8 +20,9 @@ require([
events,
contents,
configmod,
- editor,
+ editmod,
menubar,
+ savewidget,
notificationarea
){
page = new page.Page();
@@ -28,22 +30,30 @@ require([
var base_url = utils.get_body_data('baseUrl');
var file_path = utils.get_body_data('filePath');
contents = new contents.Contents({base_url: base_url});
- var config = new configmod.ConfigSection('edit', {base_url: base_url})
+ var config = new configmod.ConfigSection('edit', {base_url: base_url});
config.load();
- var editor = new editor.Editor('#texteditor-container', {
+ var editor = new editmod.Editor('#texteditor-container', {
base_url: base_url,
events: events,
contents: contents,
file_path: file_path,
+ config: config,
});
// Make it available for debugging
IPython.editor = editor;
+ var save_widget = new savewidget.SaveWidget('span#save_widget', {
+ editor: editor,
+ events: events,
+ });
+
var menus = new menubar.MenuBar('#menubar', {
base_url: base_url,
editor: editor,
+ events: events,
+ save_widget: save_widget,
});
var notification_area = new notificationarea.EditorNotificationArea(
@@ -61,4 +71,11 @@ require([
});
editor.load();
page.show();
+
+ window.onbeforeunload = function () {
+ if (editor.save_enabled && !editor.codemirror.isClean(editor.generation)) {
+ return "Unsaved changes will be lost. Close anyway?";
+ }
+ };
+
});
diff --git a/IPython/html/static/edit/js/menubar.js b/IPython/html/static/edit/js/menubar.js
index 15b1b4e..374ebe2 100644
--- a/IPython/html/static/edit/js/menubar.js
+++ b/IPython/html/static/edit/js/menubar.js
@@ -2,11 +2,14 @@
// Distributed under the terms of the Modified BSD License.
define([
- 'base/js/namespace',
'jquery',
+ 'base/js/namespace',
'base/js/utils',
+ 'base/js/dialog',
+ 'codemirror/lib/codemirror',
+ 'codemirror/mode/meta',
'bootstrap',
-], function(IPython, $, utils, bootstrap) {
+], function($, IPython, utils, dialog, CodeMirror) {
"use strict";
var MenuBar = function (selector, options) {
@@ -29,21 +32,123 @@ define([
this.base_url = options.base_url || utils.get_body_data("baseUrl");
this.selector = selector;
this.editor = options.editor;
+ this.events = options.events;
+ this.save_widget = options.save_widget;
if (this.selector !== undefined) {
this.element = $(selector);
this.bind_events();
}
+ this._load_mode_menu();
+ Object.seal(this);
};
MenuBar.prototype.bind_events = function () {
- /**
- * File
- */
var that = this;
- this.element.find('#save_file').click(function () {
- that.editor.save();
+ var editor = that.editor;
+
+ // File
+ this.element.find('#new-file').click(function () {
+ var w = window.open();
+ // Create a new file in the current directory
+ var parent = utils.url_path_split(editor.file_path)[0];
+ editor.contents.new_untitled(parent, {type: "file"}).then(
+ function (data) {
+ w.location = utils.url_join_encode(
+ that.base_url, 'edit', data.path
+ );
+ },
+ function(error) {
+ w.close();
+ dialog.modal({
+ title : 'Creating New File Failed',
+ body : "The error was: " + error.message,
+ buttons : {'OK' : {'class' : 'btn-primary'}}
+ });
+ }
+ );
+ });
+ this.element.find('#save-file').click(function () {
+ editor.save();
+ });
+ this.element.find('#rename-file').click(function () {
+ that.save_widget.rename();
+ });
+
+ // Edit
+ this.element.find('#menu-find').click(function () {
+ editor.codemirror.execCommand("find");
+ });
+ this.element.find('#menu-replace').click(function () {
+ editor.codemirror.execCommand("replace");
+ });
+ this.element.find('#menu-keymap-default').click(function () {
+ editor.update_codemirror_options({
+ vimMode: false,
+ keyMap: 'default'
+ });
+ });
+ this.element.find('#menu-keymap-sublime').click(function () {
+ editor.update_codemirror_options({
+ vimMode: false,
+ keyMap: 'sublime'
+ });
});
+ this.element.find('#menu-keymap-emacs').click(function () {
+ editor.update_codemirror_options({
+ vimMode: false,
+ keyMap: 'emacs'
+ });
+ });
+ this.element.find('#menu-keymap-vim').click(function () {
+ editor.update_codemirror_options({
+ vimMode: true,
+ keyMap: 'vim'
+ });
+ });
+
+ // View
+ this.element.find('#menu-line-numbers').click(function () {
+ var current = editor.codemirror.getOption('lineNumbers');
+ var value = Boolean(1-current);
+ editor.update_codemirror_options({lineNumbers: value});
+ });
+
+ this.events.on("config_changed.Editor", function () {
+ var keyMap = editor.codemirror.getOption('keyMap') || "default";
+ that.element.find(".selected-keymap").removeClass("selected-keymap");
+ that.element.find("#menu-keymap-" + keyMap).addClass("selected-keymap");
+ });
+
+ this.events.on("mode_changed.Editor", function (evt, modeinfo) {
+ that.element.find("#current-mode")
+ .text(modeinfo.name)
+ .attr(
+ 'title',
+ "The current language is " + modeinfo.name
+ );
+ });
+ };
+
+ MenuBar.prototype._load_mode_menu = function () {
+ var list = this.element.find("#mode-menu");
+ var editor = this.editor;
+ function make_set_mode(info) {
+ return function () {
+ editor.set_codemirror_mode(info);
+ };
+ }
+ for (var i = 0; i < CodeMirror.modeInfo.length; i++) {
+ var info = CodeMirror.modeInfo[i];
+ list.append($("
").append(
+ $("").attr("href", "#")
+ .text(info.name)
+ .click(make_set_mode(info))
+ .attr('title',
+ "Set language to " + info.name
+ )
+ ));
+ }
};
return {'MenuBar': MenuBar};
diff --git a/IPython/html/static/edit/js/savewidget.js b/IPython/html/static/edit/js/savewidget.js
new file mode 100644
index 0000000..5688780
--- /dev/null
+++ b/IPython/html/static/edit/js/savewidget.js
@@ -0,0 +1,202 @@
+// 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',
+ 'base/js/keyboard',
+ 'moment',
+], function(IPython, $, utils, dialog, keyboard, moment) {
+ "use strict";
+
+ var SaveWidget = function (selector, options) {
+ this.editor = undefined;
+ this.selector = selector;
+ this.events = options.events;
+ this.editor = options.editor;
+ this._last_modified = undefined;
+ this.keyboard_manager = options.keyboard_manager;
+ if (this.selector !== undefined) {
+ this.element = $(selector);
+ this.bind_events();
+ }
+ };
+
+
+ SaveWidget.prototype.bind_events = function () {
+ var that = this;
+ this.element.find('span.filename').click(function () {
+ that.rename();
+ });
+ this.events.on('file_loaded.Editor', function (evt, model) {
+ that.update_filename(model.name);
+ that.update_document_title(model.name);
+ that.update_last_modified(model.last_modified);
+ });
+ this.events.on('file_saved.Editor', function (evt, model) {
+ that.update_filename(model.name);
+ that.update_document_title(model.name);
+ that.update_last_modified(model.last_modified);
+ });
+ this.events.on('file_renamed.Editor', function (evt, model) {
+ that.update_filename(model.name);
+ that.update_document_title(model.name);
+ that.update_address_bar(model.path);
+ });
+ this.events.on('file_save_failed.Editor', function () {
+ that.set_save_status('Save Failed!');
+ });
+ };
+
+
+ SaveWidget.prototype.rename = function (options) {
+ options = options || {};
+ var that = this;
+ var dialog_body = $('').append(
+ $("").addClass("rename-message")
+ .text('Enter a new filename:')
+ ).append(
+ $("
")
+ ).append(
+ $('').attr('type','text').attr('size','25').addClass('form-control')
+ .val(that.editor.get_filename())
+ );
+ var d = dialog.modal({
+ title: "Rename File",
+ body: dialog_body,
+ buttons : {
+ "OK": {
+ class: "btn-primary",
+ click: function () {
+ var new_name = d.find('input').val();
+ d.find('.rename-message').text("Renaming...");
+ d.find('input[type="text"]').prop('disabled', true);
+ that.editor.rename(new_name).then(
+ function () {
+ d.modal('hide');
+ }, function (error) {
+ d.find('.rename-message').text(error.message || 'Unknown error');
+ d.find('input[type="text"]').prop('disabled', false).focus().select();
+ }
+ );
+ return false;
+ }
+ },
+ "Cancel": {}
+ },
+ open : function () {
+ // Upon ENTER, click the OK button.
+ d.find('input[type="text"]').keydown(function (event) {
+ if (event.which === keyboard.keycodes.enter) {
+ d.find('.btn-primary').first().click();
+ return false;
+ }
+ });
+ d.find('input[type="text"]').focus().select();
+ }
+ });
+ };
+
+
+ SaveWidget.prototype.update_filename = function (filename) {
+ this.element.find('span.filename').text(filename);
+ };
+
+ SaveWidget.prototype.update_document_title = function (filename) {
+ document.title = filename;
+ };
+
+ SaveWidget.prototype.update_address_bar = function (path) {
+ var state = {path : path};
+ window.history.replaceState(state, "", utils.url_join_encode(
+ this.editor.base_url,
+ "edit",
+ path)
+ );
+ };
+
+ SaveWidget.prototype.update_last_modified = function (last_modified) {
+ if (last_modified) {
+ this._last_modified = new Date(last_modified);
+ } else {
+ this._last_modified = null;
+ }
+ this._render_last_modified();
+ };
+
+ SaveWidget.prototype._render_last_modified = function () {
+ /** actually set the text in the element, from our _last_modified value
+
+ called directly, and periodically in timeouts.
+ */
+ this._schedule_render_last_modified();
+ var el = this.element.find('span.last_modified');
+ if (!this._last_modified) {
+ el.text('').attr('title', 'never saved');
+ return;
+ }
+ var chkd = moment(this._last_modified);
+ var long_date = chkd.format('llll');
+ var human_date;
+ var tdelta = Math.ceil(new Date() - this._last_modified);
+ if (tdelta < 24 * H){
+ // less than 24 hours old, use relative date
+ human_date = chkd.fromNow();
+ } else {
+ // otherwise show calendar
+ // otherwise update every hour and show
+ // at hh,mm,ss
+ human_date = chkd.calendar();
+ }
+ el.text(human_date).attr('title', long_date);
+ };
+
+
+ var S = 1000;
+ var M = 60*S;
+ var H = 60*M;
+ var thresholds = {
+ s: 45 * S,
+ m: 45 * M,
+ h: 22 * H
+ };
+ var _timeout_from_dt = function (ms) {
+ /** compute a timeout to update the last-modified timeout
+
+ based on the delta in milliseconds
+ */
+ if (ms < thresholds.s) {
+ return 5 * S;
+ } else if (ms < thresholds.m) {
+ return M;
+ } else {
+ return 5 * M;
+ }
+ };
+
+ SaveWidget.prototype._schedule_render_last_modified = function () {
+ /** schedule the next update to relative date
+
+ periodically updated, so short values like 'a few seconds ago' don't get stale.
+ */
+ var that = this;
+ if (!this._last_modified) {
+ return;
+ }
+ if ((this._last_modified_timeout)) {
+ clearTimeout(this._last_modified_timeout);
+ }
+ var dt = Math.ceil(new Date() - this._last_modified);
+ if (dt < 24 * H) {
+ this._last_modified_timeout = setTimeout(
+ $.proxy(this._render_last_modified, this),
+ _timeout_from_dt(dt)
+ );
+ }
+ };
+
+ return {'SaveWidget': SaveWidget};
+
+});
diff --git a/IPython/html/static/edit/less/edit.less b/IPython/html/static/edit/less/edit.less
new file mode 100644
index 0000000..072ffd5
--- /dev/null
+++ b/IPython/html/static/edit/less/edit.less
@@ -0,0 +1,9 @@
+#texteditor-container {
+ border-bottom: 1px solid #ccc;
+}
+
+#filename {
+ font-size: 16pt;
+ display: table;
+ padding: 0px 5px;
+}
diff --git a/IPython/html/static/edit/less/menubar.less b/IPython/html/static/edit/less/menubar.less
new file mode 100644
index 0000000..f709333
--- /dev/null
+++ b/IPython/html/static/edit/less/menubar.less
@@ -0,0 +1,14 @@
+.selected-keymap {
+ i.fa {
+ padding: 0px 5px;
+ }
+ i.fa:before {
+ content: @fa-var-check;
+ }
+}
+
+#mode-menu {
+ // truncate mode-menu, so it doesn't get longer than the screen
+ overflow: auto;
+ max-height: 20em;
+}
\ No newline at end of file
diff --git a/IPython/html/static/edit/less/style.less b/IPython/html/static/edit/less/style.less
new file mode 100644
index 0000000..b365b93
--- /dev/null
+++ b/IPython/html/static/edit/less/style.less
@@ -0,0 +1,7 @@
+/*!
+*
+* IPython text editor webapp
+*
+*/
+@import "menubar.less";
+@import "edit.less";
diff --git a/IPython/html/static/notebook/js/savewidget.js b/IPython/html/static/notebook/js/savewidget.js
index d69b32c..6b9db96 100644
--- a/IPython/html/static/notebook/js/savewidget.js
+++ b/IPython/html/static/notebook/js/savewidget.js
@@ -29,7 +29,7 @@ define([
SaveWidget.prototype.bind_events = function () {
var that = this;
- this.element.find('span#notebook_name').click(function () {
+ this.element.find('span.filename').click(function () {
that.rename_notebook({notebook: that.notebook});
});
this.events.on('notebook_loaded.Notebook', function () {
@@ -130,7 +130,7 @@ define([
SaveWidget.prototype.update_notebook_name = function () {
var nbname = this.notebook.get_notebook_name();
- this.element.find('span#notebook_name').text(nbname);
+ this.element.find('span.filename').text(nbname);
};
@@ -152,11 +152,11 @@ define([
SaveWidget.prototype.set_save_status = function (msg) {
- this.element.find('span#autosave_status').text(msg);
+ this.element.find('span.autosave_status').text(msg);
};
SaveWidget.prototype._set_checkpoint_status = function (human_date, iso_date) {
- var el = this.element.find('span#checkpoint_status');
+ var el = this.element.find('span.checkpoint_status');
if(human_date){
el.text("Last Checkpoint: "+human_date).attr('title',iso_date);
} else {
@@ -223,7 +223,7 @@ define([
// update regularly for the first 6hours and show
// ago
- if(tdelta < tdelta < 6*3600*1000){
+ if(tdelta < 6*3600*1000){
recall(_next_timeago_update(tdelta));
this._set_checkpoint_status(chkd.fromNow(), longdate);
// otherwise update every hour and show
diff --git a/IPython/html/static/notebook/less/notebook.less b/IPython/html/static/notebook/less/notebook.less
index 58ac2df..86b5cb7 100644
--- a/IPython/html/static/notebook/less/notebook.less
+++ b/IPython/html/static/notebook/less/notebook.less
@@ -15,20 +15,6 @@ body {
.border-box-sizing();
}
-span#notebook_name {
- height: 1em;
- line-height: 1em;
- padding: 3px;
- border: none;
- font-size: 146.5%;
- &:hover{
- // ensure body is lighter on dark palette,
- // and vice versa
- background-color:contrast(@body-bg, lighten(@body-bg,30%), darken(@body-bg,10%));
- }
- .corner-all;
-}
-
div#notebook_panel {
margin: 0px 0px 0px 0px;
padding: 0px;
diff --git a/IPython/html/static/notebook/less/savewidget.less b/IPython/html/static/notebook/less/savewidget.less
index 73c7922..28954a1 100644
--- a/IPython/html/static/notebook/less/savewidget.less
+++ b/IPython/html/static/notebook/less/savewidget.less
@@ -1,33 +1,41 @@
-span#save_widget {
+span.save_widget {
margin-top: 6px;
+
+ span.filename {
+ height: 1em;
+ line-height: 1em;
+ padding: 3px;
+ border: none;
+ font-size: 146.5%;
+ &:hover{
+ // ensure body is lighter on dark palette,
+ // and vice versa
+ background-color:contrast(@body-bg, lighten(@body-bg,30%), darken(@body-bg,10%));
+ }
+ .corner-all;
+ }
}
-span#checkpoint_status, span#autosave_status {
+span.checkpoint_status, span.autosave_status {
font-size: small;
}
@media (max-width: 767px) {
- span#save_widget {
+ span.save_widget {
font-size: small;
}
- span#checkpoint_status, span#autosave_status {
- font-size: x-small;
- }
-}
-
-@media (max-width: 767px) {
- span#checkpoint_status, span#autosave_status {
- display: none;
+ span.checkpoint_status, span.autosave_status {
+ display: none;
}
}
@media (min-width: 768px) and (max-width: 979px) {
- span#checkpoint_status {
+ span.checkpoint_status {
display: none;
}
- span#autosave_status {
+ span.autosave_status {
font-size: x-small;
}
}
-
+
diff --git a/IPython/html/static/services/contents.js b/IPython/html/static/services/contents.js
index 768bdd4..994244d 100644
--- a/IPython/html/static/services/contents.js
+++ b/IPython/html/static/services/contents.js
@@ -162,6 +162,7 @@ define([
var settings = {
processData : false,
type : "PUT",
+ dataType: "json",
data : JSON.stringify(model),
contentType: 'application/json',
};
diff --git a/IPython/html/static/style/style.less b/IPython/html/static/style/style.less
index ad5130d..b4113bc 100644
--- a/IPython/html/static/style/style.less
+++ b/IPython/html/static/style/style.less
@@ -23,6 +23,9 @@
// tree
@import "../tree/less/style.less";
+// edit
+@import "../edit/less/style.less";
+
// notebook
@import "../notebook/less/style.less";
diff --git a/IPython/html/static/style/style.min.css b/IPython/html/static/style/style.min.css
index 4a59dd8..c42bee0 100644
--- a/IPython/html/static/style/style.min.css
+++ b/IPython/html/static/style/style.min.css
@@ -7752,9 +7752,6 @@ div#header {
/* Initially hidden to prevent FLOUC */
display: none;
background-color: #ffffff;
- box-sizing: border-box;
- -moz-box-sizing: border-box;
- -webkit-box-sizing: border-box;
/* Display over codemirror */
z-index: 100;
}
@@ -8119,6 +8116,29 @@ ul#new-notebook-menu {
}
/*!
*
+* IPython text editor webapp
+*
+*/
+.selected-keymap i.fa {
+ padding: 0px 5px;
+}
+.selected-keymap i.fa:before {
+ content: "\f00c";
+}
+#mode-menu {
+ overflow: auto;
+ max-height: 20em;
+}
+#texteditor-container {
+ border-bottom: 1px solid #ccc;
+}
+#filename {
+ font-size: 16pt;
+ display: table;
+ padding: 0px 5px;
+}
+/*!
+*
* IPython notebook
*
*/
@@ -9421,17 +9441,6 @@ body {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
}
-span#notebook_name {
- height: 1em;
- line-height: 1em;
- padding: 3px;
- border: none;
- font-size: 146.5%;
- border-radius: 4px;
-}
-span#notebook_name:hover {
- background-color: #e6e6e6;
-}
div#notebook_panel {
margin: 0px 0px 0px 0px;
padding: 0px;
@@ -9681,7 +9690,6 @@ fieldset[disabled] #kernel_selector_widget > button.active {
margin-top: 0px;
}
#menubar {
- margin-top: 0px;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
@@ -10159,33 +10167,38 @@ div#pager .ui-resizable-handle {
/* Modern browsers */
flex: 1;
}
-span#save_widget {
+span.save_widget {
margin-top: 6px;
}
-span#checkpoint_status,
-span#autosave_status {
+span.save_widget span.filename {
+ height: 1em;
+ line-height: 1em;
+ padding: 3px;
+ border: none;
+ font-size: 146.5%;
+ border-radius: 4px;
+}
+span.save_widget span.filename:hover {
+ background-color: #e6e6e6;
+}
+span.checkpoint_status,
+span.autosave_status {
font-size: small;
}
@media (max-width: 767px) {
- span#save_widget {
+ span.save_widget {
font-size: small;
}
- span#checkpoint_status,
- span#autosave_status {
- font-size: x-small;
- }
-}
-@media (max-width: 767px) {
- span#checkpoint_status,
- span#autosave_status {
+ span.checkpoint_status,
+ span.autosave_status {
display: none;
}
}
@media (min-width: 768px) and (max-width: 979px) {
- span#checkpoint_status {
+ span.checkpoint_status {
display: none;
}
- span#autosave_status {
+ span.autosave_status {
font-size: x-small;
}
}
diff --git a/IPython/html/static/tree/js/notebooklist.js b/IPython/html/static/tree/js/notebooklist.js
index 984faf5..40c3f04 100644
--- a/IPython/html/static/tree/js/notebooklist.js
+++ b/IPython/html/static/tree/js/notebooklist.js
@@ -229,7 +229,7 @@ define([
NotebookList.uri_prefixes = {
directory: 'tree',
notebook: 'notebooks',
- file: 'files',
+ file: 'edit',
};
diff --git a/IPython/html/templates/edit.html b/IPython/html/templates/edit.html
index 7841bd4..fdf30ca 100644
--- a/IPython/html/templates/edit.html
+++ b/IPython/html/templates/edit.html
@@ -5,18 +5,6 @@
{% block stylesheet %}
-
-
{{super()}}
{% endblock %}
@@ -27,38 +15,70 @@ data-file-path="{{file_path}}"
{% endblock %}
-{% block header %}
+{% block headercontainer %}
-{{ basename }}
+
+
+
+
{% endblock %}
-{% block site %}
+{% block header %}