slab / quill

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

Consider allowing multiple editors on the same page & to use the same toolbar. #633

Open u12206050 opened 8 years ago

u12206050 commented 8 years ago

When having more than one editor on a page, each one currently seems to require their own toolbar. If I try to assign a previously assigned toolbar then the events don't work for the second editor.

Steps for Reproduction

  1. Create one toolbar
  2. Create two editors
  3. Assign both editors the same toolbar.

Expected behavior: Would be easy enough to simply keep track of which editor was last active and apply and changes to that editor eg. Changing font size etc.

Actual behavior: The tool bar seems broken as soon as the second editor is attached to it. Only the second editor is actually usable, because when trying to type in the first one, the cursor jumps to the second one.

jhchen commented 7 years ago

What is the use case for this? And why is that use case not solved with just hiding the respective toolbar and require there be one and only one toolbar?

m1kelynn commented 7 years ago

This is something that I am looking for also.

I am trying to incorporate Quill into a webpage builder. I'm hoping to have Quill handle the content and have another way to control the background images of the sections. Ideally the toolbar would be attached to the top and not connected to the editable areas. Maybe there's another way to do this?

quill-edit

Thileepan commented 7 years ago

@m1kelynn Did you managed to solve the issue with multiple editors with single toolbar in your case? Eager to know if something you could share.

dkirchhof commented 7 years ago

Hello, I got a similar use case and managed it by implementing my own toolbar.
When the editor content / cursor changes, it will get the current formattings.

this.quill.on("editor-change", (eventName, ...args) =>
{
    if(this.quill.getSelection())
    {
        this.currentFormats = this.quill.getFormat();
    }
});

The toolbar buttons will call a format function.

format(format, params)
{
    if(this.currentFormats[format] && (!params || this.currentFormats[format] == params))
    {
        this.quill.format(format, false, Quill.sources.USER);
    }
    else
    {
        this.quill.format(format, params, Quill.sources.USER)
    }
}

Then I bind the style of the toolbar button to the currentFormats variable.

TheWizz commented 7 years ago

@mrgrimmig, I have a hard time following what you did to solve this problem. I need to do something similar, and would really appreciate some clarification or a workinf code example, if possible.

Thanks!

-JM

dkirchhof commented 7 years ago

Sorry for my weird response. Actually i'm using polymer and bind current formats to the toolbar to update the buttons autmatically, but i've made a small example without any dependencies to clarify my answer.

<!DOCTYPE html>
<html>
<head>
    <script src="https://cdn.quilljs.com/1.2.6/quill.js"></script>
    <link href="https://cdn.quilljs.com/1.2.6/quill.snow.css" rel="stylesheet">

    <style>

        .active {
            background: green;
        }

    </style>
</head>
<body>

    <button id="bold" onclick="onBoldClick()">Bold</button>

    <div id="editor1">
        <p>Hello World!</p>
        <p>Some initial <strong>bold</strong> text</p>
        <p><br></p>
    </div>

    <div id="editor2">
        <p>Hello World!</p>
        <p>Some initial <strong>bold</strong> text</p>
        <p><br></p>
    </div>

    <script>

        var currentEditor; // selected / focused editor
        var currentFormats; // save the current formattings

        createEditor("#editor1");
        createEditor("#editor2");

        function createEditor(selector)
        {
            let quill = new Quill(selector, { });

            quill.on("editor-change", (eventName, ...args) =>
            {
                currentEditor = quill;
                updateButtons();
            });
        }

        // get current formattings to style the toolbar buttons
        function updateButtons()
        {
            if(currentEditor.getSelection())
            {
                currentFormats = currentEditor.getFormat();

                if(currentFormats.bold)
                {
                    bold.classList.add("active");
                }
                else
                {
                    bold.classList.remove("active");
                }
            }
        }

        // if selected text is bold => unbold it - if it isn't => bold it
        function onBoldClick()
        {
            if(!currentFormats || !currentEditor)
            {
                return;
            }

            if(currentFormats.bold)
            {
                currentEditor.format("bold", false);
            }
            else
            {
                currentEditor.format("bold", true);                
            }
        }

    </script>

</body>
</html>
benjismith commented 7 years ago

I'd love to talk about my use-case here as well...

