Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
P
poc-fe
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
poc
poc-fe
Commits
8c7b2c33
Commit
8c7b2c33
authored
Apr 08, 2025
by
tyyin lan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: markdown render代码模块支持内容复制
parent
92e219a6
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
123 additions
and
33 deletions
+123
-33
markdown-render.vue
src/components/markdown-render/markdown-render.vue
+123
-33
No files found.
src/components/markdown-render/markdown-render.vue
View file @
8c7b2c33
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
computed
,
r
ef
,
watchEffect
}
from
'vue'
import
{
computed
,
nextTick
,
ref
,
useTemplateR
ef
,
watchEffect
}
from
'vue'
import
{
Marked
}
from
'marked'
import
{
Marked
}
from
'marked'
import
{
markedHighlight
}
from
'marked-highlight'
import
{
markedHighlight
}
from
'marked-highlight'
import
'katex/dist/katex.min.css'
import
'katex/dist/katex.min.css'
import
{
throttle
}
from
'lodash-es'
import
{
throttle
,
debounce
}
from
'lodash-es'
import
DOMPurify
from
'dompurify'
import
DOMPurify
from
'dompurify'
import
hljs
from
'highlight.js'
import
hljs
from
'highlight.js'
import
'highlight.js/styles/panda-syntax-light.css'
import
'highlight.js/styles/panda-syntax-light.css'
import
'github-markdown-css'
import
'github-markdown-css'
import
markedKatex
,
{
MarkedKatexOptions
}
from
'marked-katex-extension'
import
markedKatex
,
{
MarkedKatexOptions
}
from
'marked-katex-extension'
import
{
copyToClip
}
from
'@/utils/copy'
import
{
useI18n
}
from
'vue-i18n'
interface
Props
{
interface
Props
{
rawTextContent
:
string
rawTextContent
:
string
...
@@ -29,19 +31,47 @@ const katexOptions: MarkedKatexOptions = {
...
@@ -29,19 +31,47 @@ const katexOptions: MarkedKatexOptions = {
output
:
'html'
,
output
:
'html'
,
}
}
const
marked
=
new
Marked
().
use
(
const
markdownRenderContainerRef
=
useTemplateRef
<
HTMLDivElement
>
(
'markdownRenderContainerRef'
)
const
{
t
}
=
useI18n
()
const
marked
=
new
Marked
()
.
use
({
gfm
:
true
,
async
:
true
,
breaks
:
true
})
.
use
(
markedHighlight
({
markedHighlight
({
emptyLangClass
:
'hljs'
,
emptyLangClass
:
'hljs'
,
langPrefix
:
'hljs language-'
,
langPrefix
:
'hljs language-'
,
highlight
(
code
,
lang
,
_info
)
{
highlight
(
code
,
lang
,
_info
)
{
const
language
=
hljs
.
getLanguage
(
lang
)
?
lang
:
'plaintext'
const
language
=
hljs
.
getLanguage
(
lang
)
?
lang
:
'plaintext'
return
hljs
.
highlight
(
code
,
{
language
}).
value
// return hljs.highlight(code, { language }).value
const
str
=
hljs
.
highlight
(
code
,
{
language
}).
value
return
`<div class="code-render-container"><div class="code-operation-bar-container"><span>
${
language
}
</span><span class="code-copy-btn iconfont icon-copy"></span></div><pre class="code-render-inner"><code>
${
str
}
</code></pre></div>`
},
},
}),
}),
{
gfm
:
true
,
async
:
true
,
breaks
:
true
},
)
markedKatex
(
katexOptions
),
.
use
(
markedKatex
(
katexOptions
))
)
.
use
({
hooks
:
{
preprocess
(
markdown
:
string
)
{
/**
* 1. 将 \(...\) 和 \\(...\\) 转为 $...$ 以支持行内公式
* 2. 将 \[...\] 和 \\[...\\] 转为 $$...$$ 以支持块级公式
*/
const
katexTextReplace
=
markdown
.
replace
(
/
\\\\\(
|
\\\\\)
|
\\\(
|
\\\)
/g
,
'$'
)
.
replace
(
/
\\\\\[
|
\\\\\]
|
\\\[
|
\\\]
/g
,
'$$$$'
)
return
katexTextReplace
},
postprocess
(
html
:
string
)
{
return
DOMPurify
.
sanitize
(
html
)
},
},
})
let
btnEventController
=
new
AbortController
()
const
renderTextContent
=
ref
(
''
)
const
renderTextContent
=
ref
(
''
)
...
@@ -52,28 +82,54 @@ const articleContainerStyle = computed(() => {
...
@@ -52,28 +82,54 @@ const articleContainerStyle = computed(() => {
}
}
})
})
const
katexDelimiters
=
(
text
:
string
)
=>
{
/**
* 1. 将 \(...\) 和 \\(...\\) 转为 $...$ 以支持行内公式
* 2. 将 \[...\] 和 \\[...\\] 转为 $$...$$ 以支持块级公式
*/
const
replaceKatexText
=
text
.
replace
(
/
\\\\\(
|
\\\\\)
|
\\\(
|
\\\)
/g
,
'$'
).
replace
(
/
\\\\\[
|
\\\\\]
|
\\\[
|
\\\]
/g
,
'$$$$'
)
return
replaceKatexText
}
const
textContentParser
=
throttle
(
const
textContentParser
=
throttle
(
(
text
:
string
)
=>
{
(
text
:
string
)
=>
{
;(
marked
.
parse
(
text
)
as
Promise
<
string
>
).
then
((
res
)
=>
{
;(
marked
.
parse
(
text
)
as
Promise
<
string
>
).
then
((
res
)
=>
{
renderTextContent
.
value
=
DOMPurify
.
sanitize
(
res
)
renderTextContent
.
value
=
res
})
})
},
},
500
,
500
,
{
leading
:
true
,
trailing
:
true
},
{
leading
:
true
,
trailing
:
true
},
)
)
const
codeCopyBtnEventBind
=
debounce
(
()
=>
{
if
(
markdownRenderContainerRef
.
value
)
{
const
codeCopyBtnElList
=
markdownRenderContainerRef
.
value
.
getElementsByClassName
(
'code-copy-btn'
)
btnEventController
.
abort
()
btnEventController
=
new
AbortController
()
/* 遍历 */
;[...
codeCopyBtnElList
].
forEach
((
elItem
)
=>
{
elItem
.
addEventListener
(
'click'
,
()
=>
{
const
code
=
elItem
.
parentElement
?.
nextElementSibling
?.
firstChild
?.
textContent
if
(
code
)
{
copyToClip
(
code
).
then
(()
=>
{
window
.
$message
.
success
(
t
(
'common_module.copy_success_message'
))
})
}
},
{
signal
:
btnEventController
.
signal
},
)
})
}
},
1000
,
{
leading
:
false
,
trailing
:
true
},
)
watchEffect
(()
=>
{
watchEffect
(()
=>
{
const
text
=
katexDelimiters
(
props
.
rawTextContent
)
textContentParser
(
props
.
rawTextContent
)
textContentParser
(
text
)
nextTick
(()
=>
{
setTimeout
(()
=>
{
codeCopyBtnEventBind
()
},
60
)
})
})
})
function
getRenderTextContent
()
{
function
getRenderTextContent
()
{
...
@@ -86,19 +142,53 @@ defineExpose({
...
@@ -86,19 +142,53 @@ defineExpose({
</
script
>
</
script
>
<
template
>
<
template
>
<div
class=
"markdown-render-container"
>
<div
ref=
"markdownRenderContainerRef"
class=
"markdown-render-container"
>
<article
class=
"markdown-body
article-contai
ner"
:style=
"articleContainerStyle"
v-html=
"renderTextContent"
/>
<article
class=
"markdown-body
markdown-render-in
ner"
:style=
"articleContainerStyle"
v-html=
"renderTextContent"
/>
</div>
</div>
</
template
>
</
template
>
<
style
lang=
"scss"
scoped
>
<
style
lang=
"scss"
scoped
>
@include
custom-scrollbar
(
6px
);
@include
custom-scrollbar
(
6px
);
.article-container
{
.markdown-render-container
{
.markdown-render-inner
{
overflow-x
:
auto
;
overflow-x
:
auto
;
font-family
:
'Microsoft YaHei UI'
;
font-family
:
'Microsoft YaHei UI'
;
word-break
:
break-all
;
word-break
:
break-all
;
background-color
:
unset
;
background-color
:
unset
;
&
>
:deep
(
pre
)
{
padding
:
0
;
}
}
:deep
(
.code-render-container
)
{
font-family
:
'Microsoft YaHei UI'
;
font-size
:
v-bind
(
'props.fontSize'
);
.code-operation-bar-container
{
display
:
flex
;
justify-content
:
space-between
;
padding
:
10px
16px
;
user-select
:
none
;
background-color
:
#f1f1f1
;
.code-copy-btn
{
cursor
:
pointer
;
transition
:
color
ease-in-out
0
.3s
;
&
:hover
{
color
:
#777ef9
;
}
}
}
.code-render-inner
{
padding
:
10px
16px
0
;
margin
:
0
;
overflow-x
:
auto
;
}
}
}
}
:deep
(
pre
)
{
:deep
(
pre
)
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment