태그 관리 화면을 메인/일반 전환 중심으로 단순화하고 삭제 동선을 재정리.
글쓰기 Post URL 슬러그는 한글 입력 시 발음 기반 영문 소문자로 자동 생성되도록 개선. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -22,9 +22,7 @@ const form = reactive({
|
||||
name: props.initialTag.name || '',
|
||||
slug: props.initialTag.slug || '',
|
||||
description: props.initialTag.description || '',
|
||||
sortOrder: props.initialTag.sortOrder ?? 0,
|
||||
color: props.initialTag.color || '#15171a',
|
||||
tagType: props.initialTag.tagType || 'managed'
|
||||
color: props.initialTag.color || '#15171a'
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -64,9 +62,9 @@ const submitTag = () => {
|
||||
name: form.name.trim(),
|
||||
slug: toSlug(form.slug || form.name),
|
||||
description: form.description.trim(),
|
||||
sortOrder: form.tagType === 'managed' ? Number(form.sortOrder) || 0 : 0,
|
||||
sortOrder: props.initialTag.sortOrder ?? 0,
|
||||
color: form.color,
|
||||
tagType: form.tagType
|
||||
tagType: props.initialTag.tagType || 'general'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -105,50 +103,22 @@ const submitTag = () => {
|
||||
</label>
|
||||
|
||||
<label class="admin-tag-form__field grid gap-2 text-sm">
|
||||
<span class="admin-tag-form__label font-medium">태그 유형</span>
|
||||
<select
|
||||
v-model="form.tagType"
|
||||
class="admin-tag-form__input rounded border border-line bg-white px-3 py-2"
|
||||
>
|
||||
<option value="managed">관리용 태그 (카테고리)</option>
|
||||
<option value="general">일반 태그 (배지)</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<div class="admin-tag-form__display grid gap-4 md:grid-cols-2">
|
||||
<label class="admin-tag-form__field grid gap-2 text-sm">
|
||||
<span class="admin-tag-form__label font-medium">정렬 순서</span>
|
||||
<span class="admin-tag-form__label font-medium">색상 코드</span>
|
||||
<span class="admin-tag-form__color-row flex items-center gap-3">
|
||||
<input
|
||||
v-model.number="form.sortOrder"
|
||||
class="admin-tag-form__input rounded border border-line bg-white px-3 py-2"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
:disabled="form.tagType !== 'managed'"
|
||||
v-model="form.color"
|
||||
class="admin-tag-form__color h-10 w-12 rounded border border-line bg-white p-1"
|
||||
type="color"
|
||||
>
|
||||
<span v-if="form.tagType !== 'managed'" class="text-xs text-muted">
|
||||
일반 태그는 정렬 순서를 사용하지 않습니다.
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label class="admin-tag-form__field grid gap-2 text-sm">
|
||||
<span class="admin-tag-form__label font-medium">색상 코드</span>
|
||||
<span class="admin-tag-form__color-row flex items-center gap-3">
|
||||
<input
|
||||
v-model="form.color"
|
||||
class="admin-tag-form__color h-10 w-12 rounded border border-line bg-white p-1"
|
||||
type="color"
|
||||
>
|
||||
<input
|
||||
v-model="form.color"
|
||||
class="admin-tag-form__input min-w-0 flex-1 rounded border border-line bg-white px-3 py-2"
|
||||
type="text"
|
||||
pattern="#[0-9a-fA-F]{6}"
|
||||
required
|
||||
>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.color"
|
||||
class="admin-tag-form__input min-w-0 flex-1 rounded border border-line bg-white px-3 py-2"
|
||||
type="text"
|
||||
pattern="#[0-9a-fA-F]{6}"
|
||||
required
|
||||
>
|
||||
</span>
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<div class="admin-tag-form__actions flex justify-end gap-3">
|
||||
|
||||
Reference in New Issue
Block a user