Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bug #329: Copy-pasting does not work on IE11 #330

Merged
merged 3 commits into from
Feb 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 44 additions & 13 deletions src/js/utils/paste-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,29 +29,61 @@ function parsePostFromText(text, builder, plugins) {
return post;
}

// Sets the clipboard data in a cross-browser way.
function setClipboardData(clipboardData, html, plain) {
if (clipboardData && clipboardData.setData) {
clipboardData.setData(MIME_TEXT_HTML, html);
clipboardData.setData(MIME_TEXT_PLAIN, plain);
} else if (window.clipboardData && window.clipboardData.setData) { // IE
// The Internet Explorers (including Edge) have a non-standard way of interacting with the
// Clipboard API (see http://caniuse.com/#feat=clipboard). In short, they expose a global window.clipboardData
// object instead of the per-event event.clipboardData object on the other browsers.
window.clipboardData.setData('Text', html);
}
}

// Gets the clipboard data in a cross-browser way.
function getClipboardData(clipboardData) {
let html;
let text;

if (clipboardData && clipboardData.getData) {
html = clipboardData.getData(MIME_TEXT_HTML);

if (!html || html.length === 0) { // Fallback to 'text/plain'
text = clipboardData.getData(MIME_TEXT_PLAIN);
}
} else if (window.clipboardData && window.clipboardData.getData) { // IE
// The Internet Explorers (including Edge) have a non-standard way of interacting with the
// Clipboard API (see http://caniuse.com/#feat=clipboard). In short, they expose a global window.clipboardData
// object instead of the per-event event.clipboardData object on the other browsers.
html = window.clipboardData.getData('Text');
}

return { html, text };
}

/**
* @param {Event} copyEvent
* @param {Editor}
* @return null
*/
export function setClipboardCopyData(copyEvent, editor) {
let { range, post } = editor;
let { clipboardData } = copyEvent;
const { range, post } = editor;

let mobiledoc = post.cloneRange(range);
const mobiledoc = post.cloneRange(range);

let unknownCardHandler = () => {}; // ignore unknown cards
let unknownAtomHandler = () => {}; // ignore unknown atoms
let {result: innerHTML} =
const unknownCardHandler = () => {}; // ignore unknown cards
const unknownAtomHandler = () => {}; // ignore unknown atoms
const {result: innerHTML} =
new HTMLRenderer({unknownCardHandler, unknownAtomHandler}).render(mobiledoc);

const html =
`<div data-mobiledoc='${JSON.stringify(mobiledoc)}'>${innerHTML}</div>`;
const {result: plain} =
new TextRenderer({unknownCardHandler, unknownAtomHandler}).render(mobiledoc);

clipboardData.setData(MIME_TEXT_PLAIN, plain);
clipboardData.setData(MIME_TEXT_HTML, html);
setClipboardData(copyEvent.clipboardData, html, plain);
}

/**
Expand All @@ -62,13 +94,12 @@ export function setClipboardCopyData(copyEvent, editor) {
*/
export function parsePostFromPaste(pasteEvent, builder, plugins=[]) {
let post;
let html = pasteEvent.clipboardData.getData(MIME_TEXT_HTML);

if (!html || html.length === 0) { // Fallback to 'text/plain'
let text = pasteEvent.clipboardData.getData(MIME_TEXT_PLAIN);
post = parsePostFromText(text, builder, plugins);
} else {
const { html, text } = getClipboardData(pasteEvent.clipboardData);
if (html && html.length > 0) {
post = parsePostFromHTML(html, builder, plugins);
} else if (text && text.length > 0) {
post = parsePostFromText(text, builder, plugins);
}

return post;
Expand Down
28 changes: 20 additions & 8 deletions tests/acceptance/editor-copy-paste-test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Editor } from 'mobiledoc-kit';
import Helpers from '../test-helpers';
import Range from 'mobiledoc-kit/utils/cursor/range';
import { supportsStandardClipboardAPI } from '../helpers/browsers';
import {
MIME_TEXT_PLAIN,
MIME_TEXT_HTML
Expand Down Expand Up @@ -83,9 +84,14 @@ test('paste plain text with line breaks', (assert) => {
Helpers.dom.setCopyData(MIME_TEXT_PLAIN, ['abc', 'def'].join('\n'));
Helpers.dom.triggerPasteEvent(editor);

assert.hasElement('#editor p:contains(abcabc)', 'pastes the text');
assert.hasElement('#editor p:contains(def)', 'second section is pasted');
assert.equal($('#editor p').length, 2, 'adds a second section');
if (supportsStandardClipboardAPI()) {
assert.hasElement('#editor p:contains(abcabc)', 'pastes the text');
assert.hasElement('#editor p:contains(def)', 'second section is pasted');
assert.equal($('#editor p').length, 2, 'adds a second section');
} else {
assert.hasElement('#editor p:contains(abcabc\ndef)', 'pastes the text');
assert.equal($('#editor p').length, 1, 'adds a second section');
}
});

test('paste plain text with list items', (assert) => {
Expand All @@ -103,8 +109,12 @@ test('paste plain text with list items', (assert) => {
Helpers.dom.setCopyData(MIME_TEXT_PLAIN, ['* abc', '* def'].join('\n'));
Helpers.dom.triggerPasteEvent(editor);

assert.hasElement('#editor p:contains(abcabc)', 'pastes the text');
assert.hasElement('#editor ul li:contains(def)', 'list item is pasted');
if (supportsStandardClipboardAPI()) {
assert.hasElement('#editor p:contains(abcabc)', 'pastes the text');
assert.hasElement('#editor ul li:contains(def)', 'list item is pasted');
} else {
assert.hasElement('#editor p:contains(abc* abc\n* def)', 'pastes the text');
}
});

test('can cut and then paste content', (assert) => {
Expand Down Expand Up @@ -300,10 +310,12 @@ test('copy sets html & text for pasting externally', (assert) => {

Helpers.dom.triggerCopyEvent(editor);

let text = Helpers.dom.getCopyData(MIME_TEXT_PLAIN);
let html = Helpers.dom.getCopyData(MIME_TEXT_HTML);
assert.equal(text, ["heading", "h2 subheader", "The text" ].join('\n'),
'gets plain text');
if (supportsStandardClipboardAPI()) {
let text = Helpers.dom.getCopyData(MIME_TEXT_PLAIN);
assert.equal(text, ["heading", "h2 subheader", "The text" ].join('\n'),
'gets plain text');
}

assert.ok(html.indexOf("<h1>heading") !== -1, 'html has h1');
assert.ok(html.indexOf("<h2>h2 subheader") !== -1, 'html has h2');
Expand Down
6 changes: 6 additions & 0 deletions tests/helpers/browsers.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@ export function supportsSelectionExtend() {
let selection = window.getSelection();
return !!selection.extend;
}

// See http://caniuse.com/#feat=clipboard
// This rules out the Internet Explorers.
export function supportsStandardClipboardAPI() {
return !window.clipboardData;
}
45 changes: 33 additions & 12 deletions tests/helpers/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import KEY_CODES from 'mobiledoc-kit/utils/keycodes';
import { DIRECTION, MODIFIERS } from 'mobiledoc-kit/utils/key';
import { isTextNode } from 'mobiledoc-kit/utils/dom-utils';
import { merge } from 'mobiledoc-kit/utils/merge';
import { supportsStandardClipboardAPI } from './browsers';

// walks DOWN the dom from node to childNodes, returning the element
// for which `conditionFn(element)` is true
Expand Down Expand Up @@ -270,11 +271,17 @@ function triggerLeftArrowKey(editor, modifier) {
// Allows our fake copy and paste events to communicate with each other.
const lastCopyData = {};
function triggerCopyEvent(editor) {
let event = createMockEvent('copy', editor.element, {
clipboardData: {
setData(type, value) { lastCopyData[type] = value; }
}
});
let eventData = {};

if (supportsStandardClipboardAPI()) {
eventData = {
clipboardData: {
setData(type, value) { lastCopyData[type] = value; }
}
};
}

let event = createMockEvent('copy', editor.element, eventData);
editor.triggerEvent(editor.element, 'copy', event);
}

Expand All @@ -288,20 +295,34 @@ function triggerCutEvent(editor) {
}

function triggerPasteEvent(editor) {
let event = createMockEvent('copy', editor.element, {
clipboardData: {
getData(type) { return lastCopyData[type]; }
}
});
let eventData = {};

if (supportsStandardClipboardAPI()) {
eventData = {
clipboardData: {
getData(type) { return lastCopyData[type]; }
}
};
}

let event = createMockEvent('copy', editor.element, eventData);
editor.triggerEvent(editor.element, 'paste', event);
}

function getCopyData(type) {
return lastCopyData[type];
if (supportsStandardClipboardAPI()) {
return lastCopyData[type];
} else {
return window.clipboardData.getData('Text');
}
}

function setCopyData(type, value) {
lastCopyData[type] = value;
if (supportsStandardClipboardAPI()) {
lastCopyData[type] = value;
} else {
window.clipboardData.setData('Text', value);
}
}

function clearCopyData() {
Expand Down