태그 관리 화면을 메인/일반 전환 중심으로 단순화하고 삭제 동선을 재정리.

글쓰기 Post URL 슬러그는 한글 입력 시 발음 기반 영문 소문자로 자동 생성되도록 개선.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-11 18:50:40 +09:00
parent cdc16c72b2
commit bd71ca860c
9 changed files with 312 additions and 149 deletions

View File

@@ -46,6 +46,7 @@ const autosaveStatus = ref('')
const isRestoringAutosave = ref(false)
const isSettingsOpen = ref(true)
const tagInput = ref('')
const isTagInputComposing = ref(false)
const activeMediaPickerTab = ref('upload')
const selectedMediaPickerUrl = ref('')
@@ -107,15 +108,44 @@ const autosaveKey = computed(() => `${autosaveStoragePrefix}:${props.initialPost
const postUrlLabel = computed(() => form.slug || toSlug(form.title) || '')
const postUrlHint = computed(() => props.publicUrl || (postUrlLabel.value ? `/post/${postUrlLabel.value}/` : '/post/'))
/**
* 한글 음절 1자를 영문 표기로 변환
* @param {string} char - 변환할 문자
* @returns {string} 영문 표기
*/
const romanizeHangulSyllable = (char) => {
const syllableCode = char.charCodeAt(0)
const hangulBase = 0xac00
const hangulLast = 0xd7a3
if (syllableCode < hangulBase || syllableCode > hangulLast) {
return char
}
const choseong = ['g', 'kk', 'n', 'd', 'tt', 'r', 'm', 'b', 'pp', 's', 'ss', '', 'j', 'jj', 'ch', 'k', 't', 'p', 'h']
const jungseong = ['a', 'ae', 'ya', 'yae', 'eo', 'e', 'yeo', 'ye', 'o', 'wa', 'wae', 'oe', 'yo', 'u', 'wo', 'we', 'wi', 'yu', 'eu', 'ui', 'i']
const jongseong = ['', 'k', 'k', 'ks', 'n', 'nj', 'nh', 't', 'l', 'lk', 'lm', 'lb', 'ls', 'lt', 'lp', 'lh', 'm', 'p', 'ps', 't', 't', 'ng', 't', 't', 'k', 't', 'p', 'h']
const offset = syllableCode - hangulBase
const choseongIndex = Math.floor(offset / 588)
const jungseongIndex = Math.floor((offset % 588) / 28)
const jongseongIndex = offset % 28
return `${choseong[choseongIndex]}${jungseong[jungseongIndex]}${jongseong[jongseongIndex]}`
}
/**
* 문자열을 URL 슬러그로 변환
* @param {string} value - 원본 문자열
* @returns {string} 슬러그
*/
const toSlug = (value) => value
.normalize('NFC')
.split('')
.map((char) => romanizeHangulSyllable(char))
.join('')
.trim()
.toLowerCase()
.replace(/[^a-z0-9가-힣\s-]/g, '')
.replace(/[^a-z0-9\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '')
@@ -412,6 +442,10 @@ const removeTag = (tag) => {
* @returns {void}
*/
const handleTagKeydown = (event) => {
if (event.isComposing || isTagInputComposing.value || event.keyCode === 229) {
return
}
if (event.key === 'Enter' || event.key === ',') {
event.preventDefault()
addTagFromInput()
@@ -753,6 +787,8 @@ defineExpose({
placeholder="태그 입력"
@blur="addTagFromInput"
@keydown="handleTagKeydown"
@compositionstart="isTagInputComposing = true"
@compositionend="isTagInputComposing = false"
>
<span class="admin-post-form__tag-chevron text-xs text-[#394047]" aria-hidden="true"></span>
</label>