##// END OF EJS Templates
Merge pull request #7128 from minrk/more-v-less-m...
Thomas Kluyver -
r19383:aacc2374 merge
parent child Browse files
Show More
@@ -0,0 +1,202 b''
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
3
4 define([
5 'base/js/namespace',
6 'jquery',
7 'base/js/utils',
8 'base/js/dialog',
9 'base/js/keyboard',
10 'moment',
11 ], function(IPython, $, utils, dialog, keyboard, moment) {
12 "use strict";
13
14 var SaveWidget = function (selector, options) {
15 this.editor = undefined;
16 this.selector = selector;
17 this.events = options.events;
18 this.editor = options.editor;
19 this._last_modified = undefined;
20 this.keyboard_manager = options.keyboard_manager;
21 if (this.selector !== undefined) {
22 this.element = $(selector);
23 this.bind_events();
24 }
25 };
26
27
28 SaveWidget.prototype.bind_events = function () {
29 var that = this;
30 this.element.find('span.filename').click(function () {
31 that.rename();
32 });
33 this.events.on('file_loaded.Editor', function (evt, model) {
34 that.update_filename(model.name);
35 that.update_document_title(model.name);
36 that.update_last_modified(model.last_modified);
37 });
38 this.events.on('file_saved.Editor', function (evt, model) {
39 that.update_filename(model.name);
40 that.update_document_title(model.name);
41 that.update_last_modified(model.last_modified);
42 });
43 this.events.on('file_renamed.Editor', function (evt, model) {
44 that.update_filename(model.name);
45 that.update_document_title(model.name);
46 that.update_address_bar(model.path);
47 });
48 this.events.on('file_save_failed.Editor', function () {
49 that.set_save_status('Save Failed!');
50 });
51 };
52
53
54 SaveWidget.prototype.rename = function (options) {
55 options = options || {};
56 var that = this;
57 var dialog_body = $('<div/>').append(
58 $("<p/>").addClass("rename-message")
59 .text('Enter a new filename:')
60 ).append(
61 $("<br/>")
62 ).append(
63 $('<input/>').attr('type','text').attr('size','25').addClass('form-control')
64 .val(that.editor.get_filename())
65 );
66 var d = dialog.modal({
67 title: "Rename File",
68 body: dialog_body,
69 buttons : {
70 "OK": {
71 class: "btn-primary",
72 click: function () {
73 var new_name = d.find('input').val();
74 d.find('.rename-message').text("Renaming...");
75 d.find('input[type="text"]').prop('disabled', true);
76 that.editor.rename(new_name).then(
77 function () {
78 d.modal('hide');
79 }, function (error) {
80 d.find('.rename-message').text(error.message || 'Unknown error');
81 d.find('input[type="text"]').prop('disabled', false).focus().select();
82 }
83 );
84 return false;
85 }
86 },
87 "Cancel": {}
88 },
89 open : function () {
90 // Upon ENTER, click the OK button.
91 d.find('input[type="text"]').keydown(function (event) {
92 if (event.which === keyboard.keycodes.enter) {
93 d.find('.btn-primary').first().click();
94 return false;
95 }
96 });
97 d.find('input[type="text"]').focus().select();
98 }
99 });
100 };
101
102
103 SaveWidget.prototype.update_filename = function (filename) {
104 this.element.find('span.filename').text(filename);
105 };
106
107 SaveWidget.prototype.update_document_title = function (filename) {
108 document.title = filename;
109 };
110
111 SaveWidget.prototype.update_address_bar = function (path) {
112 var state = {path : path};
113 window.history.replaceState(state, "", utils.url_join_encode(
114 this.editor.base_url,
115 "edit",
116 path)
117 );
118 };
119
120 SaveWidget.prototype.update_last_modified = function (last_modified) {
121 if (last_modified) {
122 this._last_modified = new Date(last_modified);
123 } else {
124 this._last_modified = null;
125 }
126 this._render_last_modified();
127 };
128
129 SaveWidget.prototype._render_last_modified = function () {
130 /** actually set the text in the element, from our _last_modified value
131
132 called directly, and periodically in timeouts.
133 */
134 this._schedule_render_last_modified();
135 var el = this.element.find('span.last_modified');
136 if (!this._last_modified) {
137 el.text('').attr('title', 'never saved');
138 return;
139 }
140 var chkd = moment(this._last_modified);
141 var long_date = chkd.format('llll');
142 var human_date;
143 var tdelta = Math.ceil(new Date() - this._last_modified);
144 if (tdelta < 24 * H){
145 // less than 24 hours old, use relative date
146 human_date = chkd.fromNow();
147 } else {
148 // otherwise show calendar
149 // otherwise update every hour and show
150 // <Today | yesterday|...> at hh,mm,ss
151 human_date = chkd.calendar();
152 }
153 el.text(human_date).attr('title', long_date);
154 };
155
156
157 var S = 1000;
158 var M = 60*S;
159 var H = 60*M;
160 var thresholds = {
161 s: 45 * S,
162 m: 45 * M,
163 h: 22 * H
164 };
165 var _timeout_from_dt = function (ms) {
166 /** compute a timeout to update the last-modified timeout
167
168 based on the delta in milliseconds
169 */
170 if (ms < thresholds.s) {
171 return 5 * S;
172 } else if (ms < thresholds.m) {
173 return M;
174 } else {
175 return 5 * M;
176 }
177 };
178
179 SaveWidget.prototype._schedule_render_last_modified = function () {
180 /** schedule the next update to relative date
181
182 periodically updated, so short values like 'a few seconds ago' don't get stale.
183 */
184 var that = this;
185 if (!this._last_modified) {
186 return;
187 }
188 if ((this._last_modified_timeout)) {
189 clearTimeout(this._last_modified_timeout);
190 }
191 var dt = Math.ceil(new Date() - this._last_modified);
192 if (dt < 24 * H) {
193 this._last_modified_timeout = setTimeout(
194 $.proxy(this._render_last_modified, this),
195 _timeout_from_dt(dt)
196 );
197 }
198 };
199
200 return {'SaveWidget': SaveWidget};
201
202 });
@@ -0,0 +1,9 b''
1 #texteditor-container {
2 border-bottom: 1px solid #ccc;
3 }
4
5 #filename {
6 font-size: 16pt;
7 display: table;
8 padding: 0px 5px;
9 }
@@ -0,0 +1,14 b''
1 .selected-keymap {
2 i.fa {
3 padding: 0px 5px;
4 }
5 i.fa:before {
6 content: @fa-var-check;
7 }
8 }
9
10 #mode-menu {
11 // truncate mode-menu, so it doesn't get longer than the screen
12 overflow: auto;
13 max-height: 20em;
14 } No newline at end of file
@@ -0,0 +1,7 b''
1 /*!
2 *
3 * IPython text editor webapp
4 *
5 */
6 @import "menubar.less";
7 @import "edit.less";
@@ -363,7 +363,8 b' def json_errors(method):'
363 message = e.log_message
363 message = e.log_message
364 self.log.warn(message)
364 self.log.warn(message)
365 self.set_status(e.status_code)
365 self.set_status(e.status_code)
366 self.finish(json.dumps(dict(message=message)))
366 reply = dict(message=message, reason=e.reason)
367 self.finish(json.dumps(reply))
367 except Exception:
368 except Exception:
368 self.log.error("Unhandled error in API request", exc_info=True)
369 self.log.error("Unhandled error in API request", exc_info=True)
369 status = 500
370 status = 500
@@ -371,7 +372,7 b' def json_errors(method):'
371 t, value, tb = sys.exc_info()
372 t, value, tb = sys.exc_info()
372 self.set_status(status)
373 self.set_status(status)
373 tb_text = ''.join(traceback.format_exception(t, value, tb))
374 tb_text = ''.join(traceback.format_exception(t, value, tb))
374 reply = dict(message=message, traceback=tb_text)
375 reply = dict(message=message, reason=None, traceback=tb_text)
375 self.finish(json.dumps(reply))
376 self.finish(json.dumps(reply))
376 else:
377 else:
377 return result
378 return result
@@ -281,7 +281,7 b' class FileContentsManager(ContentsManager):'
281 model['content'] = bcontent.decode('utf8')
281 model['content'] = bcontent.decode('utf8')
282 except UnicodeError as e:
282 except UnicodeError as e:
283 if format == 'text':
283 if format == 'text':
284 raise web.HTTPError(400, "%s is not UTF-8 encoded" % path)
284 raise web.HTTPError(400, "%s is not UTF-8 encoded" % path, reason='bad format')
285 else:
285 else:
286 model['format'] = 'text'
286 model['format'] = 'text'
287 default_mime = 'text/plain'
287 default_mime = 'text/plain'
@@ -348,14 +348,14 b' class FileContentsManager(ContentsManager):'
348 if os.path.isdir(os_path):
348 if os.path.isdir(os_path):
349 if type_ not in (None, 'directory'):
349 if type_ not in (None, 'directory'):
350 raise web.HTTPError(400,
350 raise web.HTTPError(400,
351 u'%s is a directory, not a %s' % (path, type_))
351 u'%s is a directory, not a %s' % (path, type_), reason='bad type')
352 model = self._dir_model(path, content=content)
352 model = self._dir_model(path, content=content)
353 elif type_ == 'notebook' or (type_ is None and path.endswith('.ipynb')):
353 elif type_ == 'notebook' or (type_ is None and path.endswith('.ipynb')):
354 model = self._notebook_model(path, content=content)
354 model = self._notebook_model(path, content=content)
355 else:
355 else:
356 if type_ == 'directory':
356 if type_ == 'directory':
357 raise web.HTTPError(400,
357 raise web.HTTPError(400,
358 u'%s is not a directory')
358 u'%s is not a directory', reason='bad type')
359 model = self._file_model(path, content=content, format=format)
359 model = self._file_model(path, content=content, format=format)
360 return model
360 return model
361
361
@@ -47,6 +47,7 b' class ContentsHandler(IPythonHandler):'
47 location = self.location_url(model['path'])
47 location = self.location_url(model['path'])
48 self.set_header('Location', location)
48 self.set_header('Location', location)
49 self.set_header('Last-Modified', model['last_modified'])
49 self.set_header('Last-Modified', model['last_modified'])
50 self.set_header('Content-Type', 'application/json')
50 self.finish(json.dumps(model, default=date_default))
51 self.finish(json.dumps(model, default=date_default))
51
52
52 @web.authenticated
53 @web.authenticated
@@ -6,20 +6,32 b' define(['
6 'base/js/utils',
6 'base/js/utils',
7 'codemirror/lib/codemirror',
7 'codemirror/lib/codemirror',
8 'codemirror/mode/meta',
8 'codemirror/mode/meta',
9 'codemirror/addon/search/search'
9 'codemirror/addon/comment/comment',
10 'codemirror/addon/dialog/dialog',
11 'codemirror/addon/edit/closebrackets',
12 'codemirror/addon/edit/matchbrackets',
13 'codemirror/addon/search/searchcursor',
14 'codemirror/addon/search/search',
15 'codemirror/keymap/emacs',
16 'codemirror/keymap/sublime',
17 'codemirror/keymap/vim',
10 ],
18 ],
11 function($,
19 function($,
12 utils,
20 utils,
13 CodeMirror
21 CodeMirror
14 ) {
22 ) {
23 "use strict";
24
15 var Editor = function(selector, options) {
25 var Editor = function(selector, options) {
26 var that = this;
16 this.selector = selector;
27 this.selector = selector;
17 this.contents = options.contents;
28 this.contents = options.contents;
18 this.events = options.events;
29 this.events = options.events;
19 this.base_url = options.base_url;
30 this.base_url = options.base_url;
20 this.file_path = options.file_path;
31 this.file_path = options.file_path;
21
32 this.config = options.config;
22 this.codemirror = CodeMirror($(this.selector)[0]);
33 this.codemirror = new CodeMirror($(this.selector)[0]);
34 this.generation = -1;
23
35
24 // It appears we have to set commands on the CodeMirror class, not the
36 // It appears we have to set commands on the CodeMirror class, not the
25 // instance. I'd like to be wrong, but since there should only be one CM
37 // instance. I'd like to be wrong, but since there should only be one CM
@@ -27,27 +39,55 b' function($,'
27 CodeMirror.commands.save = $.proxy(this.save, this);
39 CodeMirror.commands.save = $.proxy(this.save, this);
28
40
29 this.save_enabled = false;
41 this.save_enabled = false;
42
43 this.config.loaded.then(function () {
44 // load codemirror config
45 var cfg = that.config.data.Editor || {};
46 var cmopts = $.extend(true, {}, // true = recursive copy
47 Editor.default_codemirror_options,
48 cfg.codemirror_options || {}
49 );
50 that._set_codemirror_options(cmopts);
51 that.events.trigger('config_changed.Editor', {config: that.config});
52 });
53 };
54
55 // default CodeMirror options
56 Editor.default_codemirror_options = {
57 extraKeys: {
58 "Tab" : "indentMore",
59 },
60 indentUnit: 4,
61 theme: "ipython",
62 lineNumbers: true,
63 lineWrapping: true,
30 };
64 };
31
65
32 Editor.prototype.load = function() {
66 Editor.prototype.load = function() {
67 /** load the file */
33 var that = this;
68 var that = this;
34 var cm = this.codemirror;
69 var cm = this.codemirror;
35 this.contents.get(this.file_path, {type: 'file', format: 'text'})
70 return this.contents.get(this.file_path, {type: 'file', format: 'text'})
36 .then(function(model) {
71 .then(function(model) {
37 cm.setValue(model.content);
72 cm.setValue(model.content);
38
73
39 // Setting the file's initial value creates a history entry,
74 // Setting the file's initial value creates a history entry,
40 // which we don't want.
75 // which we don't want.
41 cm.clearHistory();
76 cm.clearHistory();
42
77 that._set_mode_for_model(model);
43 // Find and load the highlighting mode
44 utils.requireCodeMirrorMode(model.mimetype, function(spec) {
45 var mode = CodeMirror.getMode({}, spec);
46 cm.setOption('mode', mode);
47 });
48 that.save_enabled = true;
78 that.save_enabled = true;
79 that.generation = cm.changeGeneration();
80 that.events.trigger("file_loaded.Editor", model);
49 },
81 },
50 function(error) {
82 function(error) {
83 that.events.trigger("file_load_failed.Editor", error);
84 if (error.xhr.responseJSON.reason === 'bad format') {
85 window.location = utils.url_path_join(
86 that.base_url,
87 'files',
88 that.file_path
89 );
90 }
51 cm.setValue("Error! " + error.message +
91 cm.setValue("Error! " + error.message +
52 "\nSaving disabled.");
92 "\nSaving disabled.");
53 that.save_enabled = false;
93 that.save_enabled = false;
@@ -55,7 +95,55 b' function($,'
55 );
95 );
56 };
96 };
57
97
58 Editor.prototype.save = function() {
98 Editor.prototype._set_mode_for_model = function (model) {
99 /** Set the CodeMirror mode based on the file model */
100
101 // Find and load the highlighting mode,
102 // first by mime-type, then by file extension
103 var modeinfo = CodeMirror.findModeByMIME(model.mimetype);
104 if (modeinfo.mode === "null") {
105 // find by mime failed, use find by ext
106 var ext_idx = model.name.lastIndexOf('.');
107
108 if (ext_idx > 0) {
109 // CodeMirror.findModeByExtension wants extension without '.'
110 modeinfo = CodeMirror.findModeByExtension(model.name.slice(ext_idx + 1));
111 }
112 }
113 if (modeinfo) {
114 this.set_codemirror_mode(modeinfo);
115 }
116 };
117
118 Editor.prototype.set_codemirror_mode = function (modeinfo) {
119 /** set the codemirror mode from a modeinfo struct */
120 var that = this;
121 utils.requireCodeMirrorMode(modeinfo, function () {
122 that.codemirror.setOption('mode', modeinfo.mode);
123 that.events.trigger("mode_changed.Editor", modeinfo);
124 });
125 };
126
127 Editor.prototype.get_filename = function () {
128 return utils.url_path_split(this.file_path)[1];
129 };
130
131 Editor.prototype.rename = function (new_name) {
132 /** rename the file */
133 var that = this;
134 var parent = utils.url_path_split(this.file_path)[0];
135 var new_path = utils.url_path_join(parent, new_name);
136 return this.contents.rename(this.file_path, new_path).then(
137 function (model) {
138 that.file_path = model.path;
139 that.events.trigger('file_renamed.Editor', model);
140 that._set_mode_for_model(model);
141 }
142 );
143 };
144
145 Editor.prototype.save = function () {
146 /** save the file */
59 if (!this.save_enabled) {
147 if (!this.save_enabled) {
60 console.log("Not saving, save disabled");
148 console.log("Not saving, save disabled");
61 return;
149 return;
@@ -67,10 +155,36 b' function($,'
67 content: this.codemirror.getValue(),
155 content: this.codemirror.getValue(),
68 };
156 };
69 var that = this;
157 var that = this;
70 this.contents.save(this.file_path, model).then(function() {
158 // record change generation for isClean
71 that.events.trigger("save_succeeded.TextEditor");
159 this.generation = this.codemirror.changeGeneration();
160 return this.contents.save(this.file_path, model).then(function(data) {
161 that.events.trigger("file_saved.Editor", data);
72 });
162 });
73 };
163 };
164
165 Editor.prototype._set_codemirror_options = function (options) {
166 // update codemirror options from a dict
167 var codemirror = this.codemirror;
168 $.map(options, function (value, opt) {
169 if (value === null) {
170 value = CodeMirror.defaults[opt];
171 }
172 codemirror.setOption(opt, value);
173 });
174 };
175
176 Editor.prototype.update_codemirror_options = function (options) {
177 /** update codemirror options locally and save changes in config */
178 var that = this;
179 this._set_codemirror_options(options);
180 return this.config.update({
181 Editor: {
182 codemirror_options: options
183 }
184 }).then(
185 that.events.trigger('config_changed.Editor', {config: that.config})
186 );
187 };
74
188
75 return {Editor: Editor};
189 return {Editor: Editor};
76 });
190 });
@@ -10,6 +10,7 b' require(['
10 'services/config',
10 'services/config',
11 'edit/js/editor',
11 'edit/js/editor',
12 'edit/js/menubar',
12 'edit/js/menubar',
13 'edit/js/savewidget',
13 'edit/js/notificationarea',
14 'edit/js/notificationarea',
14 'custom/custom',
15 'custom/custom',
15 ], function(
16 ], function(
@@ -19,8 +20,9 b' require(['
19 events,
20 events,
20 contents,
21 contents,
21 configmod,
22 configmod,
22 editor,
23 editmod,
23 menubar,
24 menubar,
25 savewidget,
24 notificationarea
26 notificationarea
25 ){
27 ){
26 page = new page.Page();
28 page = new page.Page();
@@ -28,22 +30,30 b' require(['
28 var base_url = utils.get_body_data('baseUrl');
30 var base_url = utils.get_body_data('baseUrl');
29 var file_path = utils.get_body_data('filePath');
31 var file_path = utils.get_body_data('filePath');
30 contents = new contents.Contents({base_url: base_url});
32 contents = new contents.Contents({base_url: base_url});
31 var config = new configmod.ConfigSection('edit', {base_url: base_url})
33 var config = new configmod.ConfigSection('edit', {base_url: base_url});
32 config.load();
34 config.load();
33
35
34 var editor = new editor.Editor('#texteditor-container', {
36 var editor = new editmod.Editor('#texteditor-container', {
35 base_url: base_url,
37 base_url: base_url,
36 events: events,
38 events: events,
37 contents: contents,
39 contents: contents,
38 file_path: file_path,
40 file_path: file_path,
41 config: config,
39 });
42 });
40
43
41 // Make it available for debugging
44 // Make it available for debugging
42 IPython.editor = editor;
45 IPython.editor = editor;
43
46
47 var save_widget = new savewidget.SaveWidget('span#save_widget', {
48 editor: editor,
49 events: events,
50 });
51
44 var menus = new menubar.MenuBar('#menubar', {
52 var menus = new menubar.MenuBar('#menubar', {
45 base_url: base_url,
53 base_url: base_url,
46 editor: editor,
54 editor: editor,
55 events: events,
56 save_widget: save_widget,
47 });
57 });
48
58
49 var notification_area = new notificationarea.EditorNotificationArea(
59 var notification_area = new notificationarea.EditorNotificationArea(
@@ -61,4 +71,11 b' require(['
61 });
71 });
62 editor.load();
72 editor.load();
63 page.show();
73 page.show();
74
75 window.onbeforeunload = function () {
76 if (editor.save_enabled && !editor.codemirror.isClean(editor.generation)) {
77 return "Unsaved changes will be lost. Close anyway?";
78 }
79 };
80
64 });
81 });
@@ -2,11 +2,14 b''
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
6 'jquery',
5 'jquery',
6 'base/js/namespace',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/dialog',
9 'codemirror/lib/codemirror',
10 'codemirror/mode/meta',
8 'bootstrap',
11 'bootstrap',
9 ], function(IPython, $, utils, bootstrap) {
12 ], function($, IPython, utils, dialog, CodeMirror) {
10 "use strict";
13 "use strict";
11
14
12 var MenuBar = function (selector, options) {
15 var MenuBar = function (selector, options) {
@@ -29,21 +32,123 b' define(['
29 this.base_url = options.base_url || utils.get_body_data("baseUrl");
32 this.base_url = options.base_url || utils.get_body_data("baseUrl");
30 this.selector = selector;
33 this.selector = selector;
31 this.editor = options.editor;
34 this.editor = options.editor;
35 this.events = options.events;
36 this.save_widget = options.save_widget;
32
37
33 if (this.selector !== undefined) {
38 if (this.selector !== undefined) {
34 this.element = $(selector);
39 this.element = $(selector);
35 this.bind_events();
40 this.bind_events();
36 }
41 }
42 this._load_mode_menu();
43 Object.seal(this);
37 };
44 };
38
45
39 MenuBar.prototype.bind_events = function () {
46 MenuBar.prototype.bind_events = function () {
40 /**
41 * File
42 */
43 var that = this;
47 var that = this;
44 this.element.find('#save_file').click(function () {
48 var editor = that.editor;
45 that.editor.save();
49
50 // File
51 this.element.find('#new-file').click(function () {
52 var w = window.open();
53 // Create a new file in the current directory
54 var parent = utils.url_path_split(editor.file_path)[0];
55 editor.contents.new_untitled(parent, {type: "file"}).then(
56 function (data) {
57 w.location = utils.url_join_encode(
58 that.base_url, 'edit', data.path
59 );
60 },
61 function(error) {
62 w.close();
63 dialog.modal({
64 title : 'Creating New File Failed',
65 body : "The error was: " + error.message,
66 buttons : {'OK' : {'class' : 'btn-primary'}}
67 });
68 }
69 );
70 });
71 this.element.find('#save-file').click(function () {
72 editor.save();
73 });
74 this.element.find('#rename-file').click(function () {
75 that.save_widget.rename();
76 });
77
78 // Edit
79 this.element.find('#menu-find').click(function () {
80 editor.codemirror.execCommand("find");
81 });
82 this.element.find('#menu-replace').click(function () {
83 editor.codemirror.execCommand("replace");
84 });
85 this.element.find('#menu-keymap-default').click(function () {
86 editor.update_codemirror_options({
87 vimMode: false,
88 keyMap: 'default'
89 });
90 });
91 this.element.find('#menu-keymap-sublime').click(function () {
92 editor.update_codemirror_options({
93 vimMode: false,
94 keyMap: 'sublime'
95 });
46 });
96 });
97 this.element.find('#menu-keymap-emacs').click(function () {
98 editor.update_codemirror_options({
99 vimMode: false,
100 keyMap: 'emacs'
101 });
102 });
103 this.element.find('#menu-keymap-vim').click(function () {
104 editor.update_codemirror_options({
105 vimMode: true,
106 keyMap: 'vim'
107 });
108 });
109
110 // View
111 this.element.find('#menu-line-numbers').click(function () {
112 var current = editor.codemirror.getOption('lineNumbers');
113 var value = Boolean(1-current);
114 editor.update_codemirror_options({lineNumbers: value});
115 });
116
117 this.events.on("config_changed.Editor", function () {
118 var keyMap = editor.codemirror.getOption('keyMap') || "default";
119 that.element.find(".selected-keymap").removeClass("selected-keymap");
120 that.element.find("#menu-keymap-" + keyMap).addClass("selected-keymap");
121 });
122
123 this.events.on("mode_changed.Editor", function (evt, modeinfo) {
124 that.element.find("#current-mode")
125 .text(modeinfo.name)
126 .attr(
127 'title',
128 "The current language is " + modeinfo.name
129 );
130 });
131 };
132
133 MenuBar.prototype._load_mode_menu = function () {
134 var list = this.element.find("#mode-menu");
135 var editor = this.editor;
136 function make_set_mode(info) {
137 return function () {
138 editor.set_codemirror_mode(info);
139 };
140 }
141 for (var i = 0; i < CodeMirror.modeInfo.length; i++) {
142 var info = CodeMirror.modeInfo[i];
143 list.append($("<li>").append(
144 $("<a>").attr("href", "#")
145 .text(info.name)
146 .click(make_set_mode(info))
147 .attr('title',
148 "Set language to " + info.name
149 )
150 ));
151 }
47 };
152 };
48
153
49 return {'MenuBar': MenuBar};
154 return {'MenuBar': MenuBar};
@@ -29,7 +29,7 b' define(['
29
29
30 SaveWidget.prototype.bind_events = function () {
30 SaveWidget.prototype.bind_events = function () {
31 var that = this;
31 var that = this;
32 this.element.find('span#notebook_name').click(function () {
32 this.element.find('span.filename').click(function () {
33 that.rename_notebook({notebook: that.notebook});
33 that.rename_notebook({notebook: that.notebook});
34 });
34 });
35 this.events.on('notebook_loaded.Notebook', function () {
35 this.events.on('notebook_loaded.Notebook', function () {
@@ -130,7 +130,7 b' define(['
130
130
131 SaveWidget.prototype.update_notebook_name = function () {
131 SaveWidget.prototype.update_notebook_name = function () {
132 var nbname = this.notebook.get_notebook_name();
132 var nbname = this.notebook.get_notebook_name();
133 this.element.find('span#notebook_name').text(nbname);
133 this.element.find('span.filename').text(nbname);
134 };
134 };
135
135
136
136
@@ -152,11 +152,11 b' define(['
152
152
153
153
154 SaveWidget.prototype.set_save_status = function (msg) {
154 SaveWidget.prototype.set_save_status = function (msg) {
155 this.element.find('span#autosave_status').text(msg);
155 this.element.find('span.autosave_status').text(msg);
156 };
156 };
157
157
158 SaveWidget.prototype._set_checkpoint_status = function (human_date, iso_date) {
158 SaveWidget.prototype._set_checkpoint_status = function (human_date, iso_date) {
159 var el = this.element.find('span#checkpoint_status');
159 var el = this.element.find('span.checkpoint_status');
160 if(human_date){
160 if(human_date){
161 el.text("Last Checkpoint: "+human_date).attr('title',iso_date);
161 el.text("Last Checkpoint: "+human_date).attr('title',iso_date);
162 } else {
162 } else {
@@ -223,7 +223,7 b' define(['
223
223
224 // update regularly for the first 6hours and show
224 // update regularly for the first 6hours and show
225 // <x time> ago
225 // <x time> ago
226 if(tdelta < tdelta < 6*3600*1000){
226 if(tdelta < 6*3600*1000){
227 recall(_next_timeago_update(tdelta));
227 recall(_next_timeago_update(tdelta));
228 this._set_checkpoint_status(chkd.fromNow(), longdate);
228 this._set_checkpoint_status(chkd.fromNow(), longdate);
229 // otherwise update every hour and show
229 // otherwise update every hour and show
@@ -15,20 +15,6 b' body {'
15 .border-box-sizing();
15 .border-box-sizing();
16 }
16 }
17
17
18 span#notebook_name {
19 height: 1em;
20 line-height: 1em;
21 padding: 3px;
22 border: none;
23 font-size: 146.5%;
24 &:hover{
25 // ensure body is lighter on dark palette,
26 // and vice versa
27 background-color:contrast(@body-bg, lighten(@body-bg,30%), darken(@body-bg,10%));
28 }
29 .corner-all;
30 }
31
32 div#notebook_panel {
18 div#notebook_panel {
33 margin: 0px 0px 0px 0px;
19 margin: 0px 0px 0px 0px;
34 padding: 0px;
20 padding: 0px;
@@ -1,33 +1,41 b''
1 span#save_widget {
1 span.save_widget {
2 margin-top: 6px;
2 margin-top: 6px;
3
4 span.filename {
5 height: 1em;
6 line-height: 1em;
7 padding: 3px;
8 border: none;
9 font-size: 146.5%;
10 &:hover{
11 // ensure body is lighter on dark palette,
12 // and vice versa
13 background-color:contrast(@body-bg, lighten(@body-bg,30%), darken(@body-bg,10%));
14 }
15 .corner-all;
16 }
3 }
17 }
4
18
5 span#checkpoint_status, span#autosave_status {
19 span.checkpoint_status, span.autosave_status {
6 font-size: small;
20 font-size: small;
7 }
21 }
8
22
9 @media (max-width: 767px) {
23 @media (max-width: 767px) {
10 span#save_widget {
24 span.save_widget {
11 font-size: small;
25 font-size: small;
12 }
26 }
13 span#checkpoint_status, span#autosave_status {
27 span.checkpoint_status, span.autosave_status {
14 font-size: x-small;
28 display: none;
15 }
16 }
17
18 @media (max-width: 767px) {
19 span#checkpoint_status, span#autosave_status {
20 display: none;
21 }
29 }
22 }
30 }
23
31
24 @media (min-width: 768px) and (max-width: 979px) {
32 @media (min-width: 768px) and (max-width: 979px) {
25 span#checkpoint_status {
33 span.checkpoint_status {
26 display: none;
34 display: none;
27 }
35 }
28 span#autosave_status {
36 span.autosave_status {
29 font-size: x-small;
37 font-size: x-small;
30 }
38 }
31 }
39 }
32
40
33
41
@@ -162,6 +162,7 b' define(['
162 var settings = {
162 var settings = {
163 processData : false,
163 processData : false,
164 type : "PUT",
164 type : "PUT",
165 dataType: "json",
165 data : JSON.stringify(model),
166 data : JSON.stringify(model),
166 contentType: 'application/json',
167 contentType: 'application/json',
167 };
168 };
@@ -23,6 +23,9 b''
23 // tree
23 // tree
24 @import "../tree/less/style.less";
24 @import "../tree/less/style.less";
25
25
26 // edit
27 @import "../edit/less/style.less";
28
26 // notebook
29 // notebook
27 @import "../notebook/less/style.less";
30 @import "../notebook/less/style.less";
28
31
@@ -7752,9 +7752,6 b' div#header {'
7752 /* Initially hidden to prevent FLOUC */
7752 /* Initially hidden to prevent FLOUC */
7753 display: none;
7753 display: none;
7754 background-color: #ffffff;
7754 background-color: #ffffff;
7755 box-sizing: border-box;
7756 -moz-box-sizing: border-box;
7757 -webkit-box-sizing: border-box;
7758 /* Display over codemirror */
7755 /* Display over codemirror */
7759 z-index: 100;
7756 z-index: 100;
7760 }
7757 }
@@ -8119,6 +8116,29 b' ul#new-notebook-menu {'
8119 }
8116 }
8120 /*!
8117 /*!
8121 *
8118 *
8119 * IPython text editor webapp
8120 *
8121 */
8122 .selected-keymap i.fa {
8123 padding: 0px 5px;
8124 }
8125 .selected-keymap i.fa:before {
8126 content: "\f00c";
8127 }
8128 #mode-menu {
8129 overflow: auto;
8130 max-height: 20em;
8131 }
8132 #texteditor-container {
8133 border-bottom: 1px solid #ccc;
8134 }
8135 #filename {
8136 font-size: 16pt;
8137 display: table;
8138 padding: 0px 5px;
8139 }
8140 /*!
8141 *
8122 * IPython notebook
8142 * IPython notebook
8123 *
8143 *
8124 */
8144 */
@@ -9421,17 +9441,6 b' body {'
9421 -moz-box-sizing: border-box;
9441 -moz-box-sizing: border-box;
9422 -webkit-box-sizing: border-box;
9442 -webkit-box-sizing: border-box;
9423 }
9443 }
9424 span#notebook_name {
9425 height: 1em;
9426 line-height: 1em;
9427 padding: 3px;
9428 border: none;
9429 font-size: 146.5%;
9430 border-radius: 4px;
9431 }
9432 span#notebook_name:hover {
9433 background-color: #e6e6e6;
9434 }
9435 div#notebook_panel {
9444 div#notebook_panel {
9436 margin: 0px 0px 0px 0px;
9445 margin: 0px 0px 0px 0px;
9437 padding: 0px;
9446 padding: 0px;
@@ -9681,7 +9690,6 b' fieldset[disabled] #kernel_selector_widget > button.active {'
9681 margin-top: 0px;
9690 margin-top: 0px;
9682 }
9691 }
9683 #menubar {
9692 #menubar {
9684 margin-top: 0px;
9685 box-sizing: border-box;
9693 box-sizing: border-box;
9686 -moz-box-sizing: border-box;
9694 -moz-box-sizing: border-box;
9687 -webkit-box-sizing: border-box;
9695 -webkit-box-sizing: border-box;
@@ -10159,33 +10167,38 b' div#pager .ui-resizable-handle {'
10159 /* Modern browsers */
10167 /* Modern browsers */
10160 flex: 1;
10168 flex: 1;
10161 }
10169 }
10162 span#save_widget {
10170 span.save_widget {
10163 margin-top: 6px;
10171 margin-top: 6px;
10164 }
10172 }
10165 span#checkpoint_status,
10173 span.save_widget span.filename {
10166 span#autosave_status {
10174 height: 1em;
10175 line-height: 1em;
10176 padding: 3px;
10177 border: none;
10178 font-size: 146.5%;
10179 border-radius: 4px;
10180 }
10181 span.save_widget span.filename:hover {
10182 background-color: #e6e6e6;
10183 }
10184 span.checkpoint_status,
10185 span.autosave_status {
10167 font-size: small;
10186 font-size: small;
10168 }
10187 }
10169 @media (max-width: 767px) {
10188 @media (max-width: 767px) {
10170 span#save_widget {
10189 span.save_widget {
10171 font-size: small;
10190 font-size: small;
10172 }
10191 }
10173 span#checkpoint_status,
10192 span.checkpoint_status,
10174 span#autosave_status {
10193 span.autosave_status {
10175 font-size: x-small;
10176 }
10177 }
10178 @media (max-width: 767px) {
10179 span#checkpoint_status,
10180 span#autosave_status {
10181 display: none;
10194 display: none;
10182 }
10195 }
10183 }
10196 }
10184 @media (min-width: 768px) and (max-width: 979px) {
10197 @media (min-width: 768px) and (max-width: 979px) {
10185 span#checkpoint_status {
10198 span.checkpoint_status {
10186 display: none;
10199 display: none;
10187 }
10200 }
10188 span#autosave_status {
10201 span.autosave_status {
10189 font-size: x-small;
10202 font-size: x-small;
10190 }
10203 }
10191 }
10204 }
@@ -229,7 +229,7 b' define(['
229 NotebookList.uri_prefixes = {
229 NotebookList.uri_prefixes = {
230 directory: 'tree',
230 directory: 'tree',
231 notebook: 'notebooks',
231 notebook: 'notebooks',
232 file: 'files',
232 file: 'edit',
233 };
233 };
234
234
235
235
@@ -5,18 +5,6 b''
5 {% block stylesheet %}
5 {% block stylesheet %}
6 <link rel="stylesheet" href="{{ static_url('components/codemirror/lib/codemirror.css') }}">
6 <link rel="stylesheet" href="{{ static_url('components/codemirror/lib/codemirror.css') }}">
7 <link rel="stylesheet" href="{{ static_url('components/codemirror/addon/dialog/dialog.css') }}">
7 <link rel="stylesheet" href="{{ static_url('components/codemirror/addon/dialog/dialog.css') }}">
8 <style>
9 #texteditor-container {
10 border-bottom: 1px solid #ccc;
11 }
12
13 #filename {
14 font-size: 16pt;
15 display: table;
16 padding: 0px 5px;
17 }
18 </style>
19
20 {{super()}}
8 {{super()}}
21 {% endblock %}
9 {% endblock %}
22
10
@@ -27,38 +15,70 b' data-file-path="{{file_path}}"'
27
15
28 {% endblock %}
16 {% endblock %}
29
17
30 {% block header %}
18 {% block headercontainer %}
31
19
32 <span id="filename">{{ basename }}</span>
20 <span id="save_widget" class="pull-left save_widget">
21 <span class="filename"></span>
22 <span class="last_modified"></span>
23 </span>
33
24
34 {% endblock %}
25 {% endblock %}
35
26
36 {% block site %}
27 {% block header %}
37
28
38 <div id="menubar-container" class="container">
29 <div id="menubar-container" class="container">
39 <div id="menubar">
30 <div id="menubar">
40 <div id="menus" class="navbar navbar-default" role="navigation">
31 <div id="menus" class="navbar navbar-default" role="navigation">
41 <div class="container-fluid">
32 <div class="container-fluid">
42 <button type="button" class="btn btn-default navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
33 <button type="button" class="btn btn-default navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
43 <i class="fa fa-bars"></i>
34 <i class="fa fa-bars"></i>
44 <span class="navbar-text">Menu</span>
35 <span class="navbar-text">Menu</span>
45 </button>
36 </button>
46 <ul class="nav navbar-nav navbar-right">
37 <ul class="nav navbar-nav navbar-right">
47 <li id="notification_area"></li>
38 <li id="notification_area"></li>
48 </ul>
39 </ul>
49 <div class="navbar-collapse collapse">
40 <div class="navbar-collapse collapse">
50 <ul class="nav navbar-nav">
41 <ul class="nav navbar-nav">
51 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
42 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
52 <ul id="file_menu" class="dropdown-menu">
43 <ul id="file-menu" class="dropdown-menu">
53 <li id="save_file"><a href="#">Save</a></li>
44 <li id="new-file"><a href="#">New</a></li>
54 </ul>
45 <li id="save-file"><a href="#">Save</a></li>
55 </li>
46 <li id="rename-file"><a href="#">Rename</a></li>
47 </ul>
48 </li>
49 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
50 <ul id="edit-menu" class="dropdown-menu">
51 <li id="menu-find"><a href="#">Find</a></li>
52 <li id="menu-replace"><a href="#">Find &amp; Replace</a></li>
53 <li class="divider"></li>
54 <li class="dropdown-header">Key Map</li>
55 <li id="menu-keymap-default"><a href="#">Default<i class="fa"></i></a></li>
56 <li id="menu-keymap-sublime"><a href="#">Sublime Text<i class="fa"></i></a></li>
57 <li id="menu-keymap-vim"><a href="#">Vim<i class="fa"></i></a></li>
58 <li id="menu-keymap-emacs"><a href="#">emacs<i class="fa"></i></a></li>
59 </ul>
60 </li>
61 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
62 <ul id="view-menu" class="dropdown-menu">
63 <li id="menu-line-numbers"><a href="#">Toggle Line Numbers</a></li>
64 </ul>
65 </li>
66 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Language</a>
67 <ul id="mode-menu" class="dropdown-menu">
56 </ul>
68 </ul>
57 </div>
69 </li>
70 </ul>
71 <p id="current-mode" class="navbar-text navbar-right">current mode</p>
58 </div>
72 </div>
73 </div>
59 </div>
74 </div>
75 </div>
60 </div>
76 </div>
61 </div>
77
78 {% endblock %}
79
80 {% block site %}
81
62
82
63 <div id="texteditor-container" class="container"></div>
83 <div id="texteditor-container" class="container"></div>
64
84
@@ -35,10 +35,10 b' class="notebook_app"'
35 {% block headercontainer %}
35 {% block headercontainer %}
36
36
37
37
38 <span id="save_widget" class="nav pull-left">
38 <span id="save_widget" class="pull-left save_widget">
39 <span id="notebook_name"></span>
39 <span class="filename"></span>
40 <span id="checkpoint_status"></span>
40 <span class="checkpoint_status"></span>
41 <span id="autosave_status"></span>
41 <span class="autosave_status"></span>
42 </span>
42 </span>
43
43
44 <span id="kernel_selector_widget" class="pull-right dropdown">
44 <span id="kernel_selector_widget" class="pull-right dropdown">
General Comments 0
You need to be logged in to leave comments. Login now