본문 바로가기

asp.net MVC

Quill Editor 사용하기

최근에 에디터를 적용해야 하는 작업이 생겨서, 오랜만에 에디터를 알아보았습니다.

기존에 사용하던 에디터가 생각나서 검색을 하다가 아래와 같은 내용을 보고, 새 에디터로 Quill을 선택하게 되었습니다.

 

기존에 사용하던 에디터와 많이 사용되던 에디터의 보안취약점 관련 기사

https://www.boannews.com/media/view.asp?idx=111334 

 

국내에서 활용도 높은 웹 에디터 8개, 주요 보안 취약점과 해결방안은?

한국인터넷진흥원(KISA)은 중소기업 침해사고 피해지원 서비스 사업 수행 중 홈페이지에 기반한 악성코드 유포 및 개인정보 유출, 해킹 경우지 악용 등의 주요 원인이 웹 에디터 프로그램의 취약

www.boannews.com

그래서, 검색도 하고, GPT에게도 묻고, 다른 프로젝트로 Quill에디터를 선택하신 분을 보고, 저도 이것을 이용하여 작업을 해보기로 했습니다.

 

Quill Editor Official Site

https://quilljs.com/

 

Quill - Your powerful rich text editor

Sailec Light Sofia Pro Slabo 27px Roboto Slab Inconsolata Ubuntu Mono Quill Rich Text Editor Quill is a free, open source WYSIWYG editor built for the modern web. With its modular architecture and expressive API, it is completely customizable to fit any ne

quilljs.com

QuillJs Editor 실행화면

 

[New.cshtml-razor for asp.net mvc]

@Html.TextAreaFor(model => model.Content, new { style = "display: none;" })
<div id="editor"></div>
<div class="container javascript" id="delta-container"></div>

@section styles{
    <link href="~/assets/libs/@('@')quill/dist/quill.snow.css" rel="stylesheet" />
    <style>
        .ql-container {
            height: 400px;
        }
    </style>
}
@section scripts{
    <!-- Quill editor -->
    <script src="~/assets/libs/@('@')quill/dist/quill.min.js"></script>
    <script src="~/assets/libs/@('@')quill/modules/image-resize.min.js"></script>
    <!-- init js -->
    <script src="~/assets/js/pages/quill.new.extended.js"></script>
}


[quilljs 사이트에서 받은 파일에 포함된 파일들 ]

quill.snow.css

quill.min.js

[quilljs를 위한 모듈 다운로드]

image-resize.min.js - https://github.com/kensnyder/quill-image-resize-module

 

GitHub - kensnyder/quill-image-resize-module: A module for Quill rich text editor to allow images to be resized.

A module for Quill rich text editor to allow images to be resized. - GitHub - kensnyder/quill-image-resize-module: A module for Quill rich text editor to allow images to be resized.

github.com

[이미지 업로드/제거를 위한 Custom]

quill.new.extended.js

document.addEventListener('DOMContentLoaded', function () {
    // Listen for image upload event
    quill.getModule('toolbar').addHandler('image', function () {
        var input = document.createElement('input');
        input.setAttribute('type', 'file');
        input.setAttribute('accept', 'image/*');
        input.onchange = function () {
            var file = input.files[0];
            var formData = new FormData();
            formData.append('image', file);
            uploadImage(formData);
        };
        input.click();
    });
    quill.on('text-change', (delta, oldContents, source) => {
        //if (source !== 'user') return;

        const inserted = getImgUrls(delta);
        const deleted = getImgUrls(quill.getContents().diff(oldContents));
        inserted.length && console.log('insert', inserted)
        if (deleted.length > 0) {
            console.log('delete', deleted[0]);
            deleteImage(deleted[0]);
        }
    });

    function getImgUrls(delta) {
        return delta.ops.filter(i => i.insert && i.insert.image).map(i => i.insert.image);
    }

});

var form = document.querySelector('#form');
form.onsubmit = function (event) {
    var editorContent = document.querySelector('#Content');
    console.log(quill.root.innerHTML);
    editorContent.value = quill.root.innerHTML;
    console.log(editorContent.value);
};

var quill = new Quill('#editor', {
    theme: 'snow', // or 'bubble' for a simpler theme
    modules: {
        imageResize: {
            displaySize: true
        },
        toolbar: [
            ['bold', 'italic', 'underline', 'strike'], // Text formatting options
            [{ 'header': [1, 2, 3, 4, 5, 6, false] }], // Headers
            ['blockquote', 'code-block'], // Quote and code block
            [{ 'list': 'ordered' }, { 'list': 'bullet' }], // Ordered and unordered lists
            [{ 'indent': '-1' }, { 'indent': '+1' }], // Indentation
            ['link', 'image', 'video'], // Link, image, and video insertion
            ['clean'] // Remove formatting
        ]
    }
});


// Function to handle image upload
function uploadImage(formData) {
    // Send the image to the server using AJAX or fetch API
    fetch('Url here', { // Update the URL to match your server-side route
        method: 'POST',
        body: formData
    })
        .then(response => response.json())
        .then(result => {
            // Insert the uploaded image into the editor
            const range = quill.getSelection();
            quill.insertEmbed(range.index, 'image', result.imageUrl);
        })
        .catch(error => {
            console.error('Image upload error:', error);
        });
}
function deleteImage(imageUrl) {
    // Send a request to the server to delete the image
    fetch('Url here', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({ imageUrl: imageUrl }) // Send the image URL in the request body
    })
        .then(response => response.json())
        .then(result => {
            if (result.success) {
                // Image deletion was successful, remove the image from the editor
                var delta = { ops: [{ retain: imageUrl.length }, { delete: imageUrl.length }] };
                quill.updateContents(delta);
            } else {
                console.error('Image deletion error:', result.error);
            }
        })
        .catch(error => {
            console.error('Image deletion error:', error);
        });
}


이미지 제거 시, 삭제한 이미지를 추출하는 내용과 관련해서는 https://stackoverflow.com/ 에서 어느 분이 코멘트 달아주신 걸 참고해서 만들었습니다. 출처를 잃어버렸네요. ㅜㅜ

 

여기까지 제가 asp.net mvc 에서 사용하는 quilljs 사용법이었습니다.

이걸 하면서, 많은 걸 알았습니다. 요즘 Editor도 Modeul 별로 구현해야 하는구나 라는 것을..

배움은 끝이 없네요, 더욱 분발해야겠습니다. 이것을 이제라도 알았으니..

 

감사합니다.