Skip to content

Commit

Permalink
[CLEANUP] DRY copy/paste and drop event handling. Add editor#serializeTo
Browse files Browse the repository at this point in the history
  * Remove now-unused `supportsStandardClipboardAPI` test helper
  * Add `editor#serializeTo(format)`
  * Add `editor#serializePost(post, format)`
  * Add deprecate helper
  * Add `post#trimTo(range)` and deprecate `post#cloneRange`.
   `cloneRange` returns a mobiledoc, which is confusing.
  * Enable all copy-paste acceptance tests in IE. They all now use
    the copy/paste mocks from the test helpers.
  * Add an IE-compatibility test for `getContentFromPasteEvent` to
    ensure it will use `window.clipboardData`
  * Add an IE-compatibility test for `setClipboardData` to
    ensure it will use `window.clipboardData`
  • Loading branch information
bantic committed Mar 24, 2016
1 parent 0f6d232 commit 8a1ae77
Show file tree
Hide file tree
Showing 11 changed files with 353 additions and 206 deletions.
55 changes: 54 additions & 1 deletion src/js/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import { MOBILEDOC_VERSION } from 'mobiledoc-kit/renderers/mobiledoc';
import EditHistory from 'mobiledoc-kit/editor/edit-history';
import EventManager from 'mobiledoc-kit/editor/event-manager';
import EditState from 'mobiledoc-kit/editor/edit-state';
import HTMLRenderer from 'mobiledoc-html-renderer';
import TextRenderer from 'mobiledoc-text-renderer';
import Logger from 'mobiledoc-kit/utils/logger';
let log = Logger.for('editor'); /* jshint ignore:line */

Expand Down Expand Up @@ -441,8 +443,59 @@ class Editor {
return this.activeMarkups;
}

/**
* @public
* @param {string} version The mobiledoc version to serialize to.
* @return {Object} Serialized mobiledoc
*/
serialize(version=MOBILEDOC_VERSION) {
return mobiledocRenderers.render(this.post, version);
return this.serializePost(this.post, 'mobiledoc', {version});
}

/**
* @public
* Note that only mobiledoc format is lossless. If cards or atoms are present
* in the post, the html and text formats will omit them in output because
* the editor does not have access to the html and text versions of the
* cards/atoms.
* @param {string} format The format to serialize ('mobiledoc', 'text', 'html')
* @return {Object|String} The editor's post, serialized to {format}
*/
serializeTo(format) {
let post = this.post;
return this.serializePost(post, format);
}

/**
* @param {Post}
* @param {String} format Same as {serializeTo}
* @param {[Object]} version to serialize to (default: MOBILEDOC_VERSION}
* @return {Object|String}
*/
serializePost(post, format, options={}) {
const validFormats = ['mobiledoc', 'html', 'text'];
assert(`Unrecognized serialiation format ${format}`,
contains(validFormats, format));

if (format === 'mobiledoc') {
let version = options.version || MOBILEDOC_VERSION;
return mobiledocRenderers.render(post, version);
} else {
let rendered;
let mobiledoc = this.serializePost(post, 'mobiledoc');
let unknownCardHandler = () => {};
let unknownAtomHandler = () => {};
let rendererOptions = { unknownCardHandler, unknownAtomHandler };

switch (format) {
case 'html':
rendered = new HTMLRenderer(rendererOptions).render(mobiledoc);
return rendered.result;
case 'text':
rendered = new TextRenderer(rendererOptions).render(mobiledoc);
return rendered.result;
}
}
}

removeAllViews() {
Expand Down
20 changes: 14 additions & 6 deletions src/js/editor/event-manager.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import assert from 'mobiledoc-kit/utils/assert';
import {
parsePostFromPaste,
setClipboardCopyData,
setClipboardData,
parsePostFromDrop
} from 'mobiledoc-kit/utils/parse-utils';
import Range from 'mobiledoc-kit/utils/cursor/range';
Expand Down Expand Up @@ -144,13 +144,25 @@ export default class EventManager {
}

cut(event) {
event.preventDefault();

this.copy(event);
this.editor.handleDeletion();
}

copy(event) {
event.preventDefault();
setClipboardCopyData(event, this.editor);

let { editor, editor: { range, post } } = this;
post = post.trimTo(range);

let data = {
html: editor.serializePost(post, 'html'),
text: editor.serializePost(post, 'text'),
mobiledoc: editor.serializePost(post, 'mobiledoc')
};

setClipboardData(event, data, window);
}

paste(event) {
Expand All @@ -159,10 +171,6 @@ export default class EventManager {
let { editor } = this;
let range = editor.range;

// FIXME this can go, it will be handled by insertPost
if (range.head.section.isCardSection) {
return;
}
if (!range.isCollapsed) {
editor.handleDeletion();
}
Expand Down
15 changes: 12 additions & 3 deletions src/js/models/post.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Set from 'mobiledoc-kit/utils/set';
import mobiledocRenderers from 'mobiledoc-kit/renderers/mobiledoc';
import Range from 'mobiledoc-kit/utils/cursor/range';
import Position from 'mobiledoc-kit/utils/cursor/position';
import deprecate from 'mobiledoc-kit/utils/deprecate';

export default class Post {
constructor() {
Expand Down Expand Up @@ -223,10 +224,18 @@ export default class Post {
}

/**
* @param {Range} range
* @return {Mobiledoc} A mobiledoc representation of the range (JSON)
* @deprecated
*/
cloneRange(range) {
deprecate('post#cloneRange is deprecated. See post#trimTo(range) and editor#serializePost');
return mobiledocRenderers.render(this.trimTo(range));
}

/**
* @param {Range} range
* @return {Post} A new post, constrained to {range}
*/
trimTo(range) {
const post = this.builder.createPost();
const { builder } = this;

Expand Down Expand Up @@ -263,6 +272,6 @@ export default class Post {
sectionParent.sections.append(newSection);
}
});
return mobiledocRenderers.render(post);
return post;
}
}
3 changes: 3 additions & 0 deletions src/js/utils/deprecate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function deprecate(message) {
console.log(`DEPRECATED: ${message}`); // jshint ignore:line
}
132 changes: 65 additions & 67 deletions src/js/utils/parse-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
import mobiledocParsers from '../parsers/mobiledoc';
import HTMLParser from '../parsers/html';
import TextParser from '../parsers/text';
import HTMLRenderer from 'mobiledoc-html-renderer';
import TextRenderer from 'mobiledoc-text-renderer';
import Logger from 'mobiledoc-kit/utils/logger';

export const MIME_TEXT_PLAIN = 'text/plain';
Expand All @@ -13,6 +11,9 @@ export const NONSTANDARD_IE_TEXT_TYPE = 'Text';
const log = Logger.for('parse-utils');
const MOBILEDOC_REGEX = new RegExp(/data\-mobiledoc='(.*?)'>/);

/**
* @return {Post}
*/
function parsePostFromHTML(html, builder, plugins) {
let post;

Expand All @@ -27,36 +28,26 @@ function parsePostFromHTML(html, builder, plugins) {
return post;
}

/**
* @return {Post}
*/
function parsePostFromText(text, builder, plugins) {
let parser = new TextParser(builder, {plugins});
let post = parser.parse(text);
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(NONSTANDARD_IE_TEXT_TYPE, html);
}
}
/**
* @return {{html: String, text: String}}
*/
export function getContentFromPasteEvent(event, window) {
let html = '', text = '';

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

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);
}
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
Expand All @@ -68,67 +59,74 @@ function getClipboardData(clipboardData) {
}

/**
* @param {Event} copyEvent
* @param {Editor}
* @return null
* @return {{html: String, text: String}}
*/
export function setClipboardCopyData(copyEvent, editor) {
const { range, post } = editor;
function getContentFromDropEvent(event) {
let html = '', text = '';

const mobiledoc = post.cloneRange(range);
try {
html = event.dataTransfer.getData(MIME_TEXT_HTML);
text = event.dataTransfer.getData(MIME_TEXT_PLAIN);
} catch (e) {
// FIXME IE11 does not include any data in the 'text/html' or 'text/plain'
// mimetypes. It throws an error 'Invalid argument' when attempting to read
// these properties.
log('Error getting drop data: ', e);
}

return { html, text };
}

const unknownCardHandler = () => {}; // ignore unknown cards
const unknownAtomHandler = () => {}; // ignore unknown atoms
const {result: innerHTML} =
new HTMLRenderer({unknownCardHandler, unknownAtomHandler}).render(mobiledoc);
/**
* @param {CopyEvent|CutEvent}
* @param {Editor}
* @param {Window}
*/
export function setClipboardData(event, {mobiledoc, html, text}, window) {
if (mobiledoc && html) {
html = `<div data-mobiledoc='${JSON.stringify(mobiledoc)}'>${html}</div>`;
}

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

setClipboardData(copyEvent.clipboardData, html, plain);
if (clipboardData && clipboardData.setData) {
clipboardData.setData(MIME_TEXT_HTML, html);
clipboardData.setData(MIME_TEXT_PLAIN, text);
} else if (nonstandardClipboardData && nonstandardClipboardData.setData) {
// 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.
nonstandardClipboardData.setData(NONSTANDARD_IE_TEXT_TYPE, html);
}
}

/**
* @param {Event} pasteEvent
* @param {PostNodeBuilder} builder
* @param {Array} plugins parser plugins
* @param {PasteEvent}
* @param {{builder: Builder, _parserPlugins: Array}} options
* @return {Post}
*/
export function parsePostFromPaste(pasteEvent, {builder, _parserPlugins: plugins}) {
let post;
let { html, text } = getContentFromPasteEvent(pasteEvent, window);

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);
if (html && html.length) {
return parsePostFromHTML(html, builder, plugins);
} else if (text && text.length) {
return parsePostFromText(text, builder, plugins);
}

return post;
}

/**
* @param {DropEvent}
* @param {{builder: Builder, _parserPlugins: Array}} options
* @return {Post}
*/
export function parsePostFromDrop(dropEvent, {builder, _parserPlugins: plugins}) {
let post;
let { html, text } = getContentFromDropEvent(dropEvent);

let html, text;
try {
html = dropEvent.dataTransfer.getData('text/html');
text = dropEvent.dataTransfer.getData('text/plain');
} catch (e) {
// FIXME IE11 does not include any data in the 'text/html' or 'text/plain'
// mimetypes. It throws an error 'Invalid argument' when attempting to read
// these properties.
log('Error getting drop data: ', e);
return;
}

if (html && html.length > 0) {
post = parsePostFromHTML(html, builder, plugins);
} else if (text && text.length > 0) {
post = parsePostFromText(text, builder, plugins);
if (html && html.length) {
return parsePostFromHTML(html, builder, plugins);
} else if (text && text.length) {
return parsePostFromText(text, builder, plugins);
}

return post;
}
Loading

0 comments on commit 8a1ae77

Please sign in to comment.