108 lines
3.6 KiB
JavaScript
108 lines
3.6 KiB
JavaScript
import { computed, unref } from 'vue'
|
|
import { getInArticleAdAfterBlockIds } from '../lib/content-in-article-ad.js'
|
|
import { getContentLightboxImages } from '../lib/content-gallery-images.js'
|
|
import { parseMarkdownBlocks } from '../lib/markdown-block-parser.js'
|
|
import { createHeadingIdFactory } from '../lib/markdown-toc.js'
|
|
|
|
/**
|
|
* 공개 Markdown 본문의 렌더링 블록 계산 상태를 만든다.
|
|
* @param {Object} options - 계산 옵션
|
|
* @param {import('vue').Ref<string>|string} options.content - Markdown 본문
|
|
* @param {import('vue').Ref<boolean>|boolean} options.interactive - 관리자 라이브 편집 여부
|
|
* @param {import('vue').Ref<string>|string} options.inArticleAdCode - 인아티클 광고 코드
|
|
* @returns {{
|
|
* blocks: import('vue').ComputedRef<Array<Object>>,
|
|
* normalizedInArticleAdCode: import('vue').ComputedRef<string>,
|
|
* inArticleAdAfterBlockIds: import('vue').ComputedRef<string[]>,
|
|
* headingIdsByBlock: import('vue').ComputedRef<Record<string, string>>,
|
|
* renderBlocks: import('vue').ComputedRef<Array<Object>>,
|
|
* tailInsertBeforeLine: import('vue').ComputedRef<number>,
|
|
* contentLightboxImages: import('vue').ComputedRef<Array<Object>>
|
|
* }} 본문 블록 계산 결과
|
|
*/
|
|
export const useContentMarkdownBlocks = (options) => {
|
|
const blocks = computed(() => parseMarkdownBlocks(String(unref(options.content) || '')))
|
|
const normalizedInArticleAdCode = computed(() => String(unref(options.inArticleAdCode) || '').trim())
|
|
|
|
/**
|
|
* 본문 인아티클 광고를 삽입할 블록 ID 목록을 반환한다.
|
|
* @returns {string[]} 삽입 기준 블록 ID 목록
|
|
*/
|
|
const inArticleAdAfterBlockIds = computed(() => {
|
|
if (unref(options.interactive) || !normalizedInArticleAdCode.value) {
|
|
return []
|
|
}
|
|
|
|
return getInArticleAdAfterBlockIds(blocks.value)
|
|
})
|
|
|
|
const headingIdsByBlock = computed(() => {
|
|
const createHeadingId = createHeadingIdFactory()
|
|
const ids = {}
|
|
|
|
for (const block of blocks.value) {
|
|
if (block.type === 'heading' && block.level >= 1 && block.level <= 3) {
|
|
ids[block.id] = createHeadingId(block.text)
|
|
}
|
|
}
|
|
|
|
return ids
|
|
})
|
|
|
|
/**
|
|
* 공개 본문 렌더링용 블록 목록을 반환한다.
|
|
* 인아티클 광고는 별도 v-if 대신 블록으로 삽입해 fragment 주석을 줄인다.
|
|
* @returns {Array<Object>} 렌더링 블록 목록
|
|
*/
|
|
const renderBlocks = computed(() => {
|
|
const sourceBlocks = blocks.value
|
|
const adAfterBlockIds = inArticleAdAfterBlockIds.value
|
|
|
|
if (unref(options.interactive) || !adAfterBlockIds.length) {
|
|
return sourceBlocks
|
|
}
|
|
|
|
const adAfterIdSet = new Set(adAfterBlockIds)
|
|
const result = []
|
|
|
|
for (const block of sourceBlocks) {
|
|
result.push(block)
|
|
|
|
if (adAfterIdSet.has(block.id)) {
|
|
result.push({
|
|
id: `in-article-ad-after-${block.id}`,
|
|
type: 'in-article-ad',
|
|
meta: {
|
|
afterBlockId: block.id
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
return result
|
|
})
|
|
|
|
/** @type {import('vue').ComputedRef<number>} 문서 맨 아래 단일 이미지 삽입 줄 */
|
|
const tailInsertBeforeLine = computed(() => {
|
|
const lastBlock = blocks.value[blocks.value.length - 1]
|
|
|
|
if (typeof lastBlock?.meta?.endLine === 'number') {
|
|
return lastBlock.meta.endLine + 1
|
|
}
|
|
|
|
return String(unref(options.content) || '').split('\n').length
|
|
})
|
|
|
|
const contentLightboxImages = computed(() => getContentLightboxImages(blocks.value))
|
|
|
|
return {
|
|
blocks,
|
|
normalizedInArticleAdCode,
|
|
inArticleAdAfterBlockIds,
|
|
headingIdsByBlock,
|
|
renderBlocks,
|
|
tailInsertBeforeLine,
|
|
contentLightboxImages
|
|
}
|
|
}
|