프론트엔드/Nuxt

VUE + Quill.js 에디터 #Editor #에디터 #vue

짱구를왜말려? 2023. 6. 14. 16:48
반응형
SMALL

1. cdn 연결하기

* nuxt ssr일 경우 nuxt.config.js가 아니라 default.vue 레이아웃에다 직접 링크 넣어야됨

(다른 레이아웃 파일 사용 시 그 파일에다 삽입)

 

@ default.vue

<template>
    <div id="app">
        <header-vue />

        <pop />

        <section class="flex">
            <aside class="left-side"></aside>

            <transition name="page" mode="out-in">
                <Nuxt />
            </transition>
        </section>

        <footer-vue />
    </div>

</template>

<script>

export default {
    head() {
        return {
            link: [
                {rel: 'stylesheet', type: 'text/css', href: '//cdn.quilljs.com/1.3.4/quill.snow.css'},
     
            ],

            script: [
                {
                    src: '//cdn.quilljs.com/1.3.6/quill.min.js',
                    defer: true
                },
            ],
        }
    },
}
</script>

 

2. 프론트 세팅

@ InputEditor.js

<template>
    <div class="ql-editor">
        <div id="editor" ref="editor" style="height:400px;"></div>
        <input type="file" id="getFile" accept="image/*" style="position: absolute; z-index:-1; opacity:0; left:-1000px; bottom:-1000px;" @change="changeImg">
    </div>

</template>
<script>

export default {
    components: {},

    props: {
        default: "",

        required: {
            default: true
        },
    },

    data() {
        return {
            value: this.default,
            editor: "",
        }
    },

    methods: {
        changeImg(event){
            let formData = new FormData();

            formData.append("file", event.target.files[0]);

            this.$axios.post("/api/images", formData)
                .then(response => {
                    let url = response.data.data;

                    const range = this.editor.getSelection();

                   this.editor.insertEmbed(range.index, 'image', url);

                    /*this.editor.root.innerHTML += `<img src="${response.data.data}" alt=""/>`
                    return response.data;*/
                });
        },

        changeContents() {
            this.$emit("change", this.editor.root.innerHTML);
        },

        imageHandler() {
            document.getElementById('getFile').click();
        }
    },

    mounted() {
        let self = this;
        const toolbarOptions = [
            ['bold', 'italic', 'underline', 'strike'],
            ['blockquote', 'code-block'],
            [{'header': 1}, {'header': 2}],
            [{'list': 'ordered'}, {'list': 'bullet'}],
            [{'script': 'sub'}, {'script': 'super'}],
            [{'indent': '-1'}, {'indent': '+1'}],
            [{'direction': 'rtl'}],
            [{'size': ['small', false, 'large', 'huge']}],
            [{'header': [1, 2, 3, 4, 5, 6, false]}],
            [{'color': []}, {'background': []}],
            [{'font': []}],
            [{'align': []}],
            ['clean', 'image']
        ]

        this.editor = new Quill(this.$refs.editor, {
            modules: {
                toolbar: {
                    container: toolbarOptions,
                    handlers: {
                        'image' : self.imageHandler
                    }
                }
            },
            theme: 'snow',
        })

        this.editor.on("text-change", () => {
            this.changeContents();
        })

        this.editor.pasteHTML(this.default);

        // this.$refs.editor.quill.setContents(this.default);
        // this.$store.commit('setQuillInstance', quill)
    },

    watch: {
        value: function (value, oldValue) {
            this.$emit("change", value);
        }
    }
}
</script>

@ example.vue

<template>
    <input-editor :default="form.description" @change="(data) => {form.description = data}"/>
</template>
<script>

import Form from "@/utils/Form";
export default {
    data(){
        return {
            form: new Form(this.$axios, {
                description: "",
            })
        }
    },
}
</script>

 

3. 백엔드 세팅

@ Image.php (migration도 만들되, 컬럼은 id만 있으면 됨)

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;

class Image extends Model implements HasMedia
{
    use HasFactory, InteractsWithMedia;

    protected $appends = [
        "file",
    ];

    public function registerMediaCollections():void
    {
        $this->addMediaCollection('file')->singleFile();
    }

    public function getFileAttribute()
    {
        if($this->hasMedia('file')) {
            $media = $this->getMedia('file')[0];

            return [
                "name" => $media->file_name,
                "url" => $media->getFullUrl()
            ];
        }

        return null;
    }
}

@ api.php

    Route::post("/images", [\App\Http\Controllers\Api\ImageController::class, "store"]);

@ Api/ImageController.php 

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Image;
use Illuminate\Http\Request;

class ImageController extends ApiController
{
    public function store(Request $request)
    {
        $request->validate([
            "file" => "required"
        ]);

        $image = Image::create();

        $image->addMedia($request->file)->toMediaCollection("file", "s3");

        $media = $image->getMedia('file')[0];

        return $this->respondSuccessfully($media->getFullUrl());
    }
}
LIST