primeng icon indicating copy to clipboard operation
primeng copied to clipboard

Component: Editor - Upgrade Quill to 2.0

Open luin opened this issue 2 years ago • 28 comments

Describe the bug

Hi 👋 I'm the maintainer of Quill and we are currently working actively for Quill 2.0. Given PrimeNG is still using Quill 1.3, I'm wondering if you'd like to take a PR of upgrading Quill to 2.0? If that's the case, I'd happy to create a PR.

Looking at the code, I think we can make the component support ^1.3 and ^2.0 at the same time so that PrimeNG can release it without a major version bump. There are some option changes introduced in 2.0 so we will also need to update the API documentation accordingly.

Environment

Quill 2.0

Reproducer

No response

Angular version

Not applied

PrimeNG version

17.5.0

Build / Runtime

Angular CLI App

Language

TypeScript

Node version (for AoT issues node --version)

Not applied

Browser(s)

No response

Steps to reproduce the behavior

No response

Expected behavior

No response

luin avatar Feb 06 '24 06:02 luin

Is there any timeframe for the update to Quill 2.0 for <p-editor>?

It would help clear up the console warnings. It's not causing any actual runtime issues at the moment, but it would be nice to get ahead of it.

[Violation] Listener added for a synchronous 'DOMNodeInserted' DOM Mutation Event. This event type is deprecated and work is underway to remove it from this browser. Usage of this event listener will cause performance issues today, and represents a risk of future incompatibility. Consider using MutationObserver instead.

image

psarno avatar Mar 10 '24 10:03 psarno

Apparently they're deprecating mutation events this July.

Is Quill 2.0 in the roadmap? Or any workaround?

dgarciarubio avatar Mar 18 '24 10:03 dgarciarubio

As you can see Quill is again under active development since September 2023. The new v2.0 version, which will be released soon no longer has the mutation events which will be removed from Chrome/Chromium on July 2024 (Chromium 127). https://github.com/quilljs/quill/issues/3806#issuecomment-1933250524 https://github.com/quilljs/quill/releases

I do not think the community will switch to TinyMCE, because for open-source it is limited to 1,000 editor loads per month and the CKEditor has many features behind a paywall, for example paste from Microsoft Word, which works in Quill v2.0.

ThoSap avatar Apr 10 '24 15:04 ThoSap

Hello, I was testing [email protected] with PrimeNg 17.13 in Angular 17, I am using a reactive form and it does not recognize the default value of a "formControlName". I tried downgrading the version to [email protected] and it works but I get the "DOMNodeInserted" warning

Any help is welcome.

pacozevallos avatar Apr 13 '24 17:04 pacozevallos

In the transitioning phase until the PrimeNG Editor supports Quill v2.0.0 I'm using ngx-quill with the following styles.scss override, so the editor looks the same.

// Copied from PrimeNG _editor.scss and modified for ngx-quill
quill-editor {
  .ql-toolbar {
    background: $editorToolbarBg;
    border-top-right-radius: $borderRadius;
    border-top-left-radius: $borderRadius;

    &.ql-snow {
      border: $editorToolbarBorder;

      .ql-stroke {
        stroke: $editorToolbarIconColor;
      }

      .ql-fill {
        fill: $editorToolbarIconColor;
      }

      .ql-picker {
        // Custom Quill overrides start
        &:not(.ql-color-picker) {
          width: unset;
        }
        // Custom Quill overrides end

        .ql-picker-label {
          // Custom Quill overrides start
          display: flex;
          align-items: center;
          justify-content: space-between;
          // Custom Quill overrides end
          border: 0 none;
          color: $editorToolbarIconColor;

          // Custom Quill overrides start
          svg {
            position: relative;
            margin-top: unset;
            top: unset;
            right: unset;
          }
          // Custom Quill overrides end

          &:hover {
            color: $editorToolbarIconHoverColor;

            .ql-stroke {
              stroke: $editorToolbarIconHoverColor;
            }

            .ql-fill {
              fill: $editorToolbarIconHoverColor;
            }
          }
        }

        &.ql-expanded {
          .ql-picker-label {
            color: $editorToolbarIconHoverColor;

            .ql-stroke {
              stroke: $editorToolbarIconHoverColor;
            }

            .ql-fill {
              fill: $editorToolbarIconHoverColor;
            }
          }

          .ql-picker-options {
            background: $inputOverlayBg;
            border:$inputOverlayBorder;
            box-shadow:$inputOverlayShadow;
            border-radius: $borderRadius;
            padding: $inputListPadding;

            .ql-picker-item {
              color: $inputListItemTextColor;

              &:hover {
                color: $inputListItemTextHoverColor;
                background: $inputListItemHoverBg;
              }
            }
          }

          &:not(.ql-icon-picker) {
            .ql-picker-item {
              padding: $inputListItemPadding;
            }
          }
        }
      }
    }
  }

  .ql-container {
    border-bottom-right-radius: $borderRadius;
    border-bottom-left-radius: $borderRadius;

    &.ql-snow {
      border: $editorContentBorder;
    }

    .ql-editor {
      background: $inputBg;
      color: $inputTextColor;
      border-bottom-right-radius: $borderRadius;
      border-bottom-left-radius: $borderRadius;
    }
  }

  .ql-snow.ql-toolbar button:hover,
  .ql-snow.ql-toolbar button:focus {
    color: $editorToolbarIconHoverColor;

    .ql-stroke {
      stroke: $editorToolbarIconHoverColor;
    }

    .ql-fill {
      fill: $editorToolbarIconHoverColor;
    }
  }

  .ql-snow.ql-toolbar button.ql-active,
  .ql-snow.ql-toolbar .ql-picker-label.ql-active,
  .ql-snow.ql-toolbar .ql-picker-item.ql-selected {
    color: $editorIconActiveColor;

    .ql-stroke {
      stroke: $editorIconActiveColor;
    }

    .ql-fill {
      fill: $editorIconActiveColor;
    }

    .ql-picker-label {
      color: $editorIconActiveColor;
    }
  }
}

