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 |
|
|
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/ |
|
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. |
|
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 |
edit |
|
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 edit |
|
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( |
|
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 |
|
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 |
|
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 |
|
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 |
|
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( |
|
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 |
|
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 |
|
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 |
|
24 | span.save_widget { | |
11 | font-size: small; |
|
25 | font-size: small; | |
12 | } |
|
26 | } | |
13 |
span |
|
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 |
|
33 | span.checkpoint_status { | |
26 | display: none; |
|
34 | display: none; | |
27 | } |
|
35 | } | |
28 |
span |
|
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 |
|
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 |
|
10189 | span.save_widget { | |
10171 | font-size: small; |
|
10190 | font-size: small; | |
10172 | } |
|
10191 | } | |
10173 |
span |
|
10192 | span.checkpoint_status, | |
10174 |
span |
|
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 |
|
10198 | span.checkpoint_status { | |
10186 | display: none; |
|
10199 | display: none; | |
10187 | } |
|
10200 | } | |
10188 |
span |
|
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: ' |
|
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 |
|
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 |
|
|
32 | <div class="container-fluid"> | |
42 |
|
|
33 | <button type="button" class="btn btn-default navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> | |
43 |
|
|
34 | <i class="fa fa-bars"></i> | |
44 |
|
|
35 | <span class="navbar-text">Menu</span> | |
45 |
|
|
36 | </button> | |
46 |
|
|
37 | <ul class="nav navbar-nav navbar-right"> | |
47 |
|
|
38 | <li id="notification_area"></li> | |
48 |
|
|
39 | </ul> | |
49 |
|
|
40 | <div class="navbar-collapse collapse"> | |
50 |
|
|
41 | <ul class="nav navbar-nav"> | |
51 |
|
|
42 | <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a> | |
52 |
|
|
43 | <ul id="file-menu" class="dropdown-menu"> | |
53 |
|
|
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 & 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 |
</ |
|
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=" |
|
38 | <span id="save_widget" class="pull-left save_widget"> | |
39 |
<span |
|
39 | <span class="filename"></span> | |
40 |
<span |
|
40 | <span class="checkpoint_status"></span> | |
41 |
<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