게시물 인아티클 광고 슬롯 추가
This commit is contained in:
@@ -24,6 +24,12 @@ const adSlots = [
|
||||
description: '게시물 상세 본문 렌더링 직전에 표시됩니다.',
|
||||
placeholder: '<script async src="..."><' + '/script>\n<ins class="adsbygoogle" ...></ins>'
|
||||
},
|
||||
{
|
||||
key: 'adPostInArticleCode',
|
||||
label: '게시물 인아티클',
|
||||
description: '게시물 본문이 충분히 길 때 전체 블록 40% 근처 문단 뒤에 한 번 표시됩니다.',
|
||||
placeholder: '<ins class="adsbygoogle" ...></ins>'
|
||||
},
|
||||
{
|
||||
key: 'adPostBottomCode',
|
||||
label: '게시물 본문 하단',
|
||||
|
||||
@@ -59,6 +59,11 @@ const props = defineProps({
|
||||
slashSuppressedLines: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
/** 공개 게시물 본문 중간에 삽입할 인아티클 광고 코드 */
|
||||
inArticleAdCode: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
@@ -757,6 +762,71 @@ const parseMarkdownBlocks = (markdown) => {
|
||||
}
|
||||
|
||||
const blocks = computed(() => parseMarkdownBlocks(props.content))
|
||||
const normalizedInArticleAdCode = computed(() => String(props.inArticleAdCode || '').trim())
|
||||
|
||||
/**
|
||||
* 블록의 텍스트 길이를 계산한다.
|
||||
* @param {Object} block - 마크다운 블록
|
||||
* @returns {number} 공백을 제외한 텍스트 길이
|
||||
*/
|
||||
const getBlockPlainTextLength = (block) => {
|
||||
const value = Array.isArray(block.text) ? block.text.join(' ') : String(block.text || '')
|
||||
return value.replace(/\s+/g, '').length
|
||||
}
|
||||
|
||||
/**
|
||||
* 본문 인아티클 광고를 삽입할 수 있는 문단 블록인지 확인한다.
|
||||
* @param {Object} block - 마크다운 블록
|
||||
* @returns {boolean} 삽입 후보 여부
|
||||
*/
|
||||
const isInArticleAdCandidateBlock = (block) => {
|
||||
return block?.type === 'paragraph' && getBlockPlainTextLength(block) >= 12
|
||||
}
|
||||
|
||||
/**
|
||||
* 본문 인아티클 광고를 삽입할 블록 ID를 반환한다.
|
||||
* @returns {string} 삽입 기준 블록 ID
|
||||
*/
|
||||
const inArticleAdAfterBlockId = computed(() => {
|
||||
if (props.interactive || !normalizedInArticleAdCode.value) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const currentBlocks = blocks.value
|
||||
const contentBlocks = currentBlocks.filter((block) => !['spacer', 'divider'].includes(block.type))
|
||||
|
||||
if (contentBlocks.length < 10) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const candidates = currentBlocks
|
||||
.map((block, index) => ({ block, index }))
|
||||
.filter((item) => isInArticleAdCandidateBlock(item.block))
|
||||
|
||||
if (candidates.length < 6) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const minIndex = Math.max(3, Math.floor(currentBlocks.length * 0.2))
|
||||
const maxIndex = Math.min(currentBlocks.length - 4, Math.floor(currentBlocks.length * 0.75))
|
||||
|
||||
if (maxIndex < minIndex) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const targetIndex = Math.floor((currentBlocks.length - 1) * 0.4)
|
||||
const rangedCandidates = candidates.filter((item) => item.index >= minIndex && item.index <= maxIndex)
|
||||
|
||||
if (!rangedCandidates.length) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const selected = rangedCandidates.reduce((closest, item) => {
|
||||
return Math.abs(item.index - targetIndex) < Math.abs(closest.index - targetIndex) ? item : closest
|
||||
}, rangedCandidates[0])
|
||||
|
||||
return selected.block.id
|
||||
})
|
||||
const headingIdsByBlock = computed(() => {
|
||||
const createHeadingId = createHeadingIdFactory()
|
||||
const ids = {}
|
||||
@@ -3259,6 +3329,13 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</template>
|
||||
</p>
|
||||
|
||||
<SiteAdSlot
|
||||
v-if="block.id === inArticleAdAfterBlockId"
|
||||
class="content-markdown-renderer__in-article-ad my-8"
|
||||
:code="normalizedInArticleAdCode"
|
||||
location="post-in-article"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user