Laravel

[삭제대기] 텍스트 에디터(CKEDITOR5 + VUE + LARAVEL) 이미지 업로드

짱구를왜말려? 2021. 12. 27. 16:09
반응형
SMALL

# What?

- 텍스트에디터로 이미지 업로드하는법

 

# How?

1. 기본 세팅

1) ckeditor5 온라인 빌더로 원하는 기능 추가해서 생성하기

https://ckeditor.com/ckeditor-5/online-builder/

 

CKEditor 5 Online Builder | Create your own editor in 5 steps

Create your own CKEditor 5 build with customized plugins, toolbar and language in 5 simple steps.

ckeditor.com

 

2) build폴더 public/js/ckeditor 폴더에 옮기기

 

4) ckeditor js 연결 및 csrf 토큰 메타태그 추가

 

@ app.blade.php

<!DOCTYPE html>
<html>
<head>
    ...
    <script src="{{ asset('/js/ckeditor/build/ckeditor.js') }}" defer></script>
    <meta name="csrf-token" content="{{ csrf_token() }}">
   
<script>
<body>
@inertia
</body>
</html>

 

2. 커스텀 어댑터 세팅

@ Utils/MyUploadAdapter.js

class MyUploadAdapter {
    constructor(loader) {
        this.loader = loader;
    }

    upload() {
        return this.loader.file
            .then(file => new Promise((resolve, reject) => {
                this._initRequest();
                this._initListeners(resolve, reject, file);
                this._sendRequest(file);
            }));
    }

    abort() {
        if (this.xhr) {
            this.xhr.abort();
        }
    }

    _sendRequest(file) {
        const data = new FormData();

        data.append('upload', file);

        this.xhr.send(data);
    }

    _initListeners(resolve, reject, file) {
        const xhr = this.xhr;
        const loader = this.loader;
        const genericErrorText = `Couldn't upload file: ${file.name}.`;

        xhr.addEventListener('error', () => reject(genericErrorText));
        xhr.addEventListener('abort', () => reject());
        xhr.addEventListener('load', () => {
            const response = xhr.response;

            if (!response || response.error) {
                return reject(response && response.error ? response.error.message : genericErrorText);
            }

            resolve({
                default: response.url
            });
        });

        if (xhr.upload) {
            xhr.upload.addEventListener('progress', evt => {
                if (evt.lengthComputable) {
                    loader.uploadTotal = evt.total;
                    loader.uploaded = evt.loaded;
                }
            });
        }
    }

    _initRequest() {
        const xhr = this.xhr = new XMLHttpRequest();

        // 핵심
        xhr.open('POST', '/ckeditor/upload', true);
        xhr.setRequestHeader('x-csrf-token', document.querySelector('meta[name="csrf-token"]').getAttribute('content'));
        xhr.responseType = 'json';
    }


}

export default MyUploadAdapter

* 그대로 복붙하고 주석 "// 핵심" 부분만 자기가 이미지 업로드 시 서버요청할 url로 변경하면됨

 

@ CustomCkeditor.js

import MyUploadAdapter from "./MyUploadAdapter";

function SimpleUploadAdapterPlugin(editor) {
    editor.plugins.get("FileRepository").createUploadAdapter = (loader) => {
        return new MyUploadAdapter(loader);
    };
}

class CustomCkeditor {
    constructor(target = "#editor", onCreate = () => {}) {
        this.target = target;

        this.onCreate = onCreate;
    }

    create(){
        return ClassicEditor.create( document.querySelector( '#editor' ), {
            licenseKey: '',
            extraPlugins: [SimpleUploadAdapterPlugin],
        } ).then( editor => {
            window.editor = editor;

            // 세팅 완료 후 할일이 있다면
            this.onCreate();

        }).catch( error => {
            console.error( error );
        });
    }
}

export default CustomCkeditor

 

@ Edit.vue

<template>
    <form class="form type02" @submit.prevent="update">
        <div class="m-input-wrap type01">
            <div class="m-input-text type01">
                <input type="text" placeholder="주소" v-model="form.address">
                <p class="m-input-error" v-if="form.errors.address">{{ form.errors.address }}</p>
            </div>
        </div>

        <textarea v-model="form.description" id="editor"></textarea>

        <button class="m-btn type01 bg-primary">저장하기</button>
    </form>
</template>
<script>
import CustomCkeditor from "../../Utils/CustomCkeditor";

export default {
    data() {
        return {
            form: this.$inertia.form({
                address: this.$page.props.user.data.address,
                description: this.$page.props.user.data.description,
            }),
        }
    },

    methods: {
        update() {
            this.form.description = window.editor.getData();

            this.form.post("/users/update", {
                forceFormData: true,
                preserveScroll: true,
                onSuccess: (response) => {
                    if (response.props.flash.error)
                        alert(response.props.flash.error);

                    if (response.props.flash.success)
                        alert(response.props.flash.success);
                }
            });
        },
    },

    mounted() {
        new CustomCkeditor("#editor").create();
    }
}
</script>

* 주석 "//핵심" 부분 보면 form 요청을 보낼 때 editor로부터 입력된 데이터를 받아와서 저장시켜줌.

 

@ style.css

.ck-editor__editable {
    min-height: 600px;
}
.ck-editor h1 {font-size:30px; font-weight:600;}
.ck-editor h2 {font-size:24px; font-weight:600;}
.ck-editor h3 {font-size:20px; font-weight:600;}
.ck-editor ol li {list-style-type: decimal;}
.ck-editor ol li {list-style-type: decimal;}

3. 백엔드 세팅

1) 라우팅 세팅

@ web.php

Route::post("/ckeditor/upload", [\App\Http\Controllers\CkeEditorController::class, "upload"])->name('ckeditor.upload');

 

2) 모델 세팅

@ Ckeditor.php

<?php

namespace App\Models;

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

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

    protected $appends = ["file"];

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

* migration은 별다른 컬럼 추가할 필요 없음

 

3) 컨트롤러 세팅

@ CkeditorController.php

<?php

namespace App\Http\Controllers;

use App\Models\Ckeditor;
use Illuminate\Http\Request;

class CkeEditorController extends Controller
{
    public function upload(Request $request)
    {
        $ckeEditor = Ckeditor::first();

        if(!$ckeEditor)
            $ckeEditor = Ckeditor::create();

        if($request->hasFile("upload")){
            $media = $ckeEditor->addMedia($request->upload)->toMediaCollection("img", "s3");

            return response()->json([
                "url" => $media->getFullUrl()
            ]);
        }
    }
}

 

LIST