slab / quill

Quill is a modern WYSIWYG editor built for compatibility and extensibility
https://quilljs.com
BSD 3-Clause "New" or "Revised" License
43.69k stars 3.4k forks source link

Pasting in content causes scroll to "jump" before going back to original position #1374

Closed sferoze closed 6 months ago

sferoze commented 7 years ago

The Quill editor I am using auto grows as the user types. I also have multiple notes (Quill editors) on the screen at a time. I am using the scrollingContainer option correctly.

After pasting in content, Quill does resume the original scroll position but only after the screen flickers a bit. (This is because the scroll jumps to another location and then it set back right away, which causes the screen to "jump" or "flicker")

On mobile it is even worse, as the jump it very noticeable.

So the original scroll position is resumed but it looks very janky. Users will feel like the app is not solid with this side effect. Any advice on how to prevent this?

Steps for Reproduction

  1. Visit the quill demo page with auto grow and scrolling container
  2. Try pasting in content into the middle of the document. Try both on desktop and mobile
  3. You will notice the scroll area jump or flicker but then go back to it's original position.

Expected behavior:

When pasting in content, there should be no flicker, jumping, flashes or what not. It should be stable and just accept the content like pasting in Word, or any other text editing program.

Actual behavior:

The screen jumps or flashes when pasting in content, but after it resumes its original scroll position. The issue here is the flash or jump which makes the app feel janky.

Platforms:

Chrome and Safari

Version:

1.2.2

sferoze commented 7 years ago

@jhchen I'm narrowing in on the culprit....

In the onPaste function below the line this.container.focus(); is what causes the scroll to jump.

If I comment out that line the scroll does not jump anymore, but also after pasting in content, the cursor is still at the beginning of the pasted content, and not at the end.

onPaste(e) {
    if (e.defaultPrevented || !this.quill.isEnabled()) return;
    let range = this.quill.getSelection();
    let delta = new Delta().retain(range.index);
    let scrollTop = this.quill.scrollingContainer.scrollTop;
    this.container.focus(); // THIS LINE CAUSES SCROLL JUMPING
    setTimeout(() => {
      this.quill.selection.update(Quill.sources.SILENT);
      delta = delta.concat(this.convert()).delete(range.length);
      this.quill.updateContents(delta, Quill.sources.USER);
      // range.length contributes to delta.length()
      this.quill.setSelection(delta.length() - range.length, Quill.sources.SILENT);
      this.quill.scrollingContainer.scrollTop = scrollTop;
      this.quill.selection.scrollIntoView();
    }, 1);
  }
sferoze commented 7 years ago

@jhchen Good news, I fixed it! I think this fix should make it into the next update.

Here is the code I added. The code goes into the Clipboard Module.

I add code in 2 spots, in the constructor and in the onPaste function. Here is the constructor

