add new md content
This commit is contained in:
parent
a592f65828
commit
bd7eb2b57a
@ -2,12 +2,14 @@ import { Check, CopyIcon, Loader2 } from 'lucide-react'
|
|||||||
import { PropsWithChildren, useMemo, useState } from 'react'
|
import { PropsWithChildren, useMemo, useState } from 'react'
|
||||||
|
|
||||||
import { useDarkModeContext } from '../../contexts/DarkModeContext'
|
import { useDarkModeContext } from '../../contexts/DarkModeContext'
|
||||||
|
import { ToolArgs } from "../../types/apply"
|
||||||
import { InfioBlockAction } from '../../utils/parse-infio-block'
|
import { InfioBlockAction } from '../../utils/parse-infio-block'
|
||||||
|
|
||||||
import { MemoizedSyntaxHighlighterWrapper } from './SyntaxHighlighterWrapper'
|
import { MemoizedSyntaxHighlighterWrapper } from './SyntaxHighlighterWrapper'
|
||||||
|
|
||||||
export default function MarkdownActionBlock({
|
export default function MarkdownActionBlock({
|
||||||
onApply,
|
msgId,
|
||||||
|
onApply,
|
||||||
isApplying,
|
isApplying,
|
||||||
language,
|
language,
|
||||||
filename,
|
filename,
|
||||||
@ -16,12 +18,8 @@ export default function MarkdownActionBlock({
|
|||||||
action,
|
action,
|
||||||
children,
|
children,
|
||||||
}: PropsWithChildren<{
|
}: PropsWithChildren<{
|
||||||
onApply: (blockInfo: {
|
msgId: string,
|
||||||
content: string
|
onApply: (args: ToolArgs) => void
|
||||||
filename?: string
|
|
||||||
startLine?: number
|
|
||||||
endLine?: number
|
|
||||||
}) => void
|
|
||||||
isApplying: boolean
|
isApplying: boolean
|
||||||
language?: string
|
language?: string
|
||||||
filename?: string
|
filename?: string
|
||||||
@ -71,9 +69,11 @@ export default function MarkdownActionBlock({
|
|||||||
{action === InfioBlockAction.Edit && (
|
{action === InfioBlockAction.Edit && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onApply({
|
onApply({
|
||||||
|
type: 'write_to_file',
|
||||||
|
msgId,
|
||||||
content: String(children),
|
content: String(children),
|
||||||
filename,
|
filepath: filename,
|
||||||
startLine,
|
startLine,
|
||||||
endLine
|
endLine
|
||||||
})
|
})
|
||||||
@ -92,9 +92,13 @@ export default function MarkdownActionBlock({
|
|||||||
{action === InfioBlockAction.New && (
|
{action === InfioBlockAction.New && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onApply({
|
onApply({
|
||||||
|
type: 'write_to_file',
|
||||||
|
msgId,
|
||||||
content: String(children),
|
content: String(children),
|
||||||
filename
|
filepath: filename,
|
||||||
|
startLine: 1,
|
||||||
|
endLine: undefined
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
disabled={isApplying}
|
disabled={isApplying}
|
||||||
|
|||||||
119
src/components/chat-view/MarkdownEditFileBlock.tsx
Normal file
119
src/components/chat-view/MarkdownEditFileBlock.tsx
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import { Check, CopyIcon, Edit, Loader2, X } from 'lucide-react'
|
||||||
|
import { PropsWithChildren, useMemo, useState } from 'react'
|
||||||
|
|
||||||
|
import { useDarkModeContext } from '../../contexts/DarkModeContext'
|
||||||
|
import { ApplyStatus, ToolArgs } from '../../types/apply'
|
||||||
|
|
||||||
|
import { MemoizedSyntaxHighlighterWrapper } from './SyntaxHighlighterWrapper'
|
||||||
|
|
||||||
|
export default function MarkdownEditFileBlock({
|
||||||
|
mode,
|
||||||
|
applyStatus,
|
||||||
|
onApply,
|
||||||
|
language,
|
||||||
|
path,
|
||||||
|
startLine,
|
||||||
|
endLine,
|
||||||
|
children,
|
||||||
|
}: PropsWithChildren<{
|
||||||
|
mode: string
|
||||||
|
applyStatus: ApplyStatus
|
||||||
|
onApply: (args: ToolArgs) => void
|
||||||
|
language?: string
|
||||||
|
path?: string
|
||||||
|
startLine?: number
|
||||||
|
endLine?: number
|
||||||
|
}>) {
|
||||||
|
const [copied, setCopied] = useState(false)
|
||||||
|
const [applying, setApplying] = useState(false)
|
||||||
|
const { isDarkMode } = useDarkModeContext()
|
||||||
|
|
||||||
|
const wrapLines = useMemo(() => {
|
||||||
|
return !language || ['markdown'].includes(language)
|
||||||
|
}, [language])
|
||||||
|
|
||||||
|
const handleCopy = async () => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(String(children))
|
||||||
|
setCopied(true)
|
||||||
|
setTimeout(() => setCopied(false), 2000)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to copy text: ', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleApply = async () => {
|
||||||
|
if (applyStatus !== ApplyStatus.Idle) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setApplying(true)
|
||||||
|
onApply({
|
||||||
|
type: mode,
|
||||||
|
filepath: path,
|
||||||
|
content: String(children),
|
||||||
|
startLine,
|
||||||
|
endLine
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`infio-chat-code-block ${path ? 'has-filename' : ''}`}>
|
||||||
|
<div className={'infio-chat-code-block-header'}>
|
||||||
|
{path && (
|
||||||
|
<div className={'infio-chat-code-block-header-filename'}>
|
||||||
|
<Edit size={10} className="infio-chat-code-block-header-icon" />
|
||||||
|
{mode}: {path}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={'infio-chat-code-block-header-button'}>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
handleCopy()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{copied ? (
|
||||||
|
<>
|
||||||
|
<Check size={10} /> Copied
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<CopyIcon size={10} /> Copy
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleApply}
|
||||||
|
style={{ color: '#008000' }}
|
||||||
|
disabled={applyStatus !== ApplyStatus.Idle || applying}
|
||||||
|
>
|
||||||
|
{applyStatus === ApplyStatus.Idle ? (
|
||||||
|
applying ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="spinner" size={14} /> Applying...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
'Apply'
|
||||||
|
)
|
||||||
|
) : applyStatus === ApplyStatus.Applied ? (
|
||||||
|
<>
|
||||||
|
<Check size={14} /> Success
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<X size={14} /> Failed
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<MemoizedSyntaxHighlighterWrapper
|
||||||
|
isDarkMode={isDarkMode}
|
||||||
|
language={language}
|
||||||
|
hasFilename={!!path}
|
||||||
|
wrapLines={wrapLines}
|
||||||
|
>
|
||||||
|
{String(children)}
|
||||||
|
</MemoizedSyntaxHighlighterWrapper>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
52
src/components/chat-view/MarkdownListFilesBlock.tsx
Normal file
52
src/components/chat-view/MarkdownListFilesBlock.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { FolderOpen } from 'lucide-react'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { useApp } from '../../contexts/AppContext'
|
||||||
|
import { ApplyStatus, ListFilesToolArgs } from '../../types/apply'
|
||||||
|
import { openMarkdownFile } from '../../utils/obsidian'
|
||||||
|
|
||||||
|
export default function MarkdownListFilesBlock({
|
||||||
|
applyStatus,
|
||||||
|
onApply,
|
||||||
|
path,
|
||||||
|
recursive,
|
||||||
|
finish
|
||||||
|
}: {
|
||||||
|
applyStatus: ApplyStatus
|
||||||
|
onApply: (args: ListFilesToolArgs) => void
|
||||||
|
path: string,
|
||||||
|
recursive: boolean,
|
||||||
|
finish: boolean
|
||||||
|
}) {
|
||||||
|
const app = useApp()
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
openMarkdownFile(app, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
console.log('finish', finish, applyStatus)
|
||||||
|
if (finish && applyStatus === ApplyStatus.Idle) {
|
||||||
|
console.log('finish auto list files', path)
|
||||||
|
onApply({
|
||||||
|
type: 'list_files',
|
||||||
|
filepath: path,
|
||||||
|
recursive
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [finish])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`infio-chat-code-block ${path ? 'has-filename' : ''}`}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<div className={'infio-chat-code-block-header'}>
|
||||||
|
<div className={'infio-chat-code-block-header-filename'}>
|
||||||
|
<FolderOpen size={14} className="infio-chat-code-block-header-icon" />
|
||||||
|
List files: {path}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
49
src/components/chat-view/MarkdownReadFileBlock.tsx
Normal file
49
src/components/chat-view/MarkdownReadFileBlock.tsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { ExternalLink } from 'lucide-react'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { useApp } from '../../contexts/AppContext'
|
||||||
|
import { ApplyStatus, ReadFileToolArgs } from '../../types/apply'
|
||||||
|
import { openMarkdownFile } from '../../utils/obsidian'
|
||||||
|
|
||||||
|
export default function MarkdownReadFileBlock({
|
||||||
|
applyStatus,
|
||||||
|
onApply,
|
||||||
|
path,
|
||||||
|
finish
|
||||||
|
}: {
|
||||||
|
applyStatus: ApplyStatus
|
||||||
|
onApply: (args: ReadFileToolArgs) => void
|
||||||
|
path: string,
|
||||||
|
finish: boolean
|
||||||
|
}) {
|
||||||
|
const app = useApp()
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
openMarkdownFile(app, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
console.log('finish', finish, applyStatus)
|
||||||
|
if (finish && applyStatus === ApplyStatus.Idle) {
|
||||||
|
console.log('finish auto read file', path)
|
||||||
|
onApply({
|
||||||
|
type: 'read_file',
|
||||||
|
filepath: path
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [finish])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`infio-chat-code-block ${path ? 'has-filename' : ''}`}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<div className={'infio-chat-code-block-header'}>
|
||||||
|
<div className={'infio-chat-code-block-header-filename'}>
|
||||||
|
<ExternalLink size={10} className="infio-chat-code-block-header-icon" />
|
||||||
|
Read file: {path}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { ChevronDown, ChevronRight } from 'lucide-react'
|
import { ChevronDown, ChevronRight, Brain } from 'lucide-react'
|
||||||
import { PropsWithChildren, useEffect, useRef, useState } from 'react'
|
import { PropsWithChildren, useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
import { useDarkModeContext } from '../../contexts/DarkModeContext'
|
import { useDarkModeContext } from '../../contexts/DarkModeContext'
|
||||||
@ -27,6 +27,7 @@ export default function MarkdownReasoningBlock({
|
|||||||
>
|
>
|
||||||
<div className={'infio-chat-code-block-header'}>
|
<div className={'infio-chat-code-block-header'}>
|
||||||
<div className={'infio-chat-code-block-header-filename'}>
|
<div className={'infio-chat-code-block-header-filename'}>
|
||||||
|
<Brain size={10} className="infio-chat-code-block-header-icon" />
|
||||||
Reasoning
|
Reasoning
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
53
src/components/chat-view/MarkdownRegexSearchFilesBlock.tsx
Normal file
53
src/components/chat-view/MarkdownRegexSearchFilesBlock.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { FolderOpen } from 'lucide-react'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { useApp } from '../../contexts/AppContext'
|
||||||
|
import { ApplyStatus, RegexSearchFilesToolArgs } from '../../types/apply'
|
||||||
|
import { openMarkdownFile } from '../../utils/obsidian'
|
||||||
|
|
||||||
|
export default function MarkdownRegexSearchFilesBlock({
|
||||||
|
applyStatus,
|
||||||
|
onApply,
|
||||||
|
path,
|
||||||
|
regex,
|
||||||
|
finish
|
||||||
|
}: {
|
||||||
|
applyStatus: ApplyStatus
|
||||||
|
onApply: (args: RegexSearchFilesToolArgs) => void
|
||||||
|
path: string,
|
||||||
|
regex: string,
|
||||||
|
finish: boolean
|
||||||
|
}) {
|
||||||
|
const app = useApp()
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
openMarkdownFile(app, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
console.log('finish', finish, applyStatus)
|
||||||
|
if (finish && applyStatus === ApplyStatus.Idle) {
|
||||||
|
console.log('finish auto regex search files', path)
|
||||||
|
onApply({
|
||||||
|
type: 'regex_search_files',
|
||||||
|
filepath: path,
|
||||||
|
regex: regex,
|
||||||
|
file_pattern: ".md",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [finish])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`infio-chat-code-block ${path ? 'has-filename' : ''}`}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<div className={'infio-chat-code-block-header'}>
|
||||||
|
<div className={'infio-chat-code-block-header-filename'}>
|
||||||
|
<FolderOpen size={14} className="infio-chat-code-block-header-icon" />
|
||||||
|
<span>regex search files "{regex}" in {path}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
78
src/components/chat-view/MarkdownSearchAndReplace.tsx
Normal file
78
src/components/chat-view/MarkdownSearchAndReplace.tsx
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { Check, Loader2, Replace, X } from 'lucide-react'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { useApp } from '../../contexts/AppContext'
|
||||||
|
import { ApplyStatus, SearchAndReplaceToolArgs } from '../../types/apply'
|
||||||
|
import { openMarkdownFile } from '../../utils/obsidian'
|
||||||
|
|
||||||
|
export default function MarkdownSearchAndReplace({
|
||||||
|
applyStatus,
|
||||||
|
onApply,
|
||||||
|
path,
|
||||||
|
operations,
|
||||||
|
finish
|
||||||
|
}: {
|
||||||
|
applyStatus: ApplyStatus
|
||||||
|
onApply: (args: SearchAndReplaceToolArgs) => void
|
||||||
|
path: string,
|
||||||
|
operations: SearchAndReplaceToolArgs['operations'],
|
||||||
|
finish: boolean
|
||||||
|
}) {
|
||||||
|
const app = useApp()
|
||||||
|
|
||||||
|
const [applying, setApplying] = React.useState(false)
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
openMarkdownFile(app, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleApply = async () => {
|
||||||
|
if (applyStatus !== ApplyStatus.Idle) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setApplying(true)
|
||||||
|
onApply({
|
||||||
|
type: 'search_and_replace',
|
||||||
|
filepath: path,
|
||||||
|
operations
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`infio-chat-code-block ${path ? 'has-filename' : ''}`}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<div className={'infio-chat-code-block-header'}>
|
||||||
|
<div className={'infio-chat-code-block-header-filename'}>
|
||||||
|
<Replace size={10} className="infio-chat-code-block-header-icon" />
|
||||||
|
Search and replace in {path}
|
||||||
|
</div>
|
||||||
|
<div className={'infio-chat-code-block-header-button'}>
|
||||||
|
<button
|
||||||
|
onClick={handleApply}
|
||||||
|
disabled={applyStatus !== ApplyStatus.Idle || applying}
|
||||||
|
>
|
||||||
|
{applyStatus === ApplyStatus.Idle ? (
|
||||||
|
applying ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="spinner" size={14} /> Applying...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
'Apply'
|
||||||
|
)
|
||||||
|
) : applyStatus === ApplyStatus.Applied ? (
|
||||||
|
<>
|
||||||
|
<Check size={14} /> Success
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<X size={14} /> Failed
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
import { FolderOpen } from 'lucide-react'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { useApp } from '../../contexts/AppContext'
|
||||||
|
import { ApplyStatus, SemanticSearchFilesToolArgs } from '../../types/apply'
|
||||||
|
import { openMarkdownFile } from '../../utils/obsidian'
|
||||||
|
|
||||||
|
export default function MarkdownSemanticSearchFilesBlock({
|
||||||
|
applyStatus,
|
||||||
|
onApply,
|
||||||
|
path,
|
||||||
|
query,
|
||||||
|
finish
|
||||||
|
}: {
|
||||||
|
applyStatus: ApplyStatus
|
||||||
|
onApply: (args: SemanticSearchFilesToolArgs) => void
|
||||||
|
path: string,
|
||||||
|
query: string,
|
||||||
|
finish: boolean
|
||||||
|
}) {
|
||||||
|
const app = useApp()
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
openMarkdownFile(app, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
console.log('finish', finish, applyStatus)
|
||||||
|
if (finish && applyStatus === ApplyStatus.Idle) {
|
||||||
|
console.log('finish auto semantic search files', path)
|
||||||
|
onApply({
|
||||||
|
type: 'semantic_search_files',
|
||||||
|
filepath: path,
|
||||||
|
query: query,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [finish])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`infio-chat-code-block ${path ? 'has-filename' : ''}`}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<div className={'infio-chat-code-block-header'}>
|
||||||
|
<div className={'infio-chat-code-block-header-filename'}>
|
||||||
|
<FolderOpen size={14} className="infio-chat-code-block-header-icon" />
|
||||||
|
<span>semantic search files "{query}" in {path}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
48
src/components/chat-view/MarkdownWithIcon.tsx
Normal file
48
src/components/chat-view/MarkdownWithIcon.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { CircleCheckBig, CircleHelp } from 'lucide-react';
|
||||||
|
import { ComponentPropsWithoutRef } from 'react';
|
||||||
|
import ReactMarkdown from 'react-markdown';
|
||||||
|
import rehypeRaw from 'rehype-raw';
|
||||||
|
|
||||||
|
const MarkdownWithIcons = ({ markdownContent, className }: { markdownContent: string, className?: string }) => {
|
||||||
|
// 预处理markdown内容,将<icon>标签转换为ReactMarkdown可以处理的格式
|
||||||
|
const processedContent = markdownContent.replace(
|
||||||
|
/<icon\s+name=['"]([^'"]+)['"]\s+size=\{(\d+)\}(\s+className=['"]([^'"]+)['"])?[^>]*\/>/g,
|
||||||
|
(_, name, size, __, className) =>
|
||||||
|
`<span data-icon="${name}" data-size="${size}" ${className ? `class="${className}"` : ''}></span>`
|
||||||
|
);
|
||||||
|
|
||||||
|
const components = {
|
||||||
|
span: (props: ComponentPropsWithoutRef<'span'> & {
|
||||||
|
'data-icon'?: string;
|
||||||
|
'data-size'?: string;
|
||||||
|
}) => {
|
||||||
|
if (props['data-icon']) {
|
||||||
|
const name = props['data-icon'];
|
||||||
|
const size = props['data-size'] ? Number(props['data-size']) : 16;
|
||||||
|
const className = props.className || '';
|
||||||
|
|
||||||
|
switch (name) {
|
||||||
|
case 'ask_followup_question':
|
||||||
|
return <CircleHelp size={size} className={className} />;
|
||||||
|
case 'attempt_completion':
|
||||||
|
return <CircleCheckBig size={size} className={className} />;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return <span {...props} />;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ReactMarkdown
|
||||||
|
className={`${className}`}
|
||||||
|
components={components}
|
||||||
|
rehypePlugins={[rehypeRaw]}
|
||||||
|
>
|
||||||
|
{processedContent}
|
||||||
|
</ReactMarkdown>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MarkdownWithIcons;
|
||||||
Loading…
x
Reference in New Issue
Block a user