v1.2.1: 블록 설정 패널·이미지 alt 토글 및 포커스 수정

게시물 설정 사이드바 오버레이로 이미지·갤러리·임베드를 편집하고, 파일명 alt 토글과 패널 입력 중 닫힘 문제를 해결했다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-15 18:22:30 +09:00
parent 14ce897bf8
commit 47620ab24c
12 changed files with 821 additions and 201 deletions

View File

@@ -1,4 +1,10 @@
<script setup>
import {
getImageAltAttribute,
getImageCaption,
parseImageMarkdownLine
} from '../../lib/markdown-image.js'
const props = defineProps({
content: {
type: String,
@@ -27,6 +33,8 @@ const createBlock = (type = 'paragraph', text = '', level = null, id = '', optio
level,
url: options.url || '',
alt: options.alt || '',
caption: options.caption || '',
useAlt: options.useAlt === true,
title: options.title || '',
variant: options.variant || '',
ordered: options.ordered || false,
@@ -85,19 +93,7 @@ const parseCalloutOptions = (line) => {
* @param {string} line - 마크다운 행
* @returns {Object|null} 이미지 데이터
*/
const parseImageLine = (line) => {
const match = line.trim().match(/^!\[([^\]]*)\]\(([^)]+)\)(?:\{width=(regular|wide|full)\})?$/)
if (!match) {
return null
}
return {
alt: match[1],
url: match[2],
width: match[3] || 'regular'
}
}
const parseImageLine = (line) => parseImageMarkdownLine(line)
/**
* 독립 블록으로 해석할 수 있는 마크다운 행인지 확인한다.
@@ -555,6 +551,56 @@ const showPreviousImage = () => {
const showNextImage = () => {
activeLightboxIndex.value = (activeLightboxIndex.value + 1) % activeLightboxImages.value.length
}
/**
* 라이트박스 키보드 조작(Esc 닫기, 좌우 이전·다음)
* @param {KeyboardEvent} event - 키보드 이벤트
* @returns {void}
*/
const handleLightboxKeydown = (event) => {
if (!activeLightboxImage.value) {
return
}
if (event.key === 'Escape') {
event.preventDefault()
closeLightbox()
return
}
if (activeLightboxImages.value.length <= 1) {
return
}
if (event.key === 'ArrowLeft') {
event.preventDefault()
showPreviousImage()
return
}
if (event.key === 'ArrowRight') {
event.preventDefault()
showNextImage()
}
}
watch(activeLightboxImage, (image) => {
if (!import.meta.client) {
return
}
if (image) {
window.addEventListener('keydown', handleLightboxKeydown)
} else {
window.removeEventListener('keydown', handleLightboxKeydown)
}
})
onBeforeUnmount(() => {
if (import.meta.client) {
window.removeEventListener('keydown', handleLightboxKeydown)
}
})
</script>
<template>
@@ -590,8 +636,15 @@ const showNextImage = () => {
</template>
</li>
</ProseList>
<ProseImage v-else-if="block.type === 'image'" :src="block.url" :alt="block.alt" :variant="block.width">
{{ block.alt }}
<ProseImage
v-else-if="block.type === 'image'"
:src="block.url"
:alt="getImageAltAttribute(block)"
:variant="block.width"
>
<template v-if="getImageCaption(block)">
{{ getImageCaption(block) }}
</template>
</ProseImage>
<ProseCallout
v-else-if="block.type === 'callout'"
@@ -639,7 +692,7 @@ const showNextImage = () => {
type="button"
@click="openLightbox(block.images, imageIndex)"
>
<img class="content-markdown-renderer__gallery-image aspect-[4/3] w-full object-cover transition-transform hover:scale-[1.02]" :src="image.url" :alt="image.alt">
<img class="content-markdown-renderer__gallery-image aspect-[4/3] w-full object-cover transition-transform hover:scale-[1.02]" :src="image.url" :alt="getImageAltAttribute(image)">
</button>
</div>
<pre
@@ -666,6 +719,8 @@ const showNextImage = () => {
class="content-markdown-renderer__lightbox fixed inset-0 z-50 grid place-items-center bg-black/90 px-5 py-8"
role="dialog"
aria-modal="true"
aria-label="갤러리 이미지 보기"
tabindex="-1"
@click.self="closeLightbox"
>
<button class="content-markdown-renderer__lightbox-close absolute right-5 top-5 rounded bg-white px-3 py-2 text-sm font-semibold text-ink" type="button" @click="closeLightbox">
@@ -679,7 +734,11 @@ const showNextImage = () => {
>
이전
</button>
<img class="content-markdown-renderer__lightbox-image max-h-[84vh] max-w-[92vw] object-contain" :src="activeLightboxImage.url" :alt="activeLightboxImage.alt">
<img
class="content-markdown-renderer__lightbox-image max-h-[84vh] max-w-[92vw] object-contain"
:src="activeLightboxImage.url"
:alt="getImageAltAttribute(activeLightboxImage)"
>
<button
v-if="activeLightboxImages.length > 1"
class="content-markdown-renderer__lightbox-next absolute right-5 top-1/2 rounded bg-white px-3 py-2 text-sm font-semibold text-ink"

View File

@@ -17,7 +17,7 @@ defineProps({
<template>
<figure
class="prose-image my-8"
class="prose-image mb-2.5"
:class="{
'prose-image--wide lg:-mx-10 lg:max-w-none': variant === 'wide',
'prose-image--full lg:-mx-20 lg:max-w-none': variant === 'full'
@@ -26,7 +26,7 @@ defineProps({
<div class="overflow-hidden rounded-[10px] border border-[var(--site-line)] bg-[var(--site-panel)]">
<img class="prose-image__media w-full object-cover" :src="src" :alt="alt">
</div>
<figcaption v-if="$slots.default" class="prose-image__caption mt-3 text-center text-sm text-[var(--site-muted)]">
<figcaption v-if="$slots.default" class="prose-image__caption mt-1.5 text-center text-sm text-[var(--site-muted)]">
<slot />
</figcaption>
</figure>