class Clipboard extends Module {
  constructor(quill, options) {
    super(quill, options);
    this.quill.root.addEventListener('paste', this.onPaste.bind(this));
    this.container = this.quill.addContainer('ql-clipboard');
    // NEW CODE BELOW
    this.container.addEventListener('scroll', (e) => {
      e.preventDefault();
      e.stopPropagation();
      if (this.scrollTop) {
        this.quill.scrollingContainer.scrollTop = this.scrollTop
      }
      return false;
    });
    // END OF NEW CODE
    this.container.setAttribute('contenteditable', true);
    this.container.setAttribute('tabindex', -1);
    this.matchers = [];
    CLIPBOARD_CONFIG.concat(this.options.matchers).forEach((pair) => {
      this.addMatcher(...pair);
    });
  }

and the onPaste function

onPaste(e) {
    if (e.defaultPrevented || !this.quill.isEnabled()) return;
    let range = this.quill.getSelection();
    let delta = new Delta().retain(range.index);
    let scrollTop = this.quill.scrollingContainer.scrollTop;
    // NEW CODE BELOW... JUST THE ONE LINE
    this.scrollTop = scrollTop;
    // END OF NEW CODE
    this.container.focus();
    setTimeout(() => {
      this.quill.selection.update(Quill.sources.SILENT);
      delta = delta.concat(this.convert()).delete(range.length);
      this.quill.updateContents(delta, Quill.sources.USER);
      // range.length contributes to delta.length()
      this.quill.setSelection(delta.length() - range.length, Quill.sources.SILENT);
      this.quill.scrollingContainer.scrollTop = scrollTop;
      this.scrollTop = null;
      this.quill.selection.scrollIntoView();
    }, 1);
  }

As you can see I added a function to hook into the scroll event of the ql-clipboard element. When the clipboard element is focused, it calls the scroll event... and in the scroll event function I set the scrollingContainer to be the saved scroll position. This works to keep the scroll position stable while pasting.

Now, everytime I paste content, the scroll is completely stable. No jumping around or anything of the sort. Please let me know if you see any potential side effects, otherwise I recommend adding this change ASAP as the scroll jumping bug is pretty bad.

deepakjtrigent commented 7 years ago

@jhchen Found a solution which does not require scroll position manipulation.

The problem is focusing on clipboard which is located below the editor. Hence I absolute positioned the clipboard with height and width 100%, top and left 0 and made its z-index=-1.

This would place the clipboard behind the editor ensuring the focus on which does not scroll editor.

Apply the below CSS to ql-clipboard

.ql-clipboard {
    height: 100%;
    width: 100%;
    position: absolute;
    left: 0;
    top: 0;
    z-index: -1;
}

Also remove the scroll position manipulation on the onPaste event.

onPaste(e: any) {
    if (e.defaultPrevented || !this.quill.isEnabled()) return;
    let range = this.quill.getSelection();
    let delta = new Delta().retain(range.index);
    this.container.focus();
    setTimeout(() => {
      this.quill.selection.update(Quill.sources.SILENT);
      delta = delta.concat(this.convert()).delete(range.length);
      this.quill.updateContents(delta, Quill.sources.USER);
      this.quill.setSelection(delta.length() - range.length, Quill.sources.SILENT);
      this.quill.selection.scrollIntoView();
    }, 1);
  }

Please keep me in the loop if this is affecting anything else. Would be happy if the jumping issue is fixed which is persisting from a very long time.

sferoze commented 7 years ago

@deepakjtrigent I just tried this out and it does not solve the issue on mobile devices...

And if you comment out the scroll position manipulation the scroll position jumps to a random spot on mobile.

If we keep the scroll position manipulation the scroll position jumps real quick and then return to the scroll position on mobile.

deepakjtrigent commented 7 years ago

@sferoze Ok. I had just tried it on desktop, not on mobile.

jhchen commented 7 years ago

@sferoze thank you for digging into this and apologies for the delay. The volume of issues and notifications from random people poking on the internet unfortunately is such that that legitimate ones overlooked.

I tried out your suggested code and the jumping still persists on an iPad running Safari 10. Is it consistently working for you?

sferoze commented 7 years ago

@jhchen no it is not working on mobile for me.

The code I added does fix for desktop but does not fully fix the issue on mobile. Thanks!

jhchen commented 7 years ago

I don't have any new ideas on how to fix this on mobile. The two approaches are generally to avoid the flicker (which I don't think is possible at the moment) or to correct for it. The latter is the current approach but for whatever reason desktop browser it is not noticeable (perhaps performance).

sferoze commented 7 years ago

@jhchen

There is a way to prevent the flicker on desktop. I still notice the flicker on desktop with the current version of Quill. I have to use my own custom edited version with this code added to the contructor of Clipboard module

    _this.container.addEventListener('focus', function (e) {
      e.preventDefault();
      e.stopPropagation();
      if (_this.scrollTop) {
        _this.quill.scrollingContainer.scrollTop = _this.scrollTop;
      }
      return false;
    });
    _this.container.addEventListener('scroll', function (e) {
      e.preventDefault();
      e.stopPropagation();
      if (_this.scrollTop) {
        _this.quill.scrollingContainer.scrollTop = _this.scrollTop;
      }
      return false;
    });

_this.scrollTop is the saved scrolltop position from the onpaste function.

If you add this code it will improve stability of quill on paste.

guillaumepotier commented 7 years ago

Hi there,

