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} options.content - Markdown 본문 * @param {import('vue').Ref|boolean} options.interactive - 관리자 라이브 편집 여부 * @param {import('vue').Ref|string} options.inArticleAdCode - 인아티클 광고 코드 * @returns {{ * blocks: import('vue').ComputedRef>, * normalizedInArticleAdCode: import('vue').ComputedRef, * inArticleAdAfterBlockIds: import('vue').ComputedRef, * headingIdsByBlock: import('vue').ComputedRef>, * renderBlocks: import('vue').ComputedRef>, * tailInsertBeforeLine: import('vue').ComputedRef, * contentLightboxImages: import('vue').ComputedRef> * }} 본문 블록 계산 결과 */ 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} 렌더링 블록 목록 */ 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} 문서 맨 아래 단일 이미지 삽입 줄 */ 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 } }