Skip to content
This repository has been archived by the owner on Feb 6, 2023. It is now read-only.

Pasting Rich Text Into Chrome & Firefox on Windows Adds A Space On Either Side #416

Open
maxmatthews opened this issue May 24, 2016 · 21 comments

Comments

@maxmatthews
Copy link

When pasting rich text into Chrome it adds a space on either side of the string (first screenshot). When pasting in any other browser (Safari or Firefox) it behaves fine (second screenshot). It seems that Chrome is using the HTML text and the others are using plain text.
Is there a way to get Chrome to trim the string on paste? Tried using handlePastedText but I'm not sure how to update the state on firing.
Chrome:screen shot 2016-05-24 at 1 03 05 pm
Safari: screen shot 2016-05-24 at 1 04 47 pm

@steviesama
Copy link

I can confirm this.

@aem
Copy link

aem commented May 25, 2016

not sure why this would happen, but this code should work to trim the trailing whitespace assuming your change handler is called onChange:

  handlePastedText(text) {
    const {editorState} = this.state;
    const blockMap = ContentState.createFromText(text.trim()).blockMap;
    const newState = Modifier.replaceWithFragment(editorState.getCurrentContent(), editorState.getSelection(), blockMap);
    this.onChange(EditorState.push(editorState, newState, 'insert-fragment'));
    return true;
  },

@maxmatthews
Copy link
Author

@aem Thanks, your code block fixed the issue. Should I leave this issue open as it's a problem with the DraftJS framework and not just my code or just close it?

@aem
Copy link

aem commented May 25, 2016

