게시글 상세 목차 사이드바 추가 v1.5.12
This commit is contained in:
89
lib/markdown-toc.js
Normal file
89
lib/markdown-toc.js
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 제목 텍스트에서 인라인 마크다운 기호를 제거한다.
|
||||
* @param {string} value - 원본 제목 텍스트
|
||||
* @returns {string} 정리된 제목 텍스트
|
||||
*/
|
||||
export const stripMarkdownHeadingText = (value) => String(value || '')
|
||||
.replace(/!\[([^\]]*)\]\([^)]+\)/g, '$1')
|
||||
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
|
||||
.replace(/[`*_~]/g, '')
|
||||
.replace(/<[^>]+>/g, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
|
||||
/**
|
||||
* 제목 텍스트를 앵커 ID 기본값으로 변환한다.
|
||||
* @param {string} value - 제목 텍스트
|
||||
* @returns {string} 앵커 기본값
|
||||
*/
|
||||
export const createHeadingSlugBase = (value) => {
|
||||
const cleaned = stripMarkdownHeadingText(value)
|
||||
.toLowerCase()
|
||||
.replace(/[^\p{Letter}\p{Number}\s-]/gu, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/^-|-$/g, '')
|
||||
|
||||
return cleaned || 'section'
|
||||
}
|
||||
|
||||
/**
|
||||
* 중복 제목을 보정하는 제목 ID 생성기를 만든다.
|
||||
* @returns {(value: string) => string} 제목 ID 생성 함수
|
||||
*/
|
||||
export const createHeadingIdFactory = () => {
|
||||
const counts = new Map()
|
||||
|
||||
return (value) => {
|
||||
const base = createHeadingSlugBase(value)
|
||||
const nextCount = (counts.get(base) || 0) + 1
|
||||
counts.set(base, nextCount)
|
||||
|
||||
return nextCount === 1 ? base : `${base}-${nextCount}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 마크다운 본문에서 H1~H3 목차 항목을 추출한다.
|
||||
* @param {string} markdown - 마크다운 본문
|
||||
* @returns {Array<{ id: string, level: number, text: string }>} 목차 항목
|
||||
*/
|
||||
export const extractMarkdownToc = (markdown) => {
|
||||
const createHeadingId = createHeadingIdFactory()
|
||||
const toc = []
|
||||
const lines = String(markdown || '').split('\n')
|
||||
let inCodeFence = false
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmedLine = line.trim()
|
||||
|
||||
if (trimmedLine.startsWith('```')) {
|
||||
inCodeFence = !inCodeFence
|
||||
continue
|
||||
}
|
||||
|
||||
if (inCodeFence) {
|
||||
continue
|
||||
}
|
||||
|
||||
const headingMatch = trimmedLine.match(/^(#{1,3})\s+(.+)$/)
|
||||
|
||||
if (!headingMatch) {
|
||||
continue
|
||||
}
|
||||
|
||||
const text = stripMarkdownHeadingText(headingMatch[2])
|
||||
|
||||
if (!text) {
|
||||
continue
|
||||
}
|
||||
|
||||
toc.push({
|
||||
id: createHeadingId(headingMatch[2]),
|
||||
level: headingMatch[1].length,
|
||||
text
|
||||
})
|
||||
}
|
||||
|
||||
return toc
|
||||
}
|
||||
Reference in New Issue
Block a user