게시물 설정 사이드바 오버레이로 이미지·갤러리·임베드를 편집하고, 파일명 alt 토글과 패널 입력 중 닫힘 문제를 해결했다. Co-authored-by: Cursor <cursoragent@cursor.com>
101 lines
2.9 KiB
JavaScript
101 lines
2.9 KiB
JavaScript
/** @type {RegExp} 이미지 마크다운 한 줄 */
|
|
const IMAGE_MARKDOWN_LINE_RE = /^!\[(.*?)\]\((\S+?)(?:\s+"((?:[^"\\]|\\.)*)")?\)(?:\{width=(regular|wide|full)\})?$/
|
|
|
|
/**
|
|
* 캡션 문자열 이스케이프 해제
|
|
* @param {string} value - 이스케이프된 캡션
|
|
* @returns {string} 캡션
|
|
*/
|
|
const unescapeImageCaption = (value) => String(value || '').replace(/\\"/g, '"')
|
|
|
|
/**
|
|
* 캡션 문자열 이스케이프
|
|
* @param {string} value - 캡션
|
|
* @returns {string} 이스케이프된 캡션
|
|
*/
|
|
const escapeImageCaption = (value) => String(value || '').replace(/"/g, '\\"')
|
|
|
|
/**
|
|
* URL에서 기본 대체 텍스트(파일명) 추출
|
|
* @param {string} url - 이미지 URL
|
|
* @returns {string} 파일명 기반 라벨
|
|
*/
|
|
export const getImageDefaultAltLabel = (url) => {
|
|
const raw = String(url || '').trim()
|
|
|
|
if (!raw) {
|
|
return ''
|
|
}
|
|
|
|
try {
|
|
const pathname = new URL(raw, 'https://sori.studio').pathname
|
|
return decodeURIComponent(pathname.split('/').filter(Boolean).pop() || '')
|
|
} catch {
|
|
const withoutQuery = raw.split('?')[0].split('#')[0]
|
|
return decodeURIComponent(withoutQuery.split('/').filter(Boolean).pop() || '')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 이미지 마크다운 한 줄 파싱
|
|
* @param {string} line - 마크다운 줄
|
|
* @returns {{ url: string, width: string, caption: string, useAlt: boolean }|null}
|
|
*/
|
|
export const parseImageMarkdownLine = (line) => {
|
|
const match = String(line || '').trim().match(IMAGE_MARKDOWN_LINE_RE)
|
|
|
|
if (!match) {
|
|
return null
|
|
}
|
|
|
|
const alt = match[1] || ''
|
|
|
|
return {
|
|
url: match[2] || '',
|
|
caption: unescapeImageCaption(match[3]),
|
|
width: match[4] || 'regular',
|
|
/** true이면 대체 텍스트로 URL 파일명을 사용한다 */
|
|
useAlt: Boolean(alt)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 이미지 마크다운 한 줄 생성
|
|
* @param {{ url: string, caption?: string, width?: string, useAlt?: boolean }} image - 이미지 정보
|
|
* @returns {string} 마크다운 줄
|
|
*/
|
|
export const serializeImageMarkdown = (image) => {
|
|
const url = String(image.url || '').trim()
|
|
|
|
if (!url) {
|
|
return ''
|
|
}
|
|
|
|
const alt = image.useAlt === true ? getImageDefaultAltLabel(url) : ''
|
|
const caption = String(image.caption ?? '').trim()
|
|
const titlePart = caption ? ` "${escapeImageCaption(caption)}"` : ''
|
|
const width = image.width && image.width !== 'regular' ? `{width=${image.width}}` : ''
|
|
|
|
return `${width}`
|
|
}
|
|
|
|
/**
|
|
* 공개 렌더용 img alt 텍스트
|
|
* @param {{ url?: string, useAlt?: boolean }} image - 이미지 정보
|
|
* @returns {string} alt 속성값
|
|
*/
|
|
export const getImageAltAttribute = (image) => {
|
|
if (!image?.useAlt) {
|
|
return ''
|
|
}
|
|
|
|
return getImageDefaultAltLabel(image.url)
|
|
}
|
|
|
|
/**
|
|
* 공개 렌더용 캡션(표시용 figcaption)
|
|
* @param {{ caption?: string }} image - 이미지 정보
|
|
* @returns {string} 캡션
|
|
*/
|
|
export const getImageCaption = (image) => String(image?.caption || '').trim()
|