추천 사이트 위치 조정

This commit is contained in:
2026-06-17 10:36:57 +09:00
parent 54d32af617
commit d98114003d
10 changed files with 107 additions and 94 deletions

View File

@@ -1,4 +1,6 @@
<script setup>
import { getExternalFaviconUrl } from '~/lib/external-favicon-url.js'
defineProps({
menuOpen: {
type: Boolean,
@@ -25,6 +27,44 @@ const showAuthorSection = false
const STORAGE_KEY = 'sori-primary-nav-expanded'
/**
* 공개 추천 사이트 목록을 반환한다.
* @returns {Array<{ id: string, label: string, url: string, descriptionText?: string, thumbnailUrl?: string }>} 추천 사이트 목록
*/
const recommendedSites = computed(() => {
const list = navigation.value?.recommended
if (!Array.isArray(list)) {
return []
}
return list.filter((item) => item?.isVisible !== false)
})
/**
* 새 탭으로 열 외부 URL인지 확인한다.
* @param {string} url - 링크
* @returns {boolean} 외부 URL 여부
*/
const isExternalNavUrl = (url) => /^https?:\/\//i.test(String(url || '').trim())
/**
* 추천 사이트 보조 문구를 반환한다.
* @param {Object} item - 추천 사이트 항목
* @returns {string} 표시 문구
*/
const getRecommendedDisplayText = (item) => {
return String(item?.descriptionText || '').trim() || String(item?.url || '').trim()
}
/**
* 추천 사이트 이미지 URL을 반환한다.
* @param {Object} item - 추천 사이트 항목
* @returns {string} 이미지 URL
*/
const getRecommendedImageUrl = (item) => {
const thumbnailUrl = String(item?.thumbnailUrl || '').trim()
return thumbnailUrl || getExternalFaviconUrl(item?.url, 64)
}
/**
* 트리에서 하위가 있는 노드 id를 모은다.
* @param {Array<Object>} list - 노드 목록
@@ -177,6 +217,45 @@ onMounted(() => {
</div>
</div>
<div
v-if="recommendedSites.length"
class="left-sidebar__block site-sidebar-section px-5 py-4 pr-3 xl:pl-0"
>
<div class="left-sidebar__section-title flex items-center justify-between pr-2 text-xs font-semibold uppercase tracking-[0.01em] site-muted">
<span>Recommended</span>
<span class="text-sm">⌃</span>
</div>
<ul class="left-sidebar__recommended-list mt-3 flex list-none flex-col gap-2 p-0">
<li v-for="item in recommendedSites" :key="item.id">
<a
class="left-sidebar__recommended-card site-interactive flex items-center gap-2.5 rounded-[10px] border border-[var(--site-line)] bg-[var(--site-panel)] px-2.5 py-2 transition-colors hover:border-[var(--site-accent)]"
:href="item.url"
:target="isExternalNavUrl(item.url) ? '_blank' : undefined"
:rel="isExternalNavUrl(item.url) ? 'nofollow noopener noreferrer' : undefined"
>
<span class="left-sidebar__recommended-icon grid h-8 w-8 shrink-0 place-items-center overflow-hidden rounded-lg bg-[var(--site-paper)] text-xs font-bold text-[var(--site-text)] ring-1 ring-[var(--site-line)]">
<img
v-if="getRecommendedImageUrl(item)"
class="h-full w-full object-cover"
:src="getRecommendedImageUrl(item)"
width="32"
height="32"
:alt="item.thumbnailUrl ? item.label : ''"
loading="lazy"
referrerpolicy="no-referrer"
>
<span v-else class="px-1 text-center leading-none">{{ (item.label || '?').slice(0, 1) }}</span>
</span>
<span class="min-w-0 flex-1">
<span class="block truncate text-sm font-semibold text-[var(--site-text)]">{{ item.label }}</span>
<span class="mt-0.5 block truncate text-[11px] site-muted" :class="item.descriptionText ? '' : 'font-mono'">{{ getRecommendedDisplayText(item) }}</span>
</span>
<span class="shrink-0 text-xs site-muted" aria-hidden="true">↗</span>
</a>
</li>
</ul>
</div>
<div v-if="showAuthorSection" class="left-sidebar__block site-sidebar-section px-5 py-5 pr-3 xl:pl-0">
<div class="left-sidebar__section-title flex items-center justify-between text-xs font-semibold uppercase site-muted">
<span>Authors</span>