@@ -5,14 +5,14 @@ import * as React from "react";
5
5
import * as mobxReact from "mobx-react" ;
6
6
import ReactMarkdown from "react-markdown" ;
7
7
import remarkGfm from "remark-gfm" ;
8
+ import { CopyButton } from "@/elements" ;
8
9
import { clsx } from "clsx" ;
9
- import { GlobalModel } from "@/models " ;
10
- import { v4 as uuidv4 } from "uuid " ;
10
+ import * as mobx from "mobx " ;
11
+ import { If } from "tsx-control-statements/components " ;
11
12
12
13
import "./markdown.less" ;
13
- import { boundMethod } from "autobind-decorator" ;
14
14
15
- function LinkRenderer ( props : any ) : any {
15
+ function Link ( props : any ) : JSX . Element {
16
16
let newUrl = "https://extern?" + encodeURIComponent ( props . href ) ;
17
17
return (
18
18
< a href = { newUrl } target = "_blank" rel = { "noopener" } >
@@ -21,99 +21,86 @@ function LinkRenderer(props: any): any {
21
21
) ;
22
22
}
23
23
24
- function HeaderRenderer ( props : any , hnum : number ) : any {
24
+ function Header ( props : any , hnum : number ) : JSX . Element {
25
25
return < div className = { clsx ( "title" , "is-" + hnum ) } > { props . children } </ div > ;
26
26
}
27
27
28
- function CodeRenderer ( props : any ) : any {
28
+ function Code ( props : any ) : JSX . Element {
29
29
return < code > { props . children } </ code > ;
30
30
}
31
31
32
- @mobxReact . observer
33
- class CodeBlockMarkdown extends React . Component <
34
- { children : React . ReactNode ; codeSelectSelectedIndex ?: number ; uuid : string } ,
35
- { }
36
- > {
37
- blockIndex : number ;
38
- blockRef : React . RefObject < HTMLPreElement > ;
32
+ const CodeBlock = mobxReact . observer (
33
+ ( props : { children : React . ReactNode ; onClickExecute ?: ( cmd : string ) => void } ) : JSX . Element => {
34
+ const copied : OV < boolean > = mobx . observable . box ( false , { name : "copied" } ) ;
39
35
40
- constructor ( props ) {
41
- super ( props ) ;
42
- this . blockRef = React . createRef ( ) ;
43
- this . blockIndex = GlobalModel . inputModel . addCodeBlockToCodeSelect ( this . blockRef , this . props . uuid ) ;
44
- }
36
+ const getTextContent = ( children : any ) => {
37
+ if ( typeof children === "string" ) {
38
+ return children ;
39
+ } else if ( Array . isArray ( children ) ) {
40
+ return children . map ( getTextContent ) . join ( "" ) ;
41
+ } else if ( children . props && children . props . children ) {
42
+ return getTextContent ( children . props . children ) ;
43
+ }
44
+ return "" ;
45
+ } ;
45
46
46
- render ( ) {
47
- let clickHandler : ( e : React . MouseEvent < HTMLElement > , blockIndex : number ) => void ;
48
- let inputModel = GlobalModel . inputModel ;
49
- clickHandler = ( e : React . MouseEvent < HTMLElement > , blockIndex : number ) => {
50
- const sel = window . getSelection ( ) ;
51
- if ( sel ?. toString ( ) . length == 0 ) {
52
- inputModel . setCodeSelectSelectedCodeBlock ( blockIndex ) ;
47
+ const handleCopy = async ( e : React . MouseEvent ) => {
48
+ let textToCopy = getTextContent ( props . children ) ;
49
+ textToCopy = textToCopy . replace ( / \n $ / , "" ) ; // remove trailing newline
50
+ await navigator . clipboard . writeText ( textToCopy ) ;
51
+ copied . set ( true ) ;
52
+ setTimeout ( ( ) => copied . set ( false ) , 2000 ) ; // Reset copied state after 2 seconds
53
+ } ;
54
+
55
+ const handleExecute = ( e : React . MouseEvent ) => {
56
+ let textToCopy = getTextContent ( props . children ) ;
57
+ textToCopy = textToCopy . replace ( / \n $ / , "" ) ; // remove trailing newline
58
+ if ( props . onClickExecute ) {
59
+ props . onClickExecute ( textToCopy ) ;
60
+ return ;
53
61
}
54
62
} ;
55
- let selected = this . blockIndex == this . props . codeSelectSelectedIndex ;
63
+
56
64
return (
57
- < pre
58
- ref = { this . blockRef }
59
- className = { clsx ( { selected : selected } ) }
60
- onClick = { ( event ) => clickHandler ( event , this . blockIndex ) }
61
- >
62
- { this . props . children }
65
+ < pre className = "codeblock" >
66
+ { props . children }
67
+ < div className = "codeblock-actions" >
68
+ < CopyButton className = "copy-button" onClick = { handleCopy } title = "Copy" />
69
+ < If condition = { props . onClickExecute } >
70
+ < i className = "fa-regular fa-square-terminal" onClick = { handleExecute } > </ i >
71
+ </ If >
72
+ </ div >
63
73
</ pre >
64
74
) ;
65
75
}
66
- }
76
+ ) ;
67
77
68
78
@mobxReact . observer
69
79
class Markdown extends React . Component <
70
- { text : string ; style ?: any ; extraClassName ?: string ; codeSelect ?: boolean } ,
80
+ {
81
+ text : string ;
82
+ style ?: any ;
83
+ className ?: string ;
84
+ onClickExecute ?: ( cmd : string ) => void ;
85
+ } ,
71
86
{ }
72
87
> {
73
- curUuid : string ;
74
-
75
- constructor ( props ) {
76
- super ( props ) ;
77
- this . curUuid = uuidv4 ( ) ;
78
- }
79
-
80
- @boundMethod
81
- CodeBlockRenderer ( props : any , codeSelect : boolean , codeSelectIndex : number , curUuid : string ) : any {
82
- if ( codeSelect ) {
83
- return (
84
- < CodeBlockMarkdown codeSelectSelectedIndex = { codeSelectIndex } uuid = { curUuid } >
85
- { props . children }
86
- </ CodeBlockMarkdown >
87
- ) ;
88
- } else {
89
- const clickHandler = ( e : React . MouseEvent < HTMLElement > ) => {
90
- let blockText = ( e . target as HTMLElement ) . innerText ;
91
- if ( blockText ) {
92
- blockText = blockText . replace ( / \n $ / , "" ) ; // remove trailing newline
93
- navigator . clipboard . writeText ( blockText ) ;
94
- }
95
- } ;
96
- return < pre onClick = { ( event ) => clickHandler ( event ) } > { props . children } </ pre > ;
97
- }
98
- }
99
-
100
88
render ( ) {
101
- let text = this . props . text ;
102
- let codeSelect = this . props . codeSelect ;
103
- let curCodeSelectIndex = GlobalModel . inputModel . getCodeSelectSelectedIndex ( ) ;
89
+ let { text, className, onClickExecute } = this . props ;
104
90
let markdownComponents = {
105
- a : LinkRenderer ,
106
- h1 : ( props ) => HeaderRenderer ( props , 1 ) ,
107
- h2 : ( props ) => HeaderRenderer ( props , 2 ) ,
108
- h3 : ( props ) => HeaderRenderer ( props , 3 ) ,
109
- h4 : ( props ) => HeaderRenderer ( props , 4 ) ,
110
- h5 : ( props ) => HeaderRenderer ( props , 5 ) ,
111
- h6 : ( props ) => HeaderRenderer ( props , 6 ) ,
112
- code : ( props ) => CodeRenderer ( props ) ,
113
- pre : ( props ) => this . CodeBlockRenderer ( props , codeSelect , curCodeSelectIndex , this . curUuid ) ,
91
+ a : Link ,
92
+ h1 : ( props ) => < Header { ... props } hnum = { 1 } /> ,
93
+ h2 : ( props ) => < Header { ... props } hnum = { 2 } /> ,
94
+ h3 : ( props ) => < Header { ... props } hnum = { 3 } /> ,
95
+ h4 : ( props ) => < Header { ... props } hnum = { 4 } /> ,
96
+ h5 : ( props ) => < Header { ... props } hnum = { 5 } /> ,
97
+ h6 : ( props ) => < Header { ... props } hnum = { 6 } /> ,
98
+ code : Code ,
99
+ pre : ( props ) => < CodeBlock { ... props } onClickExecute = { onClickExecute } /> ,
114
100
} ;
101
+
115
102
return (
116
- < div className = { clsx ( "markdown content" , this . props . extraClassName ) } style = { this . props . style } >
103
+ < div className = { clsx ( "markdown content" , className ) } style = { this . props . style } >
117
104
< ReactMarkdown remarkPlugins = { [ remarkGfm ] } components = { markdownComponents } >
118
105
{ text }
119
106
</ ReactMarkdown >
0 commit comments