I'm using Quill as the text-editing component of a word-processor, which may have multiple documents onscreen at the same time. But all the instances should share the same toolbar. For example, here's a screenshot containing three Quill instances, one for the main document, and two containing margin comments:

Shaxpir Screenshot

Whenever the user clicks from one editor to another, the toolbar should update itself to reflect the active/enabled/disabled status of each button. And any toolbar clicks should be directed to the currently-focused editor instance.

Although some of the toolbar buttons correspond with Quill formatting functions, there are other toolbar buttons for non-Quill functionality, and those should continue to be enabled even when there are no active Quill editors onscreen at all.

Right now, I'm implementing my own toolbar, completely separate from Quill, and managing the active/enabled/disabled state of all the toolbar buttons myself. But it's pretty ugly, and I'd love to help Quill address this use case a little better.

Thank you for an incredible open-source project! I appreciate all your hard work.

jhchen commented 7 years ago

Sounds like there is sufficient demand for this feature. Can someone propose an API that would fit solve their needs? Short code snippets using the API and high level descriptions of what they do and don't do would be helpful.

TheWizz commented 7 years ago

Perhaps some inspiration could be taken from the TextAngular editor, which has this feature.

https://github.com/textAngular/textAngular

See specifically "a toolbar can be linked to multiple editors" in the accompanying wiki:

https://github.com/textAngular/textAngular/wiki/Customising-The-Toolbar

-JM

benjismith commented 7 years ago

I don't think we need to change the API very much. The only things I really need are:

  1. The ability to attach and detach Quill from additional DOM containers.
  2. The ability to retrieve an editor instance by DOM selector.
  3. New events for editor attachment and detachment, as well as focus and unfocus between editor instances.

