게시물 작성 통계 표시 추가
This commit is contained in:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user