##// END OF EJS Templates
Merge pull request #5043 from minrk/base-url-unicode...
Brian E. Granger -
r15245:aa3ba1f8 merge
parent child Browse files
Show More
@@ -32,13 +32,13 b' class LoginHandler(IPythonHandler):'
32 32
33 33 def _render(self, message=None):
34 34 self.write(self.render_template('login.html',
35 next=url_escape(self.get_argument('next', default=self.base_project_url)),
35 next=url_escape(self.get_argument('next', default=self.base_url)),
36 36 message=message,
37 37 ))
38 38
39 39 def get(self):
40 40 if self.current_user:
41 self.redirect(self.get_argument('next', default=self.base_project_url))
41 self.redirect(self.get_argument('next', default=self.base_url))
42 42 else:
43 43 self._render()
44 44
@@ -51,7 +51,7 b' class LoginHandler(IPythonHandler):'
51 51 self._render(message={'error': 'Invalid password'})
52 52 return
53 53
54 self.redirect(self.get_argument('next', default=self.base_project_url))
54 self.redirect(self.get_argument('next', default=self.base_url))
55 55
56 56
57 57 #-----------------------------------------------------------------------------
@@ -133,8 +133,8 b' class IPythonHandler(AuthenticatedHandler):'
133 133 return self.settings.get('mathjax_url', '')
134 134
135 135 @property
136 def base_project_url(self):
137 return self.settings.get('base_project_url', '/')
136 def base_url(self):
137 return self.settings.get('base_url', '/')
138 138
139 139 @property
140 140 def base_kernel_url(self):
@@ -180,7 +180,7 b' class IPythonHandler(AuthenticatedHandler):'
180 180 @property
181 181 def template_namespace(self):
182 182 return dict(
183 base_project_url=self.base_project_url,
183 base_url=self.base_url,
184 184 base_kernel_url=self.base_kernel_url,
185 185 logged_in=self.logged_in,
186 186 login_available=self.login_available,
@@ -58,7 +58,7 b' class NotebookRedirectHandler(IPythonHandler):'
58 58 nbm = self.notebook_manager
59 59 if nbm.path_exists(path):
60 60 # it's a *directory*, redirect to /tree
61 url = url_path_join(self.base_project_url, 'tree', path)
61 url = url_path_join(self.base_url, 'tree', path)
62 62 else:
63 63 # otherwise, redirect to /files
64 64 if '/files/' in path:
@@ -73,7 +73,7 b' class NotebookRedirectHandler(IPythonHandler):'
73 73 if not os.path.exists(files_path):
74 74 path = path.replace('/files/', '/', 1)
75 75
76 url = url_path_join(self.base_project_url, 'files', path)
76 url = url_path_join(self.base_url, 'files', path)
77 77 url = url_escape(url)
78 78 self.log.debug("Redirecting %s to %s", self.request.path, url)
79 79 self.redirect(url)
@@ -133,42 +133,42 b' def load_handlers(name):'
133 133 class NotebookWebApplication(web.Application):
134 134
135 135 def __init__(self, ipython_app, kernel_manager, notebook_manager,
136 cluster_manager, session_manager, log, base_project_url,
136 cluster_manager, session_manager, log, base_url,
137 137 settings_overrides):
138 138
139 139 settings = self.init_settings(
140 140 ipython_app, kernel_manager, notebook_manager, cluster_manager,
141 session_manager, log, base_project_url, settings_overrides)
141 session_manager, log, base_url, settings_overrides)
142 142 handlers = self.init_handlers(settings)
143 143
144 144 super(NotebookWebApplication, self).__init__(handlers, **settings)
145 145
146 146 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
147 cluster_manager, session_manager, log, base_project_url,
147 cluster_manager, session_manager, log, base_url,
148 148 settings_overrides):
149 149 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
150 # base_project_url will always be unicode, which will in turn
150 # base_url will always be unicode, which will in turn
151 151 # make the patterns unicode, and ultimately result in unicode
152 152 # keys in kwargs to handler._execute(**kwargs) in tornado.
153 # This enforces that base_project_url be ascii in that situation.
153 # This enforces that base_url be ascii in that situation.
154 154 #
155 155 # Note that the URLs these patterns check against are escaped,
156 156 # and thus guaranteed to be ASCII: 'héllo' is really 'h%C3%A9llo'.
157 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
157 base_url = py3compat.unicode_to_str(base_url, 'ascii')
158 158 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
159 159 settings = dict(
160 160 # basics
161 161 log_function=log_request,
162 base_project_url=base_project_url,
162 base_url=base_url,
163 163 base_kernel_url=ipython_app.base_kernel_url,
164 164 template_path=template_path,
165 165 static_path=ipython_app.static_file_path,
166 166 static_handler_class = FileFindHandler,
167 static_url_prefix = url_path_join(base_project_url,'/static/'),
167 static_url_prefix = url_path_join(base_url,'/static/'),
168 168
169 169 # authentication
170 170 cookie_secret=ipython_app.cookie_secret,
171 login_url=url_path_join(base_project_url,'/login'),
171 login_url=url_path_join(base_url,'/login'),
172 172 password=ipython_app.password,
173 173
174 174 # managers
@@ -206,10 +206,10 b' class NotebookWebApplication(web.Application):'
206 206 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
207 207 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
208 208 ])
209 # prepend base_project_url onto the patterns that we match
209 # prepend base_url onto the patterns that we match
210 210 new_handlers = []
211 211 for handler in handlers:
212 pattern = url_path_join(settings['base_project_url'], handler[0])
212 pattern = url_path_join(settings['base_url'], handler[0])
213 213 new_handler = tuple([pattern] + list(handler[1:]))
214 214 new_handlers.append(new_handler)
215 215 # add 404 on the end, which will catch everything that falls through
@@ -414,17 +414,22 b' class NotebookApp(BaseIPythonApplication):'
414 414 if not new:
415 415 self.mathjax_url = u''
416 416
417 base_project_url = Unicode('/', config=True,
417 base_url = Unicode('/', config=True,
418 418 help='''The base URL for the notebook server.
419 419
420 420 Leading and trailing slashes can be omitted,
421 421 and will automatically be added.
422 422 ''')
423 def _base_project_url_changed(self, name, old, new):
423 def _base_url_changed(self, name, old, new):
424 424 if not new.startswith('/'):
425 self.base_project_url = '/'+new
425 self.base_url = '/'+new
426 426 elif not new.endswith('/'):
427 self.base_project_url = new+'/'
427 self.base_url = new+'/'
428
429 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
430 def _base_project_url_changed(self, name, old, new):
431 self.log.warn("base_project_url is deprecated, use base_url")
432 self.base_url = new
428 433
429 434 base_kernel_url = Unicode('/', config=True,
430 435 help='''The base URL for the kernel server
@@ -473,12 +478,12 b' class NotebookApp(BaseIPythonApplication):'
473 478 if not self.enable_mathjax:
474 479 return u''
475 480 static_url_prefix = self.webapp_settings.get("static_url_prefix",
476 url_path_join(self.base_project_url, "static")
481 url_path_join(self.base_url, "static")
477 482 )
478 483
479 484 # try local mathjax, either in nbextensions/mathjax or static/mathjax
480 485 for (url_prefix, search_path) in [
481 (url_path_join(self.base_project_url, "nbextensions"), self.nbextensions_path),
486 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
482 487 (static_url_prefix, self.static_file_path),
483 488 ]:
484 489 self.log.debug("searching for local mathjax in %s", search_path)
@@ -586,7 +591,7 b' class NotebookApp(BaseIPythonApplication):'
586 591 self.web_app = NotebookWebApplication(
587 592 self, self.kernel_manager, self.notebook_manager,
588 593 self.cluster_manager, self.session_manager,
589 self.log, self.base_project_url, self.webapp_settings
594 self.log, self.base_url, self.webapp_settings
590 595 )
591 596 if self.certfile:
592 597 ssl_options = dict(certfile=self.certfile)
@@ -639,7 +644,7 b' class NotebookApp(BaseIPythonApplication):'
639 644
640 645 def _url(self, ip):
641 646 proto = 'https' if self.certfile else 'http'
642 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_project_url)
647 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
643 648
644 649 def init_signal(self):
645 650 if not sys.platform.startswith('win'):
@@ -745,7 +750,7 b' class NotebookApp(BaseIPythonApplication):'
745 750 'hostname': self.ip if self.ip else 'localhost',
746 751 'port': self.port,
747 752 'secure': bool(self.certfile),
748 'base_project_url': self.base_project_url,
753 'base_url': self.base_url,
749 754 'notebook_dir': os.path.abspath(self.notebook_manager.notebook_dir),
750 755 }
751 756
@@ -47,7 +47,7 b' class NotebookHandler(IPythonHandler):'
47 47 The URL path of the notebook.
48 48 """
49 49 return url_escape(url_path_join(
50 self.base_project_url, 'api', 'notebooks', path, name
50 self.base_url, 'api', 'notebooks', path, name
51 51 ))
52 52
53 53 def _finish_model(self, model, location=True):
@@ -242,7 +242,7 b' class NotebookCheckpointsHandler(IPythonHandler):'
242 242 nbm = self.notebook_manager
243 243 checkpoint = nbm.create_checkpoint(name, path)
244 244 data = json.dumps(checkpoint, default=date_default)
245 location = url_path_join(self.base_project_url, 'api/notebooks',
245 location = url_path_join(self.base_url, 'api/notebooks',
246 246 path, name, 'checkpoints', checkpoint['id'])
247 247 self.set_header('Location', url_escape(location))
248 248 self.set_status(201)
@@ -10,10 +10,11 b''
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 "use strict";
13 14
14 15 var LoginWidget = function (selector, options) {
15 var options = options || {};
16 this.base_url = options.baseProjectUrl || $('body').data('baseProjectUrl') ;
16 options = options || {};
17 this.base_url = options.base_url || IPython.utils.get_body_data("baseUrl");
17 18 this.selector = selector;
18 19 if (this.selector !== undefined) {
19 20 this.element = $(selector);
@@ -30,10 +31,16 b' var IPython = (function (IPython) {'
30 31 LoginWidget.prototype.bind_events = function () {
31 32 var that = this;
32 33 this.element.find("button#logout").click(function () {
33 window.location = that.base_url+"logout";
34 window.location = IPythin.utils.url_join_encode(
35 that.base_url,
36 "logout"
37 );
34 38 });
35 39 this.element.find("button#login").click(function () {
36 window.location = that.base_url+"login";
40 window.location = IPythin.utils.url_join_encode(
41 that.base_url,
42 "login"
43 );
37 44 });
38 45 };
39 46
@@ -417,15 +417,29 b' IPython.utils = (function (IPython) {'
417 417 url = url + arguments[i];
418 418 }
419 419 }
420 url = url.replace(/\/\/+/, '/');
420 421 return url;
421 422 };
422 423
424 var parse_url = function (url) {
425 // an `a` element with an href allows attr-access to the parsed segments of a URL
426 // a = parse_url("http://localhost:8888/path/name#hash")
427 // a.protocol = "http:"
428 // a.host = "localhost:8888"
429 // a.hostname = "localhost"
430 // a.port = 8888
431 // a.pathname = "/path/name"
432 // a.hash = "#hash"
433 var a = document.createElement("a");
434 a.href = url;
435 return a;
436 };
423 437
424 438 var encode_uri_components = function (uri) {
425 439 // encode just the components of a multi-segment uri,
426 440 // leaving '/' separators
427 441 return uri.split('/').map(encodeURIComponent).join('/');
428 }
442 };
429 443
430 444 var url_join_encode = function () {
431 445 // join a sequence of url components with '/',
@@ -443,7 +457,15 b' IPython.utils = (function (IPython) {'
443 457 } else {
444 458 return [filename, ''];
445 459 }
446 }
460 };
461
462
463 var get_body_data = function(key) {
464 // get a url-encoded item from body.data and decode it
465 // we should never have any encoded URLs anywhere else in code
466 // until we are building an actual request
467 return decodeURIComponent($('body').data(key));
468 };
447 469
448 470
449 471 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
@@ -508,6 +530,8 b' IPython.utils = (function (IPython) {'
508 530 fixCarriageReturn : fixCarriageReturn,
509 531 autoLinkUrls : autoLinkUrls,
510 532 points_to_pixels : points_to_pixels,
533 get_body_data : get_body_data,
534 parse_url : parse_url,
511 535 url_path_join : url_path_join,
512 536 url_join_encode : url_join_encode,
513 537 encode_uri_components : encode_uri_components,
@@ -8,7 +8,6 b''
8 8 //============================================================================
9 9 // On document ready
10 10 //============================================================================
11 "use strict";
12 11
13 12 // for the time beeing, we have to pass marked as a parameter here,
14 13 // as injecting require.js make marked not to put itself in the globals,
@@ -18,28 +17,28 b" require(['components/marked/lib/marked',"
18 17 'notebook/js/widgets/init'],
19 18
20 19 function (marked) {
20 "use strict";
21 21
22 window.marked = marked
22 window.marked = marked;
23 23
24 24 // monkey patch CM to be able to syntax highlight cell magics
25 25 // bug reported upstream,
26 26 // see https://github.com/marijnh/CodeMirror2/issues/670
27 if(CodeMirror.getMode(1,'text/plain').indent == undefined ){
27 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
28 28 console.log('patching CM for undefined indent');
29 29 CodeMirror.modes.null = function() {
30 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0}}
31 }
30 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
31 };
32 32 }
33 33
34 34 CodeMirror.patchedGetMode = function(config, mode){
35 35 var cmmode = CodeMirror.getMode(config, mode);
36 if(cmmode.indent == null)
37 {
36 if(cmmode.indent === null) {
38 37 console.log('patch mode "' , mode, '" on the fly');
39 cmmode.indent = function(){return 0};
38 cmmode.indent = function(){return 0;};
40 39 }
41 40 return cmmode;
42 }
41 };
43 42 // end monkey patching CodeMirror
44 43
45 44 IPython.mathjaxutils.init();
@@ -47,35 +46,32 b' function (marked) {'
47 46 $('#ipython-main-app').addClass('border-box-sizing');
48 47 $('div#notebook_panel').addClass('border-box-sizing');
49 48
50 var baseProjectUrl = $('body').data('baseProjectUrl');
51 var notebookPath = $('body').data('notebookPath');
52 var notebookName = $('body').data('notebookName');
53 notebookName = decodeURIComponent(notebookName);
54 notebookPath = decodeURIComponent(notebookPath);
55 console.log(notebookName);
56 if (notebookPath == 'None'){
57 notebookPath = "";
58 }
49 var opts = {
50 base_url : IPython.utils.get_body_data("baseUrl"),
51 base_kernel_url : IPython.utils.get_body_data("baseKernelUrl"),
52 notebook_path : IPython.utils.get_body_data("notebookPath"),
53 notebook_name : IPython.utils.get_body_data('notebookName')
54 };
59 55
60 56 IPython.page = new IPython.Page();
61 57 IPython.layout_manager = new IPython.LayoutManager();
62 58 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
63 59 IPython.quick_help = new IPython.QuickHelp();
64 IPython.login_widget = new IPython.LoginWidget('span#login_widget',{baseProjectUrl:baseProjectUrl});
65 IPython.notebook = new IPython.Notebook('div#notebook',{baseProjectUrl:baseProjectUrl, notebookPath:notebookPath, notebookName:notebookName});
60 IPython.login_widget = new IPython.LoginWidget('span#login_widget', opts);
61 IPython.notebook = new IPython.Notebook('div#notebook', opts);
66 62 IPython.keyboard_manager = new IPython.KeyboardManager();
67 63 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
68 IPython.menubar = new IPython.MenuBar('#menubar',{baseProjectUrl:baseProjectUrl, notebookPath: notebookPath})
69 IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container')
70 IPython.tooltip = new IPython.Tooltip()
71 IPython.notification_area = new IPython.NotificationArea('#notification_area')
64 IPython.menubar = new IPython.MenuBar('#menubar', opts);
65 IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container');
66 IPython.tooltip = new IPython.Tooltip();
67 IPython.notification_area = new IPython.NotificationArea('#notification_area');
72 68 IPython.notification_area.init_notification_widgets();
73 69
74 70 IPython.layout_manager.do_resize();
75 71
76 72 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
77 73 '<span id="test2" style="font-weight: bold;">x</span>'+
78 '<span id="test3" style="font-style: italic;">x</span></pre></div>')
74 '<span id="test3" style="font-style: italic;">x</span></pre></div>');
79 75 var nh = $('#test1').innerHeight();
80 76 var bh = $('#test2').innerHeight();
81 77 var ih = $('#test3').innerHeight();
@@ -101,7 +97,7 b' function (marked) {'
101 97
102 98 $([IPython.events]).on('notebook_loaded.Notebook', first_load);
103 99 $([IPython.events]).trigger('app_initialized.NotebookApp');
104 IPython.notebook.load_notebook(notebookName, notebookPath);
100 IPython.notebook.load_notebook(opts.notebook_name, opts.notebook_path);
105 101
106 102 if (marked) {
107 103 marked.setOptions({
@@ -121,8 +117,6 b' function (marked) {'
121 117 }
122 118 return highlighted.value;
123 119 }
124 })
120 });
125 121 }
126 }
127
128 );
122 });
@@ -30,16 +30,14 b' var IPython = (function (IPython) {'
30 30 *
31 31 * @param selector {string} selector for the menubar element in DOM
32 32 * @param {object} [options]
33 * @param [options.baseProjectUrl] {String} String to use for the
34 * Base Project url, default would be to inspect
35 * $('body').data('baseProjectUrl');
33 * @param [options.base_url] {String} String to use for the
34 * base project url. Default is to inspect
35 * $('body').data('baseUrl');
36 36 * does not support change for now is set through this option
37 37 */
38 38 var MenuBar = function (selector, options) {
39 39 options = options || {};
40 if (options.baseProjectUrl !== undefined) {
41 this._baseProjectUrl = options.baseProjectUrl;
42 }
40 this.base_url = options.base_url || IPython.utils.get_body_data("baseUrl");
43 41 this.selector = selector;
44 42 if (this.selector !== undefined) {
45 43 this.element = $(selector);
@@ -48,16 +46,6 b' var IPython = (function (IPython) {'
48 46 }
49 47 };
50 48
51 MenuBar.prototype.baseProjectUrl = function(){
52 return this._baseProjectUrl || $('body').data('baseProjectUrl');
53 };
54
55 MenuBar.prototype.notebookPath = function() {
56 var path = $('body').data('notebookPath');
57 path = decodeURIComponent(path);
58 return path;
59 };
60
61 49 MenuBar.prototype.style = function () {
62 50 this.element.addClass('border-box-sizing');
63 51 this.element.find("li").click(function (event, ui) {
@@ -71,20 +59,21 b' var IPython = (function (IPython) {'
71 59
72 60 MenuBar.prototype._nbconvert = function (format, download) {
73 61 download = download || false;
74 var notebook_name = IPython.notebook.get_notebook_name();
62 var notebook_path = IPython.notebook.notebook_path;
63 var notebook_name = IPython.notebook.notebook_name;
75 64 if (IPython.notebook.dirty) {
76 65 IPython.notebook.save_notebook({async : false});
77 66 }
78 var url = utils.url_path_join(
79 this.baseProjectUrl(),
67 var url = utils.url_join_encode(
68 this.base_url,
80 69 'nbconvert',
81 70 format,
82 this.notebookPath(),
83 notebook_name + '.ipynb'
71 notebook_path,
72 notebook_name
84 73 ) + "?download=" + download.toString();
85 74
86 75 window.open(url);
87 }
76 };
88 77
89 78 MenuBar.prototype.bind_events = function () {
90 79 // File
@@ -94,9 +83,9 b' var IPython = (function (IPython) {'
94 83 });
95 84 this.element.find('#open_notebook').click(function () {
96 85 window.open(utils.url_join_encode(
97 that.baseProjectUrl(),
86 IPython.notebook.base_url,
98 87 'tree',
99 that.notebookPath()
88 IPython.notebook.notebook_path
100 89 ));
101 90 });
102 91 this.element.find('#copy_notebook').click(function () {
@@ -104,16 +93,18 b' var IPython = (function (IPython) {'
104 93 return false;
105 94 });
106 95 this.element.find('#download_ipynb').click(function () {
107 var notebook_name = IPython.notebook.get_notebook_name();
96 var base_url = IPython.notebook.base_url;
97 var notebook_path = IPython.notebook.notebook_path;
98 var notebook_name = IPython.notebook.notebook_name;
108 99 if (IPython.notebook.dirty) {
109 100 IPython.notebook.save_notebook({async : false});
110 101 }
111 102
112 103 var url = utils.url_join_encode(
113 that.baseProjectUrl(),
104 base_url,
114 105 'files',
115 that.notebookPath(),
116 notebook_name + '.ipynb'
106 notebook_path,
107 notebook_name
117 108 );
118 109 window.location.assign(url);
119 110 });
@@ -23,10 +23,10 b' var IPython = (function (IPython) {'
23 23 * @param {Object} [options] A config object
24 24 */
25 25 var Notebook = function (selector, options) {
26 var options = options || {};
27 this._baseProjectUrl = options.baseProjectUrl;
28 this.notebook_path = options.notebookPath;
29 this.notebook_name = options.notebookName;
26 this.options = options = options || {};
27 this.base_url = options.base_url;
28 this.notebook_path = options.notebook_path;
29 this.notebook_name = options.notebook_name;
30 30 this.element = $(selector);
31 31 this.element.scroll();
32 32 this.element.data("notebook", this);
@@ -53,8 +53,8 b' var IPython = (function (IPython) {'
53 53 // single worksheet for now
54 54 this.worksheet_metadata = {};
55 55 this.notebook_name_blacklist_re = /[\/\\:]/;
56 this.nbformat = 3 // Increment this when changing the nbformat
57 this.nbformat_minor = 0 // Increment this when changing the nbformat
56 this.nbformat = 3; // Increment this when changing the nbformat
57 this.nbformat_minor = 0; // Increment this when changing the nbformat
58 58 this.style();
59 59 this.create_elements();
60 60 this.bind_events();
@@ -70,24 +70,6 b' var IPython = (function (IPython) {'
70 70 };
71 71
72 72 /**
73 * Get the root URL of the notebook server.
74 *
75 * @method baseProjectUrl
76 * @return {String} The base project URL
77 */
78 Notebook.prototype.baseProjectUrl = function() {
79 return this._baseProjectUrl || $('body').data('baseProjectUrl');
80 };
81
82 Notebook.prototype.notebookName = function() {
83 return $('body').data('notebookName');
84 };
85
86 Notebook.prototype.notebookPath = function() {
87 return $('body').data('notebookPath');
88 };
89
90 /**
91 73 * Create an HTML and CSS representation of the notebook.
92 74 *
93 75 * @method create_elements
@@ -163,7 +145,7 b' var IPython = (function (IPython) {'
163 145 };
164 146
165 147 this.element.bind('collapse_pager', function (event, extrap) {
166 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
148 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
167 149 collapse_time(time);
168 150 });
169 151
@@ -176,7 +158,7 b' var IPython = (function (IPython) {'
176 158 };
177 159
178 160 this.element.bind('expand_pager', function (event, extrap) {
179 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
161 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
180 162 expand_time(time);
181 163 });
182 164
@@ -205,7 +187,7 b' var IPython = (function (IPython) {'
205 187 } else {
206 188 return "Unsaved changes will be lost.";
207 189 }
208 };
190 }
209 191 // Null is the *only* return value that will make the browser not
210 192 // pop up the "don't leave" dialog.
211 193 return null;
@@ -237,7 +219,7 b' var IPython = (function (IPython) {'
237 219 */
238 220 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
239 221 var cells = this.get_cells();
240 var time = time || 0;
222 time = time || 0;
241 223 cell_number = Math.min(cells.length-1,cell_number);
242 224 cell_number = Math.max(0 ,cell_number);
243 225 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
@@ -349,7 +331,7 b' var IPython = (function (IPython) {'
349 331 result = ce.data('cell');
350 332 }
351 333 return result;
352 }
334 };
353 335
354 336 /**
355 337 * Get the cell below a given cell.
@@ -365,7 +347,7 b' var IPython = (function (IPython) {'
365 347 result = this.get_cell(index+1);
366 348 }
367 349 return result;
368 }
350 };
369 351
370 352 /**
371 353 * Get the cell above a given cell.
@@ -383,7 +365,7 b' var IPython = (function (IPython) {'
383 365 result = this.get_cell(index-1);
384 366 }
385 367 return result;
386 }
368 };
387 369
388 370 /**
389 371 * Get the numeric index of a given cell.
@@ -397,7 +379,7 b' var IPython = (function (IPython) {'
397 379 this.get_cell_elements().filter(function (index) {
398 380 if ($(this).data("cell") === cell) {
399 381 result = index;
400 };
382 }
401 383 });
402 384 return result;
403 385 };
@@ -444,8 +426,8 b' var IPython = (function (IPython) {'
444 426 return true;
445 427 } else {
446 428 return false;
447 };
448 }
429 }
430 };
449 431
450 432 /**
451 433 * Get the index of the currently selected cell.
@@ -458,7 +440,7 b' var IPython = (function (IPython) {'
458 440 this.get_cell_elements().filter(function (index) {
459 441 if ($(this).data("cell").selected === true) {
460 442 result = index;
461 };
443 }
462 444 });
463 445 return result;
464 446 };
@@ -475,11 +457,11 b' var IPython = (function (IPython) {'
475 457 */
476 458 Notebook.prototype.select = function (index) {
477 459 if (this.is_valid_cell_index(index)) {
478 var sindex = this.get_selected_index()
460 var sindex = this.get_selected_index();
479 461 if (sindex !== null && index !== sindex) {
480 462 this.command_mode();
481 463 this.get_cell(sindex).unselect();
482 };
464 }
483 465 var cell = this.get_cell(index);
484 466 cell.select();
485 467 if (cell.cell_type === 'heading') {
@@ -490,8 +472,8 b' var IPython = (function (IPython) {'
490 472 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
491 473 {'cell_type':cell.cell_type}
492 474 );
493 };
494 };
475 }
476 }
495 477 return this;
496 478 };
497 479
@@ -527,7 +509,7 b' var IPython = (function (IPython) {'
527 509 this.get_cell_elements().filter(function (index) {
528 510 if ($(this).data("cell").mode === 'edit') {
529 511 result = index;
530 };
512 }
531 513 });
532 514 return result;
533 515 };
@@ -539,10 +521,10 b' var IPython = (function (IPython) {'
539 521 var cell = this.get_cell(index);
540 522 if (cell) {
541 523 cell.command_mode();
542 };
524 }
543 525 this.mode = 'command';
544 526 IPython.keyboard_manager.command_mode();
545 };
527 }
546 528 };
547 529
548 530 Notebook.prototype.edit_mode = function () {
@@ -555,7 +537,7 b' var IPython = (function (IPython) {'
555 537 this.mode = 'edit';
556 538 IPython.keyboard_manager.edit_mode();
557 539 cell.edit_mode();
558 };
540 }
559 541 };
560 542
561 543 Notebook.prototype.focus_cell = function () {
@@ -584,9 +566,9 b' var IPython = (function (IPython) {'
584 566 this.select(i-1);
585 567 var cell = this.get_selected_cell();
586 568 cell.focus_cell();
587 };
569 }
588 570 this.set_dirty(true);
589 };
571 }
590 572 return this;
591 573 };
592 574
@@ -609,8 +591,8 b' var IPython = (function (IPython) {'
609 591 this.select(i+1);
610 592 var cell = this.get_selected_cell();
611 593 cell.focus_cell();
612 };
613 };
594 }
595 }
614 596 this.set_dirty();
615 597 return this;
616 598 };
@@ -650,10 +632,10 b' var IPython = (function (IPython) {'
650 632 this.select(i);
651 633 this.undelete_index = i;
652 634 this.undelete_below = false;
653 };
635 }
654 636 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
655 637 this.set_dirty(true);
656 };
638 }
657 639 return this;
658 640 };
659 641
@@ -691,7 +673,7 b' var IPython = (function (IPython) {'
691 673 this.undelete_index = null;
692 674 }
693 675 $('#undelete_cell').addClass('disabled');
694 }
676 };
695 677
696 678 /**
697 679 * Insert a cell so that after insertion the cell is at given index.
@@ -709,8 +691,8 b' var IPython = (function (IPython) {'
709 691 Notebook.prototype.insert_cell_at_index = function(type, index){
710 692
711 693 var ncells = this.ncells();
712 var index = Math.min(index,ncells);
713 index = Math.max(index,0);
694 index = Math.min(index,ncells);
695 index = Math.max(index,0);
714 696 var cell = null;
715 697
716 698 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
@@ -850,8 +832,8 b' var IPython = (function (IPython) {'
850 832 source_element.remove();
851 833 this.select(i);
852 834 this.set_dirty(true);
853 };
854 };
835 }
836 }
855 837 };
856 838
857 839 /**
@@ -870,7 +852,7 b' var IPython = (function (IPython) {'
870 852 var text = source_cell.get_text();
871 853 if (text === source_cell.placeholder) {
872 854 text = '';
873 };
855 }
874 856 // We must show the editor before setting its contents
875 857 target_cell.unrender();
876 858 target_cell.set_text(text);
@@ -883,8 +865,8 b' var IPython = (function (IPython) {'
883 865 target_cell.render();
884 866 }
885 867 this.set_dirty(true);
886 };
887 };
868 }
869 }
888 870 };
889 871
890 872 /**
@@ -904,7 +886,7 b' var IPython = (function (IPython) {'
904 886 var text = source_cell.get_text();
905 887 if (text === source_cell.placeholder) {
906 888 text = '';
907 };
889 }
908 890 // We must show the editor before setting its contents
909 891 target_cell.unrender();
910 892 target_cell.set_text(text);
@@ -914,8 +896,8 b' var IPython = (function (IPython) {'
914 896 source_element.remove();
915 897 this.select(i);
916 898 this.set_dirty(true);
917 };
918 };
899 }
900 }
919 901 };
920 902
921 903 /**
@@ -939,7 +921,7 b' var IPython = (function (IPython) {'
939 921 var text = source_cell.get_text();
940 922 if (text === source_cell.placeholder) {
941 923 text = '';
942 };
924 }
943 925 // We must show the editor before setting its contents
944 926 target_cell.set_level(level);
945 927 target_cell.unrender();
@@ -952,12 +934,12 b' var IPython = (function (IPython) {'
952 934 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
953 935 target_cell.render();
954 936 }
955 };
937 }
956 938 this.set_dirty(true);
957 939 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
958 940 {'cell_type':'heading',level:level}
959 941 );
960 };
942 }
961 943 };
962 944
963 945
@@ -978,7 +960,7 b' var IPython = (function (IPython) {'
978 960 $('#paste_cell_below').removeClass('disabled')
979 961 .on('click', function () {that.paste_cell_below();});
980 962 this.paste_enabled = true;
981 };
963 }
982 964 };
983 965
984 966 /**
@@ -992,7 +974,7 b' var IPython = (function (IPython) {'
992 974 $('#paste_cell_above').addClass('disabled').off('click');
993 975 $('#paste_cell_below').addClass('disabled').off('click');
994 976 this.paste_enabled = false;
995 };
977 }
996 978 };
997 979
998 980 /**
@@ -1003,7 +985,7 b' var IPython = (function (IPython) {'
1003 985 Notebook.prototype.cut_cell = function () {
1004 986 this.copy_cell();
1005 987 this.delete_cell();
1006 }
988 };
1007 989
1008 990 /**
1009 991 * Copy a cell.
@@ -1029,7 +1011,7 b' var IPython = (function (IPython) {'
1029 1011 var old_cell = this.get_next_cell(new_cell);
1030 1012 this.delete_cell(this.find_cell_index(old_cell));
1031 1013 this.select(this.find_cell_index(new_cell));
1032 };
1014 }
1033 1015 };
1034 1016
1035 1017 /**
@@ -1043,7 +1025,7 b' var IPython = (function (IPython) {'
1043 1025 var new_cell = this.insert_cell_above(cell_data.cell_type);
1044 1026 new_cell.fromJSON(cell_data);
1045 1027 new_cell.focus_cell();
1046 };
1028 }
1047 1029 };
1048 1030
1049 1031 /**
@@ -1057,7 +1039,7 b' var IPython = (function (IPython) {'
1057 1039 var new_cell = this.insert_cell_below(cell_data.cell_type);
1058 1040 new_cell.fromJSON(cell_data);
1059 1041 new_cell.focus_cell();
1060 };
1042 }
1061 1043 };
1062 1044
1063 1045 // Split/merge
@@ -1088,7 +1070,7 b' var IPython = (function (IPython) {'
1088 1070 new_cell.unrender();
1089 1071 new_cell.set_text(texta);
1090 1072 }
1091 };
1073 }
1092 1074 };
1093 1075
1094 1076 /**
@@ -1122,10 +1104,10 b' var IPython = (function (IPython) {'
1122 1104 // that of the original selected cell;
1123 1105 cell.render();
1124 1106 }
1125 };
1107 }
1126 1108 this.delete_cell(index-1);
1127 1109 this.select(this.find_cell_index(cell));
1128 };
1110 }
1129 1111 };
1130 1112
1131 1113 /**
@@ -1159,10 +1141,10 b' var IPython = (function (IPython) {'
1159 1141 // that of the original selected cell;
1160 1142 cell.render();
1161 1143 }
1162 };
1144 }
1163 1145 this.delete_cell(index+1);
1164 1146 this.select(this.find_cell_index(cell));
1165 };
1147 }
1166 1148 };
1167 1149
1168 1150
@@ -1365,7 +1347,7 b' var IPython = (function (IPython) {'
1365 1347 * @method start_session
1366 1348 */
1367 1349 Notebook.prototype.start_session = function () {
1368 this.session = new IPython.Session(this.notebook_name, this.notebook_path, this);
1350 this.session = new IPython.Session(this, this.options);
1369 1351 this.session.start($.proxy(this._session_started, this));
1370 1352 };
1371 1353
@@ -1382,8 +1364,8 b' var IPython = (function (IPython) {'
1382 1364 var cell = this.get_cell(i);
1383 1365 if (cell instanceof IPython.CodeCell) {
1384 1366 cell.set_kernel(this.session.kernel);
1385 };
1386 };
1367 }
1368 }
1387 1369 };
1388 1370
1389 1371 /**
@@ -1424,7 +1406,7 b' var IPython = (function (IPython) {'
1424 1406 this.command_mode();
1425 1407 cell.focus_cell();
1426 1408 this.set_dirty(true);
1427 }
1409 };
1428 1410
1429 1411 /**
1430 1412 * Execute or render cell outputs and insert a new cell below.
@@ -1520,7 +1502,7 b' var IPython = (function (IPython) {'
1520 1502 for (var i=start; i<end; i++) {
1521 1503 this.select(i);
1522 1504 this.execute_cell();
1523 };
1505 }
1524 1506 };
1525 1507
1526 1508 // Persistance and loading
@@ -1529,7 +1511,7 b' var IPython = (function (IPython) {'
1529 1511 * Getter method for this notebook's name.
1530 1512 *
1531 1513 * @method get_notebook_name
1532 * @return {String} This notebook's name
1514 * @return {String} This notebook's name (excluding file extension)
1533 1515 */
1534 1516 Notebook.prototype.get_notebook_name = function () {
1535 1517 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
@@ -1555,11 +1537,11 b' var IPython = (function (IPython) {'
1555 1537 */
1556 1538 Notebook.prototype.test_notebook_name = function (nbname) {
1557 1539 nbname = nbname || '';
1558 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1540 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1559 1541 return true;
1560 1542 } else {
1561 1543 return false;
1562 };
1544 }
1563 1545 };
1564 1546
1565 1547 /**
@@ -1577,7 +1559,7 b' var IPython = (function (IPython) {'
1577 1559 for (i=0; i<ncells; i++) {
1578 1560 // Always delete cell 0 as they get renumbered as they are deleted.
1579 1561 this.delete_cell(0);
1580 };
1562 }
1581 1563 // Save the metadata and name.
1582 1564 this.metadata = content.metadata;
1583 1565 this.notebook_name = data.name;
@@ -1601,8 +1583,8 b' var IPython = (function (IPython) {'
1601 1583
1602 1584 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1603 1585 new_cell.fromJSON(cell_data);
1604 };
1605 };
1586 }
1587 }
1606 1588 if (content.worksheets.length > 1) {
1607 1589 IPython.dialog.modal({
1608 1590 title : "Multiple worksheets",
@@ -1630,7 +1612,7 b' var IPython = (function (IPython) {'
1630 1612 var cell_array = new Array(ncells);
1631 1613 for (var i=0; i<ncells; i++) {
1632 1614 cell_array[i] = cells[i].toJSON();
1633 };
1615 }
1634 1616 var data = {
1635 1617 // Only handle 1 worksheet for now.
1636 1618 worksheets : [{
@@ -1666,7 +1648,7 b' var IPython = (function (IPython) {'
1666 1648 } else {
1667 1649 this.autosave_timer = null;
1668 1650 $([IPython.events]).trigger("autosave_disabled.Notebook");
1669 };
1651 }
1670 1652 };
1671 1653
1672 1654 /**
@@ -1701,7 +1683,7 b' var IPython = (function (IPython) {'
1701 1683 }
1702 1684 $([IPython.events]).trigger('notebook_saving.Notebook');
1703 1685 var url = utils.url_join_encode(
1704 this._baseProjectUrl,
1686 this.base_url,
1705 1687 'api/notebooks',
1706 1688 this.notebook_path,
1707 1689 this.notebook_name
@@ -1725,7 +1707,7 b' var IPython = (function (IPython) {'
1725 1707 if (this._checkpoint_after_save) {
1726 1708 this.create_checkpoint();
1727 1709 this._checkpoint_after_save = false;
1728 };
1710 }
1729 1711 };
1730 1712
1731 1713 /**
@@ -1762,7 +1744,7 b' var IPython = (function (IPython) {'
1762 1744
1763 1745 Notebook.prototype.new_notebook = function(){
1764 1746 var path = this.notebook_path;
1765 var base_project_url = this._baseProjectUrl;
1747 var base_url = this.base_url;
1766 1748 var settings = {
1767 1749 processData : false,
1768 1750 cache : false,
@@ -1773,7 +1755,7 b' var IPython = (function (IPython) {'
1773 1755 var notebook_name = data.name;
1774 1756 window.open(
1775 1757 utils.url_join_encode(
1776 base_project_url,
1758 base_url,
1777 1759 'notebooks',
1778 1760 path,
1779 1761 notebook_name
@@ -1783,7 +1765,7 b' var IPython = (function (IPython) {'
1783 1765 }
1784 1766 };
1785 1767 var url = utils.url_join_encode(
1786 base_project_url,
1768 base_url,
1787 1769 'api/notebooks',
1788 1770 path
1789 1771 );
@@ -1793,7 +1775,7 b' var IPython = (function (IPython) {'
1793 1775
1794 1776 Notebook.prototype.copy_notebook = function(){
1795 1777 var path = this.notebook_path;
1796 var base_project_url = this._baseProjectUrl;
1778 var base_url = this.base_url;
1797 1779 var settings = {
1798 1780 processData : false,
1799 1781 cache : false,
@@ -1803,7 +1785,7 b' var IPython = (function (IPython) {'
1803 1785 async : false,
1804 1786 success : function (data, status, xhr) {
1805 1787 window.open(utils.url_join_encode(
1806 base_project_url,
1788 base_url,
1807 1789 'notebooks',
1808 1790 data.path,
1809 1791 data.name
@@ -1811,7 +1793,7 b' var IPython = (function (IPython) {'
1811 1793 }
1812 1794 };
1813 1795 var url = utils.url_join_encode(
1814 base_project_url,
1796 base_url,
1815 1797 'api/notebooks',
1816 1798 path
1817 1799 );
@@ -1820,7 +1802,10 b' var IPython = (function (IPython) {'
1820 1802
1821 1803 Notebook.prototype.rename = function (nbname) {
1822 1804 var that = this;
1823 var data = {name: nbname + '.ipynb'};
1805 if (!nbname.match(/\.ipynb$/)) {
1806 nbname = nbname + ".ipynb";
1807 }
1808 var data = {name: nbname};
1824 1809 var settings = {
1825 1810 processData : false,
1826 1811 cache : false,
@@ -1833,7 +1818,7 b' var IPython = (function (IPython) {'
1833 1818 };
1834 1819 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1835 1820 var url = utils.url_join_encode(
1836 this._baseProjectUrl,
1821 this.base_url,
1837 1822 'api/notebooks',
1838 1823 this.notebook_path,
1839 1824 this.notebook_name
@@ -1850,7 +1835,7 b' var IPython = (function (IPython) {'
1850 1835 dataType: "json",
1851 1836 };
1852 1837 var url = utils.url_join_encode(
1853 this._baseProjectUrl,
1838 this.base_url,
1854 1839 'api/notebooks',
1855 1840 this.notebook_path,
1856 1841 this.notebook_name
@@ -1860,19 +1845,18 b' var IPython = (function (IPython) {'
1860 1845
1861 1846
1862 1847 Notebook.prototype.rename_success = function (json, status, xhr) {
1863 this.notebook_name = json.name;
1864 var name = this.notebook_name;
1848 var name = this.notebook_name = json.name;
1865 1849 var path = json.path;
1866 1850 this.session.rename_notebook(name, path);
1867 1851 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1868 }
1852 };
1869 1853
1870 1854 Notebook.prototype.rename_error = function (xhr, status, error) {
1871 1855 var that = this;
1872 1856 var dialog = $('<div/>').append(
1873 1857 $("<p/>").addClass("rename-message")
1874 1858 .text('This notebook name already exists.')
1875 )
1859 );
1876 1860 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1877 1861 IPython.dialog.modal({
1878 1862 title: "Notebook Rename Error!",
@@ -1896,7 +1880,7 b' var IPython = (function (IPython) {'
1896 1880 that.find('input[type="text"]').focus();
1897 1881 }
1898 1882 });
1899 }
1883 };
1900 1884
1901 1885 /**
1902 1886 * Request a notebook's data from the server.
@@ -1919,7 +1903,7 b' var IPython = (function (IPython) {'
1919 1903 };
1920 1904 $([IPython.events]).trigger('notebook_loading.Notebook');
1921 1905 var url = utils.url_join_encode(
1922 this._baseProjectUrl,
1906 this.base_url,
1923 1907 'api/notebooks',
1924 1908 this.notebook_path,
1925 1909 this.notebook_name
@@ -1946,7 +1930,7 b' var IPython = (function (IPython) {'
1946 1930 } else {
1947 1931 this.select(0);
1948 1932 this.command_mode();
1949 };
1933 }
1950 1934 this.set_dirty(false);
1951 1935 this.scroll_to_top();
1952 1936 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
@@ -1971,7 +1955,7 b' var IPython = (function (IPython) {'
1971 1955 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1972 1956 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1973 1957 this_vs + ". You can still work with this notebook, but some features " +
1974 "introduced in later notebook versions may not be available."
1958 "introduced in later notebook versions may not be available.";
1975 1959
1976 1960 IPython.dialog.modal({
1977 1961 title : "Newer Notebook",
@@ -1987,7 +1971,7 b' var IPython = (function (IPython) {'
1987 1971
1988 1972 // Create the session after the notebook is completely loaded to prevent
1989 1973 // code execution upon loading, which is a security risk.
1990 if (this.session == null) {
1974 if (this.session === null) {
1991 1975 this.start_session();
1992 1976 }
1993 1977 // load our checkpoint list
@@ -2012,10 +1996,11 b' var IPython = (function (IPython) {'
2012 1996 */
2013 1997 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2014 1998 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
1999 var msg;
2015 2000 if (xhr.status === 400) {
2016 var msg = error;
2001 msg = error;
2017 2002 } else if (xhr.status === 500) {
2018 var msg = "An unknown error occurred while loading this notebook. " +
2003 msg = "An unknown error occurred while loading this notebook. " +
2019 2004 "This version can load notebook formats " +
2020 2005 "v" + this.nbformat + " or earlier.";
2021 2006 }
@@ -2026,7 +2011,7 b' var IPython = (function (IPython) {'
2026 2011 "OK": {}
2027 2012 }
2028 2013 });
2029 }
2014 };
2030 2015
2031 2016 /********************* checkpoint-related *********************/
2032 2017
@@ -2069,7 +2054,7 b' var IPython = (function (IPython) {'
2069 2054 */
2070 2055 Notebook.prototype.list_checkpoints = function () {
2071 2056 var url = utils.url_join_encode(
2072 this._baseProjectUrl,
2057 this.base_url,
2073 2058 'api/notebooks',
2074 2059 this.notebook_path,
2075 2060 this.notebook_name,
@@ -2091,7 +2076,7 b' var IPython = (function (IPython) {'
2091 2076 * @param {jqXHR} xhr jQuery Ajax object
2092 2077 */
2093 2078 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2094 var data = $.parseJSON(data);
2079 data = $.parseJSON(data);
2095 2080 this.checkpoints = data;
2096 2081 if (data.length) {
2097 2082 this.last_checkpoint = data[data.length - 1];
@@ -2120,9 +2105,9 b' var IPython = (function (IPython) {'
2120 2105 */
2121 2106 Notebook.prototype.create_checkpoint = function () {
2122 2107 var url = utils.url_join_encode(
2123 this._baseProjectUrl,
2108 this.base_url,
2124 2109 'api/notebooks',
2125 this.notebookPath(),
2110 this.notebook_path,
2126 2111 this.notebook_name,
2127 2112 'checkpoints'
2128 2113 );
@@ -2142,7 +2127,7 b' var IPython = (function (IPython) {'
2142 2127 * @param {jqXHR} xhr jQuery Ajax object
2143 2128 */
2144 2129 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2145 var data = $.parseJSON(data);
2130 data = $.parseJSON(data);
2146 2131 this.add_checkpoint(data);
2147 2132 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2148 2133 };
@@ -2161,7 +2146,7 b' var IPython = (function (IPython) {'
2161 2146
2162 2147 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2163 2148 var that = this;
2164 var checkpoint = checkpoint || this.last_checkpoint;
2149 checkpoint = checkpoint || this.last_checkpoint;
2165 2150 if ( ! checkpoint ) {
2166 2151 console.log("restore dialog, but no checkpoint to restore to!");
2167 2152 return;
@@ -2196,7 +2181,7 b' var IPython = (function (IPython) {'
2196 2181 Cancel : {}
2197 2182 }
2198 2183 });
2199 }
2184 };
2200 2185
2201 2186 /**
2202 2187 * Restore the notebook to a checkpoint state.
@@ -2207,9 +2192,9 b' var IPython = (function (IPython) {'
2207 2192 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2208 2193 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2209 2194 var url = utils.url_join_encode(
2210 this._baseProjectUrl,
2195 this.base_url,
2211 2196 'api/notebooks',
2212 this.notebookPath(),
2197 this.notebook_path,
2213 2198 this.notebook_name,
2214 2199 'checkpoints',
2215 2200 checkpoint
@@ -2255,9 +2240,9 b' var IPython = (function (IPython) {'
2255 2240 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2256 2241 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2257 2242 var url = utils.url_join_encode(
2258 this._baseProjectUrl,
2243 this.base_url,
2259 2244 'api/notebooks',
2260 this.notebookPath(),
2245 this.notebook_path,
2261 2246 this.notebook_name,
2262 2247 'checkpoints',
2263 2248 checkpoint
@@ -127,7 +127,7 b' var IPython = (function (IPython) {'
127 127
128 128 SaveWidget.prototype.update_address_bar = function(){
129 129 var nbname = IPython.notebook.notebook_name;
130 var path = IPython.notebook.notebookPath();
130 var path = IPython.notebook.notebook_path;
131 131 var state = {path : utils.url_join_encode(path, nbname)};
132 132 window.history.replaceState(state, "", utils.url_join_encode(
133 133 "/notebooks",
@@ -25,12 +25,12 b' var IPython = (function (IPython) {'
25 25 * A Kernel Class to communicate with the Python kernel
26 26 * @Class Kernel
27 27 */
28 var Kernel = function (base_url) {
28 var Kernel = function (kernel_service_url) {
29 29 this.kernel_id = null;
30 30 this.shell_channel = null;
31 31 this.iopub_channel = null;
32 32 this.stdin_channel = null;
33 this.base_url = base_url;
33 this.kernel_service_url = kernel_service_url;
34 34 this.running = false;
35 35 this.username = "username";
36 36 this.session_id = utils.uuid();
@@ -94,8 +94,7 b' var IPython = (function (IPython) {'
94 94 params = params || {};
95 95 if (!this.running) {
96 96 var qs = $.param(params);
97 var url = this.base_url + '?' + qs;
98 $.post(url,
97 $.post(utils.url_join_encode(this.kernel_service_url) + '?' + qs,
99 98 $.proxy(this._kernel_started, this),
100 99 'json'
101 100 );
@@ -114,8 +113,7 b' var IPython = (function (IPython) {'
114 113 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
115 114 if (this.running) {
116 115 this.stop_channels();
117 var url = utils.url_join_encode(this.kernel_url, "restart");
118 $.post(url,
116 $.post(utils.url_join_encode(this.kernel_url, "restart"),
119 117 $.proxy(this._kernel_started, this),
120 118 'json'
121 119 );
@@ -133,8 +131,10 b' var IPython = (function (IPython) {'
133 131 var prot = location.protocol.replace('http', 'ws') + "//";
134 132 ws_url = prot + location.host + ws_url;
135 133 }
136 this.ws_url = ws_url;
137 this.kernel_url = utils.url_join_encode(this.base_url, this.kernel_id);
134 var parsed = utils.parse_url(ws_url);
135 this.ws_host = parsed.protocol + "//" + parsed.host;
136 this.kernel_url = utils.url_path_join(this.kernel_service_url, this.kernel_id);
137 this.ws_url = utils.url_path_join(parsed.pathname, this.kernel_url);
138 138 this.start_channels();
139 139 };
140 140
@@ -155,12 +155,18 b' var IPython = (function (IPython) {'
155 155 Kernel.prototype.start_channels = function () {
156 156 var that = this;
157 157 this.stop_channels();
158 var ws_url = this.ws_url + this.kernel_url;
159 console.log("Starting WebSockets:", ws_url);
160 this.shell_channel = new this.WebSocket(ws_url + "/shell");
161 this.stdin_channel = new this.WebSocket(ws_url + "/stdin");
162 this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
158 console.log("Starting WebSockets:", this.ws_host + this.ws_url);
159 this.shell_channel = new this.WebSocket(
160 this.ws_host + utils.url_join_encode(this.ws_url, "shell")
161 );
162 this.stdin_channel = new this.WebSocket(
163 this.ws_host + utils.url_join_encode(this.ws_url, "stdin")
164 );
165 this.iopub_channel = new this.WebSocket(
166 this.ws_host + utils.url_join_encode(this.ws_url, "iopub")
167 );
163 168
169 var ws_host_url = this.ws_host + this.ws_url;
164 170 var already_called_onclose = false; // only alert once
165 171 var ws_closed_early = function(evt){
166 172 if (already_called_onclose){
@@ -168,7 +174,7 b' var IPython = (function (IPython) {'
168 174 }
169 175 already_called_onclose = true;
170 176 if ( ! evt.wasClean ){
171 that._websocket_closed(ws_url, true);
177 that._websocket_closed(ws_host_url, true);
172 178 }
173 179 };
174 180 var ws_closed_late = function(evt){
@@ -177,7 +183,7 b' var IPython = (function (IPython) {'
177 183 }
178 184 already_called_onclose = true;
179 185 if ( ! evt.wasClean ){
180 that._websocket_closed(ws_url, false);
186 that._websocket_closed(ws_host_url, false);
181 187 }
182 188 };
183 189 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
@@ -387,7 +393,7 b' var IPython = (function (IPython) {'
387 393 Kernel.prototype.interrupt = function () {
388 394 if (this.running) {
389 395 $([IPython.events]).trigger('status_interrupting.Kernel', {kernel: this});
390 $.post(this.kernel_url + "/interrupt");
396 $.post(utils.url_join_encode(this.kernel_url, "interrupt"));
391 397 }
392 398 };
393 399
@@ -399,7 +405,7 b' var IPython = (function (IPython) {'
399 405 cache : false,
400 406 type : "DELETE"
401 407 };
402 $.ajax(this.kernel_url, settings);
408 $.ajax(utils.url_join_encode(this.kernel_url), settings);
403 409 }
404 410 };
405 411
@@ -14,13 +14,14 b' var IPython = (function (IPython) {'
14 14
15 15 var utils = IPython.utils;
16 16
17 var Session = function(notebook_name, notebook_path, notebook){
17 var Session = function(notebook, options){
18 18 this.kernel = null;
19 19 this.id = null;
20 this.name = notebook_name;
21 this.path = notebook_path;
22 20 this.notebook = notebook;
23 this._baseProjectUrl = notebook.baseProjectUrl();
21 this.name = notebook.notebook_name;
22 this.path = notebook.notebook_path;
23 this.base_url = notebook.base_url;
24 this.base_kernel_url = options.base_kernel_url || utils.get_body_data("baseKernelUrl");
24 25 };
25 26
26 27 Session.prototype.start = function(callback) {
@@ -44,7 +45,7 b' var IPython = (function (IPython) {'
44 45 }
45 46 },
46 47 };
47 var url = utils.url_join_encode(this._baseProjectUrl, 'api/sessions');
48 var url = utils.url_join_encode(this.base_url, 'api/sessions');
48 49 $.ajax(url, settings);
49 50 };
50 51
@@ -64,7 +65,7 b' var IPython = (function (IPython) {'
64 65 data: JSON.stringify(model),
65 66 dataType : "json",
66 67 };
67 var url = utils.url_join_encode(this._baseProjectUrl, 'api/sessions', this.id);
68 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
68 69 $.ajax(url, settings);
69 70 };
70 71
@@ -76,7 +77,7 b' var IPython = (function (IPython) {'
76 77 dataType : "json",
77 78 };
78 79 this.kernel.running = false;
79 var url = utils.url_join_encode(this._baseProjectUrl, 'api/sessions', this.id);
80 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
80 81 $.ajax(url, settings);
81 82 };
82 83
@@ -88,8 +89,8 b' var IPython = (function (IPython) {'
88 89 */
89 90 Session.prototype._handle_start_success = function (data, status, xhr) {
90 91 this.id = data.id;
91 var base_url = utils.url_path_join($('body').data('baseKernelUrl'), "api/kernels");
92 this.kernel = new IPython.Kernel(base_url);
92 var kernel_service_url = utils.url_path_join(this.base_kernel_url, "api/kernels");
93 this.kernel = new IPython.Kernel(kernel_service_url);
93 94 this.kernel._kernel_started(data.kernel);
94 95 };
95 96
@@ -14,17 +14,17 b' var IPython = (function (IPython) {'
14 14
15 15 var utils = IPython.utils;
16 16
17 var ClusterList = function (selector) {
17 var ClusterList = function (selector, options) {
18 18 this.selector = selector;
19 19 if (this.selector !== undefined) {
20 20 this.element = $(selector);
21 21 this.style();
22 22 this.bind_events();
23 23 }
24 };
25
26 ClusterList.prototype.baseProjectUrl = function(){
27 return this._baseProjectUrl || $('body').data('baseProjectUrl');
24 options = options || {};
25 this.options = options;
26 this.base_url = options.base_url || utils.get_body_data("baseUrl");
27 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
28 28 };
29 29
30 30 ClusterList.prototype.style = function () {
@@ -51,7 +51,7 b' var IPython = (function (IPython) {'
51 51 dataType : "json",
52 52 success : $.proxy(this.load_list_success, this)
53 53 };
54 var url = utils.url_join_encode(this.baseProjectUrl(), 'clusters');
54 var url = utils.url_join_encode(this.base_url, 'clusters');
55 55 $.ajax(url, settings);
56 56 };
57 57
@@ -65,7 +65,7 b' var IPython = (function (IPython) {'
65 65 var len = data.length;
66 66 for (var i=0; i<len; i++) {
67 67 var element = $('<div/>');
68 var item = new ClusterItem(element);
68 var item = new ClusterItem(element, this.options);
69 69 item.update_state(data[i]);
70 70 element.data('item', item);
71 71 this.element.append(element);
@@ -73,17 +73,14 b' var IPython = (function (IPython) {'
73 73 };
74 74
75 75
76 var ClusterItem = function (element) {
76 var ClusterItem = function (element, options) {
77 77 this.element = $(element);
78 this.base_url = options.base_url || utils.get_body_data("baseUrl");
79 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
78 80 this.data = null;
79 81 this.style();
80 82 };
81 83
82 ClusterItem.prototype.baseProjectUrl = function(){
83 return this._baseProjectUrl || $('body').data('baseProjectUrl');
84 };
85
86
87 84 ClusterItem.prototype.style = function () {
88 85 this.element.addClass('list_item').addClass("row-fluid");
89 86 };
@@ -138,7 +135,7 b' var IPython = (function (IPython) {'
138 135 };
139 136 status_col.text('starting');
140 137 var url = utils.url_join_encode(
141 that.baseProjectUrl(),
138 that.base_url,
142 139 'clusters',
143 140 that.data.profile,
144 141 'start'
@@ -180,7 +177,7 b' var IPython = (function (IPython) {'
180 177 };
181 178 status_col.text('stopping');
182 179 var url = utils.url_join_encode(
183 that.baseProjectUrl(),
180 that.base_url,
184 181 'clusters',
185 182 that.data.profile,
186 183 'stop'
@@ -15,12 +15,16 b' $(document).ready(function () {'
15 15 IPython.page = new IPython.Page();
16 16
17 17 $('#new_notebook').button().click(function (e) {
18 IPython.notebook_list.new_notebook($('body').data('baseProjectUrl'))
18 IPython.notebook_list.new_notebook()
19 19 });
20
21 IPython.notebook_list = new IPython.NotebookList('#notebook_list');
22 IPython.cluster_list = new IPython.ClusterList('#cluster_list');
23 IPython.login_widget = new IPython.LoginWidget('#login_widget');
20
21 var opts = {
22 base_url : IPython.utils.get_body_data("baseUrl"),
23 notebook_path : IPython.utils.get_body_data("notebookPath"),
24 };
25 IPython.notebook_list = new IPython.NotebookList('#notebook_list', opts);
26 IPython.cluster_list = new IPython.ClusterList('#cluster_list', opts);
27 IPython.login_widget = new IPython.LoginWidget('#login_widget', opts);
24 28
25 29 var interval_id=0;
26 30 // auto refresh every xx secondes, no need to be fast,
@@ -14,7 +14,7 b' var IPython = (function (IPython) {'
14 14
15 15 var utils = IPython.utils;
16 16
17 var NotebookList = function (selector) {
17 var NotebookList = function (selector, options) {
18 18 this.selector = selector;
19 19 if (this.selector !== undefined) {
20 20 this.element = $(selector);
@@ -23,16 +23,10 b' var IPython = (function (IPython) {'
23 23 }
24 24 this.notebooks_list = [];
25 25 this.sessions = {};
26 this.base_url = options.base_url || utils.get_body_data("baseUrl");
27 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
26 28 };
27 29
28 NotebookList.prototype.baseProjectUrl = function () {
29 return $('body').data('baseProjectUrl');
30 };
31
32 NotebookList.prototype.notebookPath = function() {
33 return $('body').data('notebookPath');
34 };
35
36 30 NotebookList.prototype.style = function () {
37 31 $('#notebook_toolbar').addClass('list_toolbar');
38 32 $('#drag_info').addClass('toolbar_info');
@@ -112,7 +106,7 b' var IPython = (function (IPython) {'
112 106 dataType : "json",
113 107 success : $.proxy(that.sessions_loaded, this)
114 108 };
115 var url = this.baseProjectUrl() + 'api/sessions';
109 var url = utils.url_join_encode(this.base_url, 'api/sessions');
116 110 $.ajax(url,settings);
117 111 };
118 112
@@ -152,10 +146,10 b' var IPython = (function (IPython) {'
152 146 };
153 147
154 148 var url = utils.url_join_encode(
155 this.baseProjectUrl(),
149 this.base_url,
156 150 'api',
157 151 'notebooks',
158 this.notebookPath()
152 this.notebook_path
159 153 );
160 154 $.ajax(url, settings);
161 155 };
@@ -175,7 +169,7 b' var IPython = (function (IPython) {'
175 169 span12.empty();
176 170 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
177 171 }
178 var path = this.notebookPath();
172 var path = this.notebook_path;
179 173 var offset = 0;
180 174 if (path !== '') {
181 175 item = this.new_notebook_item(0);
@@ -233,7 +227,7 b' var IPython = (function (IPython) {'
233 227 item.find("a.item_link")
234 228 .attr('href',
235 229 utils.url_join_encode(
236 this.baseProjectUrl(),
230 this.base_url,
237 231 "tree",
238 232 path,
239 233 name
@@ -250,7 +244,7 b' var IPython = (function (IPython) {'
250 244 item.find("a.item_link")
251 245 .attr('href',
252 246 utils.url_join_encode(
253 this.baseProjectUrl(),
247 this.base_url,
254 248 "notebooks",
255 249 path,
256 250 nbname
@@ -291,7 +285,7 b' var IPython = (function (IPython) {'
291 285 }
292 286 };
293 287 var url = utils.url_join_encode(
294 that.baseProjectUrl(),
288 that.base_url,
295 289 'api/sessions',
296 290 session
297 291 );
@@ -331,9 +325,9 b' var IPython = (function (IPython) {'
331 325 }
332 326 };
333 327 var url = utils.url_join_encode(
334 notebooklist.baseProjectUrl(),
328 notebooklist.base_url,
335 329 'api/notebooks',
336 notebooklist.notebookPath(),
330 notebooklist.notebook_path,
337 331 nbname
338 332 );
339 333 $.ajax(url, settings);
@@ -357,7 +351,7 b' var IPython = (function (IPython) {'
357 351 if (nbname.slice(nbname.length-6, nbname.length) != ".ipynb") {
358 352 nbname = nbname + ".ipynb";
359 353 }
360 var path = that.notebookPath();
354 var path = that.notebook_path;
361 355 var nbdata = item.data('nbdata');
362 356 var content_type = 'application/json';
363 357 var model = {
@@ -380,9 +374,9 b' var IPython = (function (IPython) {'
380 374 };
381 375
382 376 var url = utils.url_join_encode(
383 that.baseProjectUrl(),
377 that.base_url,
384 378 'api/notebooks',
385 that.notebookPath(),
379 that.notebook_path,
386 380 nbname
387 381 );
388 382 $.ajax(url, settings);
@@ -402,8 +396,8 b' var IPython = (function (IPython) {'
402 396
403 397
404 398 NotebookList.prototype.new_notebook = function(){
405 var path = this.notebookPath();
406 var base_project_url = this.baseProjectUrl();
399 var path = this.notebook_path;
400 var base_url = this.base_url;
407 401 var settings = {
408 402 processData : false,
409 403 cache : false,
@@ -414,7 +408,7 b' var IPython = (function (IPython) {'
414 408 var notebook_name = data.name;
415 409 window.open(
416 410 utils.url_join_encode(
417 base_project_url,
411 base_url,
418 412 'notebooks',
419 413 path,
420 414 notebook_name),
@@ -423,7 +417,7 b' var IPython = (function (IPython) {'
423 417 }
424 418 };
425 419 var url = utils.url_join_encode(
426 base_project_url,
420 base_url,
427 421 'api/notebooks',
428 422 path
429 423 );
@@ -20,7 +20,7 b''
20 20 <div class="container">
21 21 <div class="center-nav">
22 22 <p class="navbar-text nav">Password:</p>
23 <form action="{{base_project_url}}login?next={{next}}" method="post" class="navbar-form pull-left">
23 <form action="{{base_url}}login?next={{next}}" method="post" class="navbar-form pull-left">
24 24 <input type="password" name="password" id="password_input">
25 25 <button type="submit" id="login_submit">Log in</button>
26 26 </form>
@@ -21,9 +21,9 b''
21 21 {% endif %}
22 22
23 23 {% if not login_available %}
24 Proceed to the <a href="{{base_project_url}}">dashboard</a>.
24 Proceed to the <a href="{{base_url}}">dashboard</a>.
25 25 {% else %}
26 Proceed to the <a href="{{base_project_url}}login">login page</a>.
26 Proceed to the <a href="{{base_url}}login">login page</a>.
27 27 {% endif %}
28 28
29 29
@@ -22,7 +22,7 b' window.mathjax_url = "{{mathjax_url}}";'
22 22 {% block params %}
23 23
24 24 data-project="{{project}}"
25 data-base-project-url="{{base_project_url}}"
25 data-base-url="{{base_url}}"
26 26 data-base-kernel-url="{{base_kernel_url}}"
27 27 data-notebook-name="{{notebook_name}}"
28 28 data-notebook-path="{{notebook_path}}"
@@ -21,7 +21,7 b''
21 21 require.config({
22 22 baseUrl: '{{static_url("")}}',
23 23 paths: {
24 nbextensions : '{{ base_project_url }}nbextensions',
24 nbextensions : '{{ base_url }}nbextensions',
25 25 underscore : '{{static_url("components/underscore/underscore-min")}}',
26 26 backbone : '{{static_url("components/backbone/backbone-min")}}',
27 27 },
@@ -54,7 +54,7 b''
54 54 <div id="header" class="navbar navbar-static-top">
55 55 <div class="navbar-inner navbar-nobg">
56 56 <div class="container">
57 <div id="ipython_notebook" class="nav brand pull-left"><a href="{{base_project_url}}tree/{{notebook_path}}" alt='dashboard'><img src='{{static_url("base/images/ipynblogo.png") }}' alt='IPython Notebook'/></a></div>
57 <div id="ipython_notebook" class="nav brand pull-left"><a href="{{base_url}}tree/{{notebook_path}}" alt='dashboard'><img src='{{static_url("base/images/ipynblogo.png") }}' alt='IPython Notebook'/></a></div>
58 58
59 59 {% block login_widget %}
60 60
@@ -11,7 +11,7 b''
11 11 {% block params %}
12 12
13 13 data-project="{{project}}"
14 data-base-project-url="{{base_project_url}}"
14 data-base-url="{{base_url}}"
15 15 data-notebook-path="{{notebook_path}}"
16 16 data-base-kernel-url="{{base_kernel_url}}"
17 17
@@ -18,9 +18,12 b' casper.test_items = function (baseUrl) {'
18 18 if (!item.label.match('.ipynb$')) {
19 19 var followed_url = baseUrl+item.link;
20 20 if (!followed_url.match('/\.\.$')) {
21 casper.thenOpen(baseUrl+item.link, function () {
21 casper.thenOpen(followed_url, function () {
22 22 casper.wait_for_dashboard();
23 this.test.assertEquals(this.getCurrentUrl(), followed_url, 'Testing dashboard link: '+followed_url);
23 // getCurrentUrl is with host, and url-decoded,
24 // but item.link is without host, and url-encoded
25 var expected = baseUrl + decodeURIComponent(item.link);
26 this.test.assertEquals(this.getCurrentUrl(), expected, 'Testing dashboard link: ' + expected);
24 27 casper.test_items(baseUrl);
25 28 this.back();
26 29 });
@@ -31,7 +34,7 b' casper.test_items = function (baseUrl) {'
31 34 }
32 35
33 36 casper.dashboard_test(function () {
34 baseUrl = this.get_notebook_server()
37 baseUrl = this.get_notebook_server();
35 38 casper.test_items(baseUrl);
36 39 })
37 40
@@ -30,12 +30,12 b' class TreeHandler(IPythonHandler):'
30 30 """Render the tree view, listing notebooks, clusters, etc."""
31 31
32 32 def generate_breadcrumbs(self, path):
33 breadcrumbs = [(url_escape(url_path_join(self.base_project_url, 'tree')), '')]
33 breadcrumbs = [(url_escape(url_path_join(self.base_url, 'tree')), '')]
34 34 comps = path.split('/')
35 35 ncomps = len(comps)
36 36 for i in range(ncomps):
37 37 if comps[i]:
38 link = url_escape(url_path_join(self.base_project_url, 'tree', *comps[0:i+1]))
38 link = url_escape(url_path_join(self.base_url, 'tree', *comps[0:i+1]))
39 39 breadcrumbs.append((link, comps[i]))
40 40 return breadcrumbs
41 41
@@ -57,7 +57,7 b' class TreeHandler(IPythonHandler):'
57 57 if name is not None:
58 58 # is a notebook, redirect to notebook handler
59 59 url = url_escape(url_path_join(
60 self.base_project_url, 'notebooks', path, name
60 self.base_url, 'notebooks', path, name
61 61 ))
62 62 self.log.debug("Redirecting %s to %s", self.request.path, url)
63 63 self.redirect(url)
@@ -81,7 +81,7 b' class TreeRedirectHandler(IPythonHandler):'
81 81 @web.authenticated
82 82 def get(self, path=''):
83 83 url = url_escape(url_path_join(
84 self.base_project_url, 'tree', path.strip('/')
84 self.base_url, 'tree', path.strip('/')
85 85 ))
86 86 self.log.debug("Redirecting %s to %s", self.request.path, url)
87 87 self.redirect(url)
@@ -181,8 +181,8 b' class JSController(TestController):'
181 181 self.ipydir = TemporaryDirectory()
182 182 self.nbdir = TemporaryDirectory()
183 183 print("Running notebook tests in directory: %r" % self.nbdir.name)
184 os.makedirs(os.path.join(self.nbdir.name, os.path.join('subdir1', 'subdir1a')))
185 os.makedirs(os.path.join(self.nbdir.name, os.path.join('subdir2', 'subdir2a')))
184 os.makedirs(os.path.join(self.nbdir.name, os.path.join(u'subir1', u'sub ∂ir 1a')))
185 os.makedirs(os.path.join(self.nbdir.name, os.path.join(u'subir2', u'sub ∂ir 1b')))
186 186 self.dirs.append(self.ipydir)
187 187 self.dirs.append(self.nbdir)
188 188
General Comments 0
You need to be logged in to leave comments. Login now