Skip to content

Commit 331a7a2

Browse files
unnamedplay-rgnestor
authored andcommitted
Editor - Prompt warning when overwriting a file that is modified on disk (#2783)
* added overwrite prevention to saving * rearranging order of require variables and edit to rename * added documentation, and fixed reload button * followed suggestion by tom, started tests * Revert "followed suggestion by tom, started tests" This reverts commit 4d45ec7. * added back in reverted changes to editor.js * Fix broken reference to 'this'
1 parent b76c8d9 commit 331a7a2

File tree

1 file changed

+99
-8
lines changed

1 file changed

+99
-8
lines changed

notebook/static/edit/js/editor.js

Lines changed: 99 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
define([
55
'jquery',
66
'base/js/utils',
7+
'base/js/i18n',
8+
'base/js/dialog',
79
'codemirror/lib/codemirror',
810
'codemirror/mode/meta',
911
'codemirror/addon/comment/comment',
@@ -19,6 +21,8 @@ define([
1921
function(
2022
$,
2123
utils,
24+
i18n,
25+
dialog,
2226
CodeMirror
2327
) {
2428
"use strict";
@@ -33,6 +37,8 @@ function(
3337
this.file_path = options.file_path;
3438
this.config = options.config;
3539
this.file_extension_modes = options.file_extension_modes || {};
40+
this.last_modified = null;
41+
this._changed_on_disk_dialog = null;
3642

3743
this.codemirror = new CodeMirror($(this.selector)[0]);
3844
this.codemirror.on('changes', function(cm, changes){
@@ -106,6 +112,7 @@ function(
106112
that.generation = cm.changeGeneration();
107113
that.events.trigger("file_loaded.Editor", model);
108114
that._clean_state();
115+
that.last_modified = new Date(model.last_modified);
109116
}).catch(
110117
function(error) {
111118
that.events.trigger("file_load_failed.Editor", error);
@@ -197,6 +204,11 @@ function(
197204
}
198205
};
199206

207+
/**
208+
* Rename the file.
209+
* @param {string} new_name
210+
* @return {Promise} promise that resolves when the file is renamed.
211+
*/
200212
Editor.prototype.rename = function (new_name) {
201213
/** rename the file */
202214
var that = this;
@@ -206,32 +218,111 @@ function(
206218
function (model) {
207219
that.file_path = model.path;
208220
that.events.trigger('file_renamed.Editor', model);
221+
that.last_modified = new Date(model.last_modified);
209222
that._set_mode_for_model(model);
210223
that._clean_state();
211224
}
212225
);
213226
};
214227

215-
Editor.prototype.save = function () {
228+
229+
/**
230+
* Save this file on the server.
231+
*
232+
* @param {boolean} check_last_modified - checks if file has been modified on disk
233+
* @return {Promise} - promise that resolves when the notebook is saved.
234+
*/
235+
Editor.prototype.save = function (check_last_modified) {
216236
/** save the file */
217237
if (!this.save_enabled) {
218238
console.log("Not saving, save disabled");
219239
return;
220240
}
241+
242+
// used to check for last modified saves
243+
if (check_last_modified === undefined) {
244+
check_last_modified = true;
245+
}
246+
221247
var model = {
222248
path: this.file_path,
223249
type: 'file',
224250
format: 'text',
225251
content: this.codemirror.getValue(),
226252
};
227253
var that = this;
228-
// record change generation for isClean
229-
this.generation = this.codemirror.changeGeneration();
230-
that.events.trigger("file_saving.Editor");
231-
return this.contents.save(this.file_path, model).then(function(data) {
232-
that.events.trigger("file_saved.Editor", data);
233-
that._clean_state();
234-
});
254+
255+
var _save = function () {
256+
that.events.trigger("file_saving.Editor");
257+
return that.contents.save(that.file_path, model).then(function(data) {
258+
// record change generation for isClean
259+
that.generation = that.codemirror.changeGeneration();
260+
that.events.trigger("file_saved.Editor", data);
261+
that.last_modified = new Date(data.last_modified);
262+
that._clean_state();
263+
});
264+
};
265+
266+
/*
267+
* Gets the current working file, and checks if the file has been modified on disk. If so, it
268+
* creates & opens a modal that issues the user a warning and prompts them to overwrite the file.
269+
*
270+
* If it can't get the working file, it builds a new file and saves.
271+
*/
272+
if (check_last_modified) {
273+
return this.contents.get(that.file_path, {content: false}).then(
274+
function check_if_modified(data) {
275+
var last_modified = new Date(data.last_modified);
276+
// We want to check last_modified (disk) > that.last_modified (our last save)
277+
// In some cases the filesystem reports an inconsistent time,
278+
// so we allow 0.5 seconds difference before complaining.
279+
if ((last_modified.getTime() - that.last_modified.getTime()) > 500) { // 500 ms
280+
console.warn("Last saving was done on `"+that.last_modified+"`("+that._last_modified+"), "+
281+
"while the current file seem to have been saved on `"+data.last_modified+"`");
282+
if (that._changed_on_disk_dialog !== null) {
283+
// since the modal's event bindings are removed when destroyed, we reinstate
284+
// save & reload callbacks on the confirmation & reload buttons
285+
that._changed_on_disk_dialog.find('.save-confirm-btn').click(_save);
286+
that._changed_on_disk_dialog.find('.btn-warning').click(function () {window.location.reload()});
287+
288+
// redisplay existing dialog
289+
that._changed_on_disk_dialog.modal('show');
290+
} else {
291+
// create new dialog
292+
that._changed_on_disk_dialog = dialog.modal({
293+
keyboard_manager: that.keyboard_manager,
294+
title: i18n.msg._("File changed"),
295+
body: i18n.msg._("The file has changed on disk since the last time we opened or saved it. "
296+
+ "Do you want to overwrite the file on disk with the version open here, or load "
297+
+ "the version on disk (reload the page)?"),
298+
buttons: {
299+
Reload: {
300+
class: 'btn-warning',
301+
click: function () {
302+
window.location.reload();
303+
}
304+
},
305+
Cancel: {},
306+
Overwrite: {
307+
class: 'btn-danger save-confirm-btn',
308+
click: function () {
309+
_save();
310+
}
311+
},
312+
}
313+
});
314+
}
315+
} else {
316+
return _save();
317+
}
318+
}, function (error) {
319+
console.log(error);
320+
// maybe it has been deleted or renamed? Go ahead and save.
321+
return _save();
322+
})
323+
} else {
324+
return _save();
325+
}
235326
};
236327

237328
Editor.prototype._clean_state = function(){

0 commit comments

Comments
 (0)