diff --git a/frontend_tests/node_tests/copy_and_paste.js b/frontend_tests/node_tests/copy_and_paste.js index fbb3c8e87a..c92363617b 100644 --- a/frontend_tests/node_tests/copy_and_paste.js +++ b/frontend_tests/node_tests/copy_and_paste.js @@ -1,8 +1,9 @@ global.stub_out_jquery(); set_global('page_params', { - development: true, + development_environment: true, }); +set_global('compose_ui', {}); const { JSDOM } = require("jsdom"); const { window } = new JSDOM('

Hello world

'); @@ -13,6 +14,25 @@ global.$ = require('jquery')(window); zrequire('toMarkdown', 'node_modules/to-markdown/dist/to-markdown.js'); var copy_and_paste = zrequire('copy_and_paste'); +// Super stripped down version of the code in the drag-mock library +// https://github.com/andywer/drag-mock/blob/6d46c7c0ffd6a4d685e6612a90cd58cda80f30fc/src/DataTransfer.js +var DataTransfer = function () { + this.dataByFormat = {}; +}; +DataTransfer.prototype.getData = function (dataFormat) { + return this.dataByFormat[dataFormat]; +}; +DataTransfer.prototype.setData = function (dataFormat, data) { + this.dataByFormat[dataFormat] = data; +}; + +var createPasteEvent = function () { + var clipboardData = new DataTransfer(); + var pasteEvent = new window.Event('paste'); + pasteEvent.clipboardData = clipboardData; + return $.Event(pasteEvent); +}; + run_test('paste_handler', () => { var input = ' love the Zulip Organization.'; @@ -50,4 +70,22 @@ run_test('paste_handler', () => { input = '
Test List:
'; assert.equal(copy_and_paste.paste_handler_converter(input), 'Test List:\n* Item 1\n* Item 2'); + + var data = '

text

'; + var event = createPasteEvent(); + event.originalEvent.clipboardData.setData('text/html', data); + var insert_syntax_and_focus_called = false; + compose_ui.insert_syntax_and_focus = function () { + insert_syntax_and_focus_called = true; + }; + copy_and_paste.paste_handler(event); + assert(insert_syntax_and_focus_called); + + data = ''; + event = createPasteEvent(); + event.originalEvent.clipboardData.setData('text/html', data); + insert_syntax_and_focus_called = false; + copy_and_paste.paste_handler(event); + assert(!insert_syntax_and_focus_called); + }); diff --git a/static/js/copy_and_paste.js b/static/js/copy_and_paste.js index c5304c3d47..9a361042d3 100644 --- a/static/js/copy_and_paste.js +++ b/static/js/copy_and_paste.js @@ -235,8 +235,16 @@ exports.paste_handler = function (event) { if (clipboardData.getData) { var paste_html = clipboardData.getData('text/html'); if (paste_html && page_params.development_environment) { - event.preventDefault(); var text = exports.paste_handler_converter(paste_html); + var mdImageRegex = /^!\[.*\]\(.*\)$/; + if (text.match(mdImageRegex)) { + // This block catches cases where we are pasting an + // image into Zulip, which should be handled by the + // jQuery filedrop library, not this code path. + return; + } + event.preventDefault(); + event.stopPropagation(); compose_ui.insert_syntax_and_focus(text); } } diff --git a/static/third/jquery-filedrop/jquery.filedrop.js b/static/third/jquery-filedrop/jquery.filedrop.js index 35503c7ac8..6a3e2ed875 100644 --- a/static/third/jquery-filedrop/jquery.filedrop.js +++ b/static/third/jquery-filedrop/jquery.filedrop.js @@ -164,22 +164,52 @@ sendRawImageData(event, image); } + function dataIsImage(data) { + // Check if the clipboard data is actually an image or a thumbnail of the + // copied text. + + var text = data.getData('text/html'); + + if (!text) { + // No html is present, when pasting from image viewers or a screenshot + return true; + } + + try { + var html = $.parseHTML(text); + } catch(e) { + // This is really a problem with the software, where we copied the text + // from - but we just let the default browser behavior prevail + return false; + } + + // Some software like MS Word adds an image thumbnail, when text is + // copied. We would like to paste the actual text, instead of the + // thumbnail image in this case. + + // When an image copied in a (modern?) Browser, a 'text/html' item is + // present in the clipboard, which has an img tag for the copied image + // (along with may be a meta tag) + + var allowedTags = ["META", "IMG"]; + for (var i=0; i < html.length; i += 1){ + if (allowedTags.indexOf(html[i].nodeName) < 0){ + return false; + } + } + return true; + } + function paste(event) { if (event.originalEvent.clipboardData === undefined || event.originalEvent.clipboardData.items === undefined) { return; } - // Check if any of the items are strings, and if they are, - // then return, since we want the default browser behavior - // to deal with those. - - var itemsLength = event.originalEvent.clipboardData.items.length; - - for (var i = 0; i < itemsLength; i++) { - if (event.originalEvent.clipboardData.items[i].kind === "string") { - return; - } + // Check if the data in the clipboard is really an image, or just a + // thumbnail of the copied text. + if (!dataIsImage(event.originalEvent.clipboardData)){ + return; } // Take the first image pasted in the clipboard