게시물 작성 통계 표시 추가

This commit is contained in:
2026-06-05 16:54:41 +09:00
parent 629ef8c4c6
commit ccb6db5f89
9 changed files with 125 additions and 7 deletions

View File

@@ -85,6 +85,8 @@ const { data: adminTags } = useFetch('/admin/api/tags', {
})
const defaultTagColor = '#15171a'
/** @type {number} 한국어 본문 예상 읽기 속도(분당 공백 제외 문자 수) */
const READING_CHARS_PER_MINUTE = 600
/**
* ISO 날짜를 datetime-local 입력값으로 변환
@@ -165,6 +167,84 @@ const form = reactive({
tagsText: props.initialPost.tags?.join(', ') || ''
})
/**
* 숫자를 한국어 로케일 문자열로 변환한다.
* @param {number} value - 숫자 값
* @returns {string} 표시 문자열
*/
const formatStatNumber = (value) => Number(value || 0).toLocaleString('ko-KR')
/**
* 통계 계산용 마크다운 텍스트를 정리한다.
* @param {string} value - 마크다운 본문
* @returns {string} 표시 텍스트에 가까운 문자열
*/
const normalizePostStatisticsText = (value) => {
return String(value || '')
.replace(/<!--sori:blank-paragraph-->/g, ' ')
.replace(/^```.*$/gm, ' ')
.replace(/^:::\w*.*$/gm, ' ')
.replace(/^> ?(?:\[![^\]]+\]|\{[^}]+\})$/gm, ' ')
.replace(/!\[([^\]]*)\]\([^)]+\)/g, '$1 ')
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
.replace(/[`*_~>#-]/g, ' ')
}
/**
* 본문 단어 수를 계산한다.
* @param {string} value - 통계용 텍스트
* @returns {number} 단어 수
*/
const countPostWords = (value) => {
const tokens = String(value || '').trim().match(/\S+/g)
return tokens ? tokens.length : 0
}
/**
* 본문 블록 수를 계산한다.
* @param {string} value - 마크다운 본문
* @returns {number} 블록 수
*/
const countPostBlocks = (value) => {
const lines = String(value || '').split('\n')
return lines.filter((line) => {
const trimmed = line.trim()
return trimmed
&& trimmed !== ':::'
&& trimmed !== '```'
&& trimmed !== '<!--sori:blank-paragraph-->'
}).length
}
/**
* 본문 이미지 수를 계산한다.
* @param {string} value - 마크다운 본문
* @returns {number} 이미지 수
*/
const countPostImages = (value) => {
const matches = String(value || '').match(/!\[[^\]]*]\([^)]+\)/g)
return matches ? matches.length : 0
}
/**
* 게시물 작성 통계를 계산한다.
* @returns {{ words: number, characters: number, spaces: number, readingMinutes: number, blocks: number, images: number }} 통계 값
*/
const postWritingStats = computed(() => {
const visibleText = normalizePostStatisticsText(form.content)
const characters = Array.from(visibleText.replace(/\s/g, '')).length
const spaces = (visibleText.match(/\s/g) || []).length
return {
words: countPostWords(visibleText),
characters,
spaces,
readingMinutes: characters > 0 ? Math.max(1, Math.ceil(characters / READING_CHARS_PER_MINUTE)) : 0,
blocks: countPostBlocks(form.content),
images: countPostImages(form.content)
}
})
/**
* 서버에 반영된 게시 형태(툴바·자동 저장·슬러그 자동 연동 분기)
* @param {Object} post - 게시물
@@ -1862,8 +1942,27 @@ defineExpose({
</span>
</label>
</div>
<div v-if="showDelete" class="admin-post-form__settings-bottom shrink-0 border-t border-[#e3e6e8] px-8 py-6">
<div class="admin-post-form__settings-bottom grid shrink-0 gap-4 border-t border-[#e3e6e8] px-8 py-6">
<section class="admin-post-form__writing-stats grid gap-2 text-xs text-[#657080]" aria-label="본문 통계">
<div class="admin-post-form__writing-stats-row flex items-center justify-between gap-3">
<span class="admin-post-form__writing-stats-label">단어</span>
<span class="admin-post-form__writing-stats-value font-medium text-[#394047]">{{ formatStatNumber(postWritingStats.words) }}</span>
</div>
<div class="admin-post-form__writing-stats-row flex items-center justify-between gap-3">
<span class="admin-post-form__writing-stats-label">문자</span>
<span class="admin-post-form__writing-stats-value font-medium text-[#394047]">{{ formatStatNumber(postWritingStats.characters) }} <span class="text-[#8e9cac]">({{ formatStatNumber(postWritingStats.spaces) }} 공백)</span></span>
</div>
<div class="admin-post-form__writing-stats-row flex items-center justify-between gap-3">
<span class="admin-post-form__writing-stats-label">읽기 시간</span>
<span class="admin-post-form__writing-stats-value font-medium text-[#394047]">{{ postWritingStats.readingMinutes ? `${formatStatNumber(postWritingStats.readingMinutes)}` : '0분' }}</span>
</div>
<div class="admin-post-form__writing-stats-row flex items-center justify-between gap-3">
<span class="admin-post-form__writing-stats-label">구성</span>
<span class="admin-post-form__writing-stats-value font-medium text-[#394047]">블록 {{ formatStatNumber(postWritingStats.blocks) }} · 이미지 {{ formatStatNumber(postWritingStats.images) }}</span>
</div>
</section>
<button
v-if="showDelete"
class="admin-post-form__delete-post flex h-10 w-full items-center justify-center gap-2 rounded border border-[#d7dde2] bg-white text-sm font-bold text-[#394047] transition-colors hover:border-[#d21a26] hover:bg-red-50 hover:text-[#d21a26] disabled:opacity-50"
type="button"
:disabled="deleting"