Files
sori.studio/components/content/ProseBookmark.vue
zenn 082c6a9619 v0.0.48 Thred형 북마크·회원가입 카드와 X 임베드 보강
북마크·뉴스레터 CTA 마크다운 블록과 컴포넌트를 추가하고, Twitter/X URL은 공식 embed iframe으로 렌더링한다.
Callout 강조선과 이미지 캡션 색을 테마 변수에 맞춘다.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 09:47:49 +09:00

96 lines
3.2 KiB
Vue

<script setup>
const props = defineProps({
url: {
type: String,
required: true
},
title: {
type: String,
default: ''
},
description: {
type: String,
default: ''
},
thumbnail: {
type: String,
default: ''
}
})
/**
* 북마크 카드에 표시할 호스트명을 반환한다.
* @returns {string} www 없는 호스트 또는 빈 문자열
*/
const displayHost = computed(() => {
try {
return new URL(props.url).hostname.replace(/^www\./, '')
} catch {
return ''
}
})
/**
* 썸네일이 비었을 때 파비콘 보조 URL을 만든다.
* @returns {string} favicon 요청 URL
*/
const faviconUrl = computed(() => {
if (!displayHost.value) {
return ''
}
return `https://www.google.com/s2/favicons?domain=${encodeURIComponent(displayHost.value)}&sz=128`
})
/**
* 실제로 표시할 이미지 주소
* @returns {string}
*/
const imageSrc = computed(() => props.thumbnail || faviconUrl.value)
/**
* 표시 제목(없으면 호스트·URL)
* @returns {string}
*/
const displayTitle = computed(() => props.title || displayHost.value || props.url)
</script>
<template>
<a
class="prose-bookmark group prose-bookmark-card my-8 flex max-w-full flex-col overflow-hidden rounded-[10px] border border-[var(--site-line)] bg-[var(--site-panel)] no-underline transition-[background-color,box-shadow] hover:bg-[color-mix(in_srgb,var(--site-panel)_86%,var(--site-text)_14%)] sm:flex-row"
:href="url"
target="_blank"
rel="noopener noreferrer"
>
<div class="prose-bookmark__media relative h-36 w-full shrink-0 overflow-hidden bg-[color-mix(in_srgb,var(--site-line)_40%,var(--site-panel))] sm:h-auto sm:w-[min(44%,220px)] sm:min-h-[9rem]">
<img
v-if="imageSrc"
class="prose-bookmark__thumb h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.03]"
:src="imageSrc"
alt=""
loading="lazy"
>
</div>
<div class="prose-bookmark__body flex min-w-0 flex-1 flex-col justify-center gap-1 px-4 py-4 sm:px-5 sm:py-5">
<p v-if="displayHost" class="prose-bookmark__host text-[11px] font-semibold uppercase tracking-[0.06em] text-[var(--site-muted)]">
{{ displayHost }}
</p>
<p class="prose-bookmark__title text-[15px] font-semibold leading-snug text-[var(--site-text)]">
{{ displayTitle }}
</p>
<p v-if="description" class="prose-bookmark__desc line-clamp-2 text-sm leading-relaxed text-[var(--site-muted)]">
{{ description }}
</p>
<p class="prose-bookmark__meta mt-1 flex items-center gap-1.5 text-xs font-medium text-[var(--site-soft)]">
<svg class="shrink-0 opacity-80" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 6h-6a2 2 0 0 0 -2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-6" />
<path d="M11 13l9 -9" />
<path d="M15 4h5v5" />
</svg>
<span class="truncate">{{ url }}</span>
</p>
</div>
</a>
</template>