RD17 / react-trumbowyg

React wrapper for lightweight WYSIWYG editor Trumbowyg
MIT License
146 stars 21 forks source link

Updates and the Cursor #1

Open acomito opened 7 years ago

acomito commented 7 years ago

I'm using this to build a collaborative editor. I am saving the document on every onChange that fires. This is all working well, when user #1 makes a change, it shows up on user #2's screen instantly (and vice versa). It's working great albeit one problem: whenever the component updates with new text, the cursor is sent back to the front of the input:

http://g.recordit.co/j9VeyvM0ka.gif

Any idea how to avoid this but still have the text/contents update? I'll post an example repo shortly...

acomito commented 7 years ago

So, this is only happening when html is being injected as the value, but works fine when the value={} is receiving just a string:

http://g.recordit.co/HRoovYVz1K.gif

Here's an example repo: https://github.com/acomito/collab-editor

sochix commented 7 years ago

I have had the same problem. I decided to remove onChange for every change and fire it only when user press a Save button. But it can be applied to your situation as I understand. So, we need to find a way to move cursor inside a text box after every onChange. I recommend you to look into trymbowyg component. In mean time, I'll explore possibilities to move cursor via jQuery.

acomito commented 7 years ago

Thanks @sochix. If it's helpful, I found it the same example app worked fine with react-medium-editor even with html as the value. I haven't had a chance to dig in and compare react-medium-editor and react-trumbowyg to see how they differ in handling updates.

Alex-D commented 7 years ago

Yop ! I'm the Trumbowyg's creator. I think it's due to Trumbowyg core that didn't restore cursor position after updating the HTML content.

sochix commented 7 years ago

The problem with cursor is deep inside trymbowyg core. So need time to investogate further

acomito commented 7 years ago

Okay thanks for checking it out @sochix

bhattjay commented 7 years ago

Hey, @sochix @Alex-D Any updates on this issue?

Alex-D commented 7 years ago

Nope. I don't have the time to work on Trumbowyg for now.

sochix commented 7 years ago

@bhattjay I explored it a bit. The problem is deep inside the core of Trumbowyg.

dmarkrollins commented 6 years ago

So on edit I populate the data props from incoming container component props and init my internal state value.

On change I update the internal state value with the rich text (html) value - which does nothing to the cursor position since the data value is originally coming from props and state updates have no impact.

When user hits save I take the value from state, since that is always up to date due to being sync'd with the onChanged event, and use that to update the DB.

I am not sure if this is what @acomito meant but it works pretty well so far.

azizali commented 6 years ago

Any update on this?

azizali commented 6 years ago

Hey @acomito what did you end up doing. This is the best editor I found but this bug makes it unusable. I am looking for rich text editing plus View Source feature

azizali commented 6 years ago

@Alex-D & @sochix If you can give me some hints on where to look for this cursor issue, I would be happy to look into it

tmoody460 commented 5 years ago

I managed an ugly workaround for this (which I'm hiding in this wrapper): Tl;Dr => Use state to only trigger the editor to change if you modify the value outside the editor.

export interface WsyiwigTextAreaProps {
    value: string;
    onChange(newValue: string): void;
}

export interface WsyiwigTextAreaState {
    value: string;
}
export class WsyiwigTextArea extends React.Component<WsyiwigTextAreaProps, WsyiwigTextAreaState> {
    public constructor(props: WsyiwigTextAreaProps) {
        super(props);
        this.state = {
            value: props.value
        };
    }

    public componentWillReceiveProps(props: WsyiwigTextAreaProps) {
        // We only want to update state if the external props are different from what we have in the editor. Calling the onchange method inside the editor component causes the cursor to be reset
        if (this.props.value != props.value && props.value != $("#react-trumbowyg")[0].innerHTML) {
            this.setState({
                value: props.value
            });
        }
    }

    private handleChange = (e: any): void => {
        this.props.onChange(e.target.innerHTML);
    }

    public render() {
        let buttons = ['formatting', 'strong', 'em', 'del', 'unorderedList', 'orderedList', 'removeformat'];
        return (
            <>
                <Trumbowyg id='react-trumbowyg' data={this.state.value} onChange={this.handleChange} buttons={buttons} />
            </>
        );
    }
}
McTano commented 4 years ago

@tmoody460 I think this is a good workaround and it should be implemented inside react-trumbowyg somehow.

AMoldskred commented 4 years ago

I also had this issue and this was my solution:

const Comment: React.FC<Props> = () => {
  const [newComment, handleNewComment] = useState();
  const handleCommentChange = (e: SyntheticEvent<HTMLBaseElement>): void => {
        const nodeList = e.currentTarget.childNodes;
        handleNewComment(
            Array.from(nodeList).reduce((content: string, node: any) => {
                if (node.nodeType === 1) {
                    return content + node.outerHTML;
                }
                if (node.nodeType === 3) {
                    return `${content}<p>${node.textContent}</p>`;
                }
                return content;
            }, "")
        );
   };
  return (
    <Trumbowyg
       autogrow
       data={!newComment ? "" : undefined} // <- This is the important part
       buttons={["btnGrp-semantic", ["undo", "redo"], ["link"]]}
       placeholder={staticFormatMessage(messages.commentPlaceholder)}
       name="comment"
       onChange={handleCommentChange}
       onPaste={handleCommentChange}
       onFocus={() => setActive(true)}
   />
  )
}

The important part is in the data-prop

evg1n commented 4 years ago

@andreas0607 I don't understand what we achieve by assigning data prop's value "" or undefined. You can achieve the same thing by just passing data={""}. Then extract the value inside Trumbowyg textarea using state (event.target.innerText). Please see live Example.

Cheers.

AMoldskred commented 4 years ago

@evg1n I don't remember exactly why I used this solution. In hindsight I should've written a reason for doing it that way, BUT what I imagine was the reason was probably mutations of the parent component causing rerenders of trumbowyg. When I check if the newComment exists I am disabling the data-prop by setting it to undefined, which in turn stops trumbowyg from reading that the data has updated (which it hasn't, but is implied by a rerender). When I want to reset the field I just use my handleNewComment and set the value to undefined which resets the data-field. In your live example, this wouldn't be an issue. Because you don't have parents causing rerendering of children. Obviously, my solution is hacky. But if you get the same problem I had, then it is one possible solution.