수정 에디터

This commit is contained in:
dyhj625 2025-03-28 16:32:53 +09:00
parent d47ee95f56
commit 9035d339ac
2 changed files with 57 additions and 27 deletions

View File

@ -55,7 +55,7 @@
<div class="col-md-12"> <div class="col-md-12">
<QEditor <QEditor
v-if="contentLoaded" v-if="contentLoaded"
@update:data="content = $event" @update:data="handleEditorDataUpdate"
@update:imageUrls="imageUrls = $event" @update:imageUrls="imageUrls = $event"
@update:uploadedImgList="handleUpdateEditorImg" @update:uploadedImgList="handleUpdateEditorImg"
@update:deleteImgIndexList="handleDeleteEditorImg" @update:deleteImgIndexList="handleDeleteEditorImg"
@ -89,6 +89,7 @@
import { useToastStore } from '@s/toastStore'; import { useToastStore } from '@s/toastStore';
import { useBoardAccessStore } from '@s/useBoardAccessStore'; import { useBoardAccessStore } from '@s/useBoardAccessStore';
import axios from '@api'; import axios from '@api';
import Quill from 'quill';
// //
const $common = inject('common'); const $common = inject('common');
@ -121,25 +122,60 @@
const editorDeleteImgList = ref([]); const editorDeleteImgList = ref([]);
const originalTitle = ref(''); const originalTitle = ref('');
const originalPlainText = ref(''); const originalContent = ref({});
const originalFiles = ref([]); const originalFiles = ref([]);
const contentInitialized = ref(false);
//
const isFirstContentUpdate = ref(true);
//
const handleEditorDataUpdate = (data) => {
content.value = data;
if (isFirstContentUpdate.value) {
originalContent.value = structuredClone(data);
isFirstContentUpdate.value = false;
contentInitialized.value = true;
}
};
function extractPlainText(delta) { function isDeltaChanged(current, original) {
if (!delta || !Array.isArray(delta.ops)) return ''; const Delta = Quill.import('delta');
return delta.ops const currentDelta = new Delta(current || []);
const originalDelta = new Delta(original || []);
const diff = originalDelta.diff(currentDelta);
if (!diff || diff.ops.length === 0) return false;
//
const getPlainText = (delta) =>
(delta.ops || [])
.filter(op => typeof op.insert === 'string') .filter(op => typeof op.insert === 'string')
.map(op => op.insert.trim()) .map(op => op.insert)
.join(' ') .join('');
.trim();
const getImages = (delta) =>
(delta.ops || [])
.filter(op => typeof op.insert === 'object' && op.insert.image)
.map(op => op.insert.image);
const textCurrent = getPlainText(currentDelta);
const textOriginal = getPlainText(originalDelta);
const imgsCurrent = getImages(currentDelta);
const imgsOriginal = getImages(originalDelta);
const textEqual = textCurrent === textOriginal;
const imageEqual = JSON.stringify(imgsCurrent) === JSON.stringify(imgsOriginal);
return !(textEqual && imageEqual); // false
} }
const isChanged = computed(() => { const isChanged = computed(() => {
if (!contentInitialized.value) return false;
const isTitleChanged = title.value !== originalTitle.value; const isTitleChanged = title.value !== originalTitle.value;
const currentPlainText = extractPlainText(content.value); const isContentChanged = isDeltaChanged(content.value, originalContent.value);
const isContentChanged = currentPlainText !== originalPlainText.value;
const currentAttachedFiles = attachFiles.value.filter(f => f.id);
const isFilesChanged = const isFilesChanged =
attachFiles.value.some(f => !f.id) || // id attachFiles.value.some(f => !f.id) || // id
delFileIdx.value.length > 0 || // delFileIdx.value.length > 0 || //
@ -147,12 +183,9 @@
attachFiles.value.filter(f => f.id), // (id ) attachFiles.value.filter(f => f.id), // (id )
originalFiles.value originalFiles.value
); );
return isTitleChanged || isContentChanged || isFilesChanged ; return isTitleChanged || isContentChanged || isFilesChanged ;
}); });
watch(isChanged, (val) => {
//console.log('🔄 isChanged changed:', val);
});
// //
function isSameFiles(current, original) { function isSameFiles(current, original) {
@ -197,15 +230,11 @@
title.value = boardData.title || '제목 없음'; title.value = boardData.title || '제목 없음';
content.value = boardData.content || '내용 없음'; content.value = boardData.content || '내용 없음';
originalTitle.value = title.value; originalTitle.value = title.value;
originalContent.value = structuredClone(boardData.content);
contentInitialized.value = true;
contentLoaded.value = true; contentLoaded.value = true;
}; };
watch(content, (val) => {
if (contentLoaded.value && !originalPlainText.value) {
originalPlainText.value = extractPlainText(val);
}
}, { immediate: true });
const handleUpdateEditorImg = item => { const handleUpdateEditorImg = item => {
editorUploadedImgList.value = item; editorUploadedImgList.value = item;
}; };
@ -359,6 +388,7 @@
onMounted(async () => { onMounted(async () => {
if (currentBoardId.value) { if (currentBoardId.value) {
fetchBoardDetails(); fetchBoardDetails();
} else { } else {
console.error('잘못된 게시물 ID:', currentBoardId.value); console.error('잘못된 게시물 ID:', currentBoardId.value);
} }

View File

@ -80,7 +80,7 @@
></div> ></div>
<!-- 좋아요 버튼 --> <!-- 좋아요 버튼 -->
<div v-if="unknown" class="row justify-content-center my-10"> <div v-if="unknown || authorId" class="row justify-content-center my-10">
<BoardRecommendBtn <BoardRecommendBtn
:bigBtn="true" :bigBtn="true"
:boardId="currentBoardId" :boardId="currentBoardId"
@ -92,7 +92,7 @@
@updateReaction="handleUpdateReaction" @updateReaction="handleUpdateReaction"
/> />
</div> </div>
<div v-if="unknown" > <div v-if="unknown || authorId" >
<!-- 댓글 입력 영역 --> <!-- 댓글 입력 영역 -->
<BoardCommentArea <BoardCommentArea
:profileName="profileName" :profileName="profileName"
@ -106,7 +106,7 @@
</div> </div>
<!-- 댓글 목록 --> <!-- 댓글 목록 -->
<div v-if="unknown" class="card-footer"> <div v-if="unknown || authorId" class="card-footer">
<BoardCommentList <BoardCommentList
:unknown="unknown" :unknown="unknown"
:comments="commentsWithAuthStatus" :comments="commentsWithAuthStatus"