Ngx-quill: Register new module ImageResize

Created on 26 Mar 2017  路  9Comments  路  Source: KillerCodeMonkey/ngx-quill

I'm trying to add a new module in the editor to show image size and can resize it.

I found this module for the Quill editor but I am not able to add it in ngx-quill when it returns the editor in onEditorCreated ()

Component.js
onEditorCreated(quill) { this.editor = quill; this.editor.register('modules/ImageResize',ImageResize); console.log('quill is ready! this is current quill instance object', quill); }

`/**
 * Custom module for quilljs to allow user to resize <img> elements
 * (Works on Chrome, Edge, Safari and replaces Firefox's native resize behavior)
 * @see https://quilljs.com/blog/building-a-custom-module/
 */ 
export class ImageResize {

    constructor(quill, options = {}) {
        // save the quill reference and options
        this.quill = quill;
        this.options = options;

        // bind handlers to this instance
        this.handleClick = this.handleClick.bind(this);
        this.handleMousedown = this.handleMousedown.bind(this);
        this.handleMouseup = this.handleMouseup.bind(this);
        this.handleDrag = this.handleDrag.bind(this);
        this.checkImage = this.checkImage.bind(this);

        // track resize handles
        this.boxes = [];

        // disable native image resizing on firefox
        document.execCommand('enableObjectResizing', false, 'false');

        // respond to clicks inside the editor
        this.quill.root.addEventListener('click', this.handleClick, false);
    }

    handleClick(evt) {
        if (evt.target && evt.target.tagName && evt.target.tagName.toUpperCase() == 'IMG') {
            if (this.img === evt.target) {
                // we are already focused on this image
                return;
            }
            if (this.img) {
                // we were just focused on another image
                this.hide();
            }
            // clicked on an image inside the editor
            this.show(evt.target);
        }
        else if (this.img) {
            // clicked on a non image
            this.hide();
        }
    }

    show(img) {
        // keep track of this img element
        this.img = img;
        this.showResizers();
        this.showSizeDisplay();
        // position the resize handles at the corners
        const rect = this.img.getBoundingClientRect();
        this.positionBoxes(rect);
        this.positionSizeDisplay(rect);
    }

    hide() {
        this.hideResizers();
        this.hideSizeDisplay();
        this.img = undefined;
    }

    showResizers() {
        // prevent spurious text selection
        this.setUserSelect('none');
        // add 4 resize handles
        this.addBox('nwse-resize'); // top left
        this.addBox('nesw-resize'); // top right
        this.addBox('nwse-resize'); // bottom right
        this.addBox('nesw-resize'); // bottom left
        // listen for the image being deleted or moved
        document.addEventListener('keyup', this.checkImage, true);
        this.quill.root.addEventListener('input', this.checkImage, true);
    }

    hideResizers() {
        // stop listening for image deletion or movement
        document.removeEventListener('keyup', this.checkImage);
        this.quill.root.removeEventListener('input', this.checkImage);
        // reset user-select
        this.setUserSelect('');
        this.setCursor('');
        // remove boxes
        this.boxes.forEach(box => document.body.removeChild(box));
        // release memory
        this.dragBox = undefined;
        this.dragStartX = undefined;
        this.preDragWidth = undefined;
        this.boxes = [];
    }

    addBox(cursor) {
        // create div element for resize handle
        const box = document.createElement('div');
        // apply styles
        const styles = {
            position: 'absolute',
            height: '12px',
            width: '12px',
            backgroundColor: 'white',
            border: '1px solid #777',
            boxSizing: 'border-box',
            opacity: '0.80',
            cursor: cursor,
        };
        this.extend(box.style, styles, this.options.handleStyles || {});
        // listen for mousedown on each box
        box.addEventListener('mousedown', this.handleMousedown, false);
        // add drag handle to document
        document.body.appendChild(box);
        // keep track of drag handle
        this.boxes.push(box);
    }

    extend(destination, ...sources) {
        sources.forEach(source => {
            for (let prop in source) {
                if (source.hasOwnProperty(prop)) {
                    destination[prop] = source[prop];
                }
            }
        });
        return destination;
    }

    positionBoxes(rect) {
        // set the top and left for each drag handle
        [
            {left: rect.left - 6, top: rect.top - 6},               // top left
            {left: rect.left + rect.width - 6, top: rect.top - 6},               // top right
            {left: rect.left + rect.width - 6, top: rect.top + rect.height - 6}, // bottom right
            {left: rect.left - 6, top: rect.top + rect.height - 6}, // bottom left
        ].forEach((pos, idx) => {
            this.extend(this.boxes[idx].style, {
                top: Math.round(pos.top + window.pageYOffset) + 'px',
                left: Math.round(pos.left + window.pageXOffset) + 'px',
            });
        });
    }

    handleMousedown(evt) {
        // note which box
        this.dragBox = evt.target;
        // note starting mousedown position
        this.dragStartX = evt.clientX;
        // store the width before the drag
        this.preDragWidth = this.img.width || this.img.naturalWidth;
        // set the proper cursor everywhere
        this.setCursor(this.dragBox.style.cursor);
        // listen for movement and mouseup
        document.addEventListener('mousemove', this.handleDrag, false);
        document.addEventListener('mouseup', this.handleMouseup, false);
    }

    handleMouseup() {
        // reset cursor everywhere
        this.setCursor('');
        // stop listening for movement and mouseup
        document.removeEventListener('mousemove', this.handleDrag);
        document.removeEventListener('mouseup', this.handleMouseup);
    }

    handleDrag(evt) {
        if (!this.img) {
            // image not set yet
            return;
        }
        // update image size
        if (this.dragBox == this.boxes[0] || this.dragBox == this.boxes[3]) {
            // left-side resize handler; draging right shrinks image
            this.img.width = Math.round(this.preDragWidth - evt.clientX - this.dragStartX);
        }
        else {
            // right-side resize handler; draging right enlarges image
            this.img.width = Math.round(this.preDragWidth + evt.clientX - this.dragStartX);
        }
        // reposition the drag handles around the image
        const rect = this.img.getBoundingClientRect();
        this.positionBoxes(rect);
        this.positionSizeDisplay(rect);
    }

    setUserSelect(value) {
        [
            'userSelect',
            'mozUserSelect',
            'webkitUserSelect',
            'msUserSelect'
        ].forEach(prop => {
            // set on contenteditable element and <html>
            this.quill.root.style[prop] = value;
            document.documentElement.style[prop] = value;
        });
    }

    setCursor(value) {
        [
            document.body,
            this.img,
            this.quill.root
        ].forEach(el => el.style.cursor = value);
    }

    checkImage() {
        if (this.img) {
            this.hide();
        }
    }

    showSizeDisplay() {
        if (!this.options.displaySize) {
            return;
        }
        this.display = document.createElement('div');
        // apply styles
        const styles = {
            position: 'absolute',
            font: '12px/1.0 Arial, Helvetica, sans-serif',
            padding: '4px 8px',
            textAlign: 'center',
            backgroundColor: 'white',
            color: '#333',
            border: '1px solid #777',
            boxSizing: 'border-box',
            opacity: '0.80',
            cursor: 'default',
        };
        this.extend(this.display.style, styles, this.options.displayStyles || {});
        document.body.appendChild(this.display);
    }

    hideSizeDisplay() {
        document.body.removeChild(this.display);
        this.display = undefined;
    }

    positionSizeDisplay(rect) {
        if (!this.display || !this.img) {
            return;
        }
        const size = this.getCurrentSize();
        this.display.innerHTML = size.join(' &times; ');
        if (size[0] > 120 && size[1] > 30) {
            // position on top of image
            const dispRect = this.display.getBoundingClientRect();
            this.extend(this.display.style, {
                left: Math.round(rect.left + rect.width + window.pageXOffset - dispRect.width - 8) + 'px',
                top: Math.round(rect.top + rect.height + window.pageYOffset - dispRect.height - 8) + 'px',
            });
        }
        else {
            // position off bottom right
            this.extend(this.display.style, {
                left: Math.round(rect.left + rect.width + window.pageXOffset + 8) + 'px',
                top: Math.round(rect.top + rect.height + window.pageYOffset + 8) + 'px',
            });
        }
    }

    getCurrentSize() {
        return [
            this.img.width,
            Math.round(this.img.width / this.img.naturalWidth * this.img.naturalHeight),
        ];
    }

}
`

Someone know how to ?

help wanted question

Most helpful comment

People who are having difficulty using the quill image resize module with ANGULAR-CLI e ANGULAR 2+
Here's a way to not have to tinker with the webpack.config.ts file

terminal

npm install quill --save
npm install quill-image-resize-module --save

angular-cli.json

"scripts": [
        ...,
        "../node_modules/quill/dist/quill.min.js"
 ]

Componente.ts

import * as QuillNamespace from 'quill';
let Quill: any = QuillNamespace;
import ImageResize from 'quill-image-resize-module';
Quill.register('modules/imageResize', ImageResize);

this.editor_modules = {
      toolbar: {
        container: [
          [{ 'font': [] }],
          [{ 'size': ['small', false, 'large', 'huge'] }],
          ['bold', 'italic', 'underline', 'strike'],
          [{ 'header': 1 }, { 'header': 2 }],
          [{ 'color': [] }, { 'background': [] }],
          [{ 'list': 'ordered' }, { 'list': 'bullet' }],
          [{ 'align': [] }],
          ['link', 'image']
        ]
      },
      imageResize: true
    };

Componente.html
<quill-editor [modules]="editor_modules" [(ngModel)]="content"></quill-editor>

All 9 comments

Could you tell us, what is not working?

But if you follow this guide:
https://quilljs.com/blog/building-a-custom-module/

