const postCardThumbnailUrlPattern = /^\/uploads\/posts\/\d{4}\/\d{2}\/thumbs\/[^/?#]+-card\.webp$/i const pageCoverThumbnailUrlPattern = /^\/uploads\/posts\/\d{4}\/\d{2}\/thumbs\/[^/?#]+-page-cover\.webp$/i const postBodyThumbnailUrlPattern = /^\/uploads\/posts\/\d{4}\/\d{2}\/thumbs\/[^/?#]+-(?:body(?:-(?:720|1600|2560))?|lightbox)\.webp$/i const postUploadImageUrlPattern = /^\/uploads\/posts\/\d{4}\/\d{2}\/[^/?#]+\.(?:jpe?g|png|webp)$/i const generatedImageCacheVersion = '20260702-1' /** * 게시물 원본 이미지 URL에 대응하는 파생 이미지 URL을 만든다. * @param {string|null|undefined} imageUrl - 원본 이미지 URL * @param {'body'|'body-720'|'body-1600'|'body-2560'|'lightbox'} kind - 파생 이미지 종류 * @returns {string} 파생 이미지 URL */ const getPostDerivativeImageUrl = (imageUrl, kind) => { const normalizedUrl = String(imageUrl || '') if (!postUploadImageUrlPattern.test(normalizedUrl)) { return '' } const slashIndex = normalizedUrl.lastIndexOf('/') const directory = slashIndex >= 0 ? normalizedUrl.slice(0, slashIndex) : '' const fileName = slashIndex >= 0 ? normalizedUrl.slice(slashIndex + 1) : normalizedUrl const dotIndex = fileName.lastIndexOf('.') const stem = dotIndex >= 0 ? fileName.slice(0, dotIndex) : fileName return `${directory}/thumbs/${stem}-${kind}.webp` } /** * 게시물 카드 썸네일 URL인지 확인한다. * @param {string|null|undefined} imageUrl - 검사할 이미지 URL * @returns {boolean} 카드 썸네일 여부 */ export const isPostCardThumbnailUrl = (imageUrl) => Boolean( imageUrl && postCardThumbnailUrlPattern.test(String(imageUrl)) ) /** * 페이지 커버 썸네일 URL인지 확인한다. * @param {string|null|undefined} imageUrl - 검사할 이미지 URL * @returns {boolean} 페이지 커버 썸네일 여부 */ export const isPageCoverThumbnailUrl = (imageUrl) => Boolean( imageUrl && pageCoverThumbnailUrlPattern.test(String(imageUrl)) ) /** * 게시물 본문 표시용 파생 이미지 URL인지 확인한다. * @param {string|null|undefined} imageUrl - 검사할 이미지 URL * @returns {boolean} 본문 표시용 파생 이미지 여부 */ export const isPostBodyThumbnailUrl = (imageUrl) => Boolean( imageUrl && postBodyThumbnailUrlPattern.test(String(imageUrl)) ) /** * 자동 생성 이미지 URL인지 확인한다. * @param {string|null|undefined} imageUrl - 검사할 이미지 URL * @returns {boolean} 자동 생성 이미지이면 true */ export const isGeneratedPostImageUrl = (imageUrl) => isPostCardThumbnailUrl(imageUrl) || isPageCoverThumbnailUrl(imageUrl) || isPostBodyThumbnailUrl(imageUrl) /** * 자동 생성 이미지 URL에 캐시 우회 쿼리를 붙인다. * @param {string|null|undefined} imageUrl - 이미지 URL * @param {string} [version] - 캐시 버전 * @returns {string} 표시용 이미지 URL */ export const withGeneratedImageCacheVersion = (imageUrl, version = generatedImageCacheVersion) => { const url = String(imageUrl || '') if (!isGeneratedPostImageUrl(url)) { return url } const hashIndex = url.indexOf('#') const baseUrl = hashIndex >= 0 ? url.slice(0, hashIndex) : url const hash = hashIndex >= 0 ? url.slice(hashIndex) : '' const separator = baseUrl.includes('?') ? '&' : '?' return `${baseUrl}${separator}v=${encodeURIComponent(version)}${hash}` } /** * 게시물 원본 이미지 URL에 대응하는 본문 표시용 이미지 URL을 만든다. * @param {string|null|undefined} imageUrl - 원본 이미지 URL * @returns {string} 본문 표시용 파생 이미지 URL */ export const getPostBodyThumbnailUrl = (imageUrl) => getPostDerivativeImageUrl(imageUrl, 'body-1600') /** * 게시물 원본 이미지 URL에 대응하는 작은 본문 표시용 이미지 URL을 만든다. * @param {string|null|undefined} imageUrl - 원본 이미지 URL * @returns {string} 작은 본문 표시용 파생 이미지 URL */ export const getPostBodyPreviewThumbnailUrl = (imageUrl) => getPostDerivativeImageUrl(imageUrl, 'body-720') /** * 본문 1600 파생 이미지 URL인지 확인한다. * @param {string|null|undefined} imageUrl - 검사할 이미지 URL * @returns {boolean} body-1600이면 true */ export const isPostBody1600ThumbnailUrl = (imageUrl) => /\/thumbs\/[^/?#]+-body-1600\.webp$/i.test(String(imageUrl || '')) /** * 본문 720 파생 이미지 URL인지 확인한다. * @param {string|null|undefined} imageUrl - 검사할 이미지 URL * @returns {boolean} body-720이면 true */ export const isPostBody720ThumbnailUrl = (imageUrl) => /\/thumbs\/[^/?#]+-body-720\.webp$/i.test(String(imageUrl || '')) /** * 게시물 원본 이미지 URL에 대응하는 라이트박스용 이미지 URL을 만든다. * @param {string|null|undefined} imageUrl - 원본 이미지 URL * @returns {string} 라이트박스용 파생 이미지 URL */ export const getPostLightboxThumbnailUrl = (imageUrl) => getPostDerivativeImageUrl(imageUrl, 'body-2560') /** * 레거시 본문 표시용 이미지 URL을 만든다. * @param {string|null|undefined} imageUrl - 원본 이미지 URL * @returns {string} 레거시 본문 표시용 이미지 URL */ export const getLegacyPostBodyThumbnailUrl = (imageUrl) => getPostDerivativeImageUrl(imageUrl, 'body') /** * 레거시 라이트박스용 이미지 URL을 만든다. * @param {string|null|undefined} imageUrl - 원본 이미지 URL * @returns {string} 레거시 라이트박스용 이미지 URL */ export const getLegacyPostLightboxThumbnailUrl = (imageUrl) => getPostDerivativeImageUrl(imageUrl, 'lightbox') /** * 페이지 커버 원본 이미지 URL에 대응하는 홈 진열장 썸네일 URL을 만든다. * @param {string|null|undefined} imageUrl - 원본 이미지 URL * @returns {string} 페이지 커버 썸네일 URL */ export const getPageCoverThumbnailUrl = (imageUrl) => getPostDerivativeImageUrl(imageUrl, 'page-cover')