GithubHelp home page GithubHelp logo

reedsy / quill-cursors Goto Github PK

View Code? Open in Web Editor NEW
243.0 14.0 53.0 892 KB

A multi cursor module for Quill text editor.

License: MIT License

JavaScript 7.07% HTML 1.27% CSS 0.38% TypeScript 85.57% Shell 1.06% SCSS 4.65%
quill module collaborative-editing cursors

quill-cursors's Introduction

NPM Version Test

quill-cursors

A collaborative editing module for the Quill text editor used by the Reedsy team.

Quill cursors

Install

npm install quill-cursors --save

Usage

quill-cursors is a Quill module that exposes a number of methods to help display other users' cursors for collaborative editing.

First, set up a Quill editor.

Next, load quill-cursors through any of the options presented by UMD.

Load script in HTML:

<script src="quill-cursors.js"></script>

Using ES6-style import:

import QuillCursors from 'quill-cursors';

Using CommonJS-style require:

const QuillCursors = require('quill-cursors');

Then, register the quill-cursors module:

Quill.register('modules/cursors', QuillCursors);

const quill = new Quill('#editor', {
  modules: {
    cursors: true,
  }
});

Finally, use the exposed quill-cursors methods to update the cursors (see below). For an example setup, see the example code, which can be run with:

npm start

API

Configuration