What is the status of this issue? Do you think a fix for desktop could reach next version even if no satisfying one for mobile found?

Thanks

VioletPixel commented 7 years ago

In 1.3.0, I fixed this on both desktop (Safari 10, macOS 10.12) and mobile (Safari, iOS 10.3) by modifying the Clipboard module's onPaste() function. I added this:

this.container.style.position = 'fixed';
this.container.style.zIndex = '-1';

Immediately prior to this:

this.container.focus();

Which makes sure the paste destination is both in view (position: fixed) and not visible (z-index: -1), so no scrolling is required.

jhchen commented 7 years ago

TLDR: No proposed solution seems to work better than the current one so this is still an open issue.

Quill's paste works by adding a paste listener and shifting focus to another contenteditable container before the paste happens, and shifting focus back to the real editor after paste. The pasted content is then analyzed and inserted into Quill through standard APIs. This has two benefits:

  1. The pasted content is isolated and cannot trash Quill's editor contents in unpredictable ways as the only way it gets there is through standard APIs.
  2. More importantly, the browser fully performs a paste, which contains extra content and context that is not available by just looking at the paste event object. We tried relying on just the event object in the past and reverted for this reason.

The focus back onto Quill causes it to scroll to the top. You can try in this demo: https://jsfiddle.net/p2pgrxdc/. Scroll the blue div down and paste some text into it. Observe it scrolls to the top. I am unaware of any way to prevent this. If there is a way that would be key to solving this issue.

So instead what Quill does is save the scroll position before the focus and restores it after. This causes a flicker on slower devices, like mobile. Personally on desktop I do not see any flicker but in theory this is perhaps I use a faster machine. Nevertheless for this reason it does for me make this issue harder to debug so I can only read the code and reason about it.

Unless I am misunderstanding the code, the suggestions by @sferoze still save and restore scroll positions and would still induce the flicker. Indeed this would explain why it still manifests on mobile. The preventDefaults do not appear to do anything but I may be missing something here. Otherwise, because the flicker is so fast on desktop to a point it is indiscernible on many environments, I would be reluctant in this situation to make core changes based off anecdotal suggestions alone.

sferoze commented 7 years ago

@jhchen

The preventDefaults might not be doing anything but...

