mirror of
https://github.com/zulip/zulip.git
synced 2025-11-04 14:03:30 +00:00
paste: More robust check to verify if clipboard data is an image.
The commit f863a9b567 had modified
jquery.filedrop's paste method to exit early if any of the items in the
clipboardData is of the string kind. The early exit was added to prevent pasting
an image thumbnail for text copied from software like MS Word, instead of
pasting the actual copied text content. When copying an image in a (modern?)
Browser, though, the clipboard seems to contain a html `img` tag item, along
with the actual image file. This resulted in pastes being broken.
This commit modifies the condition checked for the early exit. We now actually
look at the html content in the clipboard to see if it is an `img` tag, in which
case we upload the image, instead of exiting early.
Closes #7130.
This commit is contained in:
committed by
Tim Abbott
parent
8cf5810fc0
commit
9c377a05f3
@@ -1,8 +1,9 @@
|
|||||||
global.stub_out_jquery();
|
global.stub_out_jquery();
|
||||||
|
|
||||||
set_global('page_params', {
|
set_global('page_params', {
|
||||||
development: true,
|
development_environment: true,
|
||||||
});
|
});
|
||||||
|
set_global('compose_ui', {});
|
||||||
|
|
||||||
const { JSDOM } = require("jsdom");
|
const { JSDOM } = require("jsdom");
|
||||||
const { window } = new JSDOM('<!DOCTYPE html><p>Hello world</p>');
|
const { window } = new JSDOM('<!DOCTYPE html><p>Hello world</p>');
|
||||||
@@ -13,6 +14,25 @@ global.$ = require('jquery')(window);
|
|||||||
zrequire('toMarkdown', 'node_modules/to-markdown/dist/to-markdown.js');
|
zrequire('toMarkdown', 'node_modules/to-markdown/dist/to-markdown.js');
|
||||||
var copy_and_paste = zrequire('copy_and_paste');
|
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', () => {
|
run_test('paste_handler', () => {
|
||||||
|
|
||||||
var input = '<meta http-equiv="content-type" content="text/html; charset=utf-8"><span style="color: rgb(34, 34, 34); font-family: arial, sans-serif; font-size: 12.8px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"><span> </span>love the<span> </span><b>Zulip</b><b> </b></span><b style="color: rgb(34, 34, 34); font-family: arial, sans-serif; font-size: 12.8px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">Organization</b><span style="color: rgb(34, 34, 34); font-family: arial, sans-serif; font-size: 12.8px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">.</span>';
|
var input = '<meta http-equiv="content-type" content="text/html; charset=utf-8"><span style="color: rgb(34, 34, 34); font-family: arial, sans-serif; font-size: 12.8px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"><span> </span>love the<span> </span><b>Zulip</b><b> </b></span><b style="color: rgb(34, 34, 34); font-family: arial, sans-serif; font-size: 12.8px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">Organization</b><span style="color: rgb(34, 34, 34); font-family: arial, sans-serif; font-size: 12.8px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">.</span>';
|
||||||
@@ -50,4 +70,22 @@ run_test('paste_handler', () => {
|
|||||||
input = '<div class="ace-line gutter-author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3z66zs0z65zz65zq8z75zlaz81zcz66zj6g2mz78zz76zmz66z22z75zfcz69zz66z ace-ltr focused-line" dir="auto" id="editor-3-ace-line-41"><span>Test List:</span></div><div class="ace-line gutter-author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3z66zs0z65zz65zq8z75zlaz81zcz66zj6g2mz78zz76zmz66z22z75zfcz69zz66z line-list-type-bullet ace-ltr" dir="auto" id="editor-3-ace-line-42"><ul class="listtype-bullet listindent1 list-bullet1"><li><span class="ace-line-pocket-zws" data-faketext="" data-contentcollector-ignore-space-at="end"></span><span class="ace-line-pocket" data-faketext="" contenteditable="false"></span><span class="ace-line-pocket-zws" data-faketext="" data-contentcollector-ignore-space-at="start"></span><span>Item 1</span></li></ul></div><div class="ace-line gutter-author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3z66zs0z65zz65zq8z75zlaz81zcz66zj6g2mz78zz76zmz66z22z75zfcz69zz66z line-list-type-bullet ace-ltr" dir="auto" id="editor-3-ace-line-43"><ul class="listtype-bullet listindent1 list-bullet1"><li><span class="ace-line-pocket-zws" data-faketext="" data-contentcollector-ignore-space-at="end"></span><span class="ace-line-pocket" data-faketext="" contenteditable="false"></span><span class="ace-line-pocket-zws" data-faketext="" data-contentcollector-ignore-space-at="start"></span><span>Item 2</span></li></ul></div>';
|
input = '<div class="ace-line gutter-author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3z66zs0z65zz65zq8z75zlaz81zcz66zj6g2mz78zz76zmz66z22z75zfcz69zz66z ace-ltr focused-line" dir="auto" id="editor-3-ace-line-41"><span>Test List:</span></div><div class="ace-line gutter-author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3z66zs0z65zz65zq8z75zlaz81zcz66zj6g2mz78zz76zmz66z22z75zfcz69zz66z line-list-type-bullet ace-ltr" dir="auto" id="editor-3-ace-line-42"><ul class="listtype-bullet listindent1 list-bullet1"><li><span class="ace-line-pocket-zws" data-faketext="" data-contentcollector-ignore-space-at="end"></span><span class="ace-line-pocket" data-faketext="" contenteditable="false"></span><span class="ace-line-pocket-zws" data-faketext="" data-contentcollector-ignore-space-at="start"></span><span>Item 1</span></li></ul></div><div class="ace-line gutter-author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3z66zs0z65zz65zq8z75zlaz81zcz66zj6g2mz78zz76zmz66z22z75zfcz69zz66z line-list-type-bullet ace-ltr" dir="auto" id="editor-3-ace-line-43"><ul class="listtype-bullet listindent1 list-bullet1"><li><span class="ace-line-pocket-zws" data-faketext="" data-contentcollector-ignore-space-at="end"></span><span class="ace-line-pocket" data-faketext="" contenteditable="false"></span><span class="ace-line-pocket-zws" data-faketext="" data-contentcollector-ignore-space-at="start"></span><span>Item 2</span></li></ul></div>';
|
||||||
assert.equal(copy_and_paste.paste_handler_converter(input),
|
assert.equal(copy_and_paste.paste_handler_converter(input),
|
||||||
'Test List:\n* Item 1\n* Item 2');
|
'Test List:\n* Item 1\n* Item 2');
|
||||||
|
|
||||||
|
var data = '<p>text</p>';
|
||||||
|
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 = '<meta http-equiv="content-type" content="text/html; charset=utf-8"><img src="http://localhost:9991/thumbnail?url=user_uploads%2F1%2Fe2%2FHPMCcGWOG9rS2M4ybHN8sEzh%2Fpasted_image.png&size=full"/>';
|
||||||
|
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);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -235,8 +235,16 @@ exports.paste_handler = function (event) {
|
|||||||
if (clipboardData.getData) {
|
if (clipboardData.getData) {
|
||||||
var paste_html = clipboardData.getData('text/html');
|
var paste_html = clipboardData.getData('text/html');
|
||||||
if (paste_html && page_params.development_environment) {
|
if (paste_html && page_params.development_environment) {
|
||||||
event.preventDefault();
|
|
||||||
var text = exports.paste_handler_converter(paste_html);
|
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);
|
compose_ui.insert_syntax_and_focus(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,22 +164,52 @@
|
|||||||
sendRawImageData(event, image);
|
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) {
|
function paste(event) {
|
||||||
if (event.originalEvent.clipboardData === undefined ||
|
if (event.originalEvent.clipboardData === undefined ||
|
||||||
event.originalEvent.clipboardData.items === undefined) {
|
event.originalEvent.clipboardData.items === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if any of the items are strings, and if they are,
|
// Check if the data in the clipboard is really an image, or just a
|
||||||
// then return, since we want the default browser behavior
|
// thumbnail of the copied text.
|
||||||
// to deal with those.
|
if (!dataIsImage(event.originalEvent.clipboardData)){
|
||||||
|
return;
|
||||||
var itemsLength = event.originalEvent.clipboardData.items.length;
|
|
||||||
|
|
||||||
for (var i = 0; i < itemsLength; i++) {
|
|
||||||
if (event.originalEvent.clipboardData.items[i].kind === "string") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take the first image pasted in the clipboard
|
// Take the first image pasted in the clipboard
|
||||||
|
|||||||
Reference in New Issue
Block a user