you see that you need to register the custom module to the global Quill and not to the instance.

Like:

import * as Quill from 'quill';
import ImageResize from 'xxx';

// register globally for QuillJS
Quill.registerModule('imageResize', ImageResize);

....
@Component({ ... })
class YourComponent {
  imageResizeInstance: ImageResize;
  ...
  onEditorCreated(quill) {
    // add custom module to quill editor instance --> it should be working if you pass 'imageResize' to the ngx-quill modules property binding as well ;)
    this.imageResizeInstance = quill.addModule('imageResize');
    ...
  }
}

i try this

import * as Quill from "quill";
import {ImageResize} from "../cabir/cabir-editor/ImageResize";

Quill.registerModule('imageResize', ImageResize);

quill.registerModule is not a function
quill.addModule is not a function

in index.d.ts of quilljs, has interface of Quill, dont see registerModule or addModule


export interface Quill {
        new (container: string | Element, options?: QuillOptionsStatic): Quill;
        deleteText(index: number, length: number, source?: Sources): void;
        disable(): void;
        enable(enabled?: boolean): void;
        getContents(index?: number, length?: number): DeltaStatic;
        getLength(): number;
        getText(index?: number, length?: number): string;
        insertEmbed(index: number, type: string, value: any, source?: Sources): void;
        insertText(index: number, text: string, source?: Sources): DeltaStatic;
        insertText(index: number, text: string, format: string, value: any, source?: Sources): DeltaStatic;
        insertText(index: number, text: string, formats: Formats, source?: Sources): DeltaStatic;
        /**
        * @deprecated Use clipboard.dangerouslyPasteHTML(index: number, html: string, source: Sources)
        */
        pasteHTML(index: number, html: string, source?: Sources): string;
        /**
        * @deprecated Use dangerouslyPasteHTML(html: string, source: Sources): void;
        */
        pasteHTML(html: string, source?: Sources): string;
        setContents(delta: DeltaStatic, source?: Sources): DeltaStatic;
        setText(text: string, source?: Sources): DeltaStatic;
        update(source?: string): void;
        updateContents(delta: DeltaStatic, source?: Sources): DeltaStatic;

        format(name: string, value: any, source?: Sources): DeltaStatic;
        formatLine(index: number, length: number, source?: Sources): DeltaStatic;
        formatLine(index: number, length: number, format: string, value: any, source?: Sources): DeltaStatic;
        formatLine(index: number, length: number, formats: Formats, source?: Sources): DeltaStatic;
        formatText(index: number, length: number, source?: Sources): DeltaStatic;
        formatText(index: number, length: number, format: string, value: any, source?: Sources): DeltaStatic;
        formatText(index: number, length: number, formats: Formats, source?: Sources): DeltaStatic;
        getFormat(range?: RangeStatic): Formats;
        getFormat(index: number, length?: number): Formats;
        removeFormat(index: number, length: number, source?: Sources): void;

        blur(): void;
        focus(): void;
        getBounds(index: number, length?: number): BoundsStatic;
        getSelection(focus?: boolean): RangeStatic;
        hasFocus(): boolean;
        setSelection(index: number, length: number, source?: Sources): void;
        setSelection(range: RangeStatic, source?: Sources): void;

