게시물 HTML 블록 분리
This commit is contained in:
@@ -2,7 +2,6 @@ import { isImageUrl, parseImageMarkdownLine } from './markdown-image.js'
|
||||
import { CALLOUT_BACKGROUND_OPTIONS, QUOTE_BACKGROUND_OPTIONS, parseCalloutOptions } from './markdown-callout.js'
|
||||
import { parseCodeFenceLine } from './markdown-code-block.js'
|
||||
import { parseToggleOpenerLine } from './markdown-toggle.js'
|
||||
import { isIframeEmbedMarkup } from './markdown-iframe-embed.js'
|
||||
|
||||
/**
|
||||
* fenced 블록 시작 줄 인덱스를 찾는다.
|
||||
@@ -191,41 +190,40 @@ const resolveGalleryBlock = (lines, currentLine) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* 임베드 fenced 블록을 파싱한다.
|
||||
* 링크·iframe·HTML fenced 블록을 파싱한다.
|
||||
* @param {string[]} lines - 본문 줄 목록
|
||||
* @param {number} currentLine - 현재 줄
|
||||
* @returns {{ kind: 'embed', startLine: number, endLine: number, url: string }|null}
|
||||
* @returns {{ kind: 'link'|'iframe'|'html', startLine: number, endLine: number, url: string }|null}
|
||||
*/
|
||||
const resolveEmbedBlock = (lines, currentLine) => {
|
||||
const standaloneUrl = String(lines[currentLine] || '').trim()
|
||||
const resolveHtmlFamilyBlock = (lines, currentLine) => {
|
||||
const blockTypes = [
|
||||
{ opener: ':::link', kind: 'link' },
|
||||
{ opener: ':::iframe', kind: 'iframe' },
|
||||
{ opener: ':::html', kind: 'html' }
|
||||
]
|
||||
|
||||
for (const blockType of blockTypes) {
|
||||
const blockStart = findFencedBlockStart(lines, currentLine, blockType.opener)
|
||||
|
||||
if (blockStart === -1) {
|
||||
continue
|
||||
}
|
||||
|
||||
const blockEnd = findFencedBlockEnd(lines, blockStart)
|
||||
|
||||
if (blockEnd === -1 || currentLine > blockEnd) {
|
||||
continue
|
||||
}
|
||||
|
||||
if ((isStandaloneUrlLine(standaloneUrl) && !isStandaloneImageUrlLine(standaloneUrl)) || isIframeEmbedMarkup(standaloneUrl)) {
|
||||
return {
|
||||
kind: 'embed',
|
||||
startLine: currentLine,
|
||||
endLine: currentLine,
|
||||
url: standaloneUrl
|
||||
kind: blockType.kind,
|
||||
startLine: blockStart,
|
||||
endLine: blockEnd,
|
||||
url: lines.slice(blockStart + 1, blockEnd).join('\n').trim()
|
||||
}
|
||||
}
|
||||
|
||||
const embedStart = findFencedBlockStart(lines, currentLine, ':::embed')
|
||||
|
||||
if (embedStart === -1) {
|
||||
return null
|
||||
}
|
||||
|
||||
const embedEnd = findFencedBlockEnd(lines, embedStart)
|
||||
|
||||
if (embedEnd === -1 || currentLine > embedEnd) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
kind: 'embed',
|
||||
startLine: embedStart,
|
||||
endLine: embedEnd,
|
||||
url: lines.slice(embedStart + 1, embedEnd).join('\n').trim()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -394,5 +392,5 @@ export const resolveActiveBlockContext = (markdown, lineIndex) => {
|
||||
return toggle
|
||||
}
|
||||
|
||||
return resolveEmbedBlock(lines, currentLine)
|
||||
return resolveHtmlFamilyBlock(lines, currentLine)
|
||||
}
|
||||
|
||||
35
lib/markdown-html-block.js
Normal file
35
lib/markdown-html-block.js
Normal file
@@ -0,0 +1,35 @@
|
||||
const DANGEROUS_TAG_NAMES = [
|
||||
'script',
|
||||
'iframe',
|
||||
'object',
|
||||
'embed',
|
||||
'form',
|
||||
'input',
|
||||
'button',
|
||||
'textarea',
|
||||
'select',
|
||||
'meta',
|
||||
'link',
|
||||
'base'
|
||||
]
|
||||
|
||||
const dangerousTagPattern = new RegExp(`<\\s*(${DANGEROUS_TAG_NAMES.join('|')})\\b[\\s\\S]*?<\\s*\\/\\s*\\1\\s*>`, 'gi')
|
||||
const dangerousSingleTagPattern = new RegExp(`<\\s*(${DANGEROUS_TAG_NAMES.join('|')})\\b[^>]*\\/?>`, 'gi')
|
||||
|
||||
/**
|
||||
* 본문 HTML 블록에서 실행성 코드와 위험 속성을 제거한다.
|
||||
* @param {string} value - 원본 HTML
|
||||
* @returns {string} 정리된 HTML
|
||||
*/
|
||||
export const sanitizeHtmlBlock = (value) => String(value || '')
|
||||
.replace(dangerousTagPattern, '')
|
||||
.replace(dangerousSingleTagPattern, '')
|
||||
.replace(/\s+on[a-z]+\s*=\s*"[^"]*"/gi, '')
|
||||
.replace(/\s+on[a-z]+\s*=\s*'[^']*'/gi, '')
|
||||
.replace(/\s+on[a-z]+\s*=\s*[^\s>]+/gi, '')
|
||||
.replace(/\s+(href|src)\s*=\s*"javascript:[^"]*"/gi, '')
|
||||
.replace(/\s+(href|src)\s*=\s*'javascript:[^']*'/gi, '')
|
||||
.replace(/\s+(href|src)\s*=\s*javascript:[^\s>]+/gi, '')
|
||||
.replace(/\s+style\s*=\s*"[^"]*expression\s*\([^"]*"/gi, '')
|
||||
.replace(/\s+style\s*=\s*'[^']*expression\s*\([^']*'/gi, '')
|
||||
.trim()
|
||||
@@ -116,12 +116,28 @@ export const MARKDOWN_SLASH_COMMANDS = [
|
||||
lines: [':::toggle 제목', '', ':::']
|
||||
},
|
||||
{
|
||||
id: 'embed',
|
||||
id: 'html',
|
||||
label: 'HTML',
|
||||
description: 'URL 또는 iframe HTML',
|
||||
keywords: ['html', 'iframe', 'embed', 'youtube', 'link', '임베드', '유튜브'],
|
||||
description: '원하는 HTML 코드 삽입',
|
||||
keywords: ['html', 'markup', 'tablehtml', 'html표', '코드', '마크업'],
|
||||
action: 'lines',
|
||||
lines: ['https://']
|
||||
lines: [':::html', '', ':::']
|
||||
},
|
||||
{
|
||||
id: 'link',
|
||||
label: '링크',
|
||||
description: 'URL 링크/임베드 카드',
|
||||
keywords: ['link', 'url', 'embed', 'youtube', '링크', '주소', '임베드', '유튜브'],
|
||||
action: 'lines',
|
||||
lines: [':::link', 'https://', ':::']
|
||||
},
|
||||
{
|
||||
id: 'iframe',
|
||||
label: 'iframe',
|
||||
description: '외부 iframe 코드 삽입',
|
||||
keywords: ['iframe', 'frame', '아이프레임', 'html'],
|
||||
action: 'lines',
|
||||
lines: [':::iframe', '<iframe src="https://"></iframe>', ':::']
|
||||
},
|
||||
{
|
||||
id: 'video',
|
||||
|
||||
Reference in New Issue
Block a user