Here's a quick example of creating the root Quill object, registering event handlers, attaching a few editors, selecting them for focus, and detaching from them again.

    let quill = new Quill({ toolbar : "#toolbar" });

    quill.on('editor-attach', function(e) { console.log("attached to:" + e.getEditorId());
    quill.on('editor-detach', function(e) { console.log("detached from:" + e.getEditorId());
    quill.on('editor-focus', function(e) { console.log("focused:" + e.getEditorId());
    quill.on('editor-unfocus', function(e) { console.log("unfocused:" + e.getEditorId());

    quill.attach('#editor-a');
    quill.attach('#editor-b');

    var editorA = quill.getEditorInstance('#editor-a");
    editorA.focus();

    var editorB = quill.getEditorInstance('#editor-b");
    editorB.focus();

    quill.detach('#editor-b');
    quill.detach('#editor-a');

The current Quill constructor, which accepts an editor ID...

    let quill = new Quill("#editor", { toolbar : "#toolbar" });

...could continue to be supported, as a shorthand for the more verbose version:

    let quill = new Quill({ toolbar : "#toolbar" });
    quill.attach("#editor");

It might also be nice to declare which toolbar buttons are enabled and disabled for each editor instance, possibly with something like this:

    let quill = new Quill({ toolbar : "#toolbar" });
    quill.attach("#editor", { enabled : [ "#button-bold", "#button-italic" ] });

However, I'd also be happy to add the enable/disable logic to my own event-handler code for the focus and unfocus events.

jhchen commented 7 years ago

To be clear you are proposing that let quill = new Quill({ toolbar : "#toolbar" }); would not create a Quill editor and instead create an editor-less toolbar?

Quill.find is already an API method (though currently experimental) by the way.

benjismith commented 7 years ago

Yep, that's what I'm suggesting!

Maybe most users will typically include an editor selector in the constructor let quill = new Quill("#editor", { toolbar : "#toolbar" }); but I think it would be very handy to startup Quill without an editor.

In my application, the toolbar is always present from startup onward, even before the user creates their first document. As soon as they create a new document, or navigate into their existing content, I create new Quill editors and manually update the toolbar based on the selection and formatting state within that editor.

In my ideal scenario, calling let quill = new Quill({ toolbar : "#toolbar" }); essentially just creates an editor factory, configured with a particular Parchment schema.

jhchen commented 7 years ago

Hmm the thing is Quill is the editor with optional modules, one of which is the toolbar. So new Quill not making an editor would be a dramatic departure.

benjismith commented 7 years ago

I totally understand, and I'm 100% open to other designs. The one I suggested just seems the most natural to me, as a user. But I'd love to see other proposals too!

fmpenz commented 7 years ago

Would it be possible to make multiple editors work if created with the same toolbar?

`var quill1 = new Quill('#editor1', { modules: { toolbar: '#toolbar' }, theme: 'snow' });

var quill2 = new Quill('#editor2', { modules: { toolbar: '#toolbar' }, theme: 'snow' });`

jhchen commented 7 years ago

If you click a button in the toolbar what editor would it modify? Both?

fmpenz commented 7 years ago

If you click a button in the toolbar only the editor which lastly had focus should be modified. The Shared Toolbar feature in CKE shows how it could be done.

TheWizz commented 6 years ago

Has there been any progress on this feature? I would really like to switch to Quill from what I'm using today (TextAngular). Anything I can do to help out moving this forward?

-JM

sconix commented 6 years ago

We would love to see this feature as well. For now I have made an Angular wrapper library (ngx-quill-wrapper) that allows this, but the way it handles it is bit hacky and definitely not optimal.

TheWizz commented 6 years ago

I'm usinbn AngularJS, so unfortunately I can't use your wrapper. But I think the idea of separating the toolbar from the editor (at least as an option) is well thought out. Hopefully, we'll see this as part of Quill itself in a not-too-distant future, since this is really something that ought to be done independently of any specific framework wrapper.

jhchen commented 6 years ago

I don't believe anyone is working on it and I probably will not get to it as there are other higher priority items at the moment. If someone with this use case wants to take it on please do. I am happy to give feedback on specific proposals but do not have the bandwidth to give general guidance for those new to Quill.

umairrazzaq commented 6 years ago

Hello guys, hope this message everyone fine.

Good topic/issue to cover here. I've implement and managed to add some buttons in toolbar by manual way which are functional as it should be, but I'm still facing some issues like Color, Font Size & Font Family etc.

Demo link: Codepen

Now what i need is to make Color, Font Size & Font Family button functional. Please review and suggest me. Thanks

PawelGlow commented 6 years ago

Would love to see this feature as well. At the moment I have to create a toolbar for every Quill editor as trying to use a single toolbar leaves the previous editor toolbar event handlers attached. After hours of trying to "hack it out", I finally created an editor + toolbar for every area I wanted to allow editing.

I was also wondering if I can use the addContainer API method, but the docs are not specific enough for me to quickly understand what it does.

Overall, Quill is amazing btw :)

PawelGlow commented 5 years ago

I see this topic has not moved, too bad. Any example on how to use the addContainer API method?

umairrazzaq commented 5 years ago

Yes you're right, but I've managed to do so with TextAngular, since i need it for angularjs project.

PawelGlow commented 5 years ago

I have been working with Quill for the past 6 months while using it in a product I am building. I have found that the documentation is ok, but lacks better examples.

With regards to this topic, I have many boxes and only one toolbar. My goal was to have a Quill editor in the active box and allow the toolbar to control the content of the active box. Here are my findings:

1. Initialize a Quill editor inside each box with the same toolbar selector class for each

var editors = [];
for (var x = 0; x < len; x ++) {
   editors.push(new Quill(document.getElementById('box-' + x), {
       modules: {
           toolbar: {
               container: '.toolbar',
           },
       },
   }));
}

This failed because the toolbar now had an event attached for each Quill object, which resulted in having many events per action. This became difficult to manage and caused many technical issues when switching between boxes to edit the content.

2. Same as point 1, except now create a toolbar for each box and display only the active box toolbar. For each Quill editor use the unique toolbar.

var editors = [];
for (var x = 0; x < len; x ++) {
   editors.push(new Quill(document.getElementById('box-' + x), {
       modules: {
           toolbar: {
               container: '.toolbar-' + x,
           },
       },
   }));
}

This worked as expected but it resulted in a very DOM heavy structure, especially if you have lots of actions in your toolbar or lots of Quill instances.

You have 2 options:

During my testing I used Angular and Vue to test the performance of rendering the toolbars dynamically. I noted a rendering performance hit when I was working with 20+ Quill instances.

3. Initialize a Quill editor inside the active box only and destroy the previous editor + one toolbar

This is the solution I am using today and it results in the best performance and a some what clean solution. When the active box changes, I get the Quill instance content, remove the previous Quill events + instance, and I update the DOM as the HTML created by the editor is not removed automatically.

I am using this quill delta to html addon

if (window.editor) {
    //Get the content
    var content = '';
    if (window.editor.getLength() > 1) {
        var delta = window.editor.getContents(); //store globally
        var converter = new QuillDeltaToHtmlConverter(delta.ops, {});
        var html = converter.convert();
        if (typeof html !== "undefined" && html !== null) {
            content = html;
        }
    }

    //remove any [on events](https://quilljs.com/docs/api/#events) you attached to the Quill instance 

    //remove Quill instance
    window.editor = undefined;

    // clean DOM and set content
    document.getElementById('previousBoxId').classList.remove('ql-container');
    document.getElementById('previousBoxId').innerHTML = content;
}

//create new quill instance
window.editor = new Quill(...)

//set the editor contents ("//store globally" comment above)
if (editorContent) {
    window.editor.setContents(editorContent)
}

window.editor.focus();
//initialize any [on events](https://quilljs.com/docs/api/#events) if you want

The downside of Quill not managing multiple instances of the editor with a single toolbar is not a big problem, but it does require you to do the research/testing + add the logic yourself, which can be a hassle.

I hope someone finds this useful.

GJMAC commented 5 years ago

I find Quill to be a very helpful control and have been working with Quill for a couple of months now and also have the need of multiple editors on a single view. I ask if this could be something like reassigning of Quill.options.modules.toolbar. If I have editor1 and editor2, I wonder if code as the following pseudo code not work:

if editor1 is selected then editor2.unassignToolbar editor1.options.modules.toolbar = theToolbar else if editor2 is selected then editor1.unassignToolbar editor2.options.modules.toolbar = theToolbar

I realize that this is a trivial example, yet I hope that it shows the intent and possible desire.

I have tried this utilizing an example, yet for some reason it did not work with the current version of Quill. So for now I am simply using an independent toolbar and using the formattext and other functions Quill provides, which unfortunately is, for some reason, causing other issues with deletion of text and typing, which I shall not bring up at this time.

PawelGlow commented 5 years ago

@GJMAC I have not tried to reassign the toolbar in "Quill.options.modules.toolbar". I doubt this would work because there are probably some event handlers being added and removed when you create the Quill editor.

Although in the Quill docs they mention event handlers are automatically garbage collected, this did not work for me, that is why I decided to destroy and recreate the Quill editor every time I wanted to change the toolbar.

Onouriis commented 5 years ago

@PawelGlow Garbage collectors seems to not work for me too. How do you destroy the Quill editor? I am using Vuejs but I think it's the same way for different frameworks/vanilla.

PawelGlow commented 5 years ago

@Onouriis You need to keep track of the Quill object and then when you want to destroy it you set the reference to undefined. Check out my previous post, #3 with the line:

//remove Quill instance window.editor = undefined;

https://github.com/quilljs/quill/issues/633#issuecomment-440623398

If you need to keep track of multiple Quill instances at once, create a JavaScript object and store them under some key.

window.editors = {
   editor1: Quill_instance,
   editor2: Quill_instance,
   ...
}
Onouriis commented 5 years ago

Ok, so I encountered the same issue with this solution: addRange(): The given range isn't in document. When I create the new editor, the previous is already destroyed but it seems listeners still continue to work and I cannot use the new editor with the global toolbar.

PawelGlow commented 5 years ago

@Onouriis It's difficult to help without a code snipped. Do you clear your HTML and reset it?

// clean DOM and set content document.getElementById('previousBoxId').classList.remove('ql-container'); document.getElementById('previousBoxId').innerHTML = content;

Onouriis commented 5 years ago

@PawelGlow I founded a nasty solution. My toolbar was not destroy between each editor I create. So, listeners are applied on the toolbar each time I create a new editor. My solution is to destroy the toolbar when editor is destroy and recreate the toolbar immediately. I think it's not the best solution but I don't know how to remove all listeners without destroy the toolbar.

PawelGlow commented 5 years ago

@Onouriis Yes, that's correct. You need to destroy the toolbar HTML to remove the event listeners. I was also not able to find a better solution in the Quill docs but I have a very large app which handles 50+ toolbars and destroying the toolbar then creating a new one each time I create a new Quill instance has not caused any issues.

Onouriis commented 5 years ago

@PawelGlow Thanks for your help and your advices.

Yexan commented 4 years ago

Hi,

I'm using ngx-quill, an Angular 2+ wrapper for Quill and I'm facing an issue trying to "refresh" the content of the toolbar dynamically.

Here is a link of the issue with a couple of exemples : https://github.com/KillerCodeMonkey/ngx-quill/issues/662

To give you a bit more of a context, I've many different editable zones on my interface and to avoid to create a lot of Quill instances I've many "display only" zones and when I click on one of them I display a Quill editor at the same place as the display zone. With this trick everything works really fine and is pretty lightweight.

But, the thing is that those zones can have different configuration options, which mean a different toolbar content. The issue is that when you declare a Quill instance with a custom toolbar, it needs to be already rendered in the DOM and I can't find a way to re-render the content of the toolbar dynamically. And this is a serious issue because when a custom toolbar is created, select elements are replaced by custom elements span.ql-picker and some ql-picker-options.

I would like to know if there is a way to trigger the "re-templating" of the toolbar content.

bbottema commented 4 years ago

This is also my exact use case. Is there any hope on getting this feature any time soon? I'm also leveraging Quill through ngx-quill btw.

Yexan commented 4 years ago

Hi @bbottema, I'm not sure they will ever implement this sadly 😞 Have you had a look at the issue I mentioned on ngx-quill repo, I've send a couple of exemples in stackblitz. If you want the same config between editable zone (same fonts / fonnt sizes allowed, ...) you can trick the thing by using some quill view to display your zones and one instance of quill-editor that you will hide an show on click on a display zone. But if you need different configs between zones, it will be like hell to refresh the toolbar. I'll put back the issue here, feel free to ask. Cheers

minzojian commented 4 years ago

I don't think we need to change the API very much. The only things I really need are:

  1. The ability to attach and detach Quill from additional DOM containers.
  2. The ability to retrieve an editor instance by DOM selector.
  3. New events for editor attachment and detachment, as well as focus and unfocus between editor instances.

Here's a quick example of creating the root Quill object, registering event handlers, attaching a few editors, selecting them for focus, and detaching from them again.

    let quill = new Quill({ toolbar : "#toolbar" });

    quill.on('editor-attach', function(e) { console.log("attached to:" + e.getEditorId());
    quill.on('editor-detach', function(e) { console.log("detached from:" + e.getEditorId());
    quill.on('editor-focus', function(e) { console.log("focused:" + e.getEditorId());
    quill.on('editor-unfocus', function(e) { console.log("unfocused:" + e.getEditorId());

    quill.attach('#editor-a');
    quill.attach('#editor-b');

    var editorA = quill.getEditorInstance('#editor-a");
    editorA.focus();

    var editorB = quill.getEditorInstance('#editor-b");
    editorB.focus();

    quill.detach('#editor-b');
    quill.detach('#editor-a');

The current Quill constructor, which accepts an editor ID...

    let quill = new Quill("#editor", { toolbar : "#toolbar" });

...could continue to be supported, as a shorthand for the more verbose version:

    let quill = new Quill({ toolbar : "#toolbar" });
    quill.attach("#editor");

It might also be nice to declare which toolbar buttons are enabled and disabled for each editor instance, possibly with something like this:

    let quill = new Quill({ toolbar : "#toolbar" });
    quill.attach("#editor", { enabled : [ "#button-bold", "#button-italic" ] });

However, I'd also be happy to add the enable/disable logic to my own event-handler code for the focus and unfocus events.

@benjismith @jhchen i totally agree with this proposal. in my case,i have lots of small textbox in the page that need to be edited(consider it is a design application like Canva.com did), i don't wanna make a mass of Quill instances one by one (thinking that would be a waste of memory), but use the same (only one) instance ATTACH to the textbox which is focused. by the way, looks Canva.com is just using Quill to deal with the textbox editing, i found they are using 'Quill Editor v2.0.0-dev.18', is there any new features in v2 to support dynamic attach? PS, the newest version in Github is v2.0.0-dev.03 what the heck about v2.0.0-dev.18

minzojian commented 4 years ago

i found @Yexan give a hack way to archive. i was thinking the same way, it could be a feasible solution so far.

https://stackblitz.com/edit/ng-quill-editor?file=src/app/app.component.ts

Yexan commented 4 years ago

@minzojian Yes if you have the same config between your zones it's doable. If you need different configs between zones with the same toolbar it won't be a piece of cake. Or at least I didn't see how to do it for now, if someone achieve it feel free to tag me 😊

markgarbers commented 4 years ago

@PawelGlow thanks for your suggestion... I came to the same conclusion after going through the same process as you. Worked a treat and much more efficient than running multiple Quill editors at the same time.

itz-vivekpawar commented 4 years ago

I have three editors in My Webpage

Here my Js File

`!function(o){"use strict";

var e=Quill.import("formats/font");

e.whitelist=["sofia","slabo","roboto","inconsolata","ubuntu"],

Quill.register(e,!0);

new Quill("#bubble-container .editor",{bounds:"#bubble-container .editor",modules:{formula:!0,syntax:!0},theme:"bubble"}),

new Quill("#snow-container .editor",{bounds:"#snow-container .editor",modules:{formula:!0,syntax:!0,toolbar:"#snow-container .quill-toolbar"},theme:"snow"}),

new Quill("#full-container .editor",{bounds:"#full-container .editor",modules:{formula:!0,syntax:!0,toolbar:[[{font:[]},{size:[]}],["bold","italic","underline","strike"],[{color:[]},{background:[]}],[{script:"super"},{script:"sub"}],[{header:"1"},{header:"2"},"blockquote","code-block"],[{list:"ordered"},{list:"bullet"},{indent:"-1"},{indent:"+1"}],["direction",{align:[]}],["link","image","video","formula"],["clean"]]},theme:"snow"});o("select[class^='ql-'], input[data-link]").addClass("browser-default")}((window,document,jQuery));`

PawelGlow commented 4 years ago

I have three editors in My Webpage

Here my Js File

`!function(o){"use strict";

var e=Quill.import("formats/font");

e.whitelist=["sofia","slabo","roboto","inconsolata","ubuntu"],

Quill.register(e,!0);

new Quill("#bubble-container .editor",{bounds:"#bubble-container .editor",modules:{formula:!0,syntax:!0},theme:"bubble"}),

new Quill("#snow-container .editor",{bounds:"#snow-container .editor",modules:{formula:!0,syntax:!0,toolbar:"#snow-container .quill-toolbar"},theme:"snow"}),

new Quill("#full-container .editor",{bounds:"#full-container .editor",modules:{formula:!0,syntax:!0,toolbar:[[{font:[]},{size:[]}],["bold","italic","underline","strike"],[{color:[]},{background:[]}],[{script:"super"},{script:"sub"}],[{header:"1"},{header:"2"},"blockquote","code-block"],[{list:"ordered"},{list:"bullet"},{indent:"-1"},{indent:"+1"}],["direction",{align:[]}],["link","image","video","formula"],["clean"]]},theme:"snow"});o("select[class^='ql-'], input[data-link]").addClass("browser-default")}((window,document,jQuery));`

What is the problem you are trying to solve? @itz-vivekpawar

maxfahl commented 4 years ago

Hey guys!

I've created a sample React app to show how I resolved this issue. This is basically how it works:

I'm creating one instance of Quill, using a custom toolbar positioned at the top. The editor element is placed in a temporary, hidden, container. When the user double clicks any of the three text containers (Editables), the editor element will be transplanted form the temporary container to a new location inside the Editable. If a user hits the escape key, the Editable will be deactivated, moving the editor element back to the temporary container.

You can test it here: https://codesandbox.io/s/hungry-pine-o8oh9?file=/src/App.js GitHub repo: https://github.com/maxfahl/Quill-Edit-Multiple

Feel free to use the code however you'd like.

NervisWreck commented 3 years ago

Hey guys!

I've created a sample React app to show how I resolved this issue. This is basically how it works:

I'm creating one instance of Quill, using a custom toolbar positioned at the top. The editor element is placed in a temporary, hidden, container. When the user double clicks any of the three text containers (Editables), the editor element will be transplanted form the temporary container to a new location inside the Editable. If a user hits the escape key, the Editable will be deactivated, moving the editor element back to the temporary container.

You can test it here: https://codesandbox.io/s/hungry-pine-o8oh9?file=/src/App.js GitHub repo: https://github.com/maxfahl/Quill-Edit-Multiple

Feel free to use the code however you'd like.

I am not familiar with React and would like to achieve the same functionality with vanilla js, albeit without the need to double click. Any suggestions on how to do the same thing without React?

maxfahl commented 3 years ago

I'm quite busy these days and will not be help you, sorry. My suggestion is that you take a look at the code, the basics of what I'm doing should be quite understandable if you're familiar with javascript.

miczed commented 3 years ago

I went with the second option that Pawel proposed but did some slight adjustments. Every quill instance will create its own toolbar instance that will be added to a global toolbarContainer. Whenever a quill instance is selected / deselected, all the other toolbars within that global container are hidden and only the relevant one is displayed.

var toolbarOptions = [
  ['bold', 'italic', 'underline', 'strike'],        // toggled buttons
  ['blockquote', 'code-block'],

  [{ 'header': 1 }, { 'header': 2 }],               // custom button values
  [{ 'list': 'ordered'}, { 'list': 'bullet' }],
  [{ 'script': 'sub'}, { 'script': 'super' }],      // superscript/subscript
  [{ 'indent': '-1'}, { 'indent': '+1' }],          // outdent/indent
  [{ 'direction': 'rtl' }],                         // text direction

  [{ 'size': ['small', false, 'large', 'huge'] }],  // custom dropdown
  [{ 'header': [1, 2, 3, 4, 5, 6, false] }],

  [{ 'color': [] }, { 'background': [] }],          // dropdown with defaults from theme
  [{ 'font': [] }],
  [{ 'align': [] }],

  ['clean']                                         // remove formatting button
];

var quillOptions = {
  modules: {
    toolbar: toolbarOptions
  },
  theme: 'snow'
}

initQuill(document.querySelector('#editor'), document.querySelector('#toolbar'))
initQuill(document.querySelector('#editor2'),document.querySelector('#toolbar'))
initQuill(document.querySelector('#editor3'),document.querySelector('#toolbar'))
initQuill(document.querySelector('#editor4'),document.querySelector('#toolbar'))
initQuill(document.querySelector('#editor5'),document.querySelector('#toolbar'))
initQuill(document.querySelector('#editor6'),document.querySelector('#toolbar'))

function initQuill(element, globalToolbarContainer) {
  var quill = new Quill(element, quillOptions);
  quill.on('selection-change', (selection) => selectionChange(selection,quill));
  var toolbar = quill.getModule('toolbar');
  globalToolbarContainer.appendChild(toolbar.container)

  // search for existing toolbar and hide it
  prevToolbar = toolbar.container.previousElementSibling
  if (prevToolbar) {
    hideToolbar(prevToolbar)
  }
}

function selectionChange(selection,quill) {
  console.log("selection change",selection,quill)
  const toolbar = quill.getModule('toolbar')
  if(selection == null) {
    hideToolbar(toolbar.container)
    console.log('quill 1 lost focus')
  } else {
    showToolbar(toolbar.container)
  }
}

function hideToolbar(toolbarContainer) {
  toolbarContainer.classList.add('hidden')
}

function showToolbar(toolbarContainer) {
  // hide all other toolbars
  const siblings = getSiblings(toolbarContainer).map((elem) => elem.classList.add('hidden'))
  toolbarContainer.classList.remove('hidden')
}

function getSiblings(elem) {
    // Setup siblings array and get the first sibling
    var siblings = [];
    var sibling = elem.parentNode.firstChild;

    // Loop through each sibling and push to the array
    while (sibling) {
        if (sibling.nodeType === 1 && sibling !== elem) {
            siblings.push(sibling);
        }
        sibling = sibling.nextSibling
    }

    return siblings;
};

I created a codepen that demonstrates this solution. It seems to work quite well.

For my use case I will never have more than 5-7 quill instances at the same time so it shouldn't affect the performance that much.

JGrimbert commented 3 years ago

@Yexan @minzojian This solution dont respect the history (ctrl-z)

I did this :

        class ToolbarAlt extends Toolbar {
          resetToolbar () {
            this.container.childNodes.forEach(el => {
              const clone = el.cloneNode(true);
              el.parentNode.replaceChild(clone, el);
            });
            this.container.childNodes.forEach((input) => {
              this.attach(input);
            }, this);
          }
       }
       Quill.register('modules/toolbar', ToolbarAlt, true);

And then call it with the quill.getModule('toolbar').resetToolbar when focusin an editor.

It needs further ajustements but it seems not a bad way

jsduffy commented 3 years ago

@JGrimbert could you provide a full example (like codesandbox or something) of this?