1
|
0
|
import React, { Component } from 'react'
|
2
|
0
|
import PropTypes from 'prop-types'
|
3
|
0
|
import cx from 'classnames'
|
4
|
|
import * as Rsg from 'react-styleguidist'
|
5
|
0
|
import { isCodeVueSfc } from 'vue-inbrowser-compiler-utils'
|
6
|
0
|
import { polyfill } from 'react-lifecycles-compat'
|
7
|
0
|
import SimpleEditor from 'react-simple-code-editor'
|
8
|
0
|
import { highlight as prismHighlight, languages } from 'prismjs'
|
9
|
0
|
import 'prismjs/components/prism-clike'
|
10
|
0
|
import 'prismjs/components/prism-markup'
|
11
|
0
|
import 'prismjs/components/prism-javascript'
|
12
|
0
|
import 'prismjs/components/prism-jsx'
|
13
|
0
|
import { space } from 'react-styleguidist/lib/client/styles/theme'
|
14
|
0
|
import prismTheme from 'react-styleguidist/lib/client/styles/prismTheme'
|
15
|
0
|
import Styled, { JssInjectedProps } from 'rsg-components/Styled'
|
16
|
0
|
import { useStyleGuideContext } from 'rsg-components/Context'
|
17
|
0
|
import getScript from '../../../loaders/utils/getScript'
|
18
|
|
import { SanitizedStyleguidistConfig } from '../../../types/StyleGuide'
|
19
|
|
|
20
|
0
|
const highlight = (lang: 'vsg' | 'html', jsxInExamples: boolean): ((code: string) => string) => {
|
21
|
1
|
if (lang === 'vsg') {
|
22
|
0
|
return code => {
|
23
|
1
|
if (!code) {
|
24
|
0
|
return ''
|
25
|
|
}
|
26
|
0
|
const scriptCode = getScript(code, jsxInExamples)
|
27
|
0
|
const scriptCodeHighlighted = prismHighlight(
|
28
|
|
scriptCode,
|
29
|
1
|
languages[jsxInExamples ? 'jsx' : 'js'],
|
30
|
|
lang
|
31
|
|
)
|
32
|
1
|
if (code.length === scriptCode.length) {
|
33
|
0
|
return scriptCodeHighlighted
|
34
|
|
}
|
35
|
0
|
const templateCode = code.slice(scriptCode.length)
|
36
|
0
|
return scriptCodeHighlighted + prismHighlight(templateCode, languages.html, lang)
|
37
|
|
}
|
38
|
|
} else {
|
39
|
0
|
const langScheme = languages[lang]
|
40
|
0
|
return code => prismHighlight(code, langScheme, lang)
|
41
|
|
}
|
42
|
|
}
|
43
|
|
|
44
|
0
|
const styles = ({ fontFamily, fontSize, color, borderRadius }: Rsg.Theme) => ({
|
45
|
|
root: {
|
46
|
|
fontFamily: fontFamily.monospace,
|
47
|
|
fontSize: fontSize.small,
|
48
|
|
borderRadius,
|
49
|
|
'& textarea': {
|
50
|
|
isolate: false,
|
51
|
|
transition: 'all ease-in-out .1s',
|
52
|
|
// important to override inline styles in react-simple-code-editor
|
53
|
|
border: `1px ${color.border} solid !important`,
|
54
|
|
borderRadius
|
55
|
|
},
|
56
|
|
'& textarea:focus': {
|
57
|
|
isolate: false,
|
58
|
|
outline: 0,
|
59
|
|
borderColor: `${color.link} !important`,
|
60
|
|
boxShadow: [[0, 0, 0, 2, color.focus]]
|
61
|
|
}
|
62
|
|
},
|
63
|
|
jssEditor: {
|
64
|
|
background: color.codeBackground,
|
65
|
|
...prismTheme({ color })
|
66
|
|
}
|
67
|
|
})
|
68
|
|
|
69
|
|
export interface UnconfiguredEditorProps extends JssInjectedProps {
|
70
|
|
code: string
|
71
|
|
jssThemedEditor: boolean
|
72
|
|
jsxInExamples: boolean
|
73
|
|
onChange: (val: string) => void
|
74
|
|
editorPadding?: number
|
75
|
|
}
|
76
|
|
|
77
|
1
|
export class UnconfiguredEditor extends Component<UnconfiguredEditorProps> {
|
78
|
0
|
public static propTypes = {
|
79
|
|
classes: PropTypes.objectOf(PropTypes.string.isRequired).isRequired,
|
80
|
|
code: PropTypes.string.isRequired,
|
81
|
|
jssThemedEditor: PropTypes.bool.isRequired,
|
82
|
|
jsxInExamples: PropTypes.bool.isRequired,
|
83
|
|
onChange: PropTypes.func.isRequired,
|
84
|
|
editorPadding: PropTypes.number
|
85
|
|
}
|
86
|
|
|
87
|
0
|
public state = { code: this.props.code, prevCode: this.props.code }
|
88
|
|
|
89
|
0
|
public static getDerivedStateFromProps(
|
90
|
|
nextProps: UnconfiguredEditorProps,
|
91
|
0
|
prevState: { code: string; prevCode: string }
|
92
|
|
) {
|
93
|
0
|
const { code } = nextProps
|
94
|
1
|
if (prevState.prevCode !== code) {
|
95
|
0
|
return {
|
96
|
|
prevCode: code,
|
97
|
|
code
|
98
|
|
}
|
99
|
|
}
|
100
|
0
|
return null
|
101
|
|
}
|
102
|
|
|
103
|
0
|
public shouldComponentUpdate(
|
104
|
|
nextProps: UnconfiguredEditorProps,
|
105
|
0
|
nextState: { code: string; prevCode: string }
|
106
|
|
) {
|
107
|
0
|
return nextState.code !== this.state.code
|
108
|
|
}
|
109
|
|
|
110
|
0
|
public handleChange = (code: string) => {
|
111
|
0
|
this.setState({ code })
|
112
|
0
|
this.props.onChange(code)
|
113
|
|
}
|
114
|
|
|
115
|
0
|
public render() {
|
116
|
0
|
const { root, jssEditor } = this.props.classes
|
117
|
0
|
const isVueSFC = isCodeVueSfc(this.state.code)
|
118
|
0
|
const { jssThemedEditor, jsxInExamples, editorPadding } = this.props
|
119
|
1
|
const langClass = isVueSFC ? 'language-html' : 'language-jsx'
|
120
|
0
|
return (
|
121
|
|
<SimpleEditor
|
122
|
1
|
className={cx(root, jssThemedEditor ? jssEditor : langClass, 'prism-editor')}
|
123
|
|
value={this.state.code}
|
124
|
|
onValueChange={this.handleChange}
|
125
|
1
|
highlight={highlight(isVueSFC ? 'html' : 'vsg', jsxInExamples)}
|
126
|
|
// Padding should be passed via a prop (not CSS) for a proper
|
127
|
|
// cursor position calculation
|
128
|
1
|
padding={editorPadding || space[2]}
|
129
|
|
// to make sure the css styles for prism are taken into account
|
130
|
1
|
preClassName={cx(!jssThemedEditor && langClass)}
|
131
|
|
/>
|
132
|
|
)
|
133
|
|
}
|
134
|
0
|
}
|
135
|
|
|
136
|
0
|
const PEditor = polyfill(UnconfiguredEditor)
|
137
|
|
|
138
|
|
type EditorProps = Omit<UnconfiguredEditorProps, 'jssThemedEditor' | 'jsxInExamples'>
|
139
|
|
|
140
|
0
|
function Editor(props: EditorProps) {
|
141
|
|
const {
|
142
|
0
|
config: { jssThemedEditor, jsxInExamples }
|
143
|
0
|
} = (useStyleGuideContext() as any) as { config: SanitizedStyleguidistConfig }
|
144
|
0
|
return <PEditor {...props} jssThemedEditor={jssThemedEditor} jsxInExamples={jsxInExamples} />
|
145
|
|
}
|
146
|
|
|
147
|
0
|
export default Styled<EditorProps>(styles as any)(Editor)
|