savewidget.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. define([
  4. 'jquery',
  5. 'base/js/utils',
  6. 'base/js/dialog',
  7. 'base/js/keyboard',
  8. 'moment',
  9. 'bidi/bidi',
  10. ], function($, utils, dialog, keyboard, moment, bidi) {
  11. "use strict";
  12. var SaveWidget = function (selector, options) {
  13. this.editor = undefined;
  14. this.selector = selector;
  15. this.events = options.events;
  16. this.editor = options.editor;
  17. this._last_modified = undefined;
  18. this._filename = undefined;
  19. this.keyboard_manager = options.keyboard_manager;
  20. if (this.selector !== undefined) {
  21. this.element = $(selector);
  22. this.bind_events();
  23. }
  24. };
  25. SaveWidget.prototype.bind_events = function () {
  26. var that = this;
  27. this.element.find('span.filename').click(function () {
  28. that.rename();
  29. });
  30. this.events.on('save_status_clean.Editor', function (evt) {
  31. that.update_document_title();
  32. });
  33. this.events.on('save_status_dirty.Editor', function (evt) {
  34. that.update_document_title(undefined, true);
  35. });
  36. this.events.on('file_loaded.Editor', function (evt, model) {
  37. that.update_filename(model.name);
  38. that.update_document_title(model.name);
  39. that.update_last_modified(model.last_modified);
  40. });
  41. this.events.on('file_saved.Editor', function (evt, model) {
  42. that.update_filename(model.name);
  43. that.update_document_title(model.name);
  44. that.update_last_modified(model.last_modified);
  45. });
  46. this.events.on('file_renamed.Editor', function (evt, model) {
  47. that.update_filename(model.name);
  48. that.update_document_title(model.name);
  49. that.update_address_bar(model.path);
  50. });
  51. this.events.on('file_save_failed.Editor', function () {
  52. that.set_save_status('Save Failed!');
  53. });
  54. };
  55. SaveWidget.prototype.rename = function (options) {
  56. options = options || {};
  57. var that = this;
  58. var dialog_body = $('<div/>').append(
  59. $("<p/>").addClass("rename-message")
  60. .text('Enter a new filename:')
  61. ).append(
  62. $("<br/>")
  63. ).append(
  64. $('<input/>').attr('type','text').attr('size','25').addClass('form-control')
  65. .val(that.editor.get_filename())
  66. );
  67. var d = dialog.modal({
  68. title: "Rename File",
  69. body: dialog_body,
  70. default_button: "Cancel",
  71. buttons : {
  72. "Cancel": {},
  73. "OK": {
  74. class: "btn-primary",
  75. click: function () {
  76. var new_name = d.find('input').val();
  77. if (!new_name) {
  78. // Reset the message
  79. d.find('.rename-message').text("Enter a new filename:");
  80. return false;
  81. }
  82. d.find('.rename-message').text("Renaming...");
  83. d.find('input[type="text"]').prop('disabled', true);
  84. that.editor.rename(new_name).then(
  85. function () {
  86. d.modal('hide');
  87. }, function (error) {
  88. d.find('.rename-message').text(error.message || 'Unknown error');
  89. d.find('input[type="text"]').prop('disabled', false).focus().select();
  90. }
  91. );
  92. return false;
  93. }
  94. }
  95. },
  96. open : function () {
  97. // Upon ENTER, click the OK button.
  98. d.find('input[type="text"]').keydown(function (event) {
  99. if (event.which === keyboard.keycodes.enter) {
  100. d.find('.btn-primary').first().click();
  101. return false;
  102. }
  103. });
  104. d.find('input[type="text"]').focus().select();
  105. }
  106. });
  107. };
  108. SaveWidget.prototype.update_filename = function (filename) {
  109. filename = bidi.applyBidi(filename);
  110. this.element.find('span.filename').text(filename);
  111. };
  112. SaveWidget.prototype.update_document_title = function (filename, dirty) {
  113. if(filename){
  114. this._filename = filename;
  115. }
  116. document.title = (dirty?'*':'')+this._filename;
  117. };
  118. SaveWidget.prototype.update_address_bar = function (path) {
  119. var state = {path : path};
  120. window.history.replaceState(state, "", utils.url_path_join(
  121. this.editor.base_url,
  122. "edit",
  123. utils.encode_uri_components(path)
  124. ));
  125. };
  126. SaveWidget.prototype.update_last_modified = function (last_modified) {
  127. if (last_modified) {
  128. this._last_modified = new Date(last_modified);
  129. } else {
  130. this._last_modified = null;
  131. }
  132. this._render_last_modified();
  133. };
  134. SaveWidget.prototype._render_last_modified = function () {
  135. /** actually set the text in the element, from our _last_modified value
  136. called directly, and periodically in timeouts.
  137. */
  138. this._schedule_render_last_modified();
  139. var el = this.element.find('span.last_modified');
  140. if (!this._last_modified) {
  141. el.text('').attr('title', 'never saved');
  142. return;
  143. }
  144. var chkd = moment(this._last_modified);
  145. var long_date = chkd.format('llll');
  146. var human_date;
  147. var tdelta = Math.ceil(new Date() - this._last_modified);
  148. if (tdelta < utils.time.milliseconds.d){
  149. // less than 24 hours old, use relative date
  150. human_date = chkd.fromNow();
  151. } else {
  152. // otherwise show calendar
  153. // <Today | yesterday|...> at hh,mm,ss
  154. human_date = chkd.calendar();
  155. }
  156. el.text(human_date).attr('title', long_date);
  157. };
  158. SaveWidget.prototype._schedule_render_last_modified = function () {
  159. /** schedule the next update to relative date
  160. periodically updated, so short values like 'a few seconds ago' don't get stale.
  161. */
  162. if (!this._last_modified) {
  163. return;
  164. }
  165. if ((this._last_modified_timeout)) {
  166. clearTimeout(this._last_modified_timeout);
  167. }
  168. var dt = Math.ceil(new Date() - this._last_modified);
  169. this._last_modified_timeout = setTimeout(
  170. $.proxy(this._render_last_modified, this),
  171. utils.time.timeout_from_dt(dt)
  172. );
  173. };
  174. return {'SaveWidget': SaveWidget};
  175. });