@@ -2101,133 +2101,127 @@ const saveNavigation = async () => {
< / ul >
< / div >
< div
< AdminModalFrame
v-if = "navCreateModalType"
c lass = " admin-navigation__ create-modal fixed inset-0 z-[60] grid place-items-center bg-black/40 px-5 py-8 "
role = "dialog "
aria -modal = " true "
aria -labelledby = " admin -navigation -create -title "
@click.self ="closeNavCreateModal"
labelled -by = " admin -navigation- create-title "
root -class = " admin -navigation__create -modal "
panel -class = " admin -navigation__create -modal -panel max -w -lg "
@close ="closeNavCreateModal"
>
< section class = "admin-navigation__create-modal-panel max-h-[90vh] w-full max-w-lg overflow-y-auto rounded border border-line bg-white p-5 shadow-xl " >
< div class = "admin-navigation__ create-modal-head " >
< h3 id = "admin-navigation-create-t itle" class = "text-lg font-semibold text-ink" >
{ { navCreateModalTitle } }
< / h3 >
< p class = "mt-1 text-xs text-muted" >
제목과 URL을 입력한 뒤 추가하면 목록에 반영됩니다 . 최종 반영은 네비게이션 저장으로 완료합니다 .
< / p >
< / div >
< div class = "admin-navigation__create-modal-head " >
< h3 id = "admin-navigation- create-title" class = "text-lg font-semibold text-ink " >
{ { navCreateModalT itle } }
< / h3 >
< p class = "mt-1 text-xs text-muted" >
제목과 URL을 입력한 뒤 추가하면 목록에 반영됩니다 . 최종 반영은 네비게이션 저장으로 완료합니다 .
< / p >
< / div >
< div class = "admin-navigation__create-modal-fields mt-5 grid gap-4" >
< label class = "grid gap-1.5 text-sm" >
< span class = "text-xs font-semibold text-muted" > 라벨 < / span >
< input
v-model = "navCreateDraft.label"
class = "rounded border border-line px-3 py-2 text-sm outline-none focus:border-[#8e9cac]"
type = "text"
placeholder = "메뉴 이름"
required
>
< / label >
< label class = "grid gap-1.5 text-sm" >
< span class = "text-xs font-semibold text-muted" > URL < / span >
< div class = "admin-navigation__create-url flex flex-col gap-2 sm:flex-row" >
< span class = "admin-navigation__create-url-prefix-wrap relative shrink-0" >
< select
v-model = "navCreateDraft.urlPrefix"
class = "admin-navigation__create-url-prefix h-[38px] appearance-none rounded border border-line bg-white py-2 pl-3 pr-10 text-sm outline-none focus:border-[#8e9cac]"
>
< option
v-for = "option in navigationUrlPrefixOptions"
:key = "option.label"
:value = "option.value"
>
{ { option . label } }
< / option >
< / select >
< svg xmlns = "http://www.w3.org/2000/svg" class = "icon icon-tabler icon-tabler-chevron-down pointer-events-none absolute right-3 top-1/2 size-5 -translate-y-1/2 text-[#15171a]" width = "24" height = "24" viewBox = "0 0 24 24" stroke -width = " 2 " stroke = "currentColor" fill = "none" stroke -linecap = " round " stroke -linejoin = " round " aria -hidden = " true " >
< path stroke = "none" d = "M0 0h24v24H0z" fill = "none" / >
< polyline points = "6 9 12 15 18 9" / >
< / svg >
< / span >
< input
v-model = "navCreateDraft.urlBody"
class = "admin-navigation__create-url-body min-w-0 flex-1 rounded border border-line px-3 py-2 font-mono text-sm outline-none focus:border-[#8e9cac]"
type = "text"
:placeholder = "getNavigationUrlBodyPlaceholder(navCreateDraft.urlPrefix)"
required
@keydown.enter.prevent ="applyNavCreate"
>
< / div >
< span v-if = "getNavCreateDraftUrl()" class="break-all pl-2 text-[11px] font-mono text-muted" >
{{ getNavCreateDraftUrl ( ) }}
< / span >
< / label >
< / div >
< div class = "admin-navigation__create-modal-actions mt-6 flex justify-end gap-2" >
< button class = "rounded border border-line px-3 py-2 text-xs font-semibold text-ink" type = "button" @click ="closeNavCreateModal" >
취소
< / button >
< button
class = "rounded bg-[#15171a] px-3 py-2 text-xs font-semibold text-white disabled:opacity-50"
type = "button"
: disabled = "!navCreateDraft.label.trim() || !getNavCreateDraftUrl()"
@click ="applyNavCreate"
>
추가
< / button >
< / div >
< / section >
< / div >
< div
v-if = "pageSectionCreateModalOpen"
class = "admin-navigation__page-section-create-modal fixed inset-0 z-[60] grid place-items-center bg-black/40 px-5 py-8"
role = "dialog"
aria -modal = " true "
aria -labelledby = " admin -navigation -page -section -create -title "
@click.self ="closePageSectionCreateModal"
>
< section class = "admin-navigation__page-section-create-modal-panel max-h-[90vh] w-full max-w-lg overflow-y-auto rounded border border-line bg-white p-5 shadow-xl" >
< div class = "admin-navigation__page-section-create-modal-head" >
< h3 id = "admin-navigation-page-section-create-title" class = "text-lg font-semibold text-ink" >
페이지 섹션 추가
< / h3 >
< p class = "mt-1 text-xs text-muted" >
홈 Pages 진열장에 사용할 섹션 이름을 입력합니다 .
< / p >
< / div >
< label class = "mt-5 grid gap-1.5 text-sm" >
< span class = "text-xs font-semibold text-muted" > 섹션명 < / span >
< div class = "admin-navigation__create-modal-fields mt-5 grid gap-4" >
< label class = "grid gap-1.5 text-sm" >
< span class = "text-xs font-semibold text-muted" > 라벨 < / span >
< input
v-model = "pageSectionCreateName "
v-model = "navCreateDraft.label "
class = "rounded border border-line px-3 py-2 text-sm outline-none focus:border-[#8e9cac]"
type = "text"
maxlength = "80 "
placeholder = "예: 게임"
placeholder = "메뉴 이름 "
required
@keydown.enter.prevent ="addPageSection"
>
< / label >
< label class = "grid gap-1.5 text-sm" >
< span class = "text-xs font-semibold text-muted" > URL < / span >
< div class = "admin-navigation__create-url flex flex-col gap-2 sm:flex-row" >
< span class = "admin-navigation__create-url-prefix-wrap relative shrink-0" >
< select
v-model = "navCreateDraft.urlPrefix"
class = "admin-navigation__create-url-prefix h-[38px] appearance-none rounded border border-line bg-white py-2 pl-3 pr-10 text-sm outline-none focus:border-[#8e9cac]"
>
< option
v-for = "option in navigationUrlPrefixOptions"
:key = "option.label"
:value = "option.value"
>
{ { option . label } }
< / option >
< / select >
< svg xmlns = "http://www.w3.org/2000/svg" class = "icon icon-tabler icon-tabler-chevron-down pointer-events-none absolute right-3 top-1/2 size-5 -translate-y-1/2 text-[#15171a]" width = "24" height = "24" viewBox = "0 0 24 24" stroke -width = " 2 " stroke = "currentColor" fill = "none" stroke -linecap = " round " stroke -linejoin = " round " aria -hidden = " true " >
< path stroke = "none" d = "M0 0h24v24H0z" fill = "none" / >
< polyline points = "6 9 12 15 18 9" / >
< / svg >
< / span >
< input
v-model = "navCreateDraft.urlBody"
class = "admin-navigation__create-url-body min-w-0 flex-1 rounded border border-line px-3 py-2 font-mono text-sm outline-none focus:border-[#8e9cac]"
type = "text"
:placeholder = "getNavigationUrlBodyPlaceholder(navCreateDraft.urlPrefix)"
required
@keydown.enter.prevent ="applyNavCreate"
>
< / div >
< span v-if = "getNavCreateDraftUrl()" class="break-all pl-2 text-[11px] font-mono text-muted" >
{{ getNavCreateDraftUrl ( ) }}
< / span >
< / label >
< / div >
< div class = "admin-navigation__page-section- create-modal-actions mt-6 flex justify-end gap-2" >
< button class = "rounded border border-line px-3 py-2 text-xs font-semibold text-ink" type = "button" @click ="closePageSection CreateModal" >
취소
< / button >
< button
class = "rounded bg-[#15171a] px-3 py-2 text-xs font-semibold text-white disabled:opacity-50"
type = "button"
: disabled = "savingPageSectionId === 'new' || !pageSectionCreateName.trim ()"
@click ="addPageSection "
>
{ { savingPageSectionId === 'new' ? '추가 중' : '추가' } }
< / button >
< / div >
< / section >
< / div >
< div class = "admin-navigation__create-modal-actions mt-6 flex justify-end gap-2" >
< button class = "rounded border border-line px-3 py-2 text-xs font-semibold text-ink" type = "button" @click ="closeNav CreateModal" >
취소
< / button >
< button
class = "rounded bg-[#15171a] px-3 py-2 text-xs font-semibold text-white disabled:opacity-50"
type = "button"
: disabled = "!navCreateDraft.label.trim() || !getNavCreateDraftUrl ()"
@click ="applyNavCreate "
>
추가
< / button >
< / div >
< / AdminModalFrame >
< AdminModalFrame
v-if = "pageSectionCreateModalOpen"
labelled -by = " admin -navigation -page -section -create -title "
root -class = " admin -navigation__page -section -create -modal "
panel -class = " admin -navigation__page -section -create -modal -panel max -w -lg "
@close ="closePageSectionCreateModal"
>
< div class = "admin-navigation__page-section-create-modal-head" >
< h3 id = "admin-navigation-page-section-create-title" class = "text-lg font-semibold text-ink" >
페이지 섹션 추가
< / h3 >
< p class = "mt-1 text-xs text-muted" >
홈 Pages 진열장에 사용할 섹션 이름을 입력합니다 .
< / p >
< / div >
< label class = "mt-5 grid gap-1.5 text-sm" >
< span class = "text-xs font-semibold text-muted" > 섹션명 < / span >
< input
v-model = "pageSectionCreateName"
class = "rounded border border-line px-3 py-2 text-sm outline-none focus:border-[#8e9cac]"
type = "text"
maxlength = "80"
placeholder = "예: 게임"
required
@keydown.enter.prevent ="addPageSection"
>
< / label >
< div class = "admin-navigation__page-section-create-modal-actions mt-6 flex justify-end gap-2" >
< button class = "rounded border border-line px-3 py-2 text-xs font-semibold text-ink" type = "button" @click ="closePageSectionCreateModal" >
취소
< / button >
< button
class = "rounded bg-[#15171a] px-3 py-2 text-xs font-semibold text-white disabled:opacity-50"
type = "button"
: disabled = "savingPageSectionId === 'new' || !pageSectionCreateName.trim()"
@click ="addPageSection"
>
{ { savingPageSectionId === 'new' ? '추가 중' : '추가' } }
< / button >
< / div >
< / AdminModalFrame >
< AdminConfirmModal
:open = "recommendedDiscardModalOpen"
@@ -2254,253 +2248,247 @@ const saveNavigation = async () => {
< / p >
< / AdminConfirmModal >
< div
< AdminModalFrame
v-if = "pageOrderModalSection"
c lass = " admin-navigation__ page-order-modal fixed inset-0 z-[60] grid place-items-center bg-black/40 px-5 py-8 "
role = "dialog "
aria -modal = " true "
aria -labelledby = " admin -navigation -page -order -title "
@click.self ="closePageOrderModal"
labelled -by = " admin -navigation- page -order-title "
root -class = " admin -navigation__page -order -modal "
panel -class = " admin -navigation__page -order -modal -panel max -w -4xl "
@close ="closePageOrderModal"
>
< section class = "admin-navigation__page-order-modal-panel max-h-[90vh] w-full max-w-4xl overflow-y-auto rounded border border-line bg-white p-5 shadow-xl " >
< div class = "admin-navigation__page-order-modal-head flex items-start justify-between gap-4" >
< div >
< h3 id = "admin-navigation-page-order-title" class = "text-lg font-semibold text-ink" >
페이지 순서 변경
< / h3 >
< p class = "mt-1 text-xs text-muted" >
{ { pageOrderModalSection . name } } 섹션의 홈 표시 순서를 조정합니다 .
< / p >
< / div >
< button class = "grid size-8 place-items-center rounded border border-line text-muted transition-colors hover:bg-[#f5f5f2] hover:text-ink" type = "button" aria -label = " 닫기 " @click ="closePageOrderModal" >
×
< / button >
< div class = "admin-navigation__page-order-modal-head flex items-start justify-between gap-4 " >
< div >
< h3 id = "admin-navigation-page-order-title" class = "text-lg font-semibold text-ink" >
페이지 순서 변경
< / h3 >
< p class = "mt-1 text-xs text-muted" >
{ { pageOrderModalSection . name } } 섹션의 홈 표시 순서를 조정합니다 .
< / p >
< / div >
< button class = "grid size-8 place-items-center rounded border border-line text-muted transition-colors hover:bg-[#f5f5f2] hover:text-ink" type = "button" aria -label = " 닫기 " @click ="closePageOrderModal" >
×
< / button >
< / div >
< div v-if = "pageOrderDraftPages.length === 0" class="admin-navigation__page-order-empty mt-5 rounded border border-dashed border-line p-6 text-center text-sm text-muted" >
이 섹션에 표시 중인 페이지가 없습니다.
< / div >
< div v-if = "pageOrderDraftPages.length === 0" class="admin-navigation__page-order-empty mt-5 rounded border border-dashed border-line p-6 text-center text-sm text-muted" >
이 섹션에 표시 중인 페이지가 없습니다.
< / div >
< div v-else class = "admin-navigation__page-order-list mt-5 flex gap-3 overflow-x-auto rounded border border-line bg-[#f9f9f7] p-3" >
< article
v-for = "(page, index) in pageOrderDraftPages"
:key = "page.id"
class = "admin-navigation__page-order-card group relative flex w-[150px] shrink-0 flex-col rounded-[8px] border border-line bg-white p-2 shadow-sm transition-colors hover:border-[#b9c0c7]"
:class = "pageOrderRowClass(page.id)"
draggable = "true"
@dragstart ="onPageOrderDragStart($event, page.id)"
@dragover ="onPageOrderDragOver($event, page.id)"
@drop ="onPageOrderDrop($event, page.id)"
@dragend ="onPageOrderDragEnd"
< div v-else class = "admin-navigation__page-order-list mt-5 flex gap-3 overflow-x-auto rounded border border-line bg-[#f9f9f7] p-3" >
< article
v-for = "(page, index) in pageOrderDraftPages"
:key = "page.id"
class = "admin-navigation__page-order-card group relative flex w-[150px] shrink-0 flex-col rounded-[8px] border border-line bg-white p-2 shadow-sm transition-colors hover:border-[#b9c0c7]"
:class = "pageOrderRowClass(page.id)"
draggable = "true"
@dragstart ="onPageOrderDragStart($event, page.id)"
@dragover ="onPageOrderDragOver($event, page.id)"
@drop ="onPageOrderDrop($event, page.id)"
@dragend ="onPageOrderDragEnd"
>
< span class = "admin-navigation__page-order-index absolute left-3 top-3 z-10 rounded-full bg-white/90 px-2 py-0.5 text-[11px] font-semibold tabular-nums text-ink shadow-sm" >
{ { index + 1 } }
< / span >
< span
v-if = "page.isFeatured"
class = "admin-navigation__page-order-featured absolute right-3 top-3 z-10 rounded-full bg-[#15171a] px-2 py-0.5 text-[10px] font-semibold text-white shadow-sm"
>
< span class = "admin-navigation__page-order-index absolute left-3 top-3 z-10 rounded-full bg-white/90 px-2 py-0.5 text-[11px] font-semibold tabular-nums text-ink shadow-sm" >
{ { index + 1 } }
< / span >
< span
v-if = "page.isFeatured "
class = "admin-navigation__page-order-featured absolute right-3 top-3 z-10 rounded-full bg-[#15171a] px-2 py-0.5 text-[10px] font-semibold text-white shadow-sm "
추천
< / span >
< figure class = "admin-navigation__page-order-cover aspect-[3/4] overflow-hidden rounded-[6px] border border-line bg-[#f5f5f2]" >
< img
v-if = "getPageOrderCardImage(page) "
class = "h-full w-full object-cover "
:src = "getPageOrderCardImage(page)"
: alt = "page.listingTitle || page.title || '페이지 썸네일'"
loading = "lazy"
>
추천
< / span >
< figure class = "admin-navigation__page-order-cover aspect-[3/4] overflow-hidden rounded-[6px] border border-line bg-[#f5f5f2]" >
< img
v-if = "getPageOrderCardImage(page)"
class = "h-full w-full object-cover"
:src = "getPageOrderCardImage(page)"
: alt = "page.listingTitle || page.title || '페이지 썸네일'"
loading = "lazy"
>
< div v-else class = "flex h-full w-full items-center justify-center p-3 text-center text-xs font-semibold leading-tight text-muted" >
{ { page . listingTitle || page . title || '제목 없음' } }
< / div >
< / figure >
< h4 class = "admin-navigation__page-order-title mt-2 line-clamp-2 min-h-[2rem] text-xs font-semibold leading-tight text-ink" >
< div v-else class = "flex h-full w-full items-center justify-center p-3 text-center text-xs font-semibold leading-tight text-muted" >
{ { page . listingTitle || page . title || '제목 없음' } }
< / h4 >
< p class = "admin-navigation__page-order-slug mt-1 truncate font-mono text-[10px] text-muted" >
/ pages / { { page . slug } }
< / p >
< button
class = "admin-navigation__page-order-remove mt-2 rounded border border-line px-2 py-1.5 text-[11 px] font-semibold text-muted transition-colors hover:border-red-200 hover:bg-red-50 hover:text-red-700"
type = "button"
@click.stop ="removePageFromOrderSection(page)"
>
섹션에서 제거
< / button>
< / article >
< / div >
< / div >
< / figure >
< h4 class = "admin-navigation__page-order-title mt-2 line-clamp-2 min-h-[2rem] text-xs font-semibold leading-tight text-ink" >
{ { page . listingTitle || page . title || '제목 없음' } }
< / h4 >
< p class = "admin-navigation__page-order-slug mt-1 truncate font-mono text-[10 px] text-muted" >
/ pages / { { page . slug } }
< / p >
< button
class = "admin-navigation__page-order-remove mt-2 rounded border border-line px-2 py-1.5 text-[11px] font-semibold text-muted transition-colors hover:border-red-200 hover:bg-red-50 hover:text-red-700"
type = " button"
@click.stop ="removePageFromOrderSection(page)"
>
섹션에서 제거
< / button >
< / article >
< / div >
< p v-if = "pageOrderRemovedCount > 0" class="admin-navigation__page-order-remove-note mt-3 rounded border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800" >
{{ pageOrderRemovedCount }} 개 페이지는 저장하면 이 섹션에서 제거됩니다.
< p v-if = "pageOrderRemovedCount > 0" class="admin-navigation__page-order-remove-note mt-3 rounded border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800" >
{{ pageOrderRemovedCount }} 개 페이지는 저장하면 이 섹션에서 제거됩니다.
< / p >
< div class = "admin-navigation__page-order-actions mt-6 flex justify-end gap-2" >
< button class = "rounded border border-line px-3 py-2 text-xs font-semibold text-ink" type = "button" @click ="closePageOrderModal" >
취소
< / button >
< button
class = "rounded bg-[#15171a] px-3 py-2 text-xs font-semibold text-white disabled:opacity-50"
type = "button"
: disabled = "savingPageOrder || (pageOrderDraftPages.length === 0 && pageOrderRemovedCount === 0)"
@click ="savePageOrder"
>
{ { savingPageOrder ? '저장 중' : '저장' } }
< / button >
< / div >
< / AdminModalFrame >
< AdminModalFrame
v-if = "recommendedEditId && recommendedEditDraft"
labelled -by = " admin -navigation -recommended -edit -title "
root -class = " admin -navigation__recommended -modal "
panel -class = " admin -navigation__recommended -modal -panel max -w -lg "
@close ="requestCloseRecommendedEdit"
>
< div class = "admin-navigation__recommended-modal-head" >
< h3 id = "admin-navigation-recommended-edit-title" class = "text-lg font-semibold text-ink" >
추천 사이트 편집
< / h3 >
< p class = "mt-1 text-xs text-muted" >
공개 Recommended 카드와 동일한 정보를 편집합니다 .
< / p >
< / div >
< div class = "admin-navigation__page-order-actions mt-6 flex justify-end gap-2 " >
< butto n class = "rounded border border-line px-3 py-2 text-x s font-semi bold text-ink" type = "button" @click ="closePageOrderModal" >
< div class = "admin-navigation__recommended-modal-preview mt-5 flex items-center gap-3 rounded border border-line bg-[#f9f9f7] p-3 " >
< spa n class = "grid h-10 w-10 shrink-0 place-items-center overflow-hidden rounded-lg border border-line bg-white text-sm font-bold text-ink" >
< img
v-if = "recommendedEditPreviewItem && getRecommendedIconPreviewUrl(recommendedEditPreviewItem)"
class = "h-full w-full object-cover"
:src = "getRecommendedIconPreviewUrl(recommendedEditPreviewItem)"
width = "40"
height = "40"
alt = ""
loading = "lazy"
referrerpolicy = "no-referrer"
>
< span v-else class = "px-1 text-center leading-none" > { { ( recommendedEditDraft . label || '?' ) . slice ( 0 , 1 ) } } < / span >
< / span >
< div class = "min-w-0 flex-1" >
< p class = "truncate text-sm font-semibold text-ink" >
{ { recommendedEditDraft . label || '제목 없음' } }
< / p >
< p class = "mt-0.5 truncate text-xs text-muted" >
{ { recommendedEditPreviewItem ? getRecommendedPreviewSubtitle ( recommendedEditPreviewItem ) : '' } }
< / p >
< p class = "mt-1 text-[11px] text-muted" >
{ { recommendedEditItem ? getRecommendedFaviconCachedLabel ( recommendedEditItem ) : '캐시 없음' } }
< / p >
< / div >
< / div >
< div class = "admin-navigation__recommended-modal-fields mt-5 grid gap-4" >
< label class = "grid gap-1.5 text-sm" >
< span class = "text-xs font-semibold text-muted" > 제목 < / span >
< input
v-model = "recommendedEditDraft.label"
class = "rounded border border-line px-3 py-2 text-sm outline-none focus:border-[#8e9cac]"
type = "text"
placeholder = "사이트 이름"
required
>
< / label >
< label class = "grid gap-1.5 text-sm" >
< span class = "text-xs font-semibold text-muted" > URL < / span >
< div class = "admin-navigation__recommended-url flex flex-col gap-2 sm:flex-row" >
< span class = "admin-navigation__recommended-url-prefix-wrap relative shrink-0" >
< select
v-model = "recommendedEditDraft.urlPrefix"
class = "admin-navigation__recommended-url-prefix h-[38px] appearance-none rounded border border-line bg-white py-2 pl-3 pr-10 text-sm outline-none focus:border-[#8e9cac]"
>
< option
v-for = "option in navigationUrlPrefixOptions"
:key = "option.label"
:value = "option.value"
>
{ { option . label } }
< / option >
< / select >
< svg xmlns = "http://www.w3.org/2000/svg" class = "icon icon-tabler icon-tabler-chevron-down pointer-events-none absolute right-3 top-1/2 size-5 -translate-y-1/2 text-[#15171a]" width = "24" height = "24" viewBox = "0 0 24 24" stroke -width = " 2 " stroke = "currentColor" fill = "none" stroke -linecap = " round " stroke -linejoin = " round " aria -hidden = " true " >
< path stroke = "none" d = "M0 0h24v24H0z" fill = "none" / >
< polyline points = "6 9 12 15 18 9" / >
< / svg >
< / span >
< input
v-model = "recommendedEditDraft.urlBody"
class = "admin-navigation__recommended-url-body min-w-0 flex-1 rounded border border-line px-3 py-2 font-mono text-sm outline-none focus:border-[#8e9cac]"
type = "text"
:placeholder = "getNavigationUrlBodyPlaceholder(recommendedEditDraft.urlPrefix)"
required
>
< / div >
< span
v-if = "getRecommendedDraftUrl(recommendedEditDraft)"
class = "break-all pl-2 text-[11px] font-mono text-muted"
>
{ { getRecommendedDraftUrl ( recommendedEditDraft ) } }
< / span >
< / label >
< label class = "grid gap-1.5 text-sm" >
< span class = "text-xs font-semibold text-muted" > 대체 텍스트 < / span >
< input
v-model = "recommendedEditDraft.descriptionText"
class = "rounded border border-line px-3 py-2 text-sm outline-none focus:border-[#8e9cac]"
type = "text"
placeholder = "URL 대신 표시할 문구"
>
< / label >
< label class = "grid gap-1.5 text-sm" >
< span class = "text-xs font-semibold text-muted" > 썸네일 URL < / span >
< textarea
v-model = "recommendedEditDraft.thumbnailUrl"
class = "min-h-24 resize-y rounded border border-line px-3 py-2 font-mono text-sm outline-none focus:border-[#8e9cac]"
placeholder = "/uploads/…"
/ >
< span class = "pl-2 text-[11px] text-muted" > 비워 두면 서버 캐시 파비콘을 사용합니다 . < / span >
< / label >
< / div >
< div class = "admin-navigation__recommended-modal-tools mt-4 flex flex-wrap items-center gap-2" >
< button
class = "rounded border border-line bg-white px-3 py-2 text-xs font-semibold text-ink transition-colors hover:bg-[#f5f5f2] disabled:cursor-not-allowed disabled:opacity-50"
type = "button"
: disabled = "!recommendedEditItem || faviconRefreshingIds.has(String(recommendedEditItem.id))"
@click ="refreshRecommendedEditFavicon"
>
{ { recommendedEditItem && faviconRefreshingIds . has ( String ( recommendedEditItem . id ) ) ? '파비콘 갱신 중…' : '파비콘 새로고침' } }
< / button >
< / div >
< div class = "admin-navigation__recommended-modal-actions mt-6 flex flex-wrap items-center justify-between gap-2" >
< button
class = "rounded border border-red-200 px-3 py-2 text-xs font-semibold text-red-700"
type = "button"
@click ="removeRecommendedFromModal"
>
삭제
< / button >
< div class = "flex flex-wrap gap-2" >
< button
class = "rounded border border-line px-3 py-2 text-xs font-semibold text-ink"
type = "button"
@click ="requestCloseRecommendedEdit"
>
취소
< / button >
< button
class = "rounded bg-[#15171a] px-3 py-2 text-xs font-semibold text-white disabled:opacity-50"
type = "button"
: disabled = "savingPageOrder || (pageOrderDraftPages.length === 0 && pageOrderRemovedCount === 0 )"
@click ="savePageOrder "
: disabled = "!recommendedEditDraft.label.trim() || !getRecommendedDraftUrl(recommendedEditDraft )"
@click ="applyRecommendedEdit "
>
{ { savingPageOrder ? '저장 중' : '저장' } }
적용
< / button >
< / div >
< / section >
< / div >
< div
v-if = "recommendedEditId && recommendedEditDraft"
class = "admin-navigation__recommended-modal fixed inset-0 z-[60] grid place-items-center bg-black/40 px-5 py-8"
role = "dialog"
aria -modal = " true "
aria -labelledby = " admin -navigation -recommended -edit -title "
@click.self ="requestCloseRecommendedEdit"
>
< section class = "admin-navigation__recommended-modal-panel max-h-[90vh] w-full max-w-lg overflow-y-auto rounded border border-line bg-white p-5 shadow-xl" >
< div class = "admin-navigation__recommended-modal-head" >
< h3 id = "admin-navigation-recommended-edit-title" class = "text-lg font-semibold text-ink" >
추천 사이트 편집
< / h3 >
< p class = "mt-1 text-xs text-muted" >
공개 Recommended 카드와 동일한 정보를 편집합니다 .
< / p >
< / div >
< div class = "admin-navigation__recommended-modal-preview mt-5 flex items-center gap-3 rounded border border-line bg-[#f9f9f7] p-3" >
< span class = "grid h-10 w-10 shrink-0 place-items-center overflow-hidden rounded-lg border border-line bg-white text-sm font-bold text-ink" >
< img
v-if = "recommendedEditPreviewItem && getRecommendedIconPreviewUrl(recommendedEditPreviewItem)"
class = "h-full w-full object-cover"
:src = "getRecommendedIconPreviewUrl(recommendedEditPreviewItem)"
width = "40"
height = "40"
alt = ""
loading = "lazy"
referrerpolicy = "no-referrer"
>
< span v-else class = "px-1 text-center leading-none" > { { ( recommendedEditDraft . label || '?' ) . slice ( 0 , 1 ) } } < / span >
< / span >
< div class = "min-w-0 flex-1" >
< p class = "truncate text-sm font-semibold text-ink" >
{ { recommendedEditDraft . label || '제목 없음' } }
< / p >
< p class = "mt-0.5 truncate text-xs text-muted" >
{ { recommendedEditPreviewItem ? getRecommendedPreviewSubtitle ( recommendedEditPreviewItem ) : '' } }
< / p >
< p class = "mt-1 text-[11px] text-muted" >
{ { recommendedEditItem ? getRecommendedFaviconCachedLabel ( recommendedEditItem ) : '캐시 없음' } }
< / p >
< / div >
< / div >
< div class = "admin-navigation__recommended-modal-fields mt-5 grid gap-4" >
< label class = "grid gap-1.5 text-sm" >
< span class = "text-xs font-semibold text-muted" > 제목 < / span >
< input
v-model = "recommendedEditDraft.label"
class = "rounded border border-line px-3 py-2 text-sm outline-none focus:border-[#8e9cac]"
type = "text"
placeholder = "사이트 이름"
required
>
< / label >
< label class = "grid gap-1.5 text-sm" >
< span class = "text-xs font-semibold text-muted" > URL < / span >
< div class = "admin-navigation__recommended-url flex flex-col gap-2 sm:flex-row" >
< span class = "admin-navigation__recommended-url-prefix-wrap relative shrink-0" >
< select
v-model = "recommendedEditDraft.urlPrefix"
class = "admin-navigation__recommended-url-prefix h-[38px] appearance-none rounded border border-line bg-white py-2 pl-3 pr-10 text-sm outline-none focus:border-[#8e9cac]"
>
< option
v-for = "option in navigationUrlPrefixOptions"
:key = "option.label"
:value = "option.value"
>
{ { option . label } }
< / option >
< / select >
< svg xmlns = "http://www.w3.org/2000/svg" class = "icon icon-tabler icon-tabler-chevron-down pointer-events-none absolute right-3 top-1/2 size-5 -translate-y-1/2 text-[#15171a]" width = "24" height = "24" viewBox = "0 0 24 24" stroke -width = " 2 " stroke = "currentColor" fill = "none" stroke -linecap = " round " stroke -linejoin = " round " aria -hidden = " true " >
< path stroke = "none" d = "M0 0h24v24H0z" fill = "none" / >
< polyline points = "6 9 12 15 18 9" / >
< / svg >
< / span >
< input
v-model = "recommendedEditDraft.urlBody"
class = "admin-navigation__recommended-url-body min-w-0 flex-1 rounded border border-line px-3 py-2 font-mono text-sm outline-none focus:border-[#8e9cac]"
type = "text"
:placeholder = "getNavigationUrlBodyPlaceholder(recommendedEditDraft.urlPrefix)"
required
>
< / div >
< span
v-if = "getRecommendedDraftUrl(recommendedEditDraft)"
class = "break-all pl-2 text-[11px] font-mono text-muted"
>
{ { getRecommendedDraftUrl ( recommendedEditDraft ) } }
< / span >
< / label >
< label class = "grid gap-1.5 text-sm" >
< span class = "text-xs font-semibold text-muted" > 대체 텍스트 < / span >
< input
v-model = "recommendedEditDraft.descriptionText"
class = "rounded border border-line px-3 py-2 text-sm outline-none focus:border-[#8e9cac]"
type = "text"
placeholder = "URL 대신 표시할 문구"
>
< / label >
< label class = "grid gap-1.5 text-sm" >
< span class = "text-xs font-semibold text-muted" > 썸네일 URL < / span >
< textarea
v-model = "recommendedEditDraft.thumbnailUrl"
class = "min-h-24 resize-y rounded border border-line px-3 py-2 font-mono text-sm outline-none focus:border-[#8e9cac]"
placeholder = "/uploads/…"
/ >
< span class = "text-[11px] pl-2 text-muted" > 비워 두면 서버 캐시 파비콘을 사용합니다 . < / span >
< / label >
< / div >
< div class = "admin-navigation__recommended-modal-tools mt-4 flex flex-wrap items-center gap-2" >
< button
class = "rounded border border-line bg-white px-3 py-2 text-xs font-semibold text-ink transition-colors hover:bg-[#f5f5f2] disabled:cursor-not-allowed disabled:opacity-50"
type = "button"
: disabled = "!recommendedEditItem || faviconRefreshingIds.has(String(recommendedEditItem.id))"
@click ="refreshRecommendedEditFavicon"
>
{ { recommendedEditItem && faviconRefreshingIds . has ( String ( recommendedEditItem . id ) ) ? '파비콘 갱신 중…' : '파비콘 새로고침' } }
< / button >
< / div >
< div class = "admin-navigation__recommended-modal-actions mt-6 flex flex-wrap items-center justify-between gap-2" >
< button
class = "rounded border border-red-200 px-3 py-2 text-xs font-semibold text-red-700"
type = "button"
@click ="removeRecommendedFromModal"
>
삭제
< / button >
< div class = "flex flex-wrap gap-2" >
< button
class = "rounded border border-line px-3 py-2 text-xs font-semibold text-ink"
type = "button"
@click ="requestCloseRecommendedEdit"
>
취소
< / button >
< button
class = "rounded bg-[#15171a] px-3 py-2 text-xs font-semibold text-white disabled:opacity-50"
type = "button"
: disabled = "!recommendedEditDraft.label.trim() || !getRecommendedDraftUrl(recommendedEditDraft)"
@click ="applyRecommendedEdit"
>
적용
< / button >
< / div >
< / div >
< / section >
< / div >
< / div >
< / AdminModalFrame >
< AdminToast :toast = "toast" / >
< / section >