The quill-cursors module has the following optional configuration:

  • template string: override the default HTML template used for a cursor
  • containerClass string (default: ql-cursors): the CSS class to add to the cursors container
  • hideDelayMs number (default: 3000): number of milliseconds to show the username flag before hiding it
  • hideSpeedMs number (default: 400): the duration of the flag hiding animation in milliseconds
  • selectionChangeSource string | null (default: api): the event source to use when emitting selection-change
  • transformOnTextChange boolean (default: false): attempt to locally infer cursor positions whenever the editor contents change, without receiving an update from the other client. This can be useful for smoother performance on high-latency connections.
  • boundsContainer HTMLElement (default: Quill's bounds container): the element container used to determine flag positioning
  • positionFlag (flag: HTMLElement, caretRectangle: ClientRect, container: ClientRect) => void (default: flip horizontally): an optional function for positioning the caret flag according to its position relative to the bounds container. By default, the flag will flip horizontally when it reaches the right-hand edge of the bounds

Provide these options when setting up the Quill editor:

const editor = new Quill('#editor', {
  modules: {
    cursors: {
      template: '<div class="custom-cursor">...</div>',
      hideDelayMs: 5000,
      hideSpeedMs: 0,
      selectionChangeSource: null,
      transformOnTextChange: true,
    },
  },
});

template

For the custom template to work correctly with the module, it should closely follow the classes in the original template.

selectionChangeSource

By default, QuillJS will suppress selection-change events when typing to avoid noise.

However, you will probably want to update the quill-cursors selection on both selection-change and text-change. In order to aid this, quill-cursors will automatically emit a selection-change event on text-change.

You can differentiate between user input and the quill-cursors module by checking the source argument for the selection-change event. By default, quill-cursors will have source = 'api', but if you need to differentiate between calls from quill-cursors and other events, then you can change this source using the selectionChangeSource option.

If emitting an event is undesirable (eg you want selection-change to act like the Quill default), then the selectionChangeSource can be set to null, and an event will not be emitted. Note that in this case, you will need to separately handle the text-change event and update the cursor position.

Methods

The module instance can be retrieved through Quill's getModule:

const cursors = editor.getModule('cursors');

createCursor

createCursor(id: string, name: string, color: string): Cursor;

Creates a Cursor instance with the given id. If a cursor with this id already exists, a new one is not created.

  • id string: the unique ID for the cursor
  • name string: the name to display on the cursor
  • color string: the CSS color to use for the cursor

Returns a Cursor object:

{
  id: string;
  name: string;
  color: string;
  range: Range; // See https://quilljs.com/docs/api/#selection-change
}

moveCursor

moveCursor(id: string, range: QuillRange): void;

Sets the selection range of the cursor with the given id.

  • id string: the ID of the cursor to move
  • range Range: the selection range

removeCursor

removeCursor(id: string): void;

Removes the cursor with the given id from the DOM.

  • id string: the ID of the cursor to remove

update

update(): void;

Redraws all of the cursors in the DOM.

clearCursors

clearCursors(): void;

Removes all the cursors from the DOM.

toggleFlag

toggleFlag(id: string, shouldShow?: boolean): void;

Toggles display of the flag for the cursor with the given id.

  • id string: the ID of the cursor whose flag should be toggled
  • shouldShow boolean (optional): if set to true, will display the flag. If set to false, will hide it. If omitted, the flag's display state will be toggled.

cursors

cursors(): Cursor[];

Returns an array of all the Cursor objects in the DOM in no particular order.

License

This code is available under the MIT license.

quill-cursors's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

quill-cursors's Issues

Can't touch near other user's cursors

This is a follow-on from #72 which was partially fixed by #73

However, the behaviour is still broken on touch devices (which don't trigger the :hover styling). We should potentially move our flag logic into JS, where we should have a bit more control than pure CSS.

npm install quill-cursors --save error

I'm trying to integrate multiple-cursors into quilljs, and would love to be able to do this! However, when I try to install with npm, it gives me an error that says CERT_UNTRUSTED... please advise!

AMD Loading

Hey there,

Trying out your project to see if I can implement basic quill cursors on selection-change and text-change. I'm loading modules with RequireJS but am having difficulty with quill-cursors. I've listed Quill as a dependency for quill-cursors, however quill-cursors seems to be looking for Quill before instantiation of a Quill instance, which causes the following error.

quill-cursors.js:1804 Uncaught ReferenceError: Quill is not defined
    at Object.<anonymous> (quill-cursors.js:1804)
    at __webpack_require__ (quill-cursors.js:20)
    at Object.rangeFix (quill-cursors.js:75)
    at __webpack_require__ (quill-cursors.js:20)
    at Object.<anonymous> (quill-cursors.js:1817)
    at __webpack_require__ (quill-cursors.js:20)
    at Object.defineProperty.value (quill-cursors.js:66)
    at quill-cursors.js:69

My reasons for migrating to Webpack are piling up, so if this isn't something you intend to address, maybe I should start the process of making a move.

Thanks for the great work

caret off by one?

When applying a cursor range using setCursor, the caret positions seem to be off by one?

Selections seem to be accurate, but especially when the caret is at position 0 the caret renders just after the first character. Here's a screen shot of a meteor quill object that's syncing deltas and now cursors with your nifty module.

The private window in the foreground is positioned at 0, you can see in the console the range is set to 0, however it's rendered just after the first char:

screen shot 2018-10-07 at 3 11 10 pm

Overlapping duplicate highlight on formatted text

Repro steps:

  1. Add some text to the editor
  2. Make some text bold
  3. Highlight all the text
  4. Notice that the bold text highlighting is darker than the other highlighting, when it should have the same opacity

Screen Shot 2019-06-28 at 10 55 02

Does the example work?

Downloading the zip and opening index.html throws a load of errors, presumably because Quill is missing...
image

However on inspection of examples/index.html there are multiple references to files which are not in the Quill repo either.
quill-cursors.min.js, quill-cursors.css, etc

Can you guys add some explanation of how to view the demo and perhaps a link to an online version?

IndexSizeError after inserting image and then selecting everything in the editor

Hello, thank you for your module. I found a strange issue while working with it.

Steps to reproduce:

  1. Open https://jsfiddle.net/ganachka/w0udaost/5/ (I used the same code you have in your example folder).
  2. Additionally open Developer tools with browser console.
  3. Paste an image to one of the editors (it's important to copy image itself and paste it, not its URL).
  4. Select the content of the editor, for example by pressing Cmd+A

After that I get IndexSizeError: Index or size is negative or greater than the allowed amount from the quill-cursors script. I also get the same error when I resize browser window after that.

The screenshot from Firefox (but experienced the same in Chrome too):

Screen Shot 2020-05-20 at 12 11 34 AM

Do you have any ideas about how it can be fixed?

Quill does not show multiple cursors until editor is focused

If I load a document that is currently being edited by someone else, I see text added to the document by the other person but the cursor does not show.

The element ql-cursors is empty so the cursor has not been built yet.

But as soon as I focus the editor, the cursor is built and appears correctly.

How come the other person cursor only shows once the document is first focused? Once the document is focused, and if it is blurred, the cursor still shows. So it really only requires an initial focus for the cursor to appear.

Engine requirement

Is there a reason that this library limits engine version to 14.x? I was trying it on Node 17.x and yarn refuses to install without --ignore-engines.

The library itself runs in the browser, so I assume the engine version is only for development (e.g. running tests). Can we relax the engine requirement here?

Newlines in html template in minified js?

Downloading 3.1.2 from NPM (and other versions as far as I can tell) seems to contain newlines in the html template used for displaying the cursor flag. These newlines render and make it appear as though there is a lot of padding around the name.

I narrowed it down to this minified code:
o='\n <span class="'.concat(i.default.SELECTION_CLASS,'"></span>\n <span class="').concat(i.default.CARET_CONTAINER_CLASS,'">\n <span class="').concat(i.default.CARET_CLASS,'"></span>\n </span>\n <div class="').concat(i.default.FLAG_CLASS,'">\n <small class="').concat(i.default.NAME_CLASS,'"></small>\n </div>\n');

When I remove the newlines from that minified code, everything renders quite well. Seems like this is unintentional.

Attached image of how it looks (with text selected highlighting the newline). Let me know if I'm just missing something!
image

import blocked due to disallowed mime type text/html...

when I type "include './quill-cursors' ", it seems to find the quill-cursors module on my server just fine, but my server seems to recognize the module as text/html and doesn't load it because of a disallowed mime type. Is quill-cursors referring to the ts files in the directory? If so, what mime type should those have? Trying to configure my server correctly

Whole paragraph highlights look weird

When selecting an entire paragraph, the p element gets included in the selection, which causes a large block overlap. This looks a bit odd, and isn't necessary, since the text nodes within the paragraph have been selected already.

Screen Shot 2019-06-28 at 17 31 27

Cursor range does not adjust when entering text

Use Case:

I'm trying to use this with websockets for online collaboration (typical use case). I have a server that just relays messages to and from the client browser.

Problem:

If person A selects a range, person B will see the range selected on their editor. If person B edits that document, while person A does nothing, the cursor range does not exhibit expected behavior.

I have two situations that occur right now.

  1. I can relay that information back and forth to get the updated selection. In other words:
    • A selects range
    • B starts typing
    • A sees the changes on their editor via websockets
    • A's editor then relays the updated selected range back thru the websocket
    • B receives the updated range and moves the cursor.

However I get this situation (and it would be worse on a server that is further away).

Kapture 2019-03-28 at 18 45 04

  1. Or if I don't forward the updated range, I get this issue:

Kapture 2019-03-28 at 18 43 37

I'm not sure if its an bug or a misconfiguration on my end, but my expected behavior is that the cursor's range would get updated automatically.

The demo is different because I think when the editor loses focus, the range disappears altogether.

Using with react-quill, cursors stays hidden

I am using this module with react-quill and managed to get it working with a reference, I see the cursors objects in the editor by inspecting the DOM. However, when I run moveCursor the cursors stay hidden afterwards and I never see the cursors.

  componentDidMount() {
    this.attachQuillRefs();
    
    cursorsObject = this.quillRef.getModule('cursors')
    
    socket.on('editorContent', (editorContent) => {
      this.setState({ text: editorContent.content })
    })
    
    socket.on('cursorMove', (cursor) => {
      cursorsObject.moveCursor(cursor.user, cursor.cursorRange)
    })
    
    socket.on('usersChanged', (userInformation) => {
      if(userInformation.action === 'joined') {
        cursorsObject.createCursor(userInformation.user, 'User '+userInformation.user, 'blue');
      } else if(userInformation.action === 'quit') {
        cursorsObject.removeCursor(userInformation.user)
      }
    })
  }

chrome_DFluQJl2ny

QuillCursors.prototype._applyDelta needs improvement

Noticed a few things, that end up being caught on the QuillCursors.prototype._updateCursor 'sanity check' when one's editing a document, namely with insert and delete operations around other people selections:

  • When one deletes the whole first line of text when another user cursor is in the middle of it โ€“ that user cursor is going to be 'shifted backwards' by the amount of the delete/the whole line length and will have a negative index afterwards;
  • When module receives a delete op that 'touches' another user active selection;

All these tweaks and improvements should be done after a look into how other collaborative text editors (Google Docs, etc.) handle other users selections when editing content close/near/inside these.

Broken non-minified dist version

So, noticed earlier that when trying to use the non-minified version of this module (/dist/quill-cursors.js) on the included example, it doesn't run and displays the following error:

TypeError: undefined is not an object (evaluating 'target.register') (...)

I think this has to do to some issue with Webpack bundling, but still haven't found out why.

Unable to Build Project With Quill-Cursors imported

Hello, I'm attempting to use quill-cursors in a SvelteKit project, using TypeScript, and when I build the project, Vite is showing this error:

ReferenceError: self is not defined at Object.<anonymous> (/.../node_modules/quill-cursors/dist/quill-cursors.js:2:206)

Bug with code when wrapped with electron

Hi!

Just want to write a note of thanks on this great module, helped greatly with a project :)

Discovered a bug that has to do with one of the javascript functions that is not supported across all browsers/chromium.

In cursors.js, line 88:

CursorsModule.prototype.update = function() { Object.values(this.cursors).forEach(this._updateCursor.bind(this)); };

Object.values will result in an error when it is run in the chromium version of an electron wrapped app. This function would be the same if you simply change the function to this:

CursorsModule.prototype.update = function() { Object.keys(this.cursors).map((key) => { this._updateCursor(this.cursors[key]) }) };

Just thought to share and hope you can apply the change!

Make tooltip delay configurable

I like how hovering over the tooltip shows the name like in Google Docs. But I feel it stays visible a bit too long. Maybe the delay could be configurable? Personally, I would make it so that it disappears when you go away from the cursor with your mouse.

Quill-cursors has a memory leak due to missed cleanup for global listeners

Adding quill-cursors to our project i found a memory (nodes) leak. Debugging leads me to these lines https://github.com/reedsy/quill-cursors/blob/master/src/quill-cursors/quill-cursors.ts#L102 that setup two global listeners that are never cleared. Manually commenting out this method code in dist js file fixed memory leak. These listeners without cleanup holds quill's dom nodes references in memory and so for each quill instance during page's lifetime.
Quill-cursors should somehow clear this listeners on quill removing.

Selections are not necessarily added in order

A selection is broken into multiple span elements. Sometimes, we may want to apply styling to these using :first-child and :last-child CSS selectors. However, the elements are not necessarily added in order (ie I'd expect the top line of a selection to be the first span in the DOM, and so on). This seems to work in order in Chrome by accident, but isn't necessarily the case eg in Firefox.

Can't get cursors to show up no matter what

Clearly doing something wrong but can't figure out what.

I'm calling:
Quill.register('modules/cursors', QuillCursors)

These are my options:
{
modules: {
cursors: {
autoRegisterListener: false
},
history: {
userOnly: true
},
toolbar: true
},
placeholder: this.placeholder,
theme: 'snow'
}

I have collaborative editing setup, that all works fine. I keep track of the connections and cursor positions and update the cursors with them, and if I inspect the data it all looks right:

screen shot 2018-03-11 at 10 08 33 pm

As you can see in that picture, the cursor is inserted and the data all looks correct, but nothing renders on the editor.

I've also tried manually inserting cursors with dummy data and nothing shows.

Is there some styling that needs to be included that I'm missing? Been messing with this for 3 days now and can't figure it out.

setStart error

We've been getting a number of errors from Rollbar like this:

IndexSizeError: Failed to execute 'setStart' on 'Range': There is no child at offset 1. (Most recent call first)

They all come from quill-cursors.ts:191

a.setStart(n[0].domNode, n[1]), a.setEnd(o[0].domNode, o[1]);

It seems that all the errors are generated by Electron 3 on Windows. However, it seems to me that the setRange functionality should be well supported by Electron 3. This is happening in our fork (but we made very few changes). It might of course be related to something we're doing, but if you have any ideas on how to investigate or solve this, I'd appreciate it!

Backtrace:

File ../node_modules/@minervaproject/quill-cursors/dist/quill-cursors.js line 225 col 1 in e._updateCursor
a.setStart(n[0].domNode, n[1]), a.setEnd(o[0].domNode, o[1]);
File ../node_modules/@minervaproject/quill-cursors/dist/quill-cursors.js line 170 col 1 in e.moveCursor
r && (r.range = e, this._updateCursor(r));
File components/BlockComponent/RichTextField.js line 96 col 21 in [anonymous]
cursors.moveCursor(userId, { index: min, length: len });
File components/BlockComponent/RichTextField.js line 62 col 15 in u.<anonymous>
srcList.forEach((src) => {
File ../node_modules/events/events.js line 142 col 1 in u.emit
ReflectApply(listeners[i], this, args);
File ../node_modules/@teamwork/sharedb/lib/client/doc.js line 1474 col 1 in [anonymous]
doc.emit('presence', srcList, submitted);
File ../node_modules/process/browser.js line 167 col 1 in d.run
this.fun.apply(null, this.array);
File ../node_modules/process/browser.js line 131 col 1 in drainQueue
currentQueue[queueIndex].run();

Cursors work great, but lose flags after a remount with React

I'm using Quill with React, Yjs, react-quill, y-quill, y-websocket, and quill-cursors. Everything works great with cursors (showing a remote users activity with a line without a flag) and with flags as long as there are no remounts. After a remount, the cursors continue to work but the flags stop showing up. This is because quillEditor.getModule('cursors').cursors() is empty. Before a remount, cursors() returns an array of users who are editing the doc, but after a remount, cursors() is always empty.

I've tried various combinations of saving (useRef) or recreating quill, cursors, binding, etc. For the most part, I fould the best approach is to store the WebsocketProvider, Y.Doc, Y.Text and then recreate the ReactQuill.getEditor, Quill.getModule('cursors'), and y-quill QuillBinding. I've tried different combinations, but all other combinations cause some other more serious problem (e.g. cursors stop working altogether).

I'm new to Quill, Yjs, etc., and I'm not really familiar with the relationship between all the packages, so not sure if this is a bug or something I'm doing wrong.

Any insights on what could be causing this or maybe just an explanation of how cursors() is populated?

thanks!

Move during transformation can lead to wrong result

Theoretically, following situation is possible:

  • We have a cursor
  • Some insert happens before it, triggering update. Transform will be scheduled for the next runloop cycle.
  • In the same moment, we receive new cursor state and trying to update it. We update it to the new position, already including latest insert.
  • Finally, transform will be executed, moving cursor even further.

Result - incorrect cursor position.

As a solution I'm ignoring moves during transform on my fork, but in this way we can lose some move, which is ok for my case, but not for a general case.

Flags can be cropped by parent containers and viewports

First raised in #51

Screenshot 2020-01-23 at 14 43 08

Flags can be cut off in small containers and viewports. In general, we should attempt to catch these cases, and flip the flag. We will probably need to flip up-down and left-right, depending on the collisions we find.

We may also allow the consumer to provide a "container" element, against whose bounds we check the flag (presumably defaulting to <body>?).

Note that the Quill tooltip already addresses this issue, so perhaps we can read across that solution (or something very similar).

Scrolling ruins the selection

Hi,

I'm trying to implement this plugin and it seems fine, but for one thing. Upon scrolling, the selection doesn't go with the scroll. I've attached some images.

It seems to be because the selection block has a "top" attribute, which doesn't update, even though the rest of the page does update. As the top is relative to the Quill container, the scrolling doesn't update the selection (as the Quill container doesn't move).

Do you know a solution? I can try to fix it myself as well, if this is not a known problem.

Perhaps something can be done like in the tooltip code: https://github.com/quilljs/quill/blob/9a77567fe356d384074df7479c33ceac509d9351/ui/tooltip.js

Before scrolling: https://imgur.com/a/4cNTG4k
After scrolling: https://imgur.com/a/tnWbA1E

Edit: created a PR #15

Add typings

It would be great if the types were also generated upon build and added to the dist so that when using the plugin with TS people can make use of the typings

Quill v2 Support

Thank you for this this! Does it also support quill version 2?

Cursor position is incorrect when direction is RTL

The cursor position is always 1 character out from the position when using RTL direction. The GIF uses the example provided in the repo along with .ql-editor { text-align: right; direction: rtl; } added styles.

Quill Cursors

Using a quill editor inside a react-flow is somehow causing cursors to render at strange places.

CleanShot 2022-06-10 at 20 45 04

I've been trying tons of things to make this work, and if it comes to it I can try to reproduce with a fiddle, but I was wondering if perhaps anyone would have a pointer on what to look for here, if there is some kind of issue using quill in this context w/ a resizing/absolute container (I think that is how react-flow works?).

Happy to provide additional info as needed, but hopefully there is just a lead I've failed to track down somewhere here :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.