_this.container.addEventListener('focus', function (e) {

_this.container.addEventListener('scroll', function (e) {

aren't these even listeners earlier hooks to restore scroll? For me, the scroll jumps on desktop with original Quill, but when I added those lines there is no jumping on desktop

jhchen commented 7 years ago

Hmm @sferoze can you try to place the line this.quill.scrollingContainer.scrollTop = scrollTop; first in the setTimeout block:

setTimeout(() => {
      this.quill.scrollingContainer.scrollTop = scrollTop;
      delta = delta.concat(this.convert()).delete(range.length);
      this.quill.updateContents(delta, Quill.sources.USER);
      // range.length contributes to delta.length()
      this.quill.setSelection(delta.length() - range.length, Quill.sources.SILENT);
      this.quill.focus();
    }, 1);

I believe this would make the restore run as soon as it possibly can.

sferoze commented 7 years ago

@jhchen

I tried saving the scroll position in the setTimeout like you mentioned, instead of right before it.

No difference on desktop, but on mobile the jump is still visible. Saving the scroll position in the setTimeout actually makes the mobile jump a bit worse. So for now my custom fix remains the same.

I also tried @ValueBerry css fix and it does not work on mobile in my testing on iOS 10.3

still an open issue, and for now the code I posted in my testing does the best job of fixing destop, and making mobile jump as small as possible.

sferoze commented 7 years ago

@jhchen why can we not set the scrolling container to window

$(window).scrollTop() always returns the proper scroll.

on chrome on mac $('body').scrollTop() has the scroll and on windows $('html').scrollTop() has the scroll.

different browsers put the scroll on body or html

but window has the scroll on all browsers.

jhchen commented 7 years ago

why can we not set the scrolling container to window

Not sure what this means. Quill also does not use jQuery and cannot add it as a project dependency but now sure if the above misunderstanding would have clarified this example

sferoze commented 7 years ago

Ah let me clarify

https://quilljs.com/docs/configuration/#scrollingcontainer

For the scrolling container option:

scrollingContainer

Default: null

DOM Element or a CSS selector for a DOM Element, specifying which container has the scrollbars

I was hoping to be able to pass window in as the scrolling container.

jhchen commented 7 years ago

Not sure window is a DOM element but it is not the entity that has the scrollbars.

magicdvd commented 7 years ago

how about the status of this issue? It is a big defect.

gnemtsov commented 7 years ago

For me all problems with scroll jumps were gone, after I had set scrollingContainer: document.documentElement

DmitrySkripkin commented 6 years ago

Another workaround. I added eventListener to scrollingContainer and move .ql-clipboard so it can be focused without scroll. This works for desktop and mobile. Maybe I didn't test it enough, but looks like 100% working solution.

jhchen commented 6 years ago

@DmitrySkripkin Can you elaborate on your solution? Not sure I understand the adding eventListener part or the moving .ql-clipboard

jrluiscarvalho commented 6 years ago

Hello everyone!

I`ve had a "scroll jump" problem when i select a text with more of two paragraphs in mobile devices, android and IOS. I tried configuring "scrollingContainer" but not works. Any one have ideas for this solution?

thomasdao commented 6 years ago

Hi @jhchen @sferoze if we put the paste content editable as child of the container, then there's no scroll jumping when the container is re-focused. I modified your original jsfiddle here https://jsfiddle.net/p2pgrxdc/6/, when I paste content into #b element, there is no scrolling. Could this be the solution to the problem?

thomasdao commented 6 years ago

However when I've tried putting the paste contenteditable (.ql-clipboard) inside ql-editor, Quill trigger error. I guess if we need to go with that solution it would probably need to modify Quill core...

Edza commented 6 years ago

Extra problem:

Quill is in an iframe. On paste, the whole page (outside of the iframe) gets scrolled HORIZONTALLY to left. Not even sure what to do.

DeanBDean commented 6 years ago

@sferoze @jhchen We are also experiencing this issue. I have modified the jsfiddle as well, and it seems to work for me in the browser as well as on iOS.

https://jsfiddle.net/DeanBDean/xLtzde6w/7/

If this solution is acceptable, I have no problem opening a PR on it.

thomasdao commented 6 years ago

For anyone interested in a work around this issue, I override the Clipboard module to accept only plain text. I guess that is still better in term of UX rather than letting the screen jump. Some code is below:

import Quill from 'quill'
import Delta from 'quill-delta'

const Clipboard = Quill.import('modules/clipboard')

class PlainTextClipboard extends Clipboard {
  onPaste (e) {
    if (e.defaultPrevented || !this.quill.isEnabled()) return
    let range = this.quill.getSelection()
    let delta = new Delta().retain(range.index)

    if (e && e.clipboardData && e.clipboardData.types && e.clipboardData.getData) {
      let text = (e.originalEvent || e).clipboardData.getData('text/plain')
      let cleanedText = this.convert(text)

      // Stop the data from actually being pasted
      e.stopPropagation()
      e.preventDefault()

      // Process cleaned text
      delta = delta.concat(cleanedText).delete(range.length)
      this.quill.updateContents(delta, Quill.sources.USER)
      // range.length contributes to delta.length()
      this.quill.setSelection(delta.length() - range.length, Quill.sources.SILENT)

      return false
    }
  }
}

Quill.register('modules/clipboard', PlainTextClipboard)
DaveDurg commented 6 years ago

Here is the fix I applied which may only work in my scenario, I'm letting the Quill editor auto grow to whatever height its content needs and i'm using the body as the scrolling container so no inner DIV to handle the scrolling.

When initializing the editor, set the scrollingContainer to 'html, body':

, scrollingContainer: 'html, body'

and in the onPaste function add the line in bold:

onPaste(e) { if (e.defaultPrevented || !this.quill.isEnabled()) return; let range = this.quill.getSelection(); let delta = new Delta().retain(range.index); let scrollTop = this.quill.scrollingContainer.scrollTop; this.container.style.top = (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0).toString() + 'px' this.container.focus(); setTimeout(() => { this.quill.selection.update(Quill.sources.SILENT); delta = delta.concat(this.convert()).delete(range.length); this.quill.updateContents(delta, Quill.sources.USER); // range.length contributes to delta.length() this.quill.setSelection(delta.length() - range.length, Quill.sources.SILENT); this.quill.scrollingContainer.scrollTop = scrollTop; this.quill.selection.scrollIntoView(); }, 1); }

ZhangCD1919 commented 6 years ago

I'm using this editor with version is 1.3.6 in my project too, and my users found this problem, here is the fix I applied.

First, I try to find why does this question comes up. I found the process of handle 'onpaste' goes through two 'focus'.

The first is in the 'onpaste' function: this.container.focus(); The second is in the 'setNativeRange' function: if (!this.hasFocus()) this.root.focus();

After I found these two places, I found 'this.container' is a contenteditable div which is the supporter of pasting texts. In css file I found it's position is absolute and it's top is 50%; So it's the jump problem's first reason. After you paste something, this div will be focused, and it's position of top is 50%(center), so, the page will scroll to center.

After that, I found 'this.root' is the whole editor div. If 'this.root' wants to focus, the page will scroll to top ,which is the position of ‘this.root’ at. Absolutely we do not want this. So let's see what 'this.hasFocus()' judges.

In the function 'hasFocus', I found these codes: function hasFocus() { return document.activeElement === this.root; } After the handles 'onpaste' function has done, until now, 'document.activeElement' is 'this.container', because 'this.container' was just be focused. So, the judge is absolutely false. then, the focus in the second will be executed. Page focus on the editor div, scroll to the top.

On the basis of the above analysis,I made two adjustments to the code. One: change the 'position' of 'this.container' to 'fixed', and 'top' to '14px', let it stay in the viewport forever. When it be focused will not change the scroll of page. Two: change the judge of setNativeRange, when 'this.container' is the activeElement, do not execute this.root.focus().

here is my changes:

css file .ql-clipboard { left: -100000px; height: 1px; overflow-y: hidden; position: fixed; // changed top: 14px; // changed }

js file ` { key: 'setNativeRange', value: function setNativeRange(startNode, startOffset) { var endNode = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : startNode; var endOffset = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : startOffset; var force = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;

  debug.info('setNativeRange', startNode, startOffset, endNode, endOffset);
  if (startNode != null && (this.root.parentNode == null || startNode.parentNode == null || endNode.parentNode == null)) {
    return;
  }
  var selection = document.getSelection();
  if (selection == null) return;
  if (startNode != null) {
    if (!this.hasFocus() && !document.activeElement) this.root.focus();  // changed
    var native = (this.getNativeRange() || {}).native; 
  .........   //  other codes don't need change

the js file which I change is under the dist Folder,named quill.js, then I copy the file out to used in my project, because I can't change the code in node_module ` After I done these changes, I found it works good, expect when the paste thing is too long to over the bottom view, the page does not scroll down to view them. It's a problem but is not so bad. And I think it's easier to solve then this problem. I'm using this method now, and had not found other effects yet. If you finds something bad, please tell me and I will find method to handle it.

Sorry for my English, hope these texts help you a little or more.

I will try to comit my changes to author, if my changes are good, I think he will adopt and fix this in the next version,if these comes true, it will be my big honour~

ArsalanSavand commented 6 years ago

For those who are struggling with this bug, here is the fix. Be sure to set the scrollingContainer to 'html, body': scrollingContainer: 'html, body'

Important: set the scrollingContainer, to the DOM element that is responsible for scrolling the view that Quill editor is inside of it. scrollingContainer can have any selector (.class, #id, tag-name)

import Quill from 'quill';
import Delta from 'quill-delta';

const Clipboard = Quill.import('modules/clipboard')

class CustomClipboard extends Clipboard {
  onPaste(e) {
    if (e.defaultPrevented || !this.quill.isEnabled()) return;
    let range = this.quill.getSelection();
    let delta = new Delta().retain(range.index);
    this.container.style.top = (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0).toString() + 'px';
    this.container.focus();
    setTimeout(() => {
      this.quill.selection.update(Quill.sources.SILENT);
      delta = delta.concat(this.convert()).delete(range.length);
      this.quill.updateContents(delta, Quill.sources.USER);
      this.quill.setSelection(delta.length() - range.length, Quill.sources.SILENT);
      let bounds = this.quill.getBounds(delta.length() - range.length, Quill.sources.SILENT);
      this.quill.scrollingContainer.scrollTop = bounds.top;
    }, 1);
  }
}

Quill.register('modules/clipboard', CustomClipboard, true);

I hope this actually fix the bug.

anatolyrr commented 6 years ago

In my case the editor is inside a different scrolling container, not 'html, body', which is why the above-mentioned fixes didn't exactly suit.

This simple workaround though did the job perfectly:

.ql-clipboard {
    position: fixed;
}
ArsalanSavand commented 6 years ago

@anatolyrr with the code I provided I also fixed the issue where when you paste anything, the screen wouldn't jump to it. The code fixes both:

Take a look at the code, it will 100% fix the issue. Code

hakonleung commented 6 years ago

@ArsalanSavand it didnt work for me, i found scrollingContainer.scrollTop is always 0, because the real scroll element in my project is div.ql-editor`s father div#qlWrapper, so i init quill with option scrollingContainer: '#qlWrapper' and then everything gos right without other code.

ArsalanSavand commented 6 years ago

@hakonleung as I said before: "Be sure to set the scrollingContainer to 'html, body': scrollingContainer: 'html, body".

After that the code will work, I also mentioned all the issues this code will fix. Fixes

hakonleung commented 6 years ago

@ArsalanSavand it`s my fault, i know what you mean now, apreciate.

idexter commented 6 years ago

For those who are struggling with this bug, here is the fix. Be sure to set the scrollingContainer to 'html, body': scrollingContainer: 'html, body' ... I hope this actually fix the bug.

Didn't work for me. After adding it editor scrolls down every time I paste a text or press Enter. But I think it because I'm using Quill with https://github.com/zenoamaro/react-quill library.

import Quill from 'quill';
import Delta from 'quill-delta';

const Clipboard = Quill.import('modules/clipboard')

class CustomClipboard extends Clipboard {
  onPaste(e) {
    // get current page offset before paste
    const top = window.pageYOffset;
    const left = window.pageXOffset;

    if (e.defaultPrevented || !this.quill.isEnabled()) return;
    let range = this.quill.getSelection();
    let delta = new Delta().retain(range.index);
    this.container.style.top = (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0).toString() + 'px';
    this.container.focus();
    setTimeout(() => {
      this.quill.selection.update(Quill.sources.SILENT);
      delta = delta.concat(this.convert()).delete(range.length);
      this.quill.updateContents(delta, Quill.sources.USER);
      this.quill.setSelection(delta.length() - range.length, Quill.sources.SILENT);
      let bounds = this.quill.getBounds(delta.length() - range.length, Quill.sources.SILENT);
      this.quill.scrollingContainer.scrollTop = bounds.top;

      // scroll window to previous position after paste
      window.scrollTo({top, left});
    }, 1);
  }
}

Quill.register('modules/clipboard', CustomClipboard, true);

So I add 3 lines of code that helps in my case. Two in start of function and one after.

const top = window.pageYOffset;
const left = window.pageXOffset;
window.scrollTo({top, left});
locpnh1995 commented 6 years ago

For those who are struggling with this bug, here is the fix. Be sure to set the scrollingContainer to 'html, body': scrollingContainer: 'html, body' ... I hope this actually fix the bug.

This really help me a lot

ArsalanSavand commented 6 years ago

@DexterHD I mentioned all the bugs I fixed https://github.com/quilljs/quill/issues/1374#issuecomment-415575851

The screen jumping to the clipboard you paste is correct, this is the default way of an editor. But in your case, you can modify it and add custom lines. Good luck.

idexter commented 6 years ago

@ArsalanSavand Its, ok when it jumps to clipboard in place where I paste text. But in my case after I add scrollingContainer: 'html, body', every time I push Enter button in editor (Not copy and paste), it jumps down and down and down to end of the page.

ArsalanSavand commented 6 years ago

@DexterHD It's actually correct, imagine having the editor with scrollbar, when hitting enter on the last line the editor should redirect you to the last line whether you are at the top of the page or the bottom of the page.

Let's say the cursor is at the last line, and user doesn't know that. When user writes anything page should scroll to the line he just wrote.

idexter commented 6 years ago

@ArsalanSavand Yes. Except if I have unlimited size of my editor field in another form. I don't say you fix is wrong. It just not working in my case. Just look at screencast. I only press Enter (look to scroll). I can't explain it proper in English :(

https://www.youtube.com/watch?v=0ssfHBBI9pM&feature=youtu.be

ArsalanSavand commented 6 years ago

@DexterHD I saw the video, if you don't want your screen to jump to the content you just wrote or paste or even hitting enter, you can try this.

Be sure to set the scrollingContainer to 'html, body': scrollingContainer: 'html, body'

import Quill from 'quill';
import Delta from 'quill-delta';

const Clipboard = Quill.import('modules/clipboard')

class CustomClipboard extends Clipboard {
  onPaste(e) {
    if (e.defaultPrevented || !this.quill.isEnabled()) return;
    let range = this.quill.getSelection();
    let delta = new Delta().retain(range.index);
    let scrollTop = this.quill.scrollingContainer.scrollTop;
    this.container.style.top = (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0).toString() + 'px';
    this.container.focus();
    setTimeout(() => {
      this.quill.selection.update(Quill.sources.SILENT);
      delta = delta.concat(this.convert()).delete(range.length);
      this.quill.updateContents(delta, Quill.sources.USER);
      this.quill.setSelection(delta.length() - range.length, Quill.sources.SILENT);
      this.quill.scrollingContainer.scrollTop = scrollTop;
    }, 1);
  }
}

Quill.register('modules/clipboard', CustomClipboard, true);

Attention to everyone, this code is only for @DexterHD case, the fix is this code https://github.com/quilljs/quill/issues/1374#issuecomment-407888147

andrei-cacio commented 5 years ago

Unfortunately none of the examples above work for my case. I still get awkward jumps when pasting and the UX is pretty unpleasant. However after I installed quill@2.0.0-dev.3, pasting seems to be fixed. There are no more jumps. Has the .ql-clipboard div been removed? Also how safe is to use this build in production?

andrei-cacio commented 5 years ago

After looking a bit in the history of the clipboard.js module I have found a working solution for me:

class CustomClipboard extends Clipboard {
    onPaste(e) {
        if (e.defaultPrevented || !this.quill.isEnabled()) return;
        const range = this.quill.getSelection(true);
        e.preventDefault();

        const formats = this.quill.getFormat(this.quill.selection.savedRange.index);
        const html = e.clipboardData.getData('text/html');
        const text = e.clipboardData.getData('text/plain');

        let delta = new Delta().retain(range.index);
        if (formats[CodeBlock.blotName]) {
            delta.insert(text, {
                [CodeBlock.blotName]: formats[CodeBlock.blotName],
            });
        } else if (!html) {
            delta.insert(text);
        } else {
            const pasteDelta = this.convert(html);
            delta = delta.concat(pasteDelta);
        }
        delta.delete(range.length);
        this.quill.updateContents(delta, Quill.sources.USER);
        // range.length contributes to delta.length()
        this.quill.setSelection(
            delta.length() - range.length,
            Quill.sources.SILENT,
        );
        this.quill.scrollIntoView();
    }
}

Plain and simple, injecting the pasted html. Are there any downsides to this solution?

ArsalanSavand commented 5 years ago

@andrei-cacio have you tried this solution? https://github.com/quilljs/quill/issues/1374#issuecomment-407888147

Read it carefully, it should work like charm.

ycjcl868 commented 5 years ago

Watching.

spacegangster commented 5 years ago

Watching. Ready to assign a small bounty. For now using a workaround by @thomasdao https://github.com/quilljs/quill/issues/1374#issuecomment-374008626

spacegangster commented 5 years ago

Made a workaround package based on @thomasdao answer https://github.com/spacegangster/quill-plain-text-paste