Merge branch 'main' into commuters
This commit is contained in:
commit
5b82d3d315
@ -1,17 +1,8 @@
|
||||
<template>
|
||||
<form @submit.prevent="search">
|
||||
<div class="input-group mb-3 d-flex">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Search"
|
||||
v-model="searchQuery"
|
||||
@input="preventLeadingSpace"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary"
|
||||
>
|
||||
<input type="text" class="form-control" placeholder="Search" v-model="searchQuery" @input="preventLeadingSpace" />
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bx bx-search bx-md"></i>
|
||||
</button>
|
||||
</div>
|
||||
@ -19,28 +10,38 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
const props = defineProps({
|
||||
maxlength: {
|
||||
type: Number,
|
||||
default: 30,
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
initKeyword: {
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(["update:data"]);
|
||||
const searchQuery = ref("");
|
||||
const emits = defineEmits(['update:data']);
|
||||
const searchQuery = ref('');
|
||||
|
||||
// 검색 실행 함수 (버튼 클릭 or 엔터 공통)
|
||||
const search = () => {
|
||||
watch(
|
||||
() => props.initKeyword,
|
||||
(newVal, oldVal) => {
|
||||
searchQuery.value = newVal;
|
||||
},
|
||||
);
|
||||
|
||||
// 검색 실행 함수 (버튼 클릭 or 엔터 공통)
|
||||
const search = () => {
|
||||
const trimmedQuery = searchQuery.value.trimStart();
|
||||
if (trimmedQuery === "") {
|
||||
emits("update:data", "");
|
||||
if (trimmedQuery === '') {
|
||||
emits('update:data', '');
|
||||
return;
|
||||
}
|
||||
if (trimmedQuery.length < 2 ) {
|
||||
alert("검색어는 최소 2글자 이상 입력해주세요.");
|
||||
if (trimmedQuery.length < 2) {
|
||||
alert('검색어는 최소 2글자 이상 입력해주세요.');
|
||||
searchQuery.value = '';
|
||||
return;
|
||||
}
|
||||
@ -52,11 +53,11 @@ const search = () => {
|
||||
searchQuery.value = trimmedQuery;
|
||||
}
|
||||
|
||||
emits("update:data", searchQuery.value);
|
||||
};
|
||||
emits('update:data', searchQuery.value);
|
||||
};
|
||||
|
||||
// 좌측 공백 제거
|
||||
const preventLeadingSpace = () => {
|
||||
// 좌측 공백 제거
|
||||
const preventLeadingSpace = () => {
|
||||
searchQuery.value = searchQuery.value.trimStart();
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -55,7 +55,7 @@
|
||||
<div class="col-md-12">
|
||||
<QEditor
|
||||
v-if="contentLoaded"
|
||||
@update:data="content = $event"
|
||||
@update:data="handleEditorDataUpdate"
|
||||
@update:imageUrls="imageUrls = $event"
|
||||
@update:uploadedImgList="handleUpdateEditorImg"
|
||||
@update:deleteImgIndexList="handleDeleteEditorImg"
|
||||
@ -89,6 +89,7 @@
|
||||
import { useToastStore } from '@s/toastStore';
|
||||
import { useBoardAccessStore } from '@s/useBoardAccessStore';
|
||||
import axios from '@api';
|
||||
import Quill from 'quill';
|
||||
|
||||
// 공통
|
||||
const $common = inject('common');
|
||||
@ -121,25 +122,60 @@
|
||||
const editorDeleteImgList = ref([]);
|
||||
|
||||
const originalTitle = ref('');
|
||||
const originalPlainText = ref('');
|
||||
const originalContent = 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) {
|
||||
if (!delta || !Array.isArray(delta.ops)) return '';
|
||||
return delta.ops
|
||||
function isDeltaChanged(current, original) {
|
||||
const Delta = Quill.import('delta');
|
||||
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')
|
||||
.map(op => op.insert.trim())
|
||||
.join(' ')
|
||||
.trim();
|
||||
.map(op => op.insert)
|
||||
.join('');
|
||||
|
||||
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(() => {
|
||||
if (!contentInitialized.value) return false;
|
||||
const isTitleChanged = title.value !== originalTitle.value;
|
||||
const currentPlainText = extractPlainText(content.value);
|
||||
const isContentChanged = currentPlainText !== originalPlainText.value;
|
||||
|
||||
const currentAttachedFiles = attachFiles.value.filter(f => f.id);
|
||||
const isContentChanged = isDeltaChanged(content.value, originalContent.value);
|
||||
const isFilesChanged =
|
||||
attachFiles.value.some(f => !f.id) || // id 없는 새 파일이 있는 경우
|
||||
delFileIdx.value.length > 0 || // 삭제된 파일이 있는 경우
|
||||
@ -147,15 +183,9 @@
|
||||
attachFiles.value.filter(f => f.id), // 기존 파일(id 있는 것만)
|
||||
originalFiles.value
|
||||
);
|
||||
console.log(isTitleChanged);
|
||||
console.log(isContentChanged);
|
||||
console.log(isFilesChanged);
|
||||
return isTitleChanged || isContentChanged || isFilesChanged;
|
||||
});
|
||||
watch(isChanged, (val) => {
|
||||
console.log('🔄 isChanged changed:', val);
|
||||
});
|
||||
|
||||
return isTitleChanged || isContentChanged || isFilesChanged ;
|
||||
});
|
||||
|
||||
// 파일 비교 함수
|
||||
function isSameFiles(current, original) {
|
||||
@ -200,15 +230,11 @@
|
||||
title.value = boardData.title || '제목 없음';
|
||||
content.value = boardData.content || '내용 없음';
|
||||
originalTitle.value = title.value;
|
||||
originalContent.value = structuredClone(boardData.content);
|
||||
contentInitialized.value = true;
|
||||
contentLoaded.value = true;
|
||||
};
|
||||
|
||||
watch(content, (val) => {
|
||||
if (contentLoaded.value && !originalPlainText.value) {
|
||||
originalPlainText.value = extractPlainText(val);
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
const handleUpdateEditorImg = item => {
|
||||
editorUploadedImgList.value = item;
|
||||
};
|
||||
@ -362,6 +388,7 @@
|
||||
onMounted(async () => {
|
||||
if (currentBoardId.value) {
|
||||
fetchBoardDetails();
|
||||
|
||||
} else {
|
||||
console.error('잘못된 게시물 ID:', currentBoardId.value);
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<div class="card-header d-flex flex-column">
|
||||
<!-- 검색창 -->
|
||||
<div class="mb-3 w-100">
|
||||
<search-bar @update:data="search" @keyup.enter="searchOnEnter" class="flex-grow-1" />
|
||||
<search-bar @update:data="search" @keyup.enter="searchOnEnter" :initKeyword="searchText" class="flex-grow-1" />
|
||||
</div>
|
||||
<div class="d-flex align-items-center" style="gap: 15px">
|
||||
<!-- 리스트 갯수 선택 -->
|
||||
|
||||
@ -78,10 +78,9 @@
|
||||
style="line-height: 1.6"
|
||||
v-html="$common.contentToHtml(boardContent)"
|
||||
></div>
|
||||
<div v-if="!unknown" class="my-12 py-12 pt-12"></div>
|
||||
|
||||
<!-- 좋아요 버튼 -->
|
||||
<div class="row justify-content-center my-10">
|
||||
<div v-if="unknown || authorId" class="row justify-content-center my-10">
|
||||
<BoardRecommendBtn
|
||||
:bigBtn="true"
|
||||
:boardId="currentBoardId"
|
||||
@ -93,7 +92,7 @@
|
||||
@updateReaction="handleUpdateReaction"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div v-if="unknown || authorId" >
|
||||
<!-- 댓글 입력 영역 -->
|
||||
<BoardCommentArea
|
||||
:profileName="profileName"
|
||||
@ -107,7 +106,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 댓글 목록 -->
|
||||
<div class="card-footer">
|
||||
<div v-if="unknown || authorId" class="card-footer">
|
||||
<BoardCommentList
|
||||
:unknown="unknown"
|
||||
:comments="commentsWithAuthStatus"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user