        on(eventName: string, callback: (<T>(delta: T, oldContents: T, source: string) => void) |
            ((name: string, ...args: any[]) => void)): Quill;
        once(eventName: string, callback: (delta: DeltaStatic, source: string) => void): Quill;
        off(eventName: string, callback: (delta: DeltaStatic, source: string) => void): Quill;

        debug(level: string): void;
        import(path: string): any;
        register(path: string, def: any, suppressWarning?: boolean): void;
        register(defs: Formats, suppressWarning?: boolean): void;
        addContainer(className: string, refNode?: any): any;
        addContainer(domNode: any, refNode?: any): any;
        getModule(name: string): any

        clipboard: ClipboardStatic;
    }

okay then follow this:
https://quilljs.com/docs/modules/#extending

use the register function and thats it. Hope this helps

https://quilljs.com/docs/api/#register

EDIT: btw. i think you will get some typescript errors during compilation, because this class is not that clean ;)

Congratulation.

with this Guide for ES6
https://quilljs.com/guides/building-a-custom-module/

import * as Quill from "quill";
import {ImageResize} from "../cabir/cabir-editor/ImageResize";

Quill.register('modules/imageResize', ImageResize);

public options: any;
this.options = {
            toolbar: [
                ['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

                ['link', 'image', 'video']                         // link and image, video
            ],
            imageResize: true
        }

html


<quill-editor [placeholder]="'Digite aqui todo o conteudo do post!'"
                              [modules]="options"
                              (onContentChanged)="onContentChanged($event)"
                              (onEditorCreated)="onEditorCreated($event)"
                              (onSelectionChanged)="onSelectionChanged($event)"
                              [(ngModel)]="editorContent">
                </quill-editor>

its work

but thanks for letting me know. i will add the "counter" example as typescript to ngx-examples.

for your interest:

checkout the last editor with word counter :)
https://killercodemonkey.github.io/ngx-quill-example/

People who are having difficulty using the quill image resize module with ANGULAR-CLI e ANGULAR 2+
Here's a way to not have to tinker with the webpack.config.ts file

terminal

npm install quill --save
npm install quill-image-resize-module --save

angular-cli.json

"scripts": [
        ...,
        "../node_modules/quill/dist/quill.min.js"
 ]

Componente.ts

import * as QuillNamespace from 'quill';
let Quill: any = QuillNamespace;
import ImageResize from 'quill-image-resize-module';
Quill.register('modules/imageResize', ImageResize);

this.editor_modules = {
      toolbar: {
        container: [
          [{ 'font': [] }],
          [{ 'size': ['small', false, 'large', 'huge'] }],
          ['bold', 'italic', 'underline', 'strike'],
          [{ 'header': 1 }, { 'header': 2 }],
          [{ 'color': [] }, { 'background': [] }],
          [{ 'list': 'ordered' }, { 'list': 'bullet' }],
          [{ 'align': [] }],
          ['link', 'image']
        ]
      },
      imageResize: true
    };

Componente.html
<quill-editor [modules]="editor_modules" [(ngModel)]="content"></quill-editor>

@viniciusaugutis
Do you have a .d.ts file about quill-image-resize-module ? or did you made it?

@KillerCodeMonkey works like expected, thx

app.module.ts

QuillModule.forRoot({
   customModules: [
      {
        implementation: PlainClipboard,
        path: 'modules/clipboard'
      }
    ],
  })

plain-clipboard.ts

import Quill from 'quill';

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

export default class PlainClipboard extends Clipboard {
    onPaste (e) {
        e.preventDefault();
        const range = this.quill.getSelection();
        const text = e.clipboardData.getData('text/plain');
        const delta = new Delta()
            .retain(range.index)
            .delete(range.length)
            .insert(text);
        const index = text.length + range.index;
        const length = 0;
        this.quill.updateContents(delta, 'silent');
        this.quill.setSelection(index, length, 'silent');
        this.quill.scrollIntoView();
    }
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

luksireiku picture luksireiku  路  5Comments

KeithGillette picture KeithGillette  路  4Comments

anshumandikshit picture anshumandikshit  路  3Comments

qikong333 picture qikong333  路  5Comments

Fzwael picture Fzwael  路  3Comments