diff --git a/components/content/ContentMarkdownRenderer.vue b/components/content/ContentMarkdownRenderer.vue index 1b1802e..8e1d4e4 100644 --- a/components/content/ContentMarkdownRenderer.vue +++ b/components/content/ContentMarkdownRenderer.vue @@ -5,6 +5,7 @@ import { isImageUrl, parseImageMarkdownLine } from '../../lib/markdown-image.js' +import { getPostBodyThumbnailUrl } from '../../lib/post-card-thumbnail-url.js' import { readEditableTextFromElement, setEditableCaretOffset } from '../../lib/markdown-inline.js' import { applyLiveSelectionDelete, @@ -989,6 +990,42 @@ const tailInsertBeforeLine = computed(() => { }) const activeLightboxImage = computed(() => activeLightboxImages.value[activeLightboxIndex.value]) +const contentLightboxImages = computed(() => { + const images = [] + + blocks.value.forEach((block) => { + if (block.type === 'image' && block.url) { + images.push({ + ...block, + lightboxKey: block.id + }) + return + } + + if (block.type === 'gallery' && Array.isArray(block.images)) { + block.images.forEach((image, imageIndex) => { + if (!image?.url) { + return + } + + images.push({ + ...image, + lightboxKey: `${block.id}:${imageIndex}` + }) + }) + } + }) + + return images +}) + +/** + * 게시물 본문 표시용 이미지 URL을 조회한다. + * @param {string|null|undefined} imageUrl - 원본 이미지 URL + * @returns {string} 표시용 이미지 URL + */ +const getContentDisplayImageUrl = (imageUrl) => getPostBodyThumbnailUrl(imageUrl) || imageUrl || '' + /** * 갤러리 이미지를 행 단위로 최대 3개씩 나눈다. * @param {Array} images - 갤러리 이미지 목록 @@ -1037,6 +1074,22 @@ const onGalleryImageLoad = (event, blockId, imageIndex, url) => { } } +/** + * 갤러리 표시용 이미지 로드 실패 시 원본 이미지로 되돌린다. + * @param {Event} event - 이미지 로드 이벤트 + * @param {string} fallbackUrl - 원본 이미지 URL + * @returns {void} + */ +const onGalleryImageError = (event, fallbackUrl) => { + const image = event.target + + if (!(image instanceof HTMLImageElement) || !fallbackUrl || image.src.endsWith(fallbackUrl)) { + return + } + + image.src = fallbackUrl +} + /** * 갤러리 이미지 셀의 flex 비율 스타일을 만든다. * @param {Object} block - 갤러리 블록 @@ -2457,6 +2510,22 @@ const openLightbox = (images, index) => { activeLightboxIndex.value = index } +/** + * 본문 전체 이미지 목록에서 지정 이미지를 라이트박스로 연다. + * @param {string} lightboxKey - 이미지 식별 키 + * @returns {void} + */ +const openContentLightbox = (lightboxKey) => { + const images = contentLightboxImages.value + const index = images.findIndex((image) => image.lightboxKey === lightboxKey) + + if (index < 0) { + return + } + + openLightbox(images, index) +} + /** * 라이트박스를 닫는다 * @returns {void} @@ -3131,18 +3200,24 @@ onBeforeUnmount(() => { {
String(props.src || '').trim().length > 0) +const loadFailed = ref(false) +const currentSrc = ref(props.src) + +const hasRenderableSrc = computed(() => String(currentSrc.value || '').trim().length > 0) const errorLabel = computed(() => { - const trimmed = String(props.src || '').trim() + const trimmed = String(props.fallbackSrc || props.src || '').trim() if (!trimmed) { return '이미지 URL이 비어 있습니다' @@ -38,6 +49,7 @@ const errorLabel = computed(() => { }) watch(() => props.src, () => { + currentSrc.value = props.src loadFailed.value = false }) @@ -46,6 +58,11 @@ watch(() => props.src, () => { * @returns {void} */ const onImageError = () => { + if (props.fallbackSrc && currentSrc.value !== props.fallbackSrc) { + currentSrc.value = props.fallbackSrc + return + } + loadFailed.value = true } @@ -56,6 +73,18 @@ const onImageError = () => { const onImageLoad = () => { loadFailed.value = false } + +/** + * 이미지 확대 요청을 전달한다. + * @returns {void} + */ +const activateImage = () => { + if (!props.expandable || !hasRenderableSrc.value || loadFailed.value) { + return + } + + emit('activate') +} +
+
본문 이미지
+
+ + 자동 생성된 본문 표시용 WebP 이미지입니다. + + + + 원본: {{ selectedMedia.bodyThumbnailStatus.originalUrl }} + + +
+
diff --git a/pages/posts/index.vue b/pages/posts/index.vue index 1bca364..d04db0d 100644 --- a/pages/posts/index.vue +++ b/pages/posts/index.vue @@ -95,15 +95,6 @@ const postCards = computed(() => (postsResponse.value?.items || []).map((post) = } })) -/** - * 게시물 목록 페이지 링크를 만든다. - * @param {number} page - 페이지 번호 - * @returns {Object} Nuxt 링크 대상 - */ -const getPostsPageTo = (page) => ({ - path: '/posts', - query: page > 1 ? { page } : {} -}) diff --git a/pages/tag/[slug].vue b/pages/tag/[slug].vue index c29666c..434a8ea 100644 --- a/pages/tag/[slug].vue +++ b/pages/tag/[slug].vue @@ -89,15 +89,6 @@ const tagPosts = computed(() => (postsResponse.value?.items || []) } })) -/** - * 태그 게시물 목록 페이지 링크를 만든다. - * @param {number} page - 페이지 번호 - * @returns {Object} Nuxt 링크 대상 - */ -const getTagPageTo = (page) => ({ - path: `/tag/${slug.value}`, - query: page > 1 ? { page } : {} -})