// For ngx-quill component quill-view-html
quill-view-html {
  .ql-container {
    .ql-editor {
      color: $inputTextColor;
    }
  }
}

Provider for Angular Standalone:

provideQuillConfig({
      modules: {
        // This enables syntax highlighting using highlight.js which then also requires us to install this dependency
        syntax: false,
        toolbar: [
          [
            {'font': []},
            {'header': 1},
            {'header': 2},
            {'size': ['small', false, 'large', 'huge']},
            'clean',
          ],
          [
            'bold',
            'italic',
            'underline',
            'strike',
            'code',
            {'script': 'sub'},
            {'script': 'super'},
          ],
          [
            {'color': []},
            {'background': []},
          ],
          [
            'blockquote',
            {'align': []},
            {'list': 'ordered'},
            {'list': 'bullet'},
            {'indent': '-1'},
            {'indent': '+1'},
          ],
          [
            'link',
            'image',
            'code-block',
          ],
        ],
      },
    }),

ThoSap avatar Apr 14 '24 07:04 ThoSap

quill 2.0 is out and reported as a security issue (disputed but still concerning): CVE-2021-3163 / GHSA-4943-9vgg-gr5r

@luin reported the upgrade almost 3 months ago and also proposed to help to build a PR.

If we had the fact that quill 1.3 won't work in Chrome this summer after deprecating mutation events this July could it be possible for the community to look at this issue and collaborate with @luin to build a fix ?

Thanks a lot

Note: All users upgrading Quill to v2 will have now their editors broken.

FYI @stephanj

aheritier avatar Apr 21 '24 19:04 aheritier

Looks like only the scrollingContainer option is affected: in v2, Quill auto detects the scrolling container, so the option would be ignored. We can add a notice in the documentation about the option is only needed for v1.

I am using a reactive form and it does not recognize the default value of a "formControlName"

Not familiar with Angular, looking at the code and don't see any usages that could break in v2. Not sure what caused it.

luin avatar Apr 22 '24 02:04 luin

I didn't dig a lot. Doing only the upgrade makes the editor empty (no visible error in the console, neither in the build). It's loaded but data are not injected.

Screenshot 2024-04-22 at 07 41 32

aheritier avatar Apr 22 '24 05:04 aheritier

@aheritier Good catch! Actually 2.0 changed the signature of clipboard.convert() that it now accepts an object with two fields html/text. Also, for getting the html content, it's recommended to use getSemanticHTML() since innerHTML may contain elements that are only needed for editing (e.g. toolbars).

Here's the changes that make the demo work for me:

diff --git a/src/app/components/editor/editor.ts b/src/app/components/editor/editor.ts
index 9e578437f..ab7c767ad 100755
--- a/src/app/components/editor/editor.ts
+++ b/src/app/components/editor/editor.ts
@@ -223,7 +223,7 @@ export class Editor implements AfterContentInit, ControlValueAccessor {
         if (this.quill) {
             if (value) {
                 const command = (): void => {
-                    this.quill.setContents(this.quill.clipboard.convert(this.value));
+                    this.quill.setContents(this.quill.clipboard.convert({ html: this.value }));
                 };
 
                 if (this.isAttachedQuillEditorToDOM) {
@@ -296,12 +296,12 @@ export class Editor implements AfterContentInit, ControlValueAccessor {
         });
 
         if (this.value) {
-            this.quill.setContents(this.quill.clipboard.convert(this.value));
+            this.quill.setContents(this.quill.clipboard.convert({ html: this.value }));
         }
 
         this.quill.on('text-change', (delta: any, oldContents: any, source: any) => {
             if (source === 'user') {
-                let html = DomHandler.findSingle(editorElement, '.ql-editor').innerHTML;
+                let html = this.quill.getSemanticHTML();
                 let text = this.quill.getText().trim();
                 if (html === '<p><br></p>') {
                     html = null;

luin avatar Apr 22 '24 06:04 luin

https://slab.com/blog/announcing-quill-2-0/ https://quilljs.com/docs/upgrading-to-2-0/ https://quilljs.com/

ThoSap avatar Apr 29 '24 10:04 ThoSap

See GitHub PrimeNG discussions: https://github.com/orgs/primefaces/discussions/367 https://github.com/orgs/primefaces/discussions/1108 https://github.com/orgs/primefaces/discussions/1300 https://github.com/orgs/primefaces/discussions/1445 https://github.com/orgs/primefaces/discussions/1716

ThoSap avatar Apr 29 '24 11:04 ThoSap

Note that we could make the change backward compatible by checking Quill.version and only make the changes when Quill.version is present and it doesn't start with 1.

luin avatar Apr 29 '24 12:04 luin

We're part of Visma and our source code is automatically scanned by Aikido... quill is used in prime-ng's p-editor... which we use in our main application.

Aikido reports quill as a security risk and advises to upgrade 1.3.7 => 2.0.0... We did, it looked OK, but on save of our document the content isn't saved.

So now we either face security-point penalties or switching to a different editor...

jcoppieters avatar May 03 '24 11:05 jcoppieters

Any idea when (or if) the upgrade to quill 2.0 is planned?

jcoppieters avatar May 03 '24 11:05 jcoppieters

I previously used the following workaround with PrimeNG 17.12.0 and Quill 2.0.0.

<p-editor (onTextChange)="textChange()" [(ngModel)]="content" [style]="{height: '400px'}"/>
content?: {
    html: string | undefined,
    text: string | undefined,
};

// In any method or service
this.content = { html: c.content, text: undefined });

// When editing/"saving"
// You do not necessarily need this callback in the `<p-editor/>`, the content variable will always have the last value due to the Angular model/two-way binding
textChange(): void {
  if (!this.content) {
    // Your logic here
  }

  // Workaround for Quill v2.0.X until PrimeNG supports it officially
  const htmlContent = this.content as unknown as string;
}

ThoSap avatar May 03 '24 12:05 ThoSap

I didn't dig a lot. Doing only the upgrade makes the editor empty (no visible error in the console, neither in the build). It's loaded but data are not injected.

Screenshot 2024-04-22 at 07 41 32

I have the same problem! How can I solve it?

evertonrivas avatar May 04 '24 14:05 evertonrivas

After upgrading to PrimeNG 17.13.0 the quill component is still broken for me: image

I already tried to update quill to 2.0.1 but didn't do anything. To be more specific, this issue only happens when you try to use the Editor inside a dialog

roliveiradvt avatar May 06 '24 14:05 roliveiradvt

I didn't dig a lot. Doing only the upgrade makes the editor empty (no visible error in the console, neither in the build). It's loaded but data are not injected.

I have the same problem! How can I solve it?

Using quill version 2.0+ with reactive forms introduces the bug that the value is not displayed correctly. Here's how I fixed it temporarily while waiting for p-editor to be updated:

  1. Change the quill version to 1.3.7 in package.json
  2. npm i --reinstall

Rodesc avatar May 06 '24 15:05 Rodesc

After upgrading to PrimeNG 17.13.0 the quill component is still broken for me: image

I already tried to update quill to 2.0.1 but didn't do anything. To be more specific, this issue only happens when you try to use the Editor inside a dialog

Looks like you didn't load the css files correctly. See the bottom of this page: https://www.primefaces.org/primeng-v14-lts/editor

Rodesc avatar May 06 '24 15:05 Rodesc

After upgrading to PrimeNG 17.13.0 the quill component is still broken for me: image I already tried to update quill to 2.0.1 but didn't do anything. To be more specific, this issue only happens when you try to use the Editor inside a dialog

Looks like you didn't load the css files correctly. See the bottom of this page: https://www.primefaces.org/primeng-v14-lts/editor

image

They are there, the editor doesn't work ONLY IF it is inside the dialog. I have another instance of the editor working in my application, just the one inside the dialog doesn't work (tried adding to other dialogs just to make sure that was the issue)

roliveiradvt avatar May 06 '24 15:05 roliveiradvt

i'm working on an angular library dependent on primeng ^17.x.x unfortunately blocked due to

  • optimization bailouts due to quill 1.3.7 dependency
  • extra ##.js files alongside scripts.js that need to be loaded when affected modules are initialized

as per

  • transitive dependency on quill 1.3.7 after npm i primeng
  • https://github.com/primefaces/primeng/blob/master/package.json:devDependencies:quill

the error node_modules\primeng\fesm2022\primeng-editor.mjs depends on 'quill'. CommonJS or AMD dependencies can cause optimization bailouts. For more info see: https://angular.io/guide/build#configuring-commonjs-dependencies

the error artifact (partial scripts.js minification) is generated whether or not primeng::EditorModule is imported image

package.json at quill 2.0.1

    "primeng": "^17.16.0",
    "quill": "^2.0.1",
    "rxjs": ">=7.5.4",
    "tslib": ">=2.3.0",

looking forward to the successful pr validation

vigouredelaruse avatar May 06 '24 22:05 vigouredelaruse

@luin I have a question. so in the old version and even in rc2 valueGetter: (quillEditor: QuillType, editorElement: HTMLElement) => string | any; method looked like this, but in the newer version and in the latest one quill has this change valueGetter: import("@angular/core").InputSignal<(quillEditor: QuillType) => string | any>; which is great that quill uses signals. In my code the usage is like this.

  editor: Quill;
  const editorValue = this.quillEditorComponent.valueGetter();

    let noChanges = editorValue(this.editor) === this.formControl.value;
    if (noChanges) return;

    // use case falsy values (example, new/temp entity)
    noChanges = !editorValue(this.editor) && !this.formControl.value;

which seems to be solution for my problem , but the test fails test:

    beforeEach(() => {
      component.formControl = new UntypedFormControl('', { updateOn });
      markAsDirtySpy = spyOn(component.formControl, 'markAsDirty');
      setValueSpy = spyOn(component.formControl, 'setValue');
      qecValidateSpy = spyOn(component.quillEditorComponent, 'validate').and.returnValue(null);
      qecValueGetterSpy = spyOn(component.quillEditorComponent, 'valueGetter');
    });

    it('should update value', () => {
      component.ngOnDestroy();
      expect(markAsDirtySpy).toHaveBeenCalled();
      expect(setValueSpy).toHaveBeenCalledWith(expectedUpdateValue);
    });

error: TypeError: editorValue is not a function i was not able to find proper way of valueGetter() for version 2.0

EdManukyan avatar May 07 '24 23:05 EdManukyan

@EdManukyan I don't think the code belongs to Quill so I may not be the best person for this question

luin avatar May 08 '24 00:05 luin

@luin my apologies this should been asked for the ngx-quill, and thanks for the quick response and beautiful upgrade of the Quill

EdManukyan avatar May 08 '24 15:05 EdManukyan

Hi @luin, any idea when (or if) the fix for v. 2.0 is planned?

MartinaAnt avatar May 28 '24 06:05 MartinaAnt

@MartinaAnt I'm not the maintainer of this repo but the patch I proposed should make this library work with Quill 2.0

luin avatar May 28 '24 09:05 luin

Hi @luin,

Thanks for the support! We've added it into 18.0.0-rc.1

cetincakiroglu avatar Jun 13 '24 12:06 cetincakiroglu

@cetincakiroglu can you please include the pull request #15764 already with the PrimeNG 17.18.2 release?

I don't think anything speaks against it as this PR both supports Quill 1.X.X and 2.X.X, then we could use Angular 17 + PrimeNG 17.18.2 and Quill after the 25th of July 2024.

And this should probably also be backported to older PrimeNG LTS versions.

ThoSap avatar Jun 18 '24 11:06 ThoSap

I previously used the following workaround with PrimeNG 17.12.0 and Quill 2.0.0.

<p-editor (onTextChange)="textChange()" [(ngModel)]="content" [style]="{height: '400px'}"/>
content?: {
    html: string | undefined,
    text: string | undefined,
};

// In any method or service
this.content = { html: c.content, text: undefined });

// When editing/"saving"
// You do not necessarily need this callback in the `<p-editor/>`, the content variable will always have the last value due to the Angular model/two-way binding
textChange(): void {
  if (!this.content) {
    // Your logic here
  }

  // Workaround for Quill v2.0.X until PrimeNG supports it officially
  const htmlContent = this.content as unknown as string;
}

I'm a newbie in Angular, it works for me. Thanks very much!

tuankhoiutc260 avatar Jul 02 '24 11:07 tuankhoiutc260

🥳🥳🥳 https://github.com/primefaces/primeng/releases/tag/17.18.3

Thanks @fabiancabau @luin @cetincakiroglu

ThoSap avatar Jul 04 '24 12:07 ThoSap