diff --git a/bower.json b/bower.json index 114b4ab1a..ae39400c5 100644 --- a/bower.json +++ b/bower.json @@ -5,7 +5,7 @@ "backbone": "components/backbone#~1.2", "bootstrap": "components/bootstrap#~3.3", "bootstrap-tour": "0.9.0", - "codemirror": "~5.8", + "codemirror": "~5.14", "es6-promise": "~1.0", "font-awesome": "components/font-awesome#~4.2.0", "google-caja": "5669", diff --git a/notebook/static/base/js/utils.js b/notebook/static/base/js/utils.js index 49178cfd4..cf9ed588a 100644 --- a/notebook/static/base/js/utils.js +++ b/notebook/static/base/js/utils.js @@ -946,6 +946,22 @@ define([ } }; + // Test if a drag'n'drop event contains a file (as opposed to an HTML + // element/text from the document) + var dnd_contain_file = function(event) { + // As per the HTML5 drag'n'drop spec, the dataTransfer.types should + // contain one "Files" type if a file is being dragged + // https://www.w3.org/TR/2011/WD-html5-20110113/dnd.html#dom-datatransfer-types + if (event.dataTransfer.types) { + for (var i = 0; i < event.dataTransfer.types.length; i++) { + if (event.dataTransfer.types[i] == "Files") { + return true; + } + } + } + return false; + }; + var utils = { is_loaded: is_loaded, load_extension: load_extension, @@ -990,6 +1006,7 @@ define([ time: time, format_datetime: format_datetime, datetime_sort_helper: datetime_sort_helper, + dnd_contain_file: dnd_contain_file, _ansispan:_ansispan }; diff --git a/notebook/static/notebook/js/textcell.js b/notebook/static/notebook/js/textcell.js index c6772b9fc..64d613710 100644 --- a/notebook/static/notebook/js/textcell.js +++ b/notebook/static/notebook/js/textcell.js @@ -110,6 +110,7 @@ define([ inner_cell.append(input_area).append(render_area); cell.append(inner_cell); this.element = cell; + this.inner_cell = inner_cell; }; @@ -282,6 +283,9 @@ define([ TextCell.apply(this, [$.extend({}, options, {config: config})]); this.cell_type = 'markdown'; + + // Used to keep track of drag events + this.drag_counter = 0; }; MarkdownCell.options_default = { @@ -363,6 +367,11 @@ define([ * @method render */ MarkdownCell.prototype.render = function () { + // We clear the dropzone here just in case the dragenter/leave + // logic of bind_events wasn't 100% successful. + this.drag_counter = 0; + this.inner_cell.removeClass('dropzone'); + var cont = TextCell.prototype.render.apply(this); if (cont) { var that = this; @@ -449,19 +458,44 @@ define([ } }); - // Allow drop event if the dragged file can be used as an attachment - this.code_mirror.on("dragstart", function(cm, evt) { - var files = evt.dataTransfer.files; - for (var i = 0; i < files.length; ++i) { - var file = files[i]; - if (attachment_regex.test(file.type)) { - return false; - } + // Allow drag event if the dragged file can be used as an attachment + // If we use this.code_mirror.on to register a "dragover" handler, we + // get an empty dataTransfer + this.code_mirror.on("dragover", function(cm, evt) { + if (utils.dnd_contain_file(evt)) { + evt.preventDefault(); } - return true; + }); + + // We want to display a visual indicator that the drop is possible. + // The dragleave event is fired when we hover a child element (which + // is often immediatly after we got the dragenter), so we keep track + // of the number of dragenter/dragleave we got, as discussed here : + // http://stackoverflow.com/q/7110353/116067 + // This doesn't seem to be 100% reliable, so we clear the dropzone + // class when the cell is rendered as well + this.code_mirror.on("dragenter", function(cm, evt) { + if (utils.dnd_contain_file(evt)) { + that.drag_counter++; + that.inner_cell.addClass('dropzone'); + } + evt.preventDefault(); + evt.stopPropagation(); + }); + + this.code_mirror.on("dragleave", function(cm, evt) { + that.drag_counter--; + if (that.drag_counter <= 0) { + that.inner_cell.removeClass('dropzone'); + } + evt.preventDefault(); + evt.stopPropagation(); }); this.code_mirror.on("drop", function(cm, evt) { + that.drag_counter = 0; + that.inner_cell.removeClass('dropzone'); + var files = evt.dataTransfer.files; for (var i = 0; i < files.length; ++i) { var file = files[i]; diff --git a/notebook/static/notebook/less/textcell.less b/notebook/static/notebook/less/textcell.less index 6e3f6ee85..3f49e8b24 100644 --- a/notebook/static/notebook/less/textcell.less +++ b/notebook/static/notebook/less/textcell.less @@ -48,6 +48,11 @@ h1,h2,h3,h4,h5,h6 { display:none; } +.text_cell .dropzone .input_area { + border: 2px dashed #bababa; + margin: -1px; +} + .cm-header-1, .cm-header-2, .cm-header-3,