feat: 커스텀 템플릿 추가 수정 중

This commit is contained in:
“hyeonggkim”
2026-01-21 18:44:08 +09:00
parent 927f7ace3b
commit cc91d99b48
3 changed files with 683 additions and 52 deletions

View File

@@ -1,55 +1,148 @@
import VueDOMPurifyHTML from 'vue-dompurify-html'
import DOMPurify from 'dompurify'
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.vueApp.use(VueDOMPurifyHTML, {
default: {
ALLOWED_TAGS: [
'br',
'div',
'b',
'strong',
'i',
'em',
'u',
's',
'a',
'p',
'ol',
'ul',
'li',
'span',
'img',
'pre',
'iframe',
'input',
'dl',
'dt',
'dd',
'blockquote',
'table',
'thead',
'tbody',
'tfoot',
'tr',
'th',
'td',
],
ALLOWED_ATTR: [
'style',
'id',
'href',
'src',
'target',
'alt',
'class',
'width',
'height',
'frameborder',
'allowfullscreen',
'colspan',
'rowspan',
'scope',
],
import type { DirectiveBinding } from 'vue'
// DOMPurify 설정 타입
interface DOMPurifyConfig {
ALLOWED_TAGS: string[]
ALLOWED_ATTR: string[]
}
// 공통 허용 태그
const commonTags = [
'br',
'div',
'b',
'strong',
'i',
'em',
'u',
'a',
'p',
'ul',
'li',
's',
'ol',
'button',
'span',
'img',
'pre',
'input',
'dl',
'dt',
'dd',
'table',
'thead',
'tbody',
'tfoot',
'tr',
'th',
'td',
] as const
// 공통 허용 속성
const commonAttrs = [
'style',
'id',
'href',
'src',
'target',
'alt',
'class',
'width',
'height',
'frameborder',
'colspan',
'rowspan',
'scope',
] as const
// 기본 설정 (일반 사용자용)
const defaultConfig: DOMPurifyConfig = {
ALLOWED_TAGS: [
...commonTags
],
ALLOWED_ATTR: [
...commonAttrs
],
}
// 관리자용 설정 (확장된 태그 및 속성)
const adminConfig: DOMPurifyConfig = {
ALLOWED_TAGS: [
...commonTags,
'iframe',
'link',
'style',
'blockquote',
],
ALLOWED_ATTR: [
...commonAttrs,
'onclick',
'rel',
'media',
'integrity',
'crossorigin',
'referrerpolicy',
'allowfullscreen',
],
}
/**
* HTML 문자열을 안전하게 정화하는 헬퍼 함수
* @param value - 정화할 HTML 문자열
* @param config - DOMPurify 설정
* @returns 정화된 HTML 문자열
*/
function sanitizeHtml(value: unknown, config: DOMPurifyConfig): string {
// SSR 환경에서는 빈 문자열 반환
if (!import.meta.client) {
return ''
}
// 값이 없거나 문자열이 아닌 경우 빈 문자열 반환
if (!value || typeof value !== 'string') {
return ''
}
try {
return DOMPurify.sanitize(value, config)
} catch (error) {
// 에러 발생 시 원본 값 반환하지 않고 빈 문자열 반환 (보안상 안전)
if (import.meta.dev) {
// eslint-disable-next-line no-console
console.error('[DOMPurify] Sanitization error:', error)
}
return ''
}
}
/**
* 디렉티브 핸들러 생성 함수
* @param config - DOMPurify 설정
* @returns 디렉티브 핸들러 객체
*/
function createDirectiveHandler(config: DOMPurifyConfig) {
return {
mounted(el: HTMLElement, binding: DirectiveBinding) {
el.innerHTML = sanitizeHtml(binding.value, config)
},
})
updated(el: HTMLElement, binding: DirectiveBinding) {
el.innerHTML = sanitizeHtml(binding.value, config)
},
}
}
export default defineNuxtPlugin((nuxtApp) => {
// SSR 환경에서는 디렉티브 등록하지 않음
// if (!import.meta.client) {
// return
// }
// 기본 디렉티브: v-dompurify-html
nuxtApp.vueApp.directive('dompurify-html', createDirectiveHandler(defaultConfig))
// 관리자용 디렉티브: v-dompurify-admin
nuxtApp.vueApp.directive('dompurify-admin', createDirectiveHandler(adminConfig))
})