게시물 HTML iframe 블록 추가
This commit is contained in:
@@ -2,6 +2,7 @@ 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 블록 시작 줄 인덱스를 찾는다.
|
||||
@@ -198,7 +199,7 @@ const resolveGalleryBlock = (lines, currentLine) => {
|
||||
const resolveEmbedBlock = (lines, currentLine) => {
|
||||
const standaloneUrl = String(lines[currentLine] || '').trim()
|
||||
|
||||
if (isStandaloneUrlLine(standaloneUrl) && !isStandaloneImageUrlLine(standaloneUrl)) {
|
||||
if ((isStandaloneUrlLine(standaloneUrl) && !isStandaloneImageUrlLine(standaloneUrl)) || isIframeEmbedMarkup(standaloneUrl)) {
|
||||
return {
|
||||
kind: 'embed',
|
||||
startLine: currentLine,
|
||||
|
||||
87
lib/markdown-iframe-embed.js
Normal file
87
lib/markdown-iframe-embed.js
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* iframe 속성 문자열을 객체로 변환한다.
|
||||
* @param {string} value - iframe 여는 태그의 속성 문자열
|
||||
* @returns {Record<string, string>} 속성 객체
|
||||
*/
|
||||
const parseIframeAttributes = (value) => {
|
||||
const attributes = {}
|
||||
const attributePattern = /([:\w-]+)\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'>]+))/g
|
||||
let match = attributePattern.exec(String(value || ''))
|
||||
|
||||
while (match) {
|
||||
attributes[match[1].toLowerCase()] = match[3] ?? match[4] ?? match[5] ?? ''
|
||||
match = attributePattern.exec(String(value || ''))
|
||||
}
|
||||
|
||||
return attributes
|
||||
}
|
||||
|
||||
/**
|
||||
* 외부 iframe src로 사용할 수 있는 URL인지 확인한다.
|
||||
* @param {string} value - 검사할 URL
|
||||
* @returns {boolean} 허용 여부
|
||||
*/
|
||||
const isSafeIframeSrc = (value) => {
|
||||
try {
|
||||
const parsedUrl = new URL(value)
|
||||
return ['http:', 'https:'].includes(parsedUrl.protocol)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* iframe 숫자 속성을 정리한다.
|
||||
* @param {string} value - 원본 값
|
||||
* @returns {string} 정리된 값
|
||||
*/
|
||||
const sanitizeSizeValue = (value) => {
|
||||
const text = String(value || '').trim()
|
||||
return /^\d{2,5}$/.test(text) ? text : ''
|
||||
}
|
||||
|
||||
/**
|
||||
* iframe allow 속성을 정리한다.
|
||||
* @param {string} value - 원본 값
|
||||
* @returns {string} 정리된 값
|
||||
*/
|
||||
const sanitizeAllowValue = (value) => String(value || '')
|
||||
.replace(/[^\w\s;*.:/-]/g, '')
|
||||
.trim()
|
||||
|
||||
/**
|
||||
* iframe HTML 코드인지 확인한다.
|
||||
* @param {string} value - 검사할 문자열
|
||||
* @returns {boolean} iframe 코드 여부
|
||||
*/
|
||||
export const isIframeEmbedMarkup = (value) => /^<iframe\b[\s\S]*?(?:<\/iframe>\s*)?$/i.test(String(value || '').trim())
|
||||
|
||||
/**
|
||||
* iframe HTML 코드에서 렌더링 가능한 정보만 추출한다.
|
||||
* @param {string} value - iframe HTML 코드
|
||||
* @returns {{ src: string, title: string, allow: string, width: string, height: string, allowFullscreen: boolean }|null} iframe 정보
|
||||
*/
|
||||
export const parseIframeEmbedMarkup = (value) => {
|
||||
const source = String(value || '').trim()
|
||||
const opener = source.match(/^<iframe\b([^>]*)>/i)
|
||||
|
||||
if (!opener) {
|
||||
return null
|
||||
}
|
||||
|
||||
const attributes = parseIframeAttributes(opener[1])
|
||||
const src = attributes.src || ''
|
||||
|
||||
if (!isSafeIframeSrc(src)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
src,
|
||||
title: attributes.title || 'Embedded content',
|
||||
allow: sanitizeAllowValue(attributes.allow || 'fullscreen; picture-in-picture; encrypted-media; web-share'),
|
||||
width: sanitizeSizeValue(attributes.width),
|
||||
height: sanitizeSizeValue(attributes.height),
|
||||
allowFullscreen: /\ballowfullscreen\b/i.test(opener[1])
|
||||
}
|
||||
}
|
||||
@@ -117,9 +117,9 @@ export const MARKDOWN_SLASH_COMMANDS = [
|
||||
},
|
||||
{
|
||||
id: 'embed',
|
||||
label: '임베드',
|
||||
description: 'YouTube·X 등 외부 URL 한 줄',
|
||||
keywords: ['embed', 'youtube', 'link', '임베드', '유튜브'],
|
||||
label: 'HTML',
|
||||
description: 'URL 또는 iframe HTML',
|
||||
keywords: ['html', 'iframe', 'embed', 'youtube', 'link', '임베드', '유튜브'],
|
||||
action: 'lines',
|
||||
lines: ['https://']
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user