ianstormtaylor / slate

A completely customizable framework for building rich text editors. (Currently in beta.)
http://slatejs.org
MIT License
29.59k stars 3.23k forks source link

repeat render when input chinese directly after the bold text #3695

Open pubuzhixing8 opened 4 years ago

pubuzhixing8 commented 4 years ago

Do you want to request a feature or report a bug?

Bug

What's the current behavior?

repeat-render

repeat rendering input text(first text is rendered by browser default action, last text is rendered by slate)

just heppen when user move cursor from right to left by arrowLeft https://www.slatejs.org/examples/richtext Slate: 0.58.1 Browser: Chrome OS: Mac

What's the expected behavior?

prevent browser default action.

pubuzhixing8 commented 4 years ago

chrome browser think the text should follow previous node when cursor is at end edge for bold text, but slate think it should follow next node

tizee commented 4 years ago

At the first glance, I thought this problem was caused by the Slate's internal selection function. In Safari, the editor's selection's path and anchor is correct after hitting <LeftArrow> that triggers the onDOMSelectionChange.

// editor's selection in Safari
{anchor: {path: [0, 1], offset: 4}, focus: {path: [0, 1], offset: 4}}
// editor's selection in Chrome
{anchor: {path: [0, 2], offset: 0}, focus: {path: [0, 2], offset: 0}}

This is because window.getSelection() has returned different result in Chrome and Safari.

As the selection for Editor.insertText is not correct in Chrome, my workaround is to use setTimeout to wait until the selection is updated by onDOMSelectionChange.

// slate-react/src/components/editable.tsx
...
              if (!IS_SAFARI && !IS_FIREFOX && event.data) {
                const data = event.data
              // insert text in next event cycle
                setTimeout(() => {
                  const { selection } = editor
                  if (selection) {
                    Transforms.setSelection(editor, {
                      anchor: {
                        path: selection.anchor.path,
                        offset: selection.anchor.offset - data.length, 
                      },
                      focus: {
                        path: selection.anchor.path,
                        offset: selection.anchor.offset - data.length,
                      },
                    })
                    Editor.insertText(editor, data)
                  }
            })
...

And it works. I also combined this with #3626 to fix cursor misplacement in Safari. chrome-fixed

kunkunL commented 4 years ago

At the first glance, I thought this problem was caused by the Slate's internal selection function. In Safari, the editor's selection's path and anchor is correct after hitting <LeftArrow> that triggers the onDOMSelectionChange.

// editor's selection in Safari
{anchor: {path: [0, 1], offset: 4}, focus: {path: [0, 1], offset: 4}}
// editor's selection in Chrome
{anchor: {path: [0, 2], offset: 0}, focus: {path: [0, 2], offset: 0}}

This is because window.getSelection() has returned different result in Chrome and Safari.

As the selection for Editor.insertText is not correct in Chrome, my workaround is to use setTimeout to wait until the selection is updated by onDOMSelectionChange.

// slate-react/src/components/editable.tsx
...
              if (!IS_SAFARI && !IS_FIREFOX && event.data) {
                const data = event.data
              // insert text in next event cycle
                setTimeout(() => {
                  const { selection } = editor
                  if (selection) {
                    Transforms.setSelection(editor, {
                      anchor: {
                        path: selection.anchor.path,
                        offset: selection.anchor.offset - data.length, 
                      },
                      focus: {
                        path: selection.anchor.path,
                        offset: selection.anchor.offset - data.length,
                      },
                    })
                    Editor.insertText(editor, data)
                  }
            })
...

And it works. I also combined this with #3626 to fix cursor misplacement in Safari. chrome-fixed

你好,这个方法确实能够解决这个中文输入重复的问题,可是在一段普通文本中先点击加粗,然后再输入文本,此时输入是否会因此导致输入为普通不带格式的文本呢?

akiwong-cn commented 4 years ago

It's contenteditable problem in React. You can insert a '\ufeff' after add or remove mark when cursor is collapsed

pubuzhixing8 commented 4 years ago

@wangqs1990 I write the slate editor by angular ,same problem, so this is a compatibility issue for Chrome

skogsmaskin commented 4 years ago

I get this behavior too, when using Swiftkey keyboard on Android. Other keyboards works fine.

ncqwer commented 4 years ago

In my opinion, this is caused by react reused the previous span and didn't update it's value. Here is the exactly reason:

  1. when you enter chinese chararacters after the decorated span(e.g.span1), the added text will be added to current span(span1) directly because of span's editable=true. However the slate element(e.g.element1) , current span associated with, didn't change anything!
  2. If you finish the keyboard input, Notified by onCompositionEnd,slate-react will create a new Text node with the event's data.And then slate will call editor.onChange to start the react rerender procedure.
  3. when react rerendering, react think element1 didn't change, so react reuse the span1 and no need do anything. As the result, react create the new span(e.g.span2) for element2 and remain span1(assocated with element1). After all you will see the repeat render.

I also meet the problem before, to simplify this problem ,I tried to tell react don't reuse the decorated span ,so I tried this and run away from the problem.

const createAlwaysNewComponent = () => ({ attributes, children }) => (
  <span
    {...attributes}
    css={css`
      color: var(--theme-color-text-lighten, #33333380);
    `}
  >
    {children}
  </span>
);
const DecoratedSpan = ({ attributes, children, leaf }) => {
  const AlwaysNew = createAlwaysNewComponent();
  return <AlwaysNew attributes={attributes}>{children}</AlwaysNew>;
};

在chrome浏览器上,这大概率是由于react复用之前的span元素导致的。具体过程为: 1.在decorated的节点span1后键入中文字符,由于editable=ture的效果,实际的文本会直接增加在当前span元素内部。注意,此时,与之对应的slate element并没有发生任何变化。 2.在确定输入后,slate通过onCompositionEnd事件知道键入内容并创立了一个新的span2,并将更新内容同步到slate value上。 3.根据react框架,span1对应的slate element并没有发生变化,故react不会更新span1(这导致前一段文本没有删除),而span2作为新增节点,react创建并绘制了新的节点。 综上,这导致了重复文本现象。

hdsuperman commented 3 years ago

@ncqwer 试了还是不行啊~