##// END OF EJS Templates
track dirty state in editor for onbeforeunload
Min RK -
Show More
@@ -1,130 +1,134 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'jquery',
5 'jquery',
6 'base/js/utils',
6 'base/js/utils',
7 'codemirror/lib/codemirror',
7 'codemirror/lib/codemirror',
8 'codemirror/mode/meta',
8 'codemirror/mode/meta',
9 'codemirror/addon/comment/comment',
9 'codemirror/addon/comment/comment',
10 'codemirror/addon/dialog/dialog',
10 'codemirror/addon/dialog/dialog',
11 'codemirror/addon/edit/closebrackets',
11 'codemirror/addon/edit/closebrackets',
12 'codemirror/addon/edit/matchbrackets',
12 'codemirror/addon/edit/matchbrackets',
13 'codemirror/addon/search/searchcursor',
13 'codemirror/addon/search/searchcursor',
14 'codemirror/addon/search/search',
14 'codemirror/addon/search/search',
15 'codemirror/keymap/emacs',
15 'codemirror/keymap/emacs',
16 'codemirror/keymap/sublime',
16 'codemirror/keymap/sublime',
17 'codemirror/keymap/vim',
17 'codemirror/keymap/vim',
18 ],
18 ],
19 function($,
19 function($,
20 utils,
20 utils,
21 CodeMirror
21 CodeMirror
22 ) {
22 ) {
23 "use strict";
23 "use strict";
24
24
25 var Editor = function(selector, options) {
25 var Editor = function(selector, options) {
26 var that = this;
26 var that = this;
27 this.selector = selector;
27 this.selector = selector;
28 this.contents = options.contents;
28 this.contents = options.contents;
29 this.events = options.events;
29 this.events = options.events;
30 this.base_url = options.base_url;
30 this.base_url = options.base_url;
31 this.file_path = options.file_path;
31 this.file_path = options.file_path;
32 this.config = options.config;
32 this.config = options.config;
33 this.codemirror = new CodeMirror($(this.selector)[0]);
33 this.codemirror = new CodeMirror($(this.selector)[0]);
34 this.generation = -1;
34
35
35 // It appears we have to set commands on the CodeMirror class, not the
36 // It appears we have to set commands on the CodeMirror class, not the
36 // instance. I'd like to be wrong, but since there should only be one CM
37 // instance. I'd like to be wrong, but since there should only be one CM
37 // instance on the page, this is good enough for now.
38 // instance on the page, this is good enough for now.
38 CodeMirror.commands.save = $.proxy(this.save, this);
39 CodeMirror.commands.save = $.proxy(this.save, this);
39
40
40 this.save_enabled = false;
41 this.save_enabled = false;
41
42
42 this.config.loaded.then(function () {
43 this.config.loaded.then(function () {
43 // load codemirror config
44 // load codemirror config
44 var cfg = that.config.data.Editor || {};
45 var cfg = that.config.data.Editor || {};
45 var cmopts = $.extend(true, {}, // true = recursive copy
46 var cmopts = $.extend(true, {}, // true = recursive copy
46 Editor.default_codemirror_options,
47 Editor.default_codemirror_options,
47 cfg.codemirror_options || {}
48 cfg.codemirror_options || {}
48 );
49 );
49 that.set_codemirror_options(cmopts, false);
50 that.set_codemirror_options(cmopts, false);
50 that.events.trigger('config_changed.Editor', {config: that.config});
51 that.events.trigger('config_changed.Editor', {config: that.config});
51 });
52 });
52 };
53 };
53
54
54 // default CodeMirror options
55 // default CodeMirror options
55 Editor.default_codemirror_options = {
56 Editor.default_codemirror_options = {
56 extraKeys: {
57 extraKeys: {
57 "Tab" : "indentMore",
58 "Tab" : "indentMore",
58 },
59 },
59 indentUnit: 4,
60 indentUnit: 4,
60 theme: "ipython",
61 theme: "ipython",
61 lineNumbers: true,
62 lineNumbers: true,
62 };
63 };
63
64
64 Editor.prototype.load = function() {
65 Editor.prototype.load = function() {
65 /** load the file */
66 /** load the file */
66 var that = this;
67 var that = this;
67 var cm = this.codemirror;
68 var cm = this.codemirror;
68 return this.contents.get(this.file_path, {type: 'file', format: 'text'})
69 return this.contents.get(this.file_path, {type: 'file', format: 'text'})
69 .then(function(model) {
70 .then(function(model) {
70 cm.setValue(model.content);
71 cm.setValue(model.content);
71
72
72 // Setting the file's initial value creates a history entry,
73 // Setting the file's initial value creates a history entry,
73 // which we don't want.
74 // which we don't want.
74 cm.clearHistory();
75 cm.clearHistory();
75
76
76 // Find and load the highlighting mode
77 // Find and load the highlighting mode
77 utils.requireCodeMirrorMode(model.mimetype, function(spec) {
78 utils.requireCodeMirrorMode(model.mimetype, function(spec) {
78 var mode = CodeMirror.getMode({}, spec);
79 var mode = CodeMirror.getMode({}, spec);
79 cm.setOption('mode', mode);
80 cm.setOption('mode', mode);
80 });
81 });
81 that.save_enabled = true;
82 that.save_enabled = true;
83 that.generation = cm.changeGeneration();
82 },
84 },
83 function(error) {
85 function(error) {
84 cm.setValue("Error! " + error.message +
86 cm.setValue("Error! " + error.message +
85 "\nSaving disabled.");
87 "\nSaving disabled.");
86 that.save_enabled = false;
88 that.save_enabled = false;
87 }
89 }
88 );
90 );
89 };
91 };
90
92
91 Editor.prototype.save = function() {
93 Editor.prototype.save = function() {
92 /** save the file */
94 /** save the file */
93 if (!this.save_enabled) {
95 if (!this.save_enabled) {
94 console.log("Not saving, save disabled");
96 console.log("Not saving, save disabled");
95 return;
97 return;
96 }
98 }
97 var model = {
99 var model = {
98 path: this.file_path,
100 path: this.file_path,
99 type: 'file',
101 type: 'file',
100 format: 'text',
102 format: 'text',
101 content: this.codemirror.getValue(),
103 content: this.codemirror.getValue(),
102 };
104 };
103 var that = this;
105 var that = this;
106 // record change generation for isClean
107 this.generation = this.codemirror.changeGeneration();
104 return this.contents.save(this.file_path, model).then(function() {
108 return this.contents.save(this.file_path, model).then(function() {
105 that.events.trigger("save_succeeded.TextEditor");
109 that.events.trigger("save_succeeded.TextEditor");
106 });
110 });
107 };
111 };
108
112
109 Editor.prototype._set_codemirror_options = function (options) {
113 Editor.prototype._set_codemirror_options = function (options) {
110 // update codemirror options from a dict
114 // update codemirror options from a dict
111 for (var opt in options) {
115 for (var opt in options) {
112 this.codemirror.setOption(opt, options[opt]);
116 this.codemirror.setOption(opt, options[opt]);
113 }
117 }
114 };
118 };
115
119
116 Editor.prototype.update_codemirror_options = function (options) {
120 Editor.prototype.update_codemirror_options = function (options) {
117 /** update codemirror options locally and save changes in config */
121 /** update codemirror options locally and save changes in config */
118 var that = this;
122 var that = this;
119 this._set_codemirror_options(options);
123 this._set_codemirror_options(options);
120 return this.config.update({
124 return this.config.update({
121 Editor: {
125 Editor: {
122 codemirror_options: options
126 codemirror_options: options
123 }
127 }
124 }).then(
128 }).then(
125 that.events.trigger('config_changed.Editor', {config: that.config})
129 that.events.trigger('config_changed.Editor', {config: that.config})
126 );
130 );
127 };
131 };
128
132
129 return {Editor: Editor};
133 return {Editor: Editor};
130 });
134 });
@@ -1,66 +1,73 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 require([
4 require([
5 'base/js/namespace',
5 'base/js/namespace',
6 'base/js/utils',
6 'base/js/utils',
7 'base/js/page',
7 'base/js/page',
8 'base/js/events',
8 'base/js/events',
9 'contents',
9 'contents',
10 'services/config',
10 'services/config',
11 'edit/js/editor',
11 'edit/js/editor',
12 'edit/js/menubar',
12 'edit/js/menubar',
13 'edit/js/notificationarea',
13 'edit/js/notificationarea',
14 'custom/custom',
14 'custom/custom',
15 ], function(
15 ], function(
16 IPython,
16 IPython,
17 utils,
17 utils,
18 page,
18 page,
19 events,
19 events,
20 contents,
20 contents,
21 configmod,
21 configmod,
22 editor,
22 editmod,
23 menubar,
23 menubar,
24 notificationarea
24 notificationarea
25 ){
25 ){
26 page = new page.Page();
26 page = new page.Page();
27
27
28 var base_url = utils.get_body_data('baseUrl');
28 var base_url = utils.get_body_data('baseUrl');
29 var file_path = utils.get_body_data('filePath');
29 var file_path = utils.get_body_data('filePath');
30 contents = new contents.Contents({base_url: base_url});
30 contents = new contents.Contents({base_url: base_url});
31 var config = new configmod.ConfigSection('edit', {base_url: base_url})
31 var config = new configmod.ConfigSection('edit', {base_url: base_url});
32 config.load();
32 config.load();
33
33
34 var editor = new editor.Editor('#texteditor-container', {
34 var editor = new editmod.Editor('#texteditor-container', {
35 base_url: base_url,
35 base_url: base_url,
36 events: events,
36 events: events,
37 contents: contents,
37 contents: contents,
38 file_path: file_path,
38 file_path: file_path,
39 config: config,
39 config: config,
40 });
40 });
41
41
42 // Make it available for debugging
42 // Make it available for debugging
43 IPython.editor = editor;
43 IPython.editor = editor;
44
44
45 var menus = new menubar.MenuBar('#menubar', {
45 var menus = new menubar.MenuBar('#menubar', {
46 base_url: base_url,
46 base_url: base_url,
47 editor: editor,
47 editor: editor,
48 events: events,
48 events: events,
49 });
49 });
50
50
51 var notification_area = new notificationarea.EditorNotificationArea(
51 var notification_area = new notificationarea.EditorNotificationArea(
52 '#notification_area', {
52 '#notification_area', {
53 events: events,
53 events: events,
54 });
54 });
55 notification_area.init_notification_widgets();
55 notification_area.init_notification_widgets();
56
56
57 config.loaded.then(function() {
57 config.loaded.then(function() {
58 if (config.data.load_extensions) {
58 if (config.data.load_extensions) {
59 var nbextension_paths = Object.getOwnPropertyNames(
59 var nbextension_paths = Object.getOwnPropertyNames(
60 config.data.load_extensions);
60 config.data.load_extensions);
61 IPython.load_extensions.apply(this, nbextension_paths);
61 IPython.load_extensions.apply(this, nbextension_paths);
62 }
62 }
63 });
63 });
64 editor.load();
64 editor.load();
65 page.show();
65 page.show();
66
67 window.onbeforeunload = function () {
68 if (!editor.codemirror.isClean(editor.generation)) {
69 return "Unsaved changes will be lost. Close anyway?";
70 }
71 };
72
66 });
73 });
General Comments 0
You need to be logged in to leave comments. Login now