231 lines
5.9 KiB
JavaScript
231 lines
5.9 KiB
JavaScript
/**
|
|
* fenced 블록 시작 줄 인덱스를 찾는다.
|
|
* @param {string[]} lines - 본문 줄 목록
|
|
* @param {number} currentLine - 현재 줄
|
|
* @param {string} opener - 시작 토큰
|
|
* @returns {number} 시작 줄 또는 -1
|
|
*/
|
|
export const findFencedBlockStart = (lines, currentLine, opener) => {
|
|
for (let index = currentLine; index >= 0; index -= 1) {
|
|
if ((lines[index] || '').trim() === opener) {
|
|
return index
|
|
}
|
|
|
|
if ((lines[index] || '').trim() === ':::') {
|
|
break
|
|
}
|
|
}
|
|
|
|
return -1
|
|
}
|
|
|
|
/**
|
|
* 조건에 맞는 fenced 블록 시작 줄 인덱스를 찾는다.
|
|
* @param {string[]} lines - 본문 줄 목록
|
|
* @param {number} currentLine - 현재 줄
|
|
* @param {(line: string) => boolean} predicate - 시작 줄 판별 함수
|
|
* @returns {number} 시작 줄 또는 -1
|
|
*/
|
|
export const findFencedBlockStartBy = (lines, currentLine, predicate) => {
|
|
for (let index = currentLine; index >= 0; index -= 1) {
|
|
if (predicate((lines[index] || '').trim())) {
|
|
return index
|
|
}
|
|
|
|
if ((lines[index] || '').trim() === ':::') {
|
|
break
|
|
}
|
|
}
|
|
|
|
return -1
|
|
}
|
|
|
|
/**
|
|
* fenced 블록 종료 줄 인덱스를 찾는다.
|
|
* @param {string[]} lines - 본문 줄 목록
|
|
* @param {number} startLine - 시작 줄
|
|
* @returns {number} 종료 줄 또는 -1
|
|
*/
|
|
export const findFencedBlockEnd = (lines, startLine) => {
|
|
for (let index = startLine + 1; index < lines.length; index += 1) {
|
|
if ((lines[index] || '').trim() === ':::') {
|
|
return index
|
|
}
|
|
}
|
|
|
|
return -1
|
|
}
|
|
|
|
/**
|
|
* 닫힘 표식까지의 행 목록을 반환한다.
|
|
* @param {Array<string>} lines - 전체 마크다운 행
|
|
* @param {number} startIndex - 본문 시작 인덱스
|
|
* @returns {{contentLines: Array<string>, endLine: number, nextIndex: number}} 블록 본문과 다음 인덱스
|
|
*/
|
|
export const collectFencedLines = (lines, startIndex) => {
|
|
const contentLines = []
|
|
let index = startIndex
|
|
|
|
while (index < lines.length && lines[index].trim() !== ':::') {
|
|
contentLines.push(lines[index])
|
|
index += 1
|
|
}
|
|
|
|
return {
|
|
contentLines,
|
|
endLine: index < lines.length ? index : Math.max(lines.length - 1, startIndex - 1, 0),
|
|
nextIndex: index < lines.length ? index + 1 : lines.length
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 북마크 fenced 블록 본문에서 URL·제목·설명·썸네일을 파싱한다.
|
|
* @param {string} raw - fenced 내부 텍스트
|
|
* @returns {{url: string, title: string, description: string, thumbnail: string}} 북마크 메타
|
|
*/
|
|
export const parseBookmarkMeta = (raw) => {
|
|
const lines = raw.split('\n').map((line) => line.trim()).filter(Boolean)
|
|
const meta = {
|
|
url: '',
|
|
title: '',
|
|
description: '',
|
|
thumbnail: ''
|
|
}
|
|
|
|
for (const line of lines) {
|
|
const kv = line.match(/^(\w+)=(.*)$/)
|
|
|
|
if (kv) {
|
|
const key = kv[1].toLowerCase()
|
|
const value = kv[2].trim()
|
|
|
|
if (key === 'url') {
|
|
meta.url = value
|
|
} else if (key === 'title') {
|
|
meta.title = value
|
|
} else if (key === 'description' || key === 'desc') {
|
|
meta.description = value
|
|
} else if (key === 'thumbnail' || key === 'image') {
|
|
meta.thumbnail = value
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
if (!meta.url && /^https?:\/\//i.test(line)) {
|
|
meta.url = line
|
|
continue
|
|
}
|
|
|
|
if (meta.url && !meta.title) {
|
|
meta.title = line
|
|
continue
|
|
}
|
|
|
|
if (meta.url && meta.title && !meta.description) {
|
|
meta.description = line
|
|
}
|
|
}
|
|
|
|
return meta
|
|
}
|
|
|
|
/**
|
|
* 회원가입(CTA) fenced 블록 본문에서 표시 문구를 파싱한다.
|
|
* @param {string} raw - fenced 내부 텍스트
|
|
* @returns {{title: string, description: string, button: string, placeholder: string}} CTA 메타
|
|
*/
|
|
export const parseSignupMeta = (raw) => {
|
|
const meta = {
|
|
title: '뉴스레터에 가입하세요',
|
|
description: '새 글이 올라오면 받아보실 수 있어요.',
|
|
button: '구독하기',
|
|
placeholder: 'you@example.com'
|
|
}
|
|
const lines = raw.split('\n').map((line) => line.trim()).filter(Boolean)
|
|
|
|
for (const line of lines) {
|
|
const kv = line.match(/^(\w+)=(.*)$/)
|
|
|
|
if (!kv) {
|
|
continue
|
|
}
|
|
|
|
const key = kv[1].toLowerCase()
|
|
const value = kv[2].trim()
|
|
|
|
if (key === 'title') {
|
|
meta.title = value
|
|
} else if (key === 'description' || key === 'desc') {
|
|
meta.description = value
|
|
} else if (key === 'button') {
|
|
meta.button = value
|
|
} else if (key === 'placeholder') {
|
|
meta.placeholder = value
|
|
}
|
|
}
|
|
|
|
return meta
|
|
}
|
|
|
|
/**
|
|
* 미디어 fenced 블록 본문에서 URL과 표시 메타를 파싱한다.
|
|
* @param {string} raw - fenced 내부 텍스트
|
|
* @returns {{url: string, title: string, description: string, poster: string, caption: string, fileName: string, size: string}} 미디어 메타
|
|
*/
|
|
export const parseMediaMeta = (raw) => {
|
|
const meta = {
|
|
url: '',
|
|
title: '',
|
|
description: '',
|
|
poster: '',
|
|
caption: '',
|
|
fileName: '',
|
|
size: ''
|
|
}
|
|
const lines = raw.split('\n').map((line) => line.trim()).filter(Boolean)
|
|
|
|
for (const line of lines) {
|
|
const kv = line.match(/^(\w+)=(.*)$/)
|
|
|
|
if (kv) {
|
|
const key = kv[1].toLowerCase()
|
|
const value = kv[2].trim()
|
|
|
|
if (key === 'url' || key === 'src') {
|
|
meta.url = value
|
|
} else if (key === 'title') {
|
|
meta.title = value
|
|
} else if (key === 'description' || key === 'desc') {
|
|
meta.description = value
|
|
} else if (key === 'poster' || key === 'thumbnail') {
|
|
meta.poster = value
|
|
} else if (key === 'caption') {
|
|
meta.caption = value
|
|
} else if (key === 'name' || key === 'filename' || key === 'file') {
|
|
meta.fileName = value
|
|
} else if (key === 'size') {
|
|
meta.size = value
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
if (!meta.url && (/^https?:\/\//i.test(line) || line.startsWith('/'))) {
|
|
meta.url = line
|
|
continue
|
|
}
|
|
|
|
if (meta.url && !meta.title) {
|
|
meta.title = line
|
|
continue
|
|
}
|
|
|
|
if (meta.url && meta.title && !meta.description) {
|
|
meta.description = line
|
|
}
|
|
}
|
|
|
|
return meta
|
|
}
|