From 8d2b1c3b9a9b70e1520bc51e4022444251eeede6 2014-12-06 15:29:06 From: Nathan Heijermans Date: 2014-12-06 15:29:06 Subject: [PATCH] Merge remote-tracking branch 'upstream/master' --- diff --git a/IPython/core/debugger.py b/IPython/core/debugger.py index d156a0f..80c3ada 100644 --- a/IPython/core/debugger.py +++ b/IPython/core/debugger.py @@ -277,7 +277,7 @@ class Pdb(OldPdb): try: OldPdb.interaction(self, frame, traceback) except KeyboardInterrupt: - self.shell.write("\nKeyboardInterrupt\n") + self.shell.write('\n' + self.shell.get_exception_only()) break else: break diff --git a/IPython/core/formatters.py b/IPython/core/formatters.py index e66d569..6ceb23c 100644 --- a/IPython/core/formatters.py +++ b/IPython/core/formatters.py @@ -40,38 +40,21 @@ else: #----------------------------------------------------------------------------- -def _valid_formatter(f): - """Return whether an object is a valid formatter - - Cases checked: +def _safe_get_formatter_method(obj, name): + """Safely get a formatter method - - bound methods OK - - unbound methods NO - - callable with zero args OK + - Classes cannot have formatter methods, only instance + - protect against proxy objects that claim to have everything """ - if f is None: - return False - elif isinstance(f, type(str.find)): - # unbound methods on compiled classes have type method_descriptor - return False - elif isinstance(f, types.BuiltinFunctionType): - # bound methods on compiled classes have type builtin_function - return True - elif callable(f): - # anything that works with zero args should be okay - try: - inspect.getcallargs(f) - except Exception: - return False - else: - return True - return False - -def _safe_get_formatter_method(obj, name): - """Safely get a formatter method""" + if inspect.isclass(obj): + # repr methods only make sense on instances, not classes + return None method = pretty._safe_getattr(obj, name, None) - # formatter methods must be bound - if _valid_formatter(method): + if callable(method): + # obj claims to have repr method... + if callable(pretty._safe_getattr(obj, '_ipython_canary_method_should_not_exist_', None)): + # ...but don't trust proxy objects that claim to have everything + return None return method diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index e4fce96..48374fe 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -22,6 +22,7 @@ import re import runpy import sys import tempfile +import traceback import types import subprocess from io import open as io_open @@ -1786,6 +1787,15 @@ class InteractiveShell(SingletonConfigurable): """ self.write_err("UsageError: %s" % exc) + def get_exception_only(self, exc_tuple=None): + """ + Return as a string (ending with a newline) the exception that + just occurred, without any traceback. + """ + etype, value, tb = self._get_exc_info(exc_tuple) + msg = traceback.format_exception_only(etype, value) + return ''.join(msg) + def showtraceback(self, exc_tuple=None, filename=None, tb_offset=None, exception_only=False): """Display the exception that just occurred. @@ -1838,7 +1848,7 @@ class InteractiveShell(SingletonConfigurable): self._showtraceback(etype, value, stb) except KeyboardInterrupt: - self.write_err("\nKeyboardInterrupt\n") + self.write_err('\n' + self.get_exception_only()) def _showtraceback(self, etype, evalue, stb): """Actually show a traceback. @@ -1993,7 +2003,7 @@ class InteractiveShell(SingletonConfigurable): continue @skip_doctest - def set_next_input(self, s): + def set_next_input(self, s, replace=False): """ Sets the 'default' input string for the next command line. Requires readline. @@ -2355,7 +2365,7 @@ class InteractiveShell(SingletonConfigurable): try: ec = os.system(cmd) except KeyboardInterrupt: - self.write_err("\nKeyboardInterrupt\n") + self.write_err('\n' + self.get_exception_only()) ec = -2 else: cmd = py3compat.unicode_to_str(cmd) @@ -2374,7 +2384,7 @@ class InteractiveShell(SingletonConfigurable): ec = subprocess.call(cmd, shell=True, executable=executable) except KeyboardInterrupt: # intercept control-C; a long traceback is not useful here - self.write_err("\nKeyboardInterrupt\n") + self.write_err('\n' + self.get_exception_only()) ec = 130 if ec > 128: ec = -(ec - 128) diff --git a/IPython/core/magics/code.py b/IPython/core/magics/code.py index fd869d7..08d8a35 100644 --- a/IPython/core/magics/code.py +++ b/IPython/core/magics/code.py @@ -353,7 +353,9 @@ class CodeMagics(Magics): print('Operation cancelled.') return - self.shell.set_next_input(contents) + contents = "# %load {}\n".format(arg_s) + contents + + self.shell.set_next_input(contents, replace=True) @staticmethod def _find_edit_target(shell, args, opts, last_call): diff --git a/IPython/core/magics/osm.py b/IPython/core/magics/osm.py index 553f49f..c7a0891 100644 --- a/IPython/core/magics/osm.py +++ b/IPython/core/magics/osm.py @@ -165,7 +165,6 @@ class OSMagics(Magics): path = [os.path.abspath(os.path.expanduser(p)) for p in os.environ.get('PATH','').split(os.pathsep)] - path = filter(os.path.isdir,path) syscmdlist = [] # Now define isexec in a cross platform manner. @@ -189,8 +188,12 @@ class OSMagics(Magics): # the innermost part if os.name == 'posix': for pdir in path: - os.chdir(pdir) - for ff in os.listdir(pdir): + try: + os.chdir(pdir) + dirlist = os.listdir(pdir) + except OSError: + continue + for ff in dirlist: if isexec(ff): try: # Removes dots from the name since ipython @@ -205,8 +208,12 @@ class OSMagics(Magics): else: no_alias = Alias.blacklist for pdir in path: - os.chdir(pdir) - for ff in os.listdir(pdir): + try: + os.chdir(pdir) + dirlist = os.listdir(pdir) + except OSError: + continue + for ff in dirlist: base, ext = os.path.splitext(ff) if isexec(ff) and base.lower() not in no_alias: if ext.lower() == '.exe': diff --git a/IPython/core/tests/test_formatters.py b/IPython/core/tests/test_formatters.py index 1c511ea..5322bb3 100644 --- a/IPython/core/tests/test_formatters.py +++ b/IPython/core/tests/test_formatters.py @@ -314,7 +314,6 @@ def test_print_method_bound(): class MyHTML(object): def _repr_html_(self): return "hello" - with capture_output() as captured: result = f(MyHTML) nt.assert_is(result, None) @@ -325,6 +324,44 @@ def test_print_method_bound(): nt.assert_equal(result, "hello") nt.assert_equal(captured.stderr, "") +def test_print_method_weird(): + + class TextMagicHat(object): + def __getattr__(self, key): + return key + + f = HTMLFormatter() + + text_hat = TextMagicHat() + nt.assert_equal(text_hat._repr_html_, '_repr_html_') + with capture_output() as captured: + result = f(text_hat) + + nt.assert_is(result, None) + nt.assert_not_in("FormatterWarning", captured.stderr) + + class CallableMagicHat(object): + def __getattr__(self, key): + return lambda : key + + call_hat = CallableMagicHat() + with capture_output() as captured: + result = f(call_hat) + + nt.assert_equal(result, None) + + class BadReprArgs(object): + def _repr_html_(self, extra, args): + return "html" + + bad = BadReprArgs() + with capture_output() as captured: + result = f(bad) + + nt.assert_is(result, None) + nt.assert_not_in("FormatterWarning", captured.stderr) + + def test_format_config(): """config objects don't pretend to support fancy reprs with lazy attrs""" f = HTMLFormatter() diff --git a/IPython/core/tests/test_interactiveshell.py b/IPython/core/tests/test_interactiveshell.py index 8374672..9f8da7a 100644 --- a/IPython/core/tests/test_interactiveshell.py +++ b/IPython/core/tests/test_interactiveshell.py @@ -482,6 +482,24 @@ class InteractiveShellTestCase(unittest.TestCase): mod = ip.new_main_mod(u'%s.py' % name, name) self.assertEqual(mod.__name__, name) + def test_get_exception_only(self): + try: + raise KeyboardInterrupt + except KeyboardInterrupt: + msg = ip.get_exception_only() + self.assertEqual(msg, 'KeyboardInterrupt\n') + + class DerivedInterrupt(KeyboardInterrupt): + pass + try: + raise DerivedInterrupt("foo") + except KeyboardInterrupt: + msg = ip.get_exception_only() + if sys.version_info[0] <= 2: + self.assertEqual(msg, 'DerivedInterrupt: foo\n') + else: + self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n') + class TestSafeExecfileNonAsciiPath(unittest.TestCase): @onlyif_unicode_paths diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index 5fa0113..a020dd5 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -245,14 +245,16 @@ def fix_frame_records_filenames(records): """ fixed_records = [] for frame, filename, line_no, func_name, lines, index in records: - # Look inside the frame's globals dictionary for __file__, which should - # be better. - better_fn = frame.f_globals.get('__file__', None) - if isinstance(better_fn, str): - # Check the type just in case someone did something weird with - # __file__. It might also be None if the error occurred during - # import. - filename = better_fn + # Look inside the frame's globals dictionary for __file__, + # which should be better. However, keep Cython filenames since + # we prefer the source filenames over the compiled .so file. + if not filename.endswith(('.pyx', '.pxd', '.pxi')): + better_fn = frame.f_globals.get('__file__', None) + if isinstance(better_fn, str): + # Check the type just in case someone did something weird with + # __file__. It might also be None if the error occurred during + # import. + filename = better_fn fixed_records.append((frame, filename, line_no, func_name, lines, index)) return fixed_records @@ -722,15 +724,23 @@ class VerboseTB(TBTools): #print '*** record:',file,lnum,func,lines,index # dbg if not file: file = '?' - elif not (file.startswith(str("<")) and file.endswith(str(">"))): - # Guess that filenames like aren't real filenames, so - # don't call abspath on them. - try: - file = abspath(file) - except OSError: - # Not sure if this can still happen: abspath now works with - # file names like - pass + elif file.startswith(str("<")) and file.endswith(str(">")): + # Not a real filename, no problem... + pass + elif not os.path.isabs(file): + # Try to make the filename absolute by trying all + # sys.path entries (which is also what linecache does) + for dirname in sys.path: + try: + fullname = os.path.join(dirname, file) + if os.path.isfile(fullname): + file = os.path.abspath(fullname) + break + except Exception: + # Just in case that sys.path contains very + # strange entries... + pass + file = py3compat.cast_unicode(file, util_path.fs_encoding) link = tpl_link % file args, varargs, varkw, locals = inspect.getargvalues(frame) diff --git a/IPython/html/services/contents/filemanager.py b/IPython/html/services/contents/filemanager.py index 2a313ba..2ca1f41 100644 --- a/IPython/html/services/contents/filemanager.py +++ b/IPython/html/services/contents/filemanager.py @@ -611,9 +611,9 @@ class FileContentsManager(ContentsManager): return "Serving notebooks from local directory: %s" % self.root_dir def get_kernel_path(self, path, model=None): - """Return the initial working dir a kernel associated with a given notebook""" + """Return the initial API path of a kernel associated with a given notebook""" if '/' in path: parent_dir = path.rsplit('/', 1)[0] else: parent_dir = '' - return self._get_os_path(parent_dir) + return parent_dir diff --git a/IPython/html/services/contents/manager.py b/IPython/html/services/contents/manager.py index 491401f..1c87064 100644 --- a/IPython/html/services/contents/manager.py +++ b/IPython/html/services/contents/manager.py @@ -187,8 +187,12 @@ class ContentsManager(LoggingConfigurable): KernelManagers can turn this value into a filesystem path, or ignore it altogether. + + The default value here will start kernels in the directory of the + notebook server. FileContentsManager overrides this to use the + directory containing the notebook. """ - return path + return '' def increment_filename(self, filename, path='', insert=''): """Increment a filename until it is unique. diff --git a/IPython/html/services/kernels/kernelmanager.py b/IPython/html/services/kernels/kernelmanager.py index e1bd5c2..db73aa4 100644 --- a/IPython/html/services/kernels/kernelmanager.py +++ b/IPython/html/services/kernels/kernelmanager.py @@ -54,14 +54,10 @@ class MappingKernelManager(MultiKernelManager): def cwd_for_path(self, path): """Turn API path into absolute OS path.""" - # short circuit for NotebookManagers that pass in absolute paths - if os.path.exists(path): - return path - os_path = to_os_path(path, self.root_dir) # in the case of notebooks and kernels not being on the same filesystem, # walk up to root_dir if the paths don't exist - while not os.path.exists(os_path) and os_path != self.root_dir: + while not os.path.isdir(os_path) and os_path != self.root_dir: os_path = os.path.dirname(os_path) return os_path diff --git a/IPython/html/services/kernelspecs/handlers.py b/IPython/html/services/kernelspecs/handlers.py index f810414..2d1a75f 100644 --- a/IPython/html/services/kernelspecs/handlers.py +++ b/IPython/html/services/kernelspecs/handlers.py @@ -7,8 +7,6 @@ from tornado import web from ...base.handlers import IPythonHandler, json_errors -from IPython.kernel.kernelspec import _pythonfirst - class MainKernelSpecHandler(IPythonHandler): SUPPORTED_METHODS = ('GET',) @@ -17,18 +15,21 @@ class MainKernelSpecHandler(IPythonHandler): @json_errors def get(self): ksm = self.kernel_spec_manager - results = [] - for kernel_name in sorted(ksm.find_kernel_specs(), key=_pythonfirst): + km = self.kernel_manager + model = {} + model['default'] = km.default_kernel_name + model['kernelspecs'] = specs = {} + for kernel_name in ksm.find_kernel_specs(): try: d = ksm.get_kernel_spec(kernel_name).to_dict() except Exception: self.log.error("Failed to load kernel spec: '%s'", kernel_name, exc_info=True) continue d['name'] = kernel_name - results.append(d) + specs[kernel_name] = d self.set_header("Content-Type", 'application/json') - self.finish(json.dumps(results)) + self.finish(json.dumps(model)) class KernelSpecHandler(IPythonHandler): diff --git a/IPython/html/services/kernelspecs/tests/test_kernelspecs_api.py b/IPython/html/services/kernelspecs/tests/test_kernelspecs_api.py index a82a751..0d43fb1 100644 --- a/IPython/html/services/kernelspecs/tests/test_kernelspecs_api.py +++ b/IPython/html/services/kernelspecs/tests/test_kernelspecs_api.py @@ -78,16 +78,22 @@ class APITest(NotebookTestBase): with open(pjoin(bad_kernel_dir, 'kernel.json'), 'w') as f: f.write("garbage") - specs = self.ks_api.list().json() - assert isinstance(specs, list) + model = self.ks_api.list().json() + assert isinstance(model, dict) + self.assertEqual(model['default'], NATIVE_KERNEL_NAME) + specs = model['kernelspecs'] + assert isinstance(specs, dict) # 2: the sample kernelspec created in setUp, and the native Python kernel self.assertGreaterEqual(len(specs), 2) shutil.rmtree(bad_kernel_dir) def test_list_kernelspecs(self): - specs = self.ks_api.list().json() - assert isinstance(specs, list) + model = self.ks_api.list().json() + assert isinstance(model, dict) + self.assertEqual(model['default'], NATIVE_KERNEL_NAME) + specs = model['kernelspecs'] + assert isinstance(specs, dict) # 2: the sample kernelspec created in setUp, and the native Python kernel self.assertGreaterEqual(len(specs), 2) @@ -98,8 +104,8 @@ class APITest(NotebookTestBase): def is_default_kernelspec(s): return s['name'] == NATIVE_KERNEL_NAME and s['display_name'].startswith("IPython") - assert any(is_sample_kernelspec(s) for s in specs), specs - assert any(is_default_kernelspec(s) for s in specs), specs + assert any(is_sample_kernelspec(s) for s in specs.values()), specs + assert any(is_default_kernelspec(s) for s in specs.values()), specs def test_get_kernelspec(self): spec = self.ks_api.kernel_spec_info('Sample').json() # Case insensitive diff --git a/IPython/html/static/base/js/page.js b/IPython/html/static/base/js/page.js index 7c25285..52e89b7 100644 --- a/IPython/html/static/base/js/page.js +++ b/IPython/html/static/base/js/page.js @@ -4,14 +4,17 @@ define([ 'base/js/namespace', 'jquery', -], function(IPython, $){ + 'base/js/events', +], function(IPython, $, events){ "use strict"; var Page = function () { this.bind_events(); + this._resize_header(); }; Page.prototype.bind_events = function () { + events.on('resize-header.Page', $.proxy(this._resize_header, this)); }; Page.prototype.show = function () { @@ -41,6 +44,11 @@ define([ $('div#site').css('display','block'); }; + Page.prototype._resize_header = function() { + // Update the header's size. + $('#header-spacer').height($('#header').height()); + }; + // Register self in the global namespace for convenience. IPython.Page = Page; return {'Page': Page}; diff --git a/IPython/html/static/base/js/utils.js b/IPython/html/static/base/js/utils.js index ce4ed4b..6d42141 100644 --- a/IPython/html/static/base/js/utils.js +++ b/IPython/html/static/base/js/utils.js @@ -541,7 +541,16 @@ define([ if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux"; return OSName; })(); - + + var get_url_param = function (name) { + // get a URL parameter. I cannot believe we actually need this. + // Based on http://stackoverflow.com/a/25359264/938949 + var match = new RegExp('[\?&]' + name + '=([^&]*)').exec(window.location.search); + if (match){ + return decodeURIComponent(match[1] || ''); + } + }; + var is_or_has = function (a, b) { /** * Is b a child of a or a itself? @@ -632,6 +641,7 @@ define([ * Like $.ajax, but returning an ES6 promise. success and error settings * will be ignored. */ + settings = settings || {}; return new Promise(function(resolve, reject) { settings.success = function(data, status, jqXHR) { resolve(data); @@ -766,6 +776,33 @@ define([ }; }; + var typeset = function(element, text) { + /** + * Apply MathJax rendering to an element, and optionally set its text + * + * If MathJax is not available, make no changes. + * + * Returns the output any number of typeset elements, or undefined if + * MathJax was not available. + * + * Parameters + * ---------- + * element: Node, NodeList, or jQuery selection + * text: option string + */ + if(!window.MathJax){ + return; + } + var $el = element.jquery ? element : $(element); + if(arguments.length > 1){ + $el.text(text); + } + return $el.map(function(){ + // MathJax takes a DOM node: $.map makes `this` the context + return MathJax.Hub.Queue(["Typeset", MathJax.Hub, this]); + }); + }; + var utils = { regex_split : regex_split, uuid : uuid, @@ -786,6 +823,7 @@ define([ from_absolute_cursor_pos : from_absolute_cursor_pos, browser : browser, platform: platform, + get_url_param: get_url_param, is_or_has : is_or_has, is_focused : is_focused, mergeopt: mergeopt, @@ -799,6 +837,7 @@ define([ load_class: load_class, resolve_promises_dict: resolve_promises_dict, reject: reject, + typeset: typeset, }; // Backwards compatability. diff --git a/IPython/html/static/base/less/page.less b/IPython/html/static/base/less/page.less index 772a4bb..baa93a1 100644 --- a/IPython/html/static/base/less/page.less +++ b/IPython/html/static/base/less/page.less @@ -20,11 +20,33 @@ body { div#header { /* Initially hidden to prevent FLOUC */ display: none; - margin-bottom: 0px; - padding-left: 30px; - padding-bottom: 5px; - border-bottom: 1px solid @navbar-default-border; - .border-box-sizing(); + margin-bottom: -6px; + position: fixed; + top: 0; + width: 100%; + background-color: @body-bg; + min-height: 31px; + + /* Display over codemirror */ + z-index: 100; + + #header-container { + margin-bottom: 0px; + padding-left: 30px; + padding-bottom: 5px; + .border-box-sizing(); + } + + .header-bar { + width: 100%; + height: 0px; + border-bottom: 1px solid @navbar-default-border; + } +} + +#header-spacer { + width: 100%; + visibility: hidden; } #ipython_notebook { diff --git a/IPython/html/static/notebook/js/cell.js b/IPython/html/static/notebook/js/cell.js index b122c2a..9c1daa8 100644 --- a/IPython/html/static/notebook/js/cell.js +++ b/IPython/html/static/notebook/js/cell.js @@ -220,10 +220,7 @@ define([ * @method typeset */ Cell.prototype.typeset = function () { - if (window.MathJax) { - var cell_math = this.element.get(0); - MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]); - } + utils.typeset(this.element); }; /** diff --git a/IPython/html/static/notebook/js/codecell.js b/IPython/html/static/notebook/js/codecell.js index 497266f..d4a9e22 100644 --- a/IPython/html/static/notebook/js/codecell.js +++ b/IPython/html/static/notebook/js/codecell.js @@ -389,7 +389,7 @@ define([ * @private */ CodeCell.prototype._handle_set_next_input = function (payload) { - var data = {'cell': this, 'text': payload.text}; + var data = {'cell': this, 'text': payload.text, replace: payload.replace}; this.events.trigger('set_next_input.Notebook', data); }; diff --git a/IPython/html/static/notebook/js/kernelselector.js b/IPython/html/static/notebook/js/kernelselector.js index 00da819..46d4882 100644 --- a/IPython/html/static/notebook/js/kernelselector.js +++ b/IPython/html/static/notebook/js/kernelselector.js @@ -25,16 +25,27 @@ define([ KernelSelector.prototype.request_kernelspecs = function() { var url = utils.url_join_encode(this.notebook.base_url, 'api/kernelspecs'); - $.ajax(url, {success: $.proxy(this._got_kernelspecs, this)}); + utils.promising_ajax(url).then($.proxy(this._got_kernelspecs, this)); }; - KernelSelector.prototype._got_kernelspecs = function(data, status, xhr) { - this.kernelspecs = {}; + KernelSelector.prototype._got_kernelspecs = function(data) { + this.kernelspecs = data.kernelspecs; var menu = this.element.find("#kernel_selector"); var change_kernel_submenu = $("#menu-change-kernel-submenu"); - for (var i = 0; i < data.length; i++) { - var ks = data[i]; - this.kernelspecs[ks.name] = ks; + var keys = Object.keys(data.kernelspecs).sort(function (a, b) { + // sort by display_name + var da = data.kernelspecs[a].display_name; + var db = data.kernelspecs[b].display_name; + if (da === db) { + return 0; + } else if (da > db) { + return 1; + } else { + return -1; + } + }); + for (var i = 0; i < keys.length; i++) { + var ks = this.kernelspecs[keys[i]]; var ksentry = $("
  • ").attr("id", "kernel-" +ks.name).append($('') .attr('href', '#') .click($.proxy(this.change_kernel, this, ks.name)) diff --git a/IPython/html/static/notebook/js/layoutmanager.js b/IPython/html/static/notebook/js/layoutmanager.js deleted file mode 100644 index e5e48d6..0000000 --- a/IPython/html/static/notebook/js/layoutmanager.js +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) IPython Development Team. -// Distributed under the terms of the Modified BSD License. - -define([ - 'base/js/namespace', - 'jquery', -], function(IPython, $) { - "use strict"; - - var LayoutManager = function () { - this.bind_events(); - this.pager = undefined; - }; - - LayoutManager.prototype.bind_events = function () { - $(window).resize($.proxy(this.do_resize,this)); - }; - - LayoutManager.prototype.app_height = function() { - var win = $(window); - var w = win.width(); - var h = win.height(); - var header_height; - if ($('div#header').css('display') === 'none') { - header_height = 0; - } else { - header_height = $('div#header').outerHeight(true); - } - var menubar_height; - if ($('div#menubar-container').css('display') === 'none') { - menubar_height = 0; - } else { - menubar_height = $('div#menubar-container').outerHeight(true); - } - return h-header_height-menubar_height; // content height - }; - - LayoutManager.prototype.do_resize = function () { - var app_height = this.app_height(); // content height - - $('#ipython-main-app').height(app_height); // content+padding+border height - if (this.pager) { - var pager_height = this.pager.percentage_height*app_height; - var pager_splitter_height = $('div#pager_splitter').outerHeight(true); - $('div#pager').outerHeight(pager_height); - if (this.pager.expanded) { - $('div#notebook').outerHeight(app_height-pager_height-pager_splitter_height); - } else { - $('div#notebook').outerHeight(app_height-pager_splitter_height); - } - } - }; - - // Backwards compatability. - IPython.LayoutManager = LayoutManager; - - return {'LayoutManager': LayoutManager}; -}); diff --git a/IPython/html/static/notebook/js/main.js b/IPython/html/static/notebook/js/main.js index fb325bc..a6c8264 100644 --- a/IPython/html/static/notebook/js/main.js +++ b/IPython/html/static/notebook/js/main.js @@ -9,7 +9,6 @@ require([ 'services/config', 'base/js/utils', 'base/js/page', - 'notebook/js/layoutmanager', 'base/js/events', 'auth/js/loginwidget', 'notebook/js/maintoolbar', @@ -34,7 +33,6 @@ require([ configmod, utils, page, - layoutmanager, events, loginwidget, maintoolbar, @@ -66,9 +64,7 @@ require([ var user_config = $.extend({}, config.default_config); var page = new page.Page(); - var layout_manager = new layoutmanager.LayoutManager(); - var pager = new pager.Pager('div#pager', 'div#pager_splitter', { - layout_manager: layout_manager, + var pager = new pager.Pager('div#pager', { events: events}); var acts = new actions.init(); var keyboard_manager = new keyboardmanager.KeyboardManager({ @@ -104,7 +100,6 @@ require([ var menubar = new menubar.MenuBar('#menubar', $.extend({ notebook: notebook, contents: contents, - layout_manager: layout_manager, events: events, save_widget: save_widget, quick_help: quick_help}, @@ -132,9 +127,7 @@ require([ page.show(); - layout_manager.do_resize(); var first_load = function () { - layout_manager.do_resize(); var hash = document.location.hash; if (hash) { document.location.hash = ''; @@ -147,7 +140,6 @@ require([ events.on('notebook_loaded.Notebook', first_load); IPython.page = page; - IPython.layout_manager = layout_manager; IPython.notebook = notebook; IPython.contents = contents; IPython.pager = pager; diff --git a/IPython/html/static/notebook/js/menubar.js b/IPython/html/static/notebook/js/menubar.js index 3b56f9f..b00c6a2 100644 --- a/IPython/html/static/notebook/js/menubar.js +++ b/IPython/html/static/notebook/js/menubar.js @@ -24,7 +24,6 @@ define([ * Dictionary of keyword arguments. * notebook: Notebook instance * contents: ContentManager instance - * layout_manager: LayoutManager instance * events: $(Events) instance * save_widget: SaveWidget instance * quick_help: QuickHelp instance @@ -37,7 +36,6 @@ define([ this.selector = selector; this.notebook = options.notebook; this.contents = options.contents; - this.layout_manager = options.layout_manager; this.events = options.events; this.save_widget = options.save_widget; this.quick_help = options.quick_help; @@ -88,6 +86,13 @@ define([ } }; + MenuBar.prototype._size_header = function() { + /** + * Update header spacer size. + */ + this.events.trigger('resize-header.Page'); + }; + MenuBar.prototype.bind_events = function () { /** * File @@ -149,6 +154,10 @@ define([ that._nbconvert('pdf', true); }); + this.element.find('#download_script').click(function () { + that._nbconvert('script', true); + }); + this.element.find('#rename_notebook').click(function () { that.save_widget.rename_notebook({notebook: that.notebook}); }); @@ -218,12 +227,12 @@ define([ // View this.element.find('#toggle_header').click(function () { - $('div#header').toggle(); - that.layout_manager.do_resize(); + $('div#header-container').toggle(); + that._size_header(); }); this.element.find('#toggle_toolbar').click(function () { $('div#maintoolbar').toggle(); - that.layout_manager.do_resize(); + that._size_header(); }); // Insert this.element.find('#insert_cell_above').click(function () { @@ -371,20 +380,6 @@ define([ var langname = (langinfo.name || 'Script') langname = langname.charAt(0).toUpperCase()+langname.substr(1) // Capitalise el.find('a').text(langname + ' ('+(langinfo.file_extension || 'txt')+')'); - - // Unregister any previously registered handlers - el.off('click'); - if (langinfo.nbconvert_exporter) { - // Metadata specifies a specific exporter, e.g. 'python' - el.click(function() { - that._nbconvert(langinfo.nbconvert_exporter, true); - }); - } else { - // Use generic 'script' exporter - el.click(function() { - that._nbconvert('script', true); - }); - } }; // Backwards compatability. diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js index 46574b4..bab4524 100644 --- a/IPython/html/static/notebook/js/notebook.js +++ b/IPython/html/static/notebook/js/notebook.js @@ -210,9 +210,14 @@ define([ var that = this; this.events.on('set_next_input.Notebook', function (event, data) { - var index = that.find_cell_index(data.cell); - var new_cell = that.insert_cell_below('code',index); - new_cell.set_text(data.text); + if (data.replace) { + data.cell.set_text(data.text); + data.cell.clear_output(); + } else { + var index = that.find_cell_index(data.cell); + var new_cell = that.insert_cell_below('code',index); + new_cell.set_text(data.text); + } that.dirty = true; }); @@ -2326,9 +2331,13 @@ define([ // Create the session after the notebook is completely loaded to prevent // code execution upon loading, which is a security risk. if (this.session === null) { - var kernelspec = this.metadata.kernelspec || {}; - var kernel_name = kernelspec.name; - + var kernel_name; + if (this.metadata.kernelspec) { + var kernelspec = this.metadata.kernelspec || {}; + kernel_name = kernelspec.name; + } else { + kernel_name = utils.get_url_param('kernel_name'); + } this.start_session(kernel_name); } // load our checkpoint list diff --git a/IPython/html/static/notebook/js/outputarea.js b/IPython/html/static/notebook/js/outputarea.js index bb9b218..4275d02 100644 --- a/IPython/html/static/notebook/js/outputarea.js +++ b/IPython/html/static/notebook/js/outputarea.js @@ -199,9 +199,7 @@ define([ // typeset with MathJax if MathJax is available OutputArea.prototype.typeset = function () { - if (window.MathJax){ - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - } + utils.typeset(this.element); }; diff --git a/IPython/html/static/notebook/js/pager.js b/IPython/html/static/notebook/js/pager.js index 9656fd4..aa372d7 100644 --- a/IPython/html/static/notebook/js/pager.js +++ b/IPython/html/static/notebook/js/pager.js @@ -8,48 +8,22 @@ define([ ], function(IPython, $, utils) { "use strict"; - var Pager = function (pager_selector, pager_splitter_selector, options) { + var Pager = function (pager_selector, options) { /** * Constructor * * Parameters: * pager_selector: string - * pager_splitter_selector: string * options: dictionary * Dictionary of keyword arguments. * events: $(Events) instance - * layout_manager: LayoutManager instance */ this.events = options.events; this.pager_element = $(pager_selector); - this.pager_button_area = $('#pager_button_area'); - var that = this; - this.percentage_height = 0.40; - options.layout_manager.pager = this; - this.pager_splitter_element = $(pager_splitter_selector) - .draggable({ - containment: 'window', - axis:'y', - helper: null , - drag: function(event, ui) { - /** - * recalculate the amount of space the pager should take - */ - var pheight = ($(document.body).height()-event.clientY-4); - var downprct = pheight/options.layout_manager.app_height(); - downprct = Math.min(0.9, downprct); - if (downprct < 0.1) { - that.percentage_height = 0.1; - that.collapse({'duration':0}); - } else if (downprct > 0.2) { - that.percentage_height = downprct; - that.expand({'duration':0}); - } - options.layout_manager.do_resize(); - } - }); + this.pager_button_area = $('#pager-button-area'); + this._default_end_space = 200; + this.pager_element.resizable({handles: 'n', resize: $.proxy(this._resize, this)}); this.expanded = false; - this.style(); this.create_button_area(); this.bind_events(); }; @@ -61,7 +35,6 @@ define([ .attr('title',"Open the pager in an external window") .addClass('ui-button') .click(function(){that.detach();}) - .attr('style','position: absolute; right: 20px;') .append( $('').addClass("ui-icon ui-icon-extlink") ) @@ -71,49 +44,37 @@ define([ .attr('title',"Close the pager") .addClass('ui-button') .click(function(){that.collapse();}) - .attr('style','position: absolute; right: 5px;') .append( $('').addClass("ui-icon ui-icon-close") ) ); }; - Pager.prototype.style = function () { - this.pager_splitter_element.addClass('ui-widget ui-state-default'); - this.pager_splitter_element.attr('title', 'Click to Show/Hide pager area, drag to Resize'); - }; - Pager.prototype.bind_events = function () { var that = this; this.pager_element.bind('collapse_pager', function (event, extrap) { - var time = 'fast'; - if (extrap && extrap.duration) { - time = extrap.duration; - } - that.pager_element.hide(time); + // Animate hiding of the pager. + var time = (extrap && extrap.duration) ? extrap.duration : 'fast'; + that.pager_element.hide(time, function() { + $('.end_space').css('height', that._default_end_space); + }); }); this.pager_element.bind('expand_pager', function (event, extrap) { - var time = 'fast'; - if (extrap && extrap.duration) { - time = extrap.duration; - } - that.pager_element.show(time); - }); - - this.pager_splitter_element.hover( - function () { - that.pager_splitter_element.addClass('ui-state-hover'); - }, - function () { - that.pager_splitter_element.removeClass('ui-state-hover'); - } - ); - - this.pager_splitter_element.click(function () { - that.toggle(); + // Clear the pager's height attr if it's set. This allows the + // pager to size itself according to its contents. + that.pager_element.height('initial'); + + // Animate the showing of the pager + var time = (extrap && extrap.duration) ? extrap.duration : 'fast'; + that.pager_element.show(time, function() { + // Explicitly set pager height once the pager has shown itself. + // This allows the pager-contents div to use percentage sizing. + that.pager_element.height(that.pager_element.height()); + that._resize(); + }); }); this.events.on('open_with_text.Pager', function (event, payload) { @@ -130,7 +91,7 @@ define([ Pager.prototype.collapse = function (extrap) { if (this.expanded === true) { this.expanded = false; - this.pager_element.add($('div#notebook')).trigger('collapse_pager', extrap); + this.pager_element.trigger('collapse_pager', extrap); } }; @@ -138,7 +99,7 @@ define([ Pager.prototype.expand = function (extrap) { if (this.expanded !== true) { this.expanded = true; - this.pager_element.add($('div#notebook')).trigger('expand_pager', extrap); + this.pager_element.trigger('expand_pager', extrap); } }; @@ -184,6 +145,18 @@ define([ this.pager_element.find(".container").append($('
    ').html(utils.fixCarriageReturn(utils.fixConsole(text))));
         };
     
    +
    +    Pager.prototype._resize = function() {
    +        /**
    +         * Update document based on pager size.
    +         */
    +        
    +        // Make sure the padding at the end of the notebook is large
    +        // enough that the user can scroll to the bottom of the 
    +        // notebook.
    +        $('.end_space').css('height', Math.max(this.pager_element.height(), this._default_end_space));
    +    };
    +
         // Backwards compatability.
         IPython.Pager = Pager;
     
    diff --git a/IPython/html/static/notebook/less/menubar.less b/IPython/html/static/notebook/less/menubar.less
    index 63e784a..f7df468 100644
    --- a/IPython/html/static/notebook/less/menubar.less
    +++ b/IPython/html/static/notebook/less/menubar.less
    @@ -1,12 +1,13 @@
     #menubar {
       margin-top: 0px;
    -  margin-bottom: -19px;
    +  margin-bottom: -24px;
       position: relative;
       .border-box-sizing();
     
       .navbar {
         border-top: 1px;
         border-radius: 0px 0px @border-radius-base @border-radius-base;
    +    margin-bottom: 23px;
       }
       
       .navbar-toggle {
    diff --git a/IPython/html/static/notebook/less/notebook.less b/IPython/html/static/notebook/less/notebook.less
    index 87f1503..58ac2df 100644
    --- a/IPython/html/static/notebook/less/notebook.less
    +++ b/IPython/html/static/notebook/less/notebook.less
    @@ -3,10 +3,6 @@ body {
         background-color:   @body-bg;
     }
     
    -body.notebook_app {
    -    overflow: hidden;
    -}
    -
     @media (max-width: 767px) {
         // remove bootstrap-responsive's body padding on small screens
         body.notebook_app {
    @@ -36,19 +32,17 @@ span#notebook_name {
     div#notebook_panel {
         margin: 0px 0px 0px 0px;
         padding: 0px;
    -    .box-shadow(@notebook-shadow);
         .border-box-sizing();
     }
     div#notebook {
         font-size: @notebook_font_size;
         line-height: @notebook_line_height;
    -    overflow-y: scroll;
    +    overflow-y: hidden;
         overflow-x: auto;
         width: 100%;
         /* This spaces the cell away from the edge of the notebook area */
    -    padding: 1em 0 1em 0;
    +    padding: 2em 0 2em 0;
         margin: 0px;
    -    border-top: 1px solid @navbar-default-border;
         outline: none;
         .border-box-sizing();
     }
    @@ -86,3 +80,14 @@ p {
     .end_space {
         height: 200px;
     }
    +
    +.lower-header-bar {
    +    width: 100%;
    +    height: 0px;
    +    border-bottom: 1px solid @navbar-default-border;
    +    margin-bottom: -1px;
    +}
    +
    +.notebook_app #header {
    +    .box-shadow(@notebook-shadow);
    +}
    diff --git a/IPython/html/static/notebook/less/pager.less b/IPython/html/static/notebook/less/pager.less
    index 41ca682..6fe021b 100644
    --- a/IPython/html/static/notebook/less/pager.less
    +++ b/IPython/html/static/notebook/less/pager.less
    @@ -1,25 +1,51 @@
    -div#pager_splitter {
    -    height: 8px;
    -    .border-box-sizing();
    -}
    -
    -#pager-container {
    -    position: relative;
    -    padding: 15px 0px;
    -    .border-box-sizing();
    -}
    -
     div#pager {
    +    background-color:   @body-bg;
         font-size: @notebook_font_size;
         line-height: @notebook_line_height;
    -    overflow: auto;
    +    overflow: hidden;
         display: none;
    +    position: fixed;
    +    bottom: 0px;
    +    width: 100%;
    +    max-height: 50%;
    +    padding-top: 7px;
    +
    +    /* Display over codemirror */
    +    z-index: 100;
    +
    +    /* Hack which prevents jquery ui resizable from changing top. */
    +    top: inherit !important; 
     
         pre {
    -      line-height: @code_line_height;
    -      color: @text-color;
    -      background-color: @cell_background;
    -      padding: @code_padding;
    +        line-height: @code_line_height;
    +        color: @text-color;
    +        background-color: @cell_background;
    +        padding: @code_padding;
    +    }
    +
    +    #pager-button-area {
    +        position: absolute;
    +        top: 7px;
    +        right: 20px; 
    +    }
    +
    +    #pager-contents {
    +        position: relative;
    +        overflow: auto;
    +        width: 100%;
    +        height: 100%;
    +
    +        #pager-container {
    +            position: relative;
    +            padding: 15px 0px;
    +            .border-box-sizing();
    +        }
    +    }
    +
    +    .ui-resizable-handle {
    +        top: 0px;
    +        height: 7px;
    +        background: @light_border_color;
    +        border-bottom: 1px solid @border_color;
         }
    -    .border-box-sizing();
     }
    diff --git a/IPython/html/static/notebook/less/toolbar.less b/IPython/html/static/notebook/less/toolbar.less
    index 26f7cf6..0599021 100644
    --- a/IPython/html/static/notebook/less/toolbar.less
    +++ b/IPython/html/static/notebook/less/toolbar.less
    @@ -2,6 +2,7 @@
         padding: 0px;
         margin-left: -5px;
         margin-top: -5px;
    +    margin-bottom: 5px;
     
         select, label {
             width: auto;
    @@ -33,8 +34,8 @@
       border: 0px;
       min-height: 27px;
       margin-left: 32px;
    -  padding-top: 6px;
    -  padding-bottom: 8px;
    +  padding-top: 11px;
    +  padding-bottom: 3px;
     
       .navbar-text {
         float: none;
    @@ -44,10 +45,6 @@
         margin-right: 0px;
         margin-top: 0px;
       }
    -
    -  .toolbar {
    -    margin-top: 0px;
    -  }
     }
     
     .select-xs {
    diff --git a/IPython/html/static/notebook/less/variables.less b/IPython/html/static/notebook/less/variables.less
    index c11250e..466cb09 100644
    --- a/IPython/html/static/notebook/less/variables.less
    +++ b/IPython/html/static/notebook/less/variables.less
    @@ -10,7 +10,7 @@
     @notebook_line_height:          20px;
     @code_line_height:              1.21429em;  // changed from 1.231 to get 17px even
     @code_padding:                  0.4em;  // 5.6 px
    -@notebook-shadow:               inset 1px 4px 9px -6px rgba(0,0,0,.25);
    +@notebook-shadow:               1px 4px 9px -6px rgba(0,0,0,.25);
     @rendered_html_border_color:    black;
     @input_prompt_color:            navy;
     @output_prompt_color:           darkred;
    diff --git a/IPython/html/static/style/style.min.css b/IPython/html/static/style/style.min.css
    index 8c276b3..15ee5ca 100644
    --- a/IPython/html/static/style/style.min.css
    +++ b/IPython/html/static/style/style.min.css
    @@ -7751,14 +7751,32 @@ body {
     div#header {
       /* Initially hidden to prevent FLOUC */
       display: none;
    +  margin-bottom: -6px;
    +  position: fixed;
    +  top: 0;
    +  width: 100%;
    +  background-color: #ffffff;
    +  min-height: 31px;
    +  /* Display over codemirror */
    +  z-index: 100;
    +}
    +div#header #header-container {
       margin-bottom: 0px;
       padding-left: 30px;
       padding-bottom: 5px;
    -  border-bottom: 1px solid #e7e7e7;
       box-sizing: border-box;
       -moz-box-sizing: border-box;
       -webkit-box-sizing: border-box;
     }
    +div#header .header-bar {
    +  width: 100%;
    +  height: 0px;
    +  border-bottom: 1px solid #e7e7e7;
    +}
    +#header-spacer {
    +  width: 100%;
    +  visibility: hidden;
    +}
     #ipython_notebook {
       padding-left: 0px;
     }
    @@ -7981,14 +7999,15 @@ span#login_widget > .button .badge,
       margin: 0;
     }
     .alternate_upload input.fileinput {
    -  background-color: red;
    -  position: relative;
    +  display: inline;
       opacity: 0;
       z-index: 2;
    -  width: 295px;
    -  margin-left: 163px;
    -  cursor: pointer;
    -  height: 26px;
    +  width: 12ex;
    +  margin-right: -12ex;
    +}
    +.alternate_upload .input-overlay {
    +  display: inline-block;
    +  font-weight: bold;
     }
     /**
      * Primary styles
    @@ -8146,6 +8165,21 @@ input.engine_num_input {
     .file_icon:before.pull-right {
       margin-left: .3em;
     }
    +ul#new-notebook-menu {
    +  left: auto;
    +  right: 0;
    +}
    +.kernel-menu-icon {
    +  padding-right: 12px;
    +  width: 24px;
    +  content: "\f096";
    +}
    +.kernel-menu-icon:before {
    +  content: "\f096";
    +}
    +.kernel-menu-icon-current:before {
    +  content: "\f00c";
    +}
     /*!
     *
     * IPython notebook
    @@ -9461,9 +9495,6 @@ h6:hover .anchor-link {
     body {
       background-color: #ffffff;
     }
    -body.notebook_app {
    -  overflow: hidden;
    -}
     @media (max-width: 767px) {
       body.notebook_app {
         padding-left: 0px;
    @@ -9489,8 +9520,6 @@ span#notebook_name:hover {
     div#notebook_panel {
       margin: 0px 0px 0px 0px;
       padding: 0px;
    -  -webkit-box-shadow: inset 1px 4px 9px -6px rgba(0, 0, 0, 0.25);
    -  box-shadow: inset 1px 4px 9px -6px rgba(0, 0, 0, 0.25);
       box-sizing: border-box;
       -moz-box-sizing: border-box;
       -webkit-box-sizing: border-box;
    @@ -9498,13 +9527,12 @@ div#notebook_panel {
     div#notebook {
       font-size: 14px;
       line-height: 20px;
    -  overflow-y: scroll;
    +  overflow-y: hidden;
       overflow-x: auto;
       width: 100%;
       /* This spaces the cell away from the edge of the notebook area */
    -  padding: 1em 0 1em 0;
    +  padding: 2em 0 2em 0;
       margin: 0px;
    -  border-top: 1px solid #e7e7e7;
       outline: none;
       box-sizing: border-box;
       -moz-box-sizing: border-box;
    @@ -9542,6 +9570,16 @@ p {
     .end_space {
       height: 200px;
     }
    +.lower-header-bar {
    +  width: 100%;
    +  height: 0px;
    +  border-bottom: 1px solid #e7e7e7;
    +  margin-bottom: -1px;
    +}
    +.notebook_app #header {
    +  -webkit-box-shadow: 1px 4px 9px -6px rgba(0, 0, 0, 0.25);
    +  box-shadow: 1px 4px 9px -6px rgba(0, 0, 0, 0.25);
    +}
     /* CSS for the cell toolbar */
     .celltoolbar {
       border: thin solid #CFCFCF;
    @@ -9779,7 +9817,7 @@ fieldset[disabled] #kernel_selector_widget > button.active {
     }
     #menubar {
       margin-top: 0px;
    -  margin-bottom: -19px;
    +  margin-bottom: -24px;
       position: relative;
       box-sizing: border-box;
       -moz-box-sizing: border-box;
    @@ -9788,6 +9826,7 @@ fieldset[disabled] #kernel_selector_widget > button.active {
     #menubar .navbar {
       border-top: 1px;
       border-radius: 0px 0px 4px 4px;
    +  margin-bottom: 23px;
     }
     #menubar .navbar-toggle {
       float: left;
    @@ -10256,27 +10295,21 @@ fieldset[disabled] .notification_widget.danger.active {
       color: #d9534f;
       background-color: #ffffff;
     }
    -div#pager_splitter {
    -  height: 8px;
    -  box-sizing: border-box;
    -  -moz-box-sizing: border-box;
    -  -webkit-box-sizing: border-box;
    -}
    -#pager-container {
    -  position: relative;
    -  padding: 15px 0px;
    -  box-sizing: border-box;
    -  -moz-box-sizing: border-box;
    -  -webkit-box-sizing: border-box;
    -}
     div#pager {
    +  background-color: #ffffff;
       font-size: 14px;
       line-height: 20px;
    -  overflow: auto;
    +  overflow: hidden;
       display: none;
    -  box-sizing: border-box;
    -  -moz-box-sizing: border-box;
    -  -webkit-box-sizing: border-box;
    +  position: fixed;
    +  bottom: 0px;
    +  width: 100%;
    +  max-height: 50%;
    +  padding-top: 7px;
    +  /* Display over codemirror */
    +  z-index: 100;
    +  /* Hack which prevents jquery ui resizable from changing top. */
    +  top: inherit !important;
     }
     div#pager pre {
       line-height: 1.21429em;
    @@ -10284,6 +10317,30 @@ div#pager pre {
       background-color: #f7f7f7;
       padding: 0.4em;
     }
    +div#pager #pager-button-area {
    +  position: absolute;
    +  top: 7px;
    +  right: 20px;
    +}
    +div#pager #pager-contents {
    +  position: relative;
    +  overflow: auto;
    +  width: 100%;
    +  height: 100%;
    +}
    +div#pager #pager-contents #pager-container {
    +  position: relative;
    +  padding: 15px 0px;
    +  box-sizing: border-box;
    +  -moz-box-sizing: border-box;
    +  -webkit-box-sizing: border-box;
    +}
    +div#pager .ui-resizable-handle {
    +  top: 0px;
    +  height: 7px;
    +  background: #cfcfcf;
    +  border-bottom: 1px solid #ababab;
    +}
     .quickhelp {
       /* Old browsers */
       display: -webkit-box;
    @@ -10350,6 +10407,7 @@ span#autosave_status {
       padding: 0px;
       margin-left: -5px;
       margin-top: -5px;
    +  margin-bottom: 5px;
       box-sizing: border-box;
       -moz-box-sizing: border-box;
       -webkit-box-sizing: border-box;
    @@ -10380,8 +10438,8 @@ span#autosave_status {
       border: 0px;
       min-height: 27px;
       margin-left: 32px;
    -  padding-top: 6px;
    -  padding-bottom: 8px;
    +  padding-top: 11px;
    +  padding-bottom: 3px;
     }
     #maintoolbar .navbar-text {
       float: none;
    @@ -10391,9 +10449,6 @@ span#autosave_status {
       margin-right: 0px;
       margin-top: 0px;
     }
    -#maintoolbar .toolbar {
    -  margin-top: 0px;
    -}
     .select-xs {
       height: 24px;
     }
    diff --git a/IPython/html/static/tree/js/main.js b/IPython/html/static/tree/js/main.js
    index 617f5f5..194bd38 100644
    --- a/IPython/html/static/tree/js/main.js
    +++ b/IPython/html/static/tree/js/main.js
    @@ -8,12 +8,14 @@ require([
         'base/js/events',
         'base/js/page',
         'base/js/utils',
    +    'services/config',
         'contents',
         'tree/js/notebooklist',
         'tree/js/clusterlist',
         'tree/js/sessionlist',
         'tree/js/kernellist',
         'tree/js/terminallist',
    +    'tree/js/newnotebook',
         'auth/js/loginwidget',
         // only loaded, not used:
         'jqueryui',
    @@ -26,12 +28,14 @@ require([
         events,
         page,
         utils,
    +    config,
         contents_service,
    -    notebooklist, 
    -    clusterlist, 
    -    sesssionlist, 
    +    notebooklist,
    +    clusterlist,
    +    sesssionlist,
         kernellist,
         terminallist,
    +    newnotebook,
         loginwidget){
         "use strict";
     
    @@ -41,6 +45,10 @@ require([
             base_url: utils.get_body_data("baseUrl"),
             notebook_path: utils.get_body_data("notebookPath"),
         };
    +    var cfg = new config.ConfigSection('tree', common_options);
    +    cfg.load();
    +    common_options.config = cfg;
    +    
         var session_list = new sesssionlist.SesssionList($.extend({
             events: events}, 
             common_options));
    @@ -63,24 +71,12 @@ require([
     
         var login_widget = new loginwidget.LoginWidget('#login_widget', common_options);
     
    -    $('#new_notebook').click(function (e) {
    -        var w = window.open();
    -        contents.new_untitled(common_options.notebook_path, {type: "notebook"}).then(
    -                function (data) {
    -                    w.location = utils.url_join_encode(
    -                            common_options.base_url, 'notebooks', data.path
    -                        );
    -                },
    -                function(error) {
    -                    w.close();
    -                    dialog.modal({
    -                        title : 'Creating Notebook Failed',
    -                        body : "The error was: " + error.message,
    -                        buttons : {'OK' : {'class' : 'btn-primary'}}
    -                    });
    -                }
    -            );
    -    });
    +    var nnw = new newnotebook.NewNotebookWidget("#new-notebook-buttons",
    +        $.extend(
    +            {contents: contents},
    +            common_options
    +        )
    +    );
     
         var interval_id=0;
         // auto refresh every xx secondes, no need to be fast,
    @@ -93,18 +89,18 @@ require([
              */
             session_list.load_sessions();
             cluster_list.load_list();
    -	if (terminal_list) {
    -	    terminal_list.load_terminals();
    -	}
    +        if (terminal_list) {
    +            terminal_list.load_terminals();
    +        }
             if (!interval_id){
                 interval_id = setInterval(function(){
    -                    session_list.load_sessions();
    -                    cluster_list.load_list();
    -		    if (terminal_list) {
    -		        terminal_list.load_terminals();
    -		    }
    -                }, time_refresh*1000);
    -            }
    +                session_list.load_sessions();
    +                cluster_list.load_list();
    +                if (terminal_list) {
    +                    terminal_list.load_terminals();
    +                }
    +            }, time_refresh*1000);
    +        }
         };
     
         var disable_autorefresh = function(){
    @@ -134,6 +130,7 @@ require([
         IPython.session_list = session_list;
         IPython.kernel_list = kernel_list;
         IPython.login_widget = login_widget;
    +    IPython.new_notebook_widget = nnw;
     
         events.trigger('app_initialized.DashboardApp');
         
    diff --git a/IPython/html/static/tree/js/newnotebook.js b/IPython/html/static/tree/js/newnotebook.js
    new file mode 100644
    index 0000000..f93ef14
    --- /dev/null
    +++ b/IPython/html/static/tree/js/newnotebook.js
    @@ -0,0 +1,135 @@
    +// Copyright (c) IPython Development Team.
    +// Distributed under the terms of the Modified BSD License.
    +
    +define([
    +    'jquery',
    +    'base/js/namespace',
    +    'base/js/utils',
    +    'base/js/dialog',
    +], function ($, IPython, utils, dialog) {
    +    "use strict";
    +    
    +    var NewNotebookWidget = function (selector, options) {
    +        this.selector = selector;
    +        this.base_url = options.base_url;
    +        this.notebook_path = options.notebook_path;
    +        this.contents = options.contents;
    +        this.default_kernel = null;
    +        this.config = options.config;
    +        this.kernelspecs = {};
    +        if (this.selector !== undefined) {
    +            this.element = $(selector);
    +            this.request_kernelspecs();
    +        }
    +        this.bind_events();
    +    };
    +    
    +    NewNotebookWidget.prototype.bind_events = function () {
    +        var that = this;
    +        this.element.find('#new_notebook').click(function () {
    +            that.new_notebook();
    +        });
    +    };
    +    
    +    NewNotebookWidget.prototype.request_kernelspecs = function () {
    +        /** request and then load kernel specs */
    +        var url = utils.url_join_encode(this.base_url, 'api/kernelspecs');
    +        utils.promising_ajax(url).then($.proxy(this._load_kernelspecs, this));
    +    };
    +    
    +    NewNotebookWidget.prototype._load_kernelspecs = function (data) {
    +        /** load kernelspec list */
    +        var that = this;
    +        this.kernelspecs = data.kernelspecs;
    +        var menu = this.element.find("#new-notebook-menu");
    +        var keys = Object.keys(data.kernelspecs).sort(function (a, b) {
    +            var da = data.kernelspecs[a].display_name;
    +            var db = data.kernelspecs[b].display_name;
    +            if (da === db) {
    +                return 0;
    +            } else if (da > db) {
    +                return 1;
    +            } else {
    +                return -1;
    +            }
    +        });
    +        for (var i = 0; i < keys.length; i++) {
    +            var ks = this.kernelspecs[keys[i]];
    +            var li = $("
  • ") + .attr("id", "kernel-" +ks.name) + .data('kernelspec', ks).append( + $('') + .attr('href', '#') + .click($.proxy(this.new_notebook, this, ks.name)) + .text(ks.display_name) + .attr('title', 'Create a new notebook with ' + ks.display_name) + ); + menu.append(li); + } + this.config.loaded.then(function () { + that._load_default_kernelspec(data['default']); + }); + }; + + NewNotebookWidget.prototype._load_default_kernelspec = function (default_name) { + /** load default kernelspec name from config, if defined */ + if (this.config.data.NewNotebookWidget && + this.config.data.NewNotebookWidget.default_kernel && + this.kernelspecs[this.config.data.NewNotebookWidget.default_kernel] !== undefined + ) { + default_name = this.config.data.NewNotebookWidget.default_kernel; + } + this.set_default_kernel(default_name); + }; + + NewNotebookWidget.prototype.set_default_kernel = function (kernel_name) { + /** select the current default kernel */ + this.default_kernel = kernel_name; + this.config.update({ + NewNotebookWidget: { + default_kernel: kernel_name + } + }); + var spec = this.kernelspecs[kernel_name]; + var display_name; + if (spec) { + display_name = spec.display_name; + this.element.find("#current-kernel") + .text(display_name) + .attr('title', display_name + " is the default kernel for new notebooks"); + } else { + display_name = 'default kernel'; + } + this.element.find("#new_notebook").attr('title', + 'Create a new notebook with ' + display_name + ); + }; + + NewNotebookWidget.prototype.new_notebook = function (kernel_name) { + /** create and open a new notebook */ + var that = this; + kernel_name = kernel_name || this.default_kernel; + var w = window.open(); + this.contents.new_untitled(that.notebook_path, {type: "notebook"}).then( + function (data) { + var url = utils.url_join_encode( + that.base_url, 'notebooks', data.path + ); + if (kernel_name) { + url += "?kernel_name=" + kernel_name; + } + w.location = url; + }, + function (error) { + w.close(); + dialog.modal({ + title : 'Creating Notebook Failed', + body : "The error was: " + error.message, + buttons : {'OK' : {'class' : 'btn-primary'}} + }); + } + ); + }; + + return {'NewNotebookWidget': NewNotebookWidget}; +}); diff --git a/IPython/html/static/tree/less/altuploadform.less b/IPython/html/static/tree/less/altuploadform.less index 70e865d..1ce7c22 100644 --- a/IPython/html/static/tree/less/altuploadform.less +++ b/IPython/html/static/tree/less/altuploadform.less @@ -15,12 +15,14 @@ .alternate_upload input.fileinput { - background-color:red; - position:relative; + display: inline; opacity: 0; z-index: 2; - width: 295px; - margin-left:163px; - cursor: pointer; - height: 26px; + width: 12ex; + margin-right: -12ex; +} + +.alternate_upload .input-overlay { + display: inline-block; + font-weight: bold; } diff --git a/IPython/html/static/tree/less/tree.less b/IPython/html/static/tree/less/tree.less index b1d22cf..8ed509a 100644 --- a/IPython/html/static/tree/less/tree.less +++ b/IPython/html/static/tree/less/tree.less @@ -154,3 +154,23 @@ input.engine_num_input { .file_icon:before { .icon(@fa-var-file-o) } + +ul#new-notebook-menu { + // align right instead of left + left: auto; + right: 0; +} + +.kernel-menu-icon { + padding-right: 12px; + width: 24px; + content: @fa-var-square-o; +} + +.kernel-menu-icon:before { + content: @fa-var-square-o; +} + +.kernel-menu-icon-current:before { + content: @fa-var-check; +} diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js index edc6510..f3630ab 100644 --- a/IPython/html/static/widgets/js/manager.js +++ b/IPython/html/static/widgets/js/manager.js @@ -112,6 +112,7 @@ define([ return Promise.resolve(view.render()).then(function() {return view;}); }).catch(utils.reject("Couldn't create a view for model id '" + String(model.id) + "'", true)); }); + model.views[utils.uuid()] = model.state_change; return model.state_change; }; diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index 7e4239b..fb3f150 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -65,11 +65,12 @@ define(["widgets/js/manager", delete this.comm.model; // Delete ref so GC will collect widget model. delete this.comm; delete this.model_id; // Delete id from model so widget manager cleans up. - for (var id in this.views) { - if (this.views.hasOwnProperty(id)) { - this.views[id].remove(); - } - } + _.each(this.views, function(v, id, views) { + v.then(function(view) { + view.remove(); + delete views[id]; + }); + }); }, _handle_comm_msg: function (msg) { @@ -318,8 +319,6 @@ define(["widgets/js/manager", */ this.model.on('change',this.update,this); this.options = parameters.options; - this.id = this.id || utils.uuid(); - this.model.views[this.id] = this; this.on('displayed', function() { this.is_displayed = true; }, this); @@ -388,7 +387,7 @@ define(["widgets/js/manager", } else { this.on('displayed', callback, context); } - }, + } }); @@ -575,6 +574,10 @@ define(["widgets/js/manager", } return elements; }, + + typeset: function(element, text){ + utils.typeset.apply(null, arguments); + }, }); diff --git a/IPython/html/static/widgets/js/widget_bool.js b/IPython/html/static/widgets/js/widget_bool.js index 6fea2cd..9b84118 100644 --- a/IPython/html/static/widgets/js/widget_bool.js +++ b/IPython/html/static/widgets/js/widget_bool.js @@ -62,8 +62,7 @@ define([ if (description.trim().length === 0) { this.$label.hide(); } else { - this.$label.text(description); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + this.typeset(this.$label, description); this.$label.show(); } } diff --git a/IPython/html/static/widgets/js/widget_box.js b/IPython/html/static/widgets/js/widget_box.js index 317bea4..e7850ce 100644 --- a/IPython/html/static/widgets/js/widget_box.js +++ b/IPython/html/static/widgets/js/widget_box.js @@ -319,8 +319,7 @@ define([ if (description.trim().length === 0) { this.$title.html(" "); // Preserve title height } else { - this.$title.text(description); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$title.get(0)]); + this.typeset(this.$title, description); } var button_text = this.model.get('button_text'); diff --git a/IPython/html/static/widgets/js/widget_int.js b/IPython/html/static/widgets/js/widget_int.js index 1c08f26..4b10705 100644 --- a/IPython/html/static/widgets/js/widget_int.js +++ b/IPython/html/static/widgets/js/widget_int.js @@ -155,8 +155,7 @@ define([ if (description.length === 0) { this.$label.hide(); } else { - this.$label.text(description); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + this.typeset(this.$label, description); this.$label.show(); } @@ -323,8 +322,7 @@ define([ if (description.length === 0) { this.$label.hide(); } else { - this.$label.text(description); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + this.typeset(this.$label, description); this.$label.show(); } } @@ -443,8 +441,7 @@ define([ if (description.length === 0) { this.$label.hide(); } else { - this.$label.text(description); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + this.typeset(this.$label, description); this.$label.show(); } return ProgressView.__super__.update.apply(this); diff --git a/IPython/html/static/widgets/js/widget_selection.js b/IPython/html/static/widgets/js/widget_selection.js index b33e2eb..52742ae 100644 --- a/IPython/html/static/widgets/js/widget_selection.js +++ b/IPython/html/static/widgets/js/widget_selection.js @@ -97,8 +97,7 @@ define([ if (description.length === 0) { this.$label.hide(); } else { - this.$label.text(description); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + this.typeset(this.$label, description); this.$label.show(); } } @@ -231,7 +230,7 @@ define([ this.$label.hide(); } else { this.$label.text(description); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + this.typeset(this.$label, description); this.$label.show(); } } @@ -345,8 +344,8 @@ define([ if (description.length === 0) { this.$label.hide(); } else { - this.$label.text(description); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + this.$label.text(); + this.typeset(this.$label, description); this.$label.show(); } } @@ -468,8 +467,7 @@ define([ if (description.length === 0) { this.$label.hide(); } else { - this.$label.text(description); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + this.typeset(this.$label, description); this.$label.show(); } } diff --git a/IPython/html/static/widgets/js/widget_string.js b/IPython/html/static/widgets/js/widget_string.js index bd896a9..eb1d783 100644 --- a/IPython/html/static/widgets/js/widget_string.js +++ b/IPython/html/static/widgets/js/widget_string.js @@ -43,9 +43,7 @@ define([ * Called when the model is changed. The model may have been * changed by another view or by a state update from the back-end. */ - this.$el.text(this.model.get('value')); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$el.get(0)]); - + this.typeset(this.$el, this.model.get('value')); return LatexView.__super__.update.apply(this); }, }); @@ -116,8 +114,7 @@ define([ if (description.length === 0) { this.$label.hide(); } else { - this.$label.text(description); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + this.typeset(this.$label, description); this.$label.show(); } } @@ -200,8 +197,7 @@ define([ if (description.length === 0) { this.$label.hide(); } else { - this.$label.text(description); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); + this.typeset(this.$label, description); this.$label.show(); } } diff --git a/IPython/html/templates/notebook.html b/IPython/html/templates/notebook.html index 2bbea6e..e872a55 100644 --- a/IPython/html/templates/notebook.html +++ b/IPython/html/templates/notebook.html @@ -32,7 +32,7 @@ class="notebook_app" {% endblock %} -{% block header %} +{% block headercontainer %} @@ -43,18 +43,16 @@ class="notebook_app" -{% endblock %} - - -{% block site %} +{% endblock headercontainer %} +{% block header %} + -
    +
    +{% endblock header %} +{% block site %} + + +
    -
    -
    -
    -
    -
    -
    +
    +
    +
    +
    +
    +
    + diff --git a/IPython/html/templates/page.html b/IPython/html/templates/page.html index ea7274c..122f390 100644 --- a/IPython/html/templates/page.html +++ b/IPython/html/templates/page.html @@ -5,7 +5,7 @@ {% block title %}IPython Notebook{% endblock %} - + {% block favicon %}{% endblock %} @@ -81,7 +81,7 @@