Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 9 additions & 15 deletions src/components/markdown/MarkdownPreview.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
import MarkdownIt from 'markdown-it'
import DOMPurify from 'dompurify'
import { md } from '@/utils/markdown'

type HeadingInfo = {
id: string
Expand All @@ -16,11 +16,6 @@ const props = defineProps<{
const headings = defineModel<HeadingInfo[]>('headings')
const htmltext = ref('')

const md = new MarkdownIt({
breaks: true,
linkify: true,
})

// mdtext 変更時に HTML と見出し情報を更新
watch(
() => props.mdtext,
Expand All @@ -29,8 +24,9 @@ watch(
const headingInfos: HeadingInfo[] = []
let idCounter = 0

// DOMPurify を使用して HTML をサニタイズ(script 等を除去)
const cleanHtml = DOMPurify.sanitize(rawHtml, {
// DOMPurify を使用して HTML をサニタイズし、DOM を直接取得
const cleanDom = DOMPurify.sanitize(rawHtml, {
RETURN_DOM: true,
ALLOWED_TAGS: [
'h1',
'h2',
Expand Down Expand Up @@ -61,23 +57,21 @@ watch(
'del',
],
ALLOWED_ATTR: ['class', 'id', 'href'],
})
}) as HTMLElement

// DOM パーサーを使用して安全に見出しを処理
const parser = new DOMParser()
const doc = parser.parseFromString(cleanHtml, 'text/html')
const headingElements = doc.querySelectorAll('h1, h2, h3, h4, h5, h6')
// サニタイズされた DOM から見出しを処理
const headingElements = cleanDom.querySelectorAll('h1, h2, h3, h4, h5, h6')

headingElements.forEach((heading) => {
const level = parseInt(heading.tagName.charAt(1))
const id = `heading-${idCounter++}`
const text = heading.textContent ?? ''
const text = heading.textContent
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HeadingInfo.text is typed as string, but heading.textContent is string | null under strict: true. This will either fail type-checking or force downstream handling of null. Consider using a nullish fallback (e.g., heading.textContent ?? '') or updating HeadingInfo.text to allow null if that's intended.

Suggested change
const text = heading.textContent
const text = heading.textContent ?? ''

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strict: true の場合にも heading.textContent は string と型推論され、null にはなりえません。heading 自体が Element として型推論されているからです


heading.setAttribute('id', id)
headingInfos.push({ id, level, text })
})

htmltext.value = doc.body.innerHTML
htmltext.value = cleanDom.innerHTML
headings.value = headingInfos
},
{ immediate: true },
Expand Down
7 changes: 7 additions & 0 deletions src/utils/markdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import MarkdownIt from 'markdown-it'

// 再利用可能な MarkdownIt インスタンス
export const md = new MarkdownIt({
breaks: true,
linkify: true,
})