게시물 상세 사이드바 목차·광고 재배치와 세션 확인 개선

게시물 상세에서는 오른쪽 사이드에 목차와 광고를 배치하고, 비로그인 세션 확인 시 콘솔 401 로그가 나지 않도록 정리했다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-12 15:40:35 +09:00
parent eba7704ab8
commit 06271b3674
15 changed files with 214 additions and 61 deletions

View File

@@ -15,6 +15,8 @@ const { data: siteSettings } = await useFetch('/api/site-settings', {
logoText: '井',
logoUrl: '',
socialLinks: [],
adSidebarCode: '',
adPostSidebarCode: '',
copyrightText: '©2026 sori.studio'
})
})
@@ -39,7 +41,7 @@ const recommendedSites = computed(() => {
return list.filter((x) => x?.isVisible !== false)
})
const isPostDetailRoute = computed(() => route.path.startsWith('/post/'))
const sidebarAdCode = computed(() => isPostDetailRoute.value ? '' : siteSettings.value?.adSidebarCode)
const sidebarAdCode = computed(() => isPostDetailRoute.value ? siteSettings.value?.adPostSidebarCode : siteSettings.value?.adSidebarCode)
const postTocItems = computed(() => Array.isArray(postToc.value) ? postToc.value : [])
const followLinks = computed(() => getVisibleSocialLinks(siteSettings.value?.socialLinks || []))
@@ -100,20 +102,23 @@ const scrollActiveTocIntoView = (id) => {
}
const nav = tocNavRef.value
const scrollContainer = nav.closest('.site-sidebar-scroll')
const link = nav.querySelector(`[data-toc-id="${id}"]`)
if (!(link instanceof HTMLElement)) {
if (!(link instanceof HTMLElement) || !(scrollContainer instanceof HTMLElement)) {
return
}
const navTop = nav.scrollTop
const navBottom = navTop + nav.clientHeight
const linkTop = link.offsetTop
const containerRect = scrollContainer.getBoundingClientRect()
const linkRect = link.getBoundingClientRect()
const navTop = scrollContainer.scrollTop
const navBottom = navTop + scrollContainer.clientHeight
const linkTop = navTop + linkRect.top - containerRect.top
const linkBottom = linkTop + link.offsetHeight
const buffer = 24
if (linkTop < navTop + buffer) {
nav.scrollTo({
scrollContainer.scrollTo({
top: Math.max(0, linkTop - buffer),
behavior: 'smooth'
})
@@ -121,8 +126,8 @@ const scrollActiveTocIntoView = (id) => {
}
if (linkBottom > navBottom - buffer) {
nav.scrollTo({
top: linkBottom - nav.clientHeight + buffer,
scrollContainer.scrollTo({
top: linkBottom - scrollContainer.clientHeight + buffer,
behavior: 'smooth'
})
}
@@ -240,7 +245,7 @@ watch([postTocItems, () => route.fullPath], async () => {
<template>
<aside class="right-sidebar site-sidebar flex w-full flex-col overflow-hidden border-[var(--site-line)] max-lg:border-l-0 max-lg:border-t max-lg:px-4 max-lg:pb-10 lg:sticky lg:top-[57px] lg:z-10 lg:h-[calc(100vh-57px)] lg:max-h-[calc(100vh-57px)] lg:w-[287px] lg:self-start lg:border-l lg:border-t-0 lg:px-0">
<div class="right-sidebar__scroll site-sidebar-scroll flex min-h-0 flex-1 flex-col">
<div class="right-sidebar__block site-sidebar-section py-5 pl-5 pr-5 sm:pr-0 max-lg:px-0">
<div v-if="!isPostDetailRoute" class="right-sidebar__block site-sidebar-section py-5 pl-5 pr-5 sm:pr-0 max-lg:px-0">
<div class="right-sidebar__profile flex items-center gap-3">
<div class="right-sidebar__logo grid h-12 w-12 shrink-0 place-items-center overflow-hidden rounded-2xl text-2xl font-bold text-[var(--site-invert-text)]">
<img
@@ -262,7 +267,7 @@ watch([postTocItems, () => route.fullPath], async () => {
</div>
</div>
<div v-if="followLinks.length" class="right-sidebar__block site-sidebar-section py-5 sm:pl-5 pr-0">
<div v-if="!isPostDetailRoute && followLinks.length" class="right-sidebar__block site-sidebar-section py-5 sm:pl-5 pr-0">
<div class="right-sidebar__row flex items-center justify-between">
<p class="right-sidebar__eyebrow text-xs font-semibold uppercase site-muted">
Follow
@@ -404,24 +409,29 @@ watch([postTocItems, () => route.fullPath], async () => {
<div
v-if="isPostDetailRoute"
class="right-sidebar__block right-sidebar__toc flex min-h-0 flex-1 flex-col py-5 pl-5 pr-0 max-lg:hidden"
class="right-sidebar__block right-sidebar__toc py-5 pl-5 pr-0 max-lg:hidden"
>
<div class="right-sidebar__row flex shrink-0 items-center justify-between">
<p class="right-sidebar__eyebrow text-xs font-semibold uppercase site-muted">
TOC
목차
</p>
</div>
<nav ref="tocNavRef" class="right-sidebar__toc-nav mt-4 min-h-0 flex-1 overflow-y-auto pr-2" aria-label="게시글 목차">
<ul v-if="postTocItems.length" class="right-sidebar__toc-list list-none space-y-2 p-0">
<li v-for="item in postTocItems" :key="item.id">
<nav ref="tocNavRef" class="right-sidebar__toc-nav mt-4 pr-2" aria-label="게시글 목차">
<ul v-if="postTocItems.length" class="right-sidebar__toc-list flex list-none flex-col gap-2 border-l border-[var(--site-line)] p-0">
<li
v-for="item in postTocItems"
:key="item.id"
class="right-sidebar__toc-item relative flex h-6 items-center transition-colors"
:class="activeTocId === item.id ? 'right-sidebar__toc-item--active' : ''"
>
<a
class="right-sidebar__toc-link site-interactive block rounded-md py-1.5 pr-3 text-sm leading-snug transition-colors hover:text-[var(--site-accent)]"
class="right-sidebar__toc-link site-interactive flex h-full items-center rounded-md pr-3 text-sm leading-snug transition-colors hover:text-[var(--site-accent)]"
:class="{
'border-[var(--site-accent)] bg-[var(--site-panel)] text-[var(--site-accent)] font-semibold': activeTocId === item.id,
'border-transparent text-[var(--site-text)]': activeTocId !== item.id,
'pl-2 font-semibold': item.level === 1,
'pl-5': item.level === 2,
'pl-8 text-xs': item.level === 3,
'bg-[var(--site-panel)] text-[var(--site-accent)] font-semibold': activeTocId === item.id,
'text-[var(--site-text)]': activeTocId !== item.id,
'pl-4 font-semibold': item.level === 1,
'pl-7': item.level === 2,
'pl-10': item.level === 3,
'site-muted': item.level === 3 && activeTocId !== item.id
}"
:href="`#${item.id}`"
@@ -491,7 +501,7 @@ watch([postTocItems, () => route.fullPath], async () => {
<SiteAdSlot
class="right-sidebar__ad-slot py-5 pl-5 pr-0 max-lg:px-0"
:code="sidebarAdCode"
location="sidebar"
:location="isPostDetailRoute ? 'post-sidebar-right' : 'sidebar'"
/>
</div>
@@ -511,4 +521,15 @@ watch([postTocItems, () => route.fullPath], async () => {
flex-shrink: 0;
line-height: 1;
}
.right-sidebar__toc-item--active::before {
content: "";
position: absolute;
left: -2px;
top: 50%;
width: 3px;
height: 24px;
background: var(--site-accent);
transform: translateY(-50%);
}
</style>