@maxmatthews i'm not really a code contributor on this project so I can't say for sure, I would leave it open and wait until next week when the primary contributor returns from vacation (#355)

@maxmatthews
Copy link
Author

Thanks I'll wait until @hellendag returns.

@hellendag
Copy link

What is the source application for the paste?

@smerchek
Copy link

We've seen this issue as well, this time with Chrome on a Windows machine. Will try out the workaround though.

@leejaen
Copy link

leejaen commented Dec 21, 2016

@aem, Thanks, the code works fine.

@spence
Copy link

spence commented Dec 22, 2016

For those using version 0.9.0+ (per to e64c2c3#diff-3e4f7f7129a394d16c3592dd894fd31eL92):

this.state is undefined. It should be:

handlePastedText(text) {
    const {editorState} = this; // this.state is the context
    // ...

Or bind this via:

<Editor handlePastedText={this.handlePastedText.bind(this)} ... />

@aem
Copy link

aem commented Dec 22, 2016

@spence it's been a couple of months since I've worked with Draft, so correct me if I'm wrong, but the location of editorState should be whatever the client wants it to be, no? you could keep the editor state in Redux state and then it would look like const {editorState} = this.props;. I'm guessing your specific use just assigns the new editor state in onChange to this.editorState = editorState instead of using setState or a Redux function

@spence
Copy link

spence commented Dec 22, 2016

@aem I'm following the Overview page which uses this.setState({editorState}). I'm not doing anything special that I can see.

Since the referenced change (and without binding the handlePastedText call) I can't access the resulting component (TestEditor below). handlePastedText is called using the Editor as context.

Here's the full example with bind:

class TestEditor extends React.Component {
  constructor(props) {
    super(props);
    const initialContent = ContentState.createFromText(props.inititalText);
    const editorState = EditorState.createWithContent(initialContent);
    this.state = {editorState: editorState};
    this.onChange = (editorState) => this.setState({editorState});
  }
  handlePastedText(text) {
    const {editorState} = this.state;
    const blockMap = ContentState.createFromText(stripNewLines(text)).blockMap;
    const newState = Modifier.replaceWithFragment(editorState.getCurrentContent(), editorState.getSelection(), blockMap);
    this.onChange(EditorState.push(editorState, newState, 'insert-fragment'));
    return true;
  }
  render() {
    return (
      <Editor editorState={this.state.editorState} onChange={this.onChange}
              handlePastedText={this.handlePastedText.bind(this)} />
    );
  }
}
// ...
<TestEditor inititalText="Test" />

@aem
Copy link

aem commented Dec 22, 2016

ES6 class members aren't auto-bound like they are in React.createClass, implementing members as arrow functions solves this problem:

class TestEditor extends React.Component {
  constructor(props) {
    super(props);
    const initialContent = ContentState.createFromText(props.inititalText);
    const editorState = EditorState.createWithContent(initialContent);
    this.state = {editorState};
  }

  onChange = (editorState) => this.setState({editorState});

  handlePastedText = (text) => {
    const {editorState} = this.state;
    const blockMap = ContentState.createFromText(stripNewLines(text)).blockMap;
    const newState = Modifier.replaceWithFragment(editorState.getCurrentContent(), editorState.getSelection(), blockMap);
    this.onChange(EditorState.push(editorState, newState, 'insert-fragment'));
    return true;
  };

  render() {
    return (
      <Editor editorState={this.state.editorState} onChange={this.onChange}
              handlePastedText={this.handlePastedText} />
    );
  }
}

@khawkinson
Copy link

khawkinson commented Feb 10, 2018

So I'm having the same issue (windows chrome, works fine on mac chrome/ff windows ff), and unfortunately using the custom handlePastedText won't work with html like blah, it will just paste 'blah'.
I've done a little debugging and I'm noticing in the code here:

if (!isOldIE && document.implementation && document.implementation.createHTMLDocument) {
    doc = document.implementation.createHTMLDocument('foo');
    !doc.documentElement ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Missing doc.documentElement') : invariant(false) : void 0;
    doc.documentElement.innerHTML = html;
    root = doc.getElementsByTagName('body')[0];
  }
return root;

root.innerText is different on win/mac chrome.

For mac I see the following:
screen shot 2018-02-09 at 6 54 53 pm

and for windows I'm seeing this:
screen shot 2018-02-09 at 6 55 28 pm

I'm not familiar on why we are using createHTMLDocument nor am I really all that familiar with the document.implementation api. But I thought I'd pass this info along.

Thanks!

@niveditc
Copy link
Contributor

niveditc commented Sep 8, 2018

Hey folks - I can't repro this on the latest Chrome v68 on a Mac. I assume that it got fixed at some point – can anyone on this thread still repro this?

@khawkinson
Copy link

hey @niveditc I can still reproduce this on Windows chrome/ff. Mac Chrome/ff works fine for me.

@niveditc niveditc changed the title Pasting Rich Text Into Chrome (Mac) Adds A Space On Either Side Pasting Rich Text Into Chrome & Firefox on Windows Adds A Space On Either Side Sep 9, 2018
@niveditc
Copy link
Contributor

niveditc commented Sep 9, 2018

Thanks @khawkinson. Still figuring out what the next steps will be for bugs like this one that only repro in certain environments, so just adding the appropriate tags for now.

@Obiwarn
Copy link

Obiwarn commented Jan 10, 2019

How is this still a bug in 2019?
Windows + Chrome is among the most popular combinations of my users.

@crispiestsquid
Copy link

This is still an issue for me using Chrome on Mac and PC. I paste rich text and I have stripPastedStyles to false. All of the formatting is great, but I get an empty leading paragraph block and a trailing space. I tried the workaround with handlePastedText, but it doesn't work in my case because I very much need the rich text formatting. Any help would be greatly appreciated!

@martinkutter
Copy link

martinkutter commented Jun 8, 2020

Hey @crispiestsquid, our team handled this issue by some ugly replaces within the handlePastedText Handler. Maybe it handles your Mac issue too?

Only the part that cleans up the inserted HTML:

/**
 * When pasting text in some cases (e.g. from Word or another editor instance on Windows),
 * an empty space and/or empty lines are inserted, because DraftJS doesn't handle newlines correctly in this instance.
 * This workaround removes newlines before/after the main content to fix the behavior.
 */

const handleMicrosoftNewlines = (html: string): string => {
    const microsoftCopyPasteStartFragment = /^<html[^]*<!--StartFragment-->[^]*?</;
    const microsoftCopyPasteEndFragment = />[^<]*?<!--EndFragment-->[^]*$/;

    const startFragment = microsoftCopyPasteStartFragment.exec(html);
    const cleanedStartFragment = startFragment ? startFragment[0].replace(/>\s+</g, '><') : '';

    const endFragment = microsoftCopyPasteEndFragment.exec(html);
    const cleanedEndFragment = endFragment ? endFragment[0].replace(/>\s+</g, '><') : '';

    return html
        .replace(microsoftCopyPasteStartFragment, cleanedStartFragment)
        .replace(microsoftCopyPasteEndFragment, cleanedEndFragment);
};

@crispiestsquid
Copy link

Hey @crispiestsquid, our team handled this issue by some ugly replaces within the handlePastedText Handler. Maybe it handles your Mac issue too?

Only the part cleans the pasted HTML:

/**
 * When pasting text in some cases (e.g. from Word or another editor instance on Windows),
 * an empty space and/or empty lines are inserted, because DraftJS doesn't handle newlines correctly in this instance.
 * This workaround removes newlines before/after the main content to fix the behavior.
 */

const handleMicrosoftNewlines = (html: string): string => {
    const microsoftCopyPasteStartFragment = /^<html[^]*<!--StartFragment-->[^]*?</;
    const microsoftCopyPasteEndFragment = />[^<]*?<!--EndFragment-->[^]*$/;

    const startFragment = microsoftCopyPasteStartFragment.exec(html);
    const cleanedStartFragment = startFragment ? startFragment[0].replace(/>\s+</g, '><') : '';

    const endFragment = microsoftCopyPasteEndFragment.exec(html);
    const cleanedEndFragment = endFragment ? endFragment[0].replace(/>\s+</g, '><') : '';

    return html
        .replace(microsoftCopyPasteStartFragment, cleanedStartFragment)
        .replace(microsoftCopyPasteEndFragment, cleanedEndFragment);
};

@martinkutter Thanks for the response! So I am trying now to figure out how I should get this new clean HTML into the editor. The method that I am trying is inserting the HTML itself as the text, and not displaying the content as rich text like I need. What method should I be using to get the clean HTML into the editor?

@crispiestsquid
Copy link

@martinkutter Never mind. I actually discovered the solution on my own. I did use your function, so thank you very much again! Leaving my HandlePasted.js file for reference


const HandlePasted = (state, onChange) => ({
    handlePastedText(text, html) {
        const { editorState } = state;
        const blocksFromHTML = convertFromHTML(handleMicrosoftNewlines(html));
        const newState = ContentState.createFromBlockArray(
            blocksFromHTML.contentBlocks,
            blocksFromHTML.entityMap,
        );
        const finalState = Modifier.replaceWithFragment(editorState.getCurrentContent(), editorState.getSelection(), newState.getBlockMap());
        onChange(EditorState.push(editorState, finalState, 'insert-fragment'));
        return 'handled';
    }
});

const handleMicrosoftNewlines = html => {
    const microsoftCopyPasteStartFragment = /^<html[^]*<!--StartFragment-->[^]*?</;
    const microsoftCopyPasteEndFragment = />[^<]*?<!--EndFragment-->[^]*$/;

    const startFragment = microsoftCopyPasteStartFragment.exec(html);
    const cleanedStartFragment = startFragment ? startFragment[0].replace(/>\s+</g, '><') : '';

    const endFragment = microsoftCopyPasteEndFragment.exec(html);
    const cleanedEndFragment = endFragment ? endFragment[0].replace(/>\s+</g, '><') : '';

    return html
        .replace(microsoftCopyPasteStartFragment, cleanedStartFragment)
        .replace(microsoftCopyPasteEndFragment, cleanedEndFragment);
};

export default HandlePasted;

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests