인아티클 광고 위치 계산 분리
This commit is contained in:
@@ -47,6 +47,7 @@ import {
|
||||
QUOTE_BACKGROUND_OPTIONS,
|
||||
parseCalloutOptions
|
||||
} from '../../lib/markdown-callout.js'
|
||||
import { getInArticleAdAfterBlockIds } from '../../lib/content-in-article-ad.js'
|
||||
import { createHeadingIdFactory } from '../../lib/markdown-toc.js'
|
||||
import ContentMarkdownCodeBlockEditor from './ContentMarkdownCodeBlockEditor.vue'
|
||||
import ProseCodeBlock from './ProseCodeBlock.vue'
|
||||
@@ -105,13 +106,6 @@ const BLANK_PARAGRAPH_MARKER = '<!--sori:blank-paragraph-->'
|
||||
const LIVE_IMAGE_DRAG_MIME = 'application/x-sori-live-image'
|
||||
/** @type {string} 본문 리스트 마커 색상 */
|
||||
const CONTENT_LIST_MARKER_COLOR = '#2eb6ea'
|
||||
/** @type {number} 인아티클 광고 최소 본문 글자 수 */
|
||||
const IN_ARTICLE_AD_MIN_TEXT_LENGTH = 2000
|
||||
/** @type {number} 인아티클 광고 2회 표시 기준 본문 글자 수 */
|
||||
const IN_ARTICLE_AD_SECOND_TEXT_LENGTH = 6000
|
||||
/** @type {number} 인아티클 광고 사이 최소 블록 간격 */
|
||||
const IN_ARTICLE_AD_MIN_BLOCK_SPACING = 8
|
||||
|
||||
const activeLightboxImages = ref([])
|
||||
const activeLightboxIndex = ref(0)
|
||||
/** @type {import('vue').Ref<Record<string, number>>} 갤러리 이미지 자연 비율 */
|
||||
@@ -621,42 +615,6 @@ 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
|
||||
}
|
||||
|
||||
/**
|
||||
* 본문 인아티클 광고 목표 위치 비율 목록을 반환한다.
|
||||
* @param {number} plainTextLength - 본문 글자 수
|
||||
* @returns {number[]} 광고 목표 위치 비율
|
||||
*/
|
||||
const getInArticleAdTargetRatios = (plainTextLength) => {
|
||||
if (plainTextLength >= IN_ARTICLE_AD_SECOND_TEXT_LENGTH) {
|
||||
return [0.35, 0.7]
|
||||
}
|
||||
|
||||
if (plainTextLength >= IN_ARTICLE_AD_MIN_TEXT_LENGTH) {
|
||||
return [0.4]
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* 본문 인아티클 광고를 삽입할 블록 ID 목록을 반환한다.
|
||||
* @returns {string[]} 삽입 기준 블록 ID 목록
|
||||
@@ -666,58 +624,7 @@ const inArticleAdAfterBlockIds = computed(() => {
|
||||
return []
|
||||
}
|
||||
|
||||
const currentBlocks = blocks.value
|
||||
const contentBlocks = currentBlocks.filter((block) => !['spacer', 'divider'].includes(block.type))
|
||||
const plainTextLength = contentBlocks.reduce((sum, block) => sum + getBlockPlainTextLength(block), 0)
|
||||
const targetRatios = getInArticleAdTargetRatios(plainTextLength)
|
||||
|
||||
if (contentBlocks.length < 10 || !targetRatios.length) {
|
||||
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.8))
|
||||
|
||||
if (maxIndex < minIndex) {
|
||||
return []
|
||||
}
|
||||
|
||||
const rangedCandidates = candidates.filter((item) => item.index >= minIndex && item.index <= maxIndex)
|
||||
|
||||
if (!rangedCandidates.length) {
|
||||
return []
|
||||
}
|
||||
|
||||
const selectedItems = []
|
||||
|
||||
for (const ratio of targetRatios) {
|
||||
const targetIndex = Math.floor((currentBlocks.length - 1) * ratio)
|
||||
const availableCandidates = rangedCandidates.filter((item) => {
|
||||
return selectedItems.every((selected) => Math.abs(selected.index - item.index) >= IN_ARTICLE_AD_MIN_BLOCK_SPACING)
|
||||
})
|
||||
|
||||
if (!availableCandidates.length) {
|
||||
continue
|
||||
}
|
||||
|
||||
const selected = availableCandidates.reduce((closest, item) => {
|
||||
return Math.abs(item.index - targetIndex) < Math.abs(closest.index - targetIndex) ? item : closest
|
||||
}, availableCandidates[0])
|
||||
|
||||
selectedItems.push(selected)
|
||||
}
|
||||
|
||||
return selectedItems
|
||||
.sort((a, b) => a.index - b.index)
|
||||
.map((item) => item.block.id)
|
||||
return getInArticleAdAfterBlockIds(blocks.value)
|
||||
})
|
||||
|
||||
const headingIdsByBlock = computed(() => {
|
||||
|
||||
Reference in New Issue
Block a user