diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index 5c754e2a0f..db9cee85cb 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -265,6 +265,7 @@ def init_handlers(self, settings): handlers.extend([(r"/login", settings['login_handler_class'])]) handlers.extend([(r"/logout", settings['logout_handler_class'])]) handlers.extend(load_handlers('files.handlers')) + handlers.extend(load_handlers('view.handlers')) handlers.extend(load_handlers('notebook.handlers')) handlers.extend(load_handlers('nbconvert.handlers')) handlers.extend(load_handlers('bundler.handlers')) diff --git a/notebook/static/tree/js/notebooklist.js b/notebook/static/tree/js/notebooklist.js index 5203e32cc2..f91ecd39e8 100644 --- a/notebook/static/tree/js/notebooklist.js +++ b/notebook/static/tree/js/notebooklist.js @@ -139,6 +139,8 @@ define([ $('.download-button').click($.proxy(this.download_selected, this)); $('.shutdown-button').click($.proxy(this.shutdown_selected, this)); $('.duplicate-button').click($.proxy(this.duplicate_selected, this)); + $('.view-button').click($.proxy(this.view_selected, this)); + $('.edit-button').click($.proxy(this.edit_selected, this)); $('.delete-button').click($.proxy(this.delete_selected, this)); // Bind events for selection menu buttons. @@ -558,9 +560,9 @@ define([ $('.rename-button').css('display', 'none'); } - // Move is visible iff at least one item is selected, and none of them + // Move is visible if at least one item is selected, and none of them // are a running notebook. - if (selected.length >= 1 && !has_running_notebook) { + if (selected.length > 0 && !has_running_notebook) { $('.move-button').css('display', 'inline-block'); } else { $('.move-button').css('display', 'none'); @@ -597,6 +599,22 @@ define([ $('.delete-button').css('display', 'none'); } + // View is visible when an item is renderable or downloadable + if (selected.length > 0 && !has_directory && selected.every(function(el) { + return el.path.match(/html?|json|jpe?g|png|gif|tiff?|svg|bmp|ico|pdf|doc|xls/); + })) { + $('.view-button').css('display', 'inline-block'); + } else { + $('.view-button').css('display', 'none'); + } + + // Edit is visible when an item is editable + if (selected.length > 0 && !has_directory) { + $('.edit-button').css('display', 'inline-block'); + } else { + $('.edit-button').css('display', 'none'); + } + // If all of the items are selected, show the selector as checked. If // some of the items are selected, show it as checked. Otherwise, // uncheck it. @@ -887,7 +905,7 @@ define([ var item_path = that.selected[0].path; - window.open(utils.url_path_join(that.base_url, 'files', item_path) + '?download=1'); + window.open(utils.url_path_join(that.base_url, 'files', utils.encode_uri_components(item_path)) + '?download=1', IPython._target); }; NotebookList.prototype.delete_selected = function() { @@ -937,6 +955,26 @@ define([ }); }; + NotebookList.prototype.view_selected = function() { + var that = this; + that.selected.forEach(function(item) { + var item_path = utils.encode_uri_components(item.path); + // Handle HTML files differently + var item_type = item_path.endsWith('.html') ? 'view' : 'files'; + window.open(utils.url_path_join(that.base_url, item_type, utils.encode_uri_components(item_path)), IPython._target); + }); + }; + + NotebookList.prototype.edit_selected = function() { + var that = this; + that.selected.forEach(function(item) { + var item_path = utils.encode_uri_components(item.path); + // Handle ipynb files differently + var item_type = item_path.endsWith('.ipynb') ? 'notebooks' : 'edit'; + window.open(utils.url_path_join(that.base_url, item_type, utils.encode_uri_components(item_path)), IPython._target); + }); + }; + NotebookList.prototype.duplicate_selected = function() { var message; var selected = this.selected.slice(); // Don't let that.selected change out from under us diff --git a/notebook/templates/tree.html b/notebook/templates/tree.html index f42285ab7e..8848512b08 100644 --- a/notebook/templates/tree.html +++ b/notebook/templates/tree.html @@ -33,6 +33,8 @@ + + diff --git a/notebook/templates/view.html b/notebook/templates/view.html new file mode 100644 index 0000000000..7fd8c38c56 --- /dev/null +++ b/notebook/templates/view.html @@ -0,0 +1,30 @@ + + + + + + {{page_title}} + + + + + +
+ +
+ + + diff --git a/notebook/tests/test_files.py b/notebook/tests/test_files.py index 96c6bf93ae..2568b3991a 100644 --- a/notebook/tests/test_files.py +++ b/notebook/tests/test_files.py @@ -113,6 +113,16 @@ def test_download(self): disposition = r.headers.get('Content-Disposition', '') self.assertIn('attachment', disposition) self.assertIn('filename="test.txt"', disposition) + + def test_view_html(self): + nbdir = self.notebook_dir.name + + html = '
Test test
' + with open(pjoin(nbdir, 'test.html'), 'w') as f: + f.write(html) + + r = self.request('GET', 'view/test.html') + self.assertEqual(r.status_code, 200) def test_old_files_redirect(self): """pre-2.0 'files/' prefixed links are properly redirected""" @@ -145,4 +155,3 @@ def test_old_files_redirect(self): r = self.request('GET', url) self.assertEqual(r.status_code, 200) self.assertEqual(r.text, prefix + '/f3') - diff --git a/notebook/view/__init__.py b/notebook/view/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/notebook/view/handlers.py b/notebook/view/handlers.py new file mode 100644 index 0000000000..4d7e978442 --- /dev/null +++ b/notebook/view/handlers.py @@ -0,0 +1,26 @@ +#encoding: utf-8 +"""Tornado handlers for viewing HTML files.""" + +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + +from tornado import web +from ..base.handlers import IPythonHandler, path_regex +from ..utils import url_escape + +class ViewHandler(IPythonHandler): + """Render HTML files within an iframe.""" + @web.authenticated + def get(self, path): + path = path.strip('/') + if not self.contents_manager.file_exists(path): + raise web.HTTPError(404, u'File does not exist: %s' % path) + + basename = path.rsplit('/', 1)[-1] + self.write( + self.render_template('view.html', file_path=url_escape(path), page_title=basename) + ) + +default_handlers = [ + (r"/view%s" % path_regex, ViewHandler), +]