Skip to content

Commit 5766341

Browse files
authored
Merge pull request #3289 from Madhu94/add-save-as-menu-option
Add save as menu option
2 parents 90894ee + 0c81fc3 commit 5766341

File tree

4 files changed

+138
-2
lines changed

4 files changed

+138
-2
lines changed

notebook/static/notebook/js/menubar.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,10 @@ define([
170170
that.notebook.copy_notebook();
171171
return false;
172172
});
173+
this.element.find('#save_notebook_as').click(function() {
174+
that.notebook.save_notebook_as();
175+
return false;
176+
});
173177
this.element.find('#download_ipynb').click(function () {
174178
var base_url = that.notebook.base_url;
175179
var notebook_path = utils.encode_uri_components(that.notebook.notebook_path);

notebook/static/notebook/js/notebook.js

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2846,7 +2846,96 @@ define([
28462846
this._checkpoint_after_save = false;
28472847
}
28482848
};
2849-
2849+
2850+
Notebook.prototype.save_notebook_as = function() {
2851+
var that = this;
2852+
var current_dir = $('body').attr('data-notebook-path').split('/').slice(0, -1).join("/");
2853+
current_dir = current_dir? current_dir + "/": "";
2854+
var dialog_body = $('<div/>').append(
2855+
$('<p/>').addClass('save-message')
2856+
.text(i18n.msg._('Enter a notebook path relative to notebook dir'))
2857+
).append(
2858+
$('<br/>')
2859+
).append(
2860+
$('<input/>').attr('type','text').attr('size','25')
2861+
.attr('data-testid', 'save-as')
2862+
.addClass('form-control')
2863+
);
2864+
2865+
var d = dialog.modal({
2866+
title: 'Save As',
2867+
body: dialog_body,
2868+
keyboard_manager: this.keyboard_manager,
2869+
notebook: this,
2870+
buttons: {
2871+
Cancel: {},
2872+
Save: {
2873+
class: 'btn-primary',
2874+
click: function() {
2875+
var nb_path = d.find('input').val();
2876+
var nb_name = nb_path.split('/').slice(-1).pop();
2877+
// If notebook name does not contain extension '.ipynb' add it
2878+
var ext = utils.splitext(nb_name)[1];
2879+
if (ext === '') {
2880+
nb_name = nb_name + '.ipynb';
2881+
nb_path = nb_path + '.ipynb';
2882+
}
2883+
var save_thunk = function() {
2884+
var model = {
2885+
'type': 'notebook',
2886+
'content': that.toJSON(),
2887+
'name': nb_name
2888+
};
2889+
return that.contents.save(nb_path, model)
2890+
.then(function(data) {
2891+
d.modal('hide');
2892+
that.notebook_name = data.name;
2893+
that.notebook_path = data.path;
2894+
that.session.rename_notebook(data.path);
2895+
that.events.trigger('notebook_renamed.Notebook', data);
2896+
}, function(error) {
2897+
var msg = i18n.msg._(error.message || 'Unknown error saving notebook');
2898+
$(".save-message").html(
2899+
$("<span>")
2900+
.attr("style", "color:red;")
2901+
.text(msg)
2902+
);
2903+
});
2904+
};
2905+
that.contents.get(nb_path, {type: 'notebook', content: false}).then(function(data) {
2906+
var warning_body = $('<div/>').append(
2907+
$("<p/>").text(i18n.msg._('Notebook with that name exists.')));
2908+
dialog.modal({
2909+
title: 'Save As',
2910+
body: warning_body,
2911+
buttons: {Cancel: {},
2912+
Overwrite: {
2913+
class: 'btn-warning',
2914+
click: function() {
2915+
return save_thunk();
2916+
}
2917+
}
2918+
}
2919+
});
2920+
}, function(err) {
2921+
return save_thunk();
2922+
});
2923+
return false;
2924+
}
2925+
},
2926+
},
2927+
open : function () {
2928+
d.find('input[type="text"]').keydown(function (event) {
2929+
if (event.which === keyboard.keycodes.enter) {
2930+
d.find('.btn-primary').first().click();
2931+
return false;
2932+
}
2933+
});
2934+
d.find('input[type="text"]').val(current_dir).focus();
2935+
}
2936+
});
2937+
};
2938+
28502939
/**
28512940
* Update the autosave interval based on the duration of the last save.
28522941
*
@@ -3387,4 +3476,4 @@ define([
33873476
};
33883477

33893478
return {Notebook: Notebook};
3390-
});
3479+
});

notebook/templates/notebook.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@
8989
<li id="copy_notebook"
9090
title="{% trans %}Open a copy of this notebook's contents and start a new kernel{% endtrans %}">
9191
<a href="#">{% trans %}Make a Copy...{% endtrans %}</a></li>
92+
<li id="save_notebook_as"
93+
title="{% trans %}Save a copy of the notebook's contents and start a new kernel{% endtrans %}">
94+
<a href="#">{% trans %}Save as...{% endtrans %}</a></li>
9295
<li id="rename_notebook"><a href="#">{% trans %}Rename...{% endtrans %}</a></li>
9396
<li id="save_checkpoint"><a href="#">{% trans %}Save and Checkpoint{% endtrans %}</a></li>
9497
<!-- <hr/> -->
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from notebook.tests.selenium.utils import wait_for_selector
2+
from selenium.webdriver.common.keys import Keys
3+
from selenium.webdriver.support.ui import WebDriverWait
4+
5+
def wait_for_rename(browser, nbname, timeout=10):
6+
wait = WebDriverWait(browser, timeout)
7+
def notebook_renamed(browser):
8+
elem = browser.find_element_by_id('notebook_name')
9+
current_name = browser.execute_script('return arguments[0].innerText', elem)
10+
return current_name == nbname
11+
return wait.until(notebook_renamed)
12+
13+
def save_as(nb):
14+
JS = 'Jupyter.notebook.save_notebook_as()'
15+
return nb.browser.execute_script(JS)
16+
17+
def get_notebook_name(nb):
18+
JS = 'return Jupyter.notebook.notebook_name'
19+
return nb.browser.execute_script(JS)
20+
21+
def set_notebook_name(nb, name):
22+
JS = 'Jupyter.notebook.rename("{}")'.format(name)
23+
nb.browser.execute_script(JS)
24+
25+
def test_save_notebook_as(notebook):
26+
# Set a name for comparison later
27+
set_notebook_name(notebook, name="nb1.ipynb")
28+
wait_for_rename(notebook.browser, "nb1")
29+
assert get_notebook_name(notebook) == "nb1.ipynb"
30+
# Wait for Save As modal, save
31+
save_as(notebook)
32+
wait_for_selector(notebook.browser, '.save-message')
33+
inp = notebook.browser.find_element_by_xpath('//input[@data-testid="save-as"]')
34+
inp.send_keys('new_notebook.ipynb')
35+
inp.send_keys(Keys.RETURN)
36+
wait_for_rename(notebook.browser, "new_notebook")
37+
# Test that the name changed
38+
assert get_notebook_name(notebook) == "new_notebook.ipynb"
39+
# Test that address bar was updated (TODO: get the base url)
40+
assert "new_notebook.ipynb" in notebook.browser.current_url

0 commit comments

Comments
 (0)