diff --git a/components/admin/AdminMarkdownEditor.vue b/components/admin/AdminMarkdownEditor.vue
index ce21fbd..fc48ffc 100644
--- a/components/admin/AdminMarkdownEditor.vue
+++ b/components/admin/AdminMarkdownEditor.vue
@@ -26,9 +26,11 @@ import { buildCalloutOpenerLine, CALLOUT_BACKGROUND_OPTIONS, QUOTE_BACKGROUND_OP
import { buildCodeFenceOpener } from '../../lib/markdown-code-block.js'
import { buildToggleOpenerLine } from '../../lib/markdown-toggle.js'
import {
+ getPostBodyPreviewThumbnailUrl,
isPageCoverThumbnailUrl,
isPostBodyThumbnailUrl,
- isPostCardThumbnailUrl
+ isPostCardThumbnailUrl,
+ withGeneratedImageCacheVersion
} from '../../lib/post-card-thumbnail-url.js'
import { getTextareaCaretCoordinates } from '../../lib/textarea-caret-coordinates.js'
import {
@@ -1840,6 +1842,47 @@ const isMediaItemSelectableForTarget = (item) => {
return kind === mediaPickerTarget.value
}
+/**
+ * 미디어 선택 모달에서 사용할 가벼운 이미지 미리보기 URL을 반환한다.
+ * 선택·삽입 값은 원본 URL을 유지하고, 화면 표시만 파생 이미지를 우선 사용한다.
+ * @param {Object} item - 미디어 항목
+ * @returns {string} 미리보기 URL
+ */
+const getMediaPickerPreviewUrl = (item) => {
+ const url = String(item?.url || '')
+
+ if (getMediaItemKind(item) !== 'image') {
+ return url
+ }
+
+ if (item?.bodyThumbnailStatus?.hasThumbnail) {
+ return withGeneratedImageCacheVersion(getPostBodyPreviewThumbnailUrl(url), `${item?.updatedAt || ''}-${item?.size || ''}`)
+ }
+
+ if (item?.thumbnailStatus?.hasThumbnail && item.thumbnailStatus.thumbnailUrl) {
+ return withGeneratedImageCacheVersion(item.thumbnailStatus.thumbnailUrl, `${item?.updatedAt || ''}-${item?.size || ''}`)
+ }
+
+ return url
+}
+
+/**
+ * 미디어 선택 모달 파생 이미지 로드 실패 시 원본 URL로 되돌린다.
+ * @param {Event} event - 이미지 오류 이벤트
+ * @param {Object} item - 미디어 항목
+ * @returns {void}
+ */
+const onMediaPickerImageError = (event, item) => {
+ const image = event.target
+
+ if (!(image instanceof HTMLImageElement) || image.dataset.fallbackApplied === 'true') {
+ return
+ }
+
+ image.dataset.fallbackApplied = 'true'
+ image.src = String(item?.url || '')
+}
+
/**
* 드롭된 파일이 현재 미디어 선택 대상에 맞는지 확인한다.
* @param {File} file - 브라우저 파일
@@ -3775,8 +3818,11 @@ const handleKeydown = (event) => {
} ownerByDerivativeUrl - 파생 URL별 원본 URL 맵
+ * @returns {Array} 파생 URL 목록
+ */
+const getOwnedDerivativeUrls = (originalUrl, ownerByDerivativeUrl) => [...ownerByDerivativeUrl.entries()]
+ .filter(([, ownerUrl]) => ownerUrl === originalUrl)
+ .map(([derivativeUrl]) => derivativeUrl)
+
+/**
+ * 원본 이미지에 연결된 파생 이미지 사용처를 조회한다.
+ * @param {string} originalUrl - 원본 미디어 URL
+ * @param {Map} ownerByDerivativeUrl - 파생 URL별 원본 URL 맵
+ * @param {Array