게시글을 작성시에 에디터는 필수로 들어가는 요소이다
vue3와 어울리는 쓸만한 editor를 찾다 커스텀도 자유로이 가능한 ckeditor를 발견했다
아래는 내가 사용한 ckeditor사용방법이다.
1. ckeditor 패키지 설치
npm install --save @ckeditor/ckeditor5-vue @ckeditor/ckeditor5-build-classic
2. ckeditor 설정
import { createApp } from "vue";
import { createPinia } from "pinia";
import router from "@/routes";
import CKEditor from '@ckeditor/ckeditor5-vue'
import "@/assets/css/editor/content-style.css";
let app = createApp(App);
let pinia = createPinia();
app.config.globalProperties.$axios = axios;
app.use(router);
app.use(pinia);
app.use(CKEditor);
app.mount("#app");
3. ckeditor 사용
- 기존에 content가 존재할 시 props로 content를 받는다
- props데이터를 바인딩할 수 있도록 v-model에 text를 바인딩 한다( 6번의 watch 함수에서 기존 데이터 text변수에 주입 )
<template>
<Ckeditor
:editor="editor"
v-model="text"
:config="editorConfig"
@ready="onEditorReady"
></Ckeditor>
</template>
<script setup>
import { ClassicEditor } from "@ckeditor/ckeditor5-editor-classic";
import { Essentials } from "@ckeditor/ckeditor5-essentials";
import { Bold, Italic } from "@ckeditor/ckeditor5-basic-styles";
import { BlockQuote } from "@ckeditor/ckeditor5-block-quote";
import { Font } from "@ckeditor/ckeditor5-font";
import { Link } from "@ckeditor/ckeditor5-link";
import { Paragraph } from "@ckeditor/ckeditor5-paragraph";
import { Indent } from "@ckeditor/ckeditor5-indent";
import { List } from "@ckeditor/ckeditor5-list";
import { Heading } from "@ckeditor/ckeditor5-heading";
import {
Table,
TableColumnResize,
TableToolbar,
} from "@ckeditor/ckeditor5-table";
import { TextTransformation } from "@ckeditor/ckeditor5-typing";
import { Alignment } from "@ckeditor/ckeditor5-alignment";
import {
Image,
ImageCaption,
ImageStyle,
ImageToolbar,
ImageUpload,
ImageResize,
} from "@ckeditor/ckeditor5-image";
import { reactive, ref, watch } from "vue";
import UploadAdapter from "#/Editor/UploadAdapter";
import axios from "@/utils/axiosInstance.js";
let text = ref();
const props = defineProps({
selectedContent: String,
boardIdx: Number,
isContentUpdating: Boolean,
});
const emit = defineEmits([
"update:modelValue",
"update:images",
"update:deletedImages",
]);
const editor = ClassicEditor;
const editorConfig = {
extraPlugins: [CustomUploadAdapterPlugin],
plugins: [
Essentials,
Heading,
Bold,
Italic,
Font,
Link,
Paragraph,
BlockQuote,
Indent,
List,
Table,
TableToolbar,
TableColumnResize,
TextTransformation,
Alignment,
Image,
ImageCaption,
ImageStyle,
ImageToolbar,
ImageUpload,
ImageResize,
],
toolbar: {
items: [
"heading",
"bold",
"italic",
"fontColor",
"fontBackgroundColor",
"link",
"imageUpload",
"indent",
"outdent",
"numberedList",
"alignment",
"blockQuote",
"undo",
"redo",
"insertTable",
],
},
image: {
toolbar: [
"toggleImageCaption",
"imageStyle:inline",
"imageStyle:block",
"imageStyle:side",
],
},
table: {
contentToolbar: [
"tableColumn",
"tableRow",
"mergeTableCells",
"tableProperties",
"tableCellProperties",
],
},
language: "ko",
};
</script>
<style>
@import "@ckeditor/ckeditor5-editor-classic/theme/classiceditor.css";
.ck.ck-editor {
font-size: 14px;
}
.ck-editor__editable {
min-height: 300px;
max-height: 350px;
}
.ck-content {
/* padding-left: 24px !important; */
}
ol {
padding-left: 15px !important;
}
strong {
font-weight: bold !important;
}
i {
font-style: italic !important;
}
h2 {
font-size: 2em !important;
}
h3 {
font-size: 1.5em !important;
}
h4 {
font-size: 1.17em !important;
}
</style>
4. 이미지 UploadAdapter.js 구현
// util/axiosInstance.js
import axios from 'axios';
const instance = axios.create({
baseURL: 'http://localhost:8080/api/'
});
// UploadAdapter.js
import axios from '@/utils/axiosInstance.js';
export default class UploadAdapter {
constructor(loader, uplodedImageUrls) {
this.loader = loader;
this.uplodedImageUrls = uplodedImageUrls;
this.loader.file.then((pic) => (this.file = pic));
}
// Starts the upload process.
upload() {
return this.loader.file.then((uploadedFile) => {
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append('upload', uploadedFile);
axios.post('/editor/imgUpload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
.then((res) => {
const returnUrl = res.data.url;
const decodeUrl = decodeURIComponent(returnUrl);
// console.log('File uploaded:', returnUrl, decodeUrl); // 업로드된 URL을 로그로 확인
this.uplodedImageUrls.push(decodeUrl);
console.log('Image URL added to uplodedImageUrls:', this.uplodedImageUrls); // 추가된 URL을 로그로 확인
resolve({
default: decodeUrl,
});
})
.catch((error) => {
console.error('File upload failed:', error);
reject(error.response?.data?.message || 'Upload failed');
});
});
});
}
}
5. image처리 로직 추가
const editor = ClassicEditor;
let imageUrls = reactive([]); // 이미지 URL들을 저장할 배열
let deletedImageUrls = reactive([]); // 이미지 URL들을 저장할 배열
// Custom Upload Adapter Plugin function
function CustomUploadAdapterPlugin(editor) {
editor.plugins.get("FileRepository").createUploadAdapter = (loader) => {
return new UploadAdapter(loader, imageUrls);
};
}
const deleteImageFromServer = async (imageURL) => {
const imageName = imageURL.split("/").pop();
return await axios
.delete(`/editor/imgDelete/${imageName}`)
.then((response) => {
console.log("Image deleted successfully");
})
.catch((error) => {
console.error("Error deleting image:", error);
});
};
const onEditorReady = (editorInstance) => {
editorInstance.model.document.on("change:data", () => {
if (props.isContentUpdating) return;
const editorContent = editorInstance.getData();
for (let i = 0; i < imageUrls.length; i++) {
imageUrls[i] = decodeURIComponent(imageUrls[i]);
if (!editorContent.includes(imageUrls[i])) {
deleteImageFromServer(imageUrls[i]);
let deletedImageName = imageUrls[i].replace(
"http://localhost:8080",
""
);
deletedImageUrls.push(deletedImageName); // 삭제된 이미지를 추가
imageUrls.splice(i, 1);
}
}
});
};
// 모든 이미지를 content에서 추출하여 imageUrls에 넣는다
const extractInitialImageUrls = (content) => {
const parser = new DOMParser();
const doc = parser.parseFromString(content, "text/html");
const imgs = doc.querySelectorAll("img");
imageUrls.length = 0; // 기존 이미지 URL 목록을 초기화합니다.
imgs.forEach((img) => {
imageUrls.push(img.src);
});
};
watch(
() => props.selectedContent,
() => {
text.value = props.selectedContent;
extractInitialImageUrls(props.selectedContent);
},
{ immediate: true }
);
백엔드 이미지 처리 SpringBoot
2024.07.23 - [스프링] - ckeditor의 이미지 업로드 및 삭제 백엔드( SpringBoot )
'프론트 > vue' 카테고리의 다른 글
vue Multi-word오류(eslint) 해결책 (0) | 2023.03.30 |
---|---|
Router-view 사용하기 (0) | 2023.03.30 |
vue3 프로젝트 생성 (0) | 2023.03.30 |