Skip to content

Commit

Permalink
Merge pull request #330 from YoranBrondsema/yb-fix-bug-329
Browse files Browse the repository at this point in the history
Fix bug #329: Copy-pasting does not work on IE11
  • Loading branch information
bantic committed Feb 19, 2016
2 parents 59a1a3b + f6307ea commit 5ef7d08
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 33 deletions.
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

0 comments on commit 5ef7d08

Please sign in to comment.