Skip to content

Commit 00a717b

Browse files
authored
Merge pull request #4307 from yoution/feature/add-editor
feat: add tui.editor
2 parents f1b7987 + 17b8e68 commit 00a717b

File tree

6 files changed

+251
-11
lines changed

6 files changed

+251
-11
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
},
8585
"dependencies": {
8686
"@contentful/rich-text-react-renderer": "^13.4.0",
87+
"@toast-ui/editor": "^2.5.1",
8788
"appirio-tech-react-components": "git+https://github.com/appirio-tech/react-components.git#feature/connectv2",
8889
"axios": "^0.19.2",
8990
"brace": "^0.11.1",
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
@import '~tc-ui/src/styles/tc-includes';
2+
3+
.editor {
4+
background: red;
5+
6+
> div {
7+
h1 {
8+
all: revert;
9+
font-size: 24px;
10+
}
11+
}
12+
13+
b,
14+
i,
15+
s,
16+
hr,
17+
blockquote,
18+
ul,
19+
li,
20+
ol,
21+
table,
22+
thead,
23+
tr,
24+
th,
25+
img,
26+
a,
27+
code,
28+
pre {
29+
all: revert;
30+
}
31+
32+
:global {
33+
// add style for heading list in headings selection popup, because 'all:revert' has been setted before
34+
.te-heading-add ul {
35+
list-style: none;
36+
}
37+
38+
// hide uplodd file
39+
.tui-editor-popup{
40+
box-shadow: 0px 0px 15px 5px rgba(0,0,0,0.26);
41+
}
42+
43+
.te-popup-add-image .te-tab button, .te-popup-add-image .te-file-type{
44+
display: none !important;
45+
}
46+
47+
.te-popup-add-image .te-url-type{
48+
display: block !important;
49+
}
50+
51+
}
52+
}

src/components/TuiEditor/index.jsx

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
* TuiEditor
3+
* wrap toast-ui editor with react and support react 15
4+
*/
5+
6+
import React from 'react'
7+
import PropTypes from 'prop-types'
8+
import Editor from '@toast-ui/editor'
9+
import styles from './TuiEditor.scss'
10+
import cn from 'classnames'
11+
import 'codemirror/lib/codemirror.css'
12+
import '@toast-ui/editor/dist/toastui-editor.css'
13+
14+
class TuiEditor extends React.Component {
15+
constructor(props) {
16+
super(props)
17+
this.handleValueChange = this.handleValueChange.bind(this)
18+
}
19+
20+
getRootElement() {
21+
return this.refs.rootEl
22+
}
23+
24+
getInstance() {
25+
return this.editorInst
26+
}
27+
28+
bindEventHandlers(props) {
29+
Object.keys(props)
30+
.filter(key => /^on[A-Z][a-zA-Z]+/.test(key))
31+
.forEach(key => {
32+
const eventName = key[2].toLowerCase() + key.slice(3)
33+
this.editorInst.off(eventName)
34+
this.editorInst.on(eventName, props[key])
35+
})
36+
}
37+
38+
componentDidMount() {
39+
const props = {
40+
...this.props,
41+
onChange: this.handleValueChange
42+
}
43+
this.editorInst = new Editor({
44+
el: this.refs.rootEl,
45+
...props
46+
})
47+
this.bindEventHandlers(props)
48+
}
49+
50+
handleValueChange(){
51+
if (this.props.onChange) {
52+
this.props.onChange(this.getInstance().getMarkdown())
53+
}
54+
}
55+
56+
shouldComponentUpdate(nextProps) {
57+
const instance = this.getInstance()
58+
const { height, previewStyle, className } = nextProps
59+
60+
if (this.props.height !== height) {
61+
instance.height(height)
62+
}
63+
64+
if (this.props.previewStyle !== previewStyle) {
65+
instance.changePreviewStyle(previewStyle)
66+
}
67+
68+
if (this.props.className !== className) {
69+
return true
70+
}
71+
// this.bindEventHandlers(nextProps, this.props)
72+
73+
return false
74+
}
75+
76+
render() {
77+
return <div ref="rootEl" className={cn(styles.editor, this.props.className)} />
78+
}
79+
}
80+
81+
82+
TuiEditor.defaultProps = {
83+
height: '300px',
84+
minHeight: '300px',
85+
initialValue: '',
86+
previewStyle: '',
87+
initialEditType: 'wysiwyg',
88+
language: 'en-US',
89+
useCommandShortcut: true,
90+
customHTMLSanitizer: null,
91+
frontMatter: false,
92+
hideModeSwitch: false,
93+
referenceDefinition:false,
94+
usageStatistics: false,
95+
useDefaultHTMLSanitizer: true
96+
}
97+
98+
TuiEditor.propTypes = {
99+
className: PropTypes.string,
100+
// Markdown editor's preview style (tab, vertical)
101+
previewStyle: PropTypes.string.isRequired,
102+
// Editor's height style value. Height is applied as border-box ex) '300px', '100%', 'auto'
103+
height: PropTypes.string,
104+
// Initial editor type (markdown, wysiwyg)
105+
initialEditType: PropTypes.string,
106+
// Editor's initial value
107+
initialValue: PropTypes.string,
108+
// Editor's min-height style value in pixel ex) '300px'
109+
minHeight: PropTypes.string,
110+
// The placeholder text of the editable element.
111+
placeholder: PropTypes.string,
112+
// hide mode switch tab bar
113+
hideModeSwitch: PropTypes.bool,
114+
// language, 'en-US'
115+
language: PropTypes.string,
116+
// whether use keyboard shortcuts to perform commands
117+
useCommandShortcut: PropTypes.bool,
118+
// It would be emitted when editor fully load1
119+
onLoad: PropTypes.func,
120+
// It would be emitted when content changed
121+
onChange: PropTypes.func,
122+
// It would be emitted when format change by cursor position
123+
onStateChange: PropTypes.func,
124+
// It would be emitted when editor get focus
125+
onFocus: PropTypes.func,
126+
// It would be emitted when editor loose focus
127+
onBlur: PropTypes.func,
128+
// hooks
129+
hooks: PropTypes.arrayOf(PropTypes.object),
130+
// send hostname to google analytics
131+
usageStatistics: PropTypes.bool,
132+
// use default htmlSanitizer
133+
useDefaultHTMLSanitizer: PropTypes.bool,
134+
// toolbar items.
135+
toolbarItems: PropTypes.arrayOf(PropTypes.object),
136+
// Array of plugins. A plugin can be either a function or an array in the form of [function, options].
137+
plugins: PropTypes.arrayOf(PropTypes.object),
138+
// Using extended Autolinks specified in GFM spec
139+
extendedAutolinks: PropTypes.object,
140+
// convertor extention
141+
customConvertor: PropTypes.object,
142+
// Attributes of anchor element that should be rel, target, contenteditable, hreflang, type
143+
linkAttribute: PropTypes.object,
144+
// Object containing custom renderer functions correspond to markdown node
145+
customHTMLRenderer: PropTypes.object,
146+
// whether use the specification of link reference definition
147+
referenceDefinition: PropTypes.bool,
148+
// custom HTML sanitizer
149+
customHTMLSanitizer: PropTypes.func,
150+
// whether use the front matter
151+
frontMatter: PropTypes.bool
152+
}
153+
154+
export default TuiEditor
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* DescriptionField
3+
*/
4+
5+
import React from 'react'
6+
import PropTypes from 'prop-types'
7+
8+
import TuiEditor from '../../../../components/TuiEditor'
9+
10+
const DescriptionField = (props) => (
11+
<TuiEditor
12+
{...props}
13+
previewStyle="vertical"
14+
height="400px"
15+
hideModeSwitch
16+
initialEditType="wysiwyg"
17+
initialValue={props.value}
18+
/>
19+
)
20+
21+
22+
DescriptionField.propTypes = {
23+
onChange: PropTypes.func.isRequired,
24+
className: PropTypes.string,
25+
placeholder: PropTypes.string,
26+
value: PropTypes.string,
27+
}
28+
29+
export default DescriptionField

src/projects/detail/components/JobPickerRow/JobPickerRow.jsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import IconAdd from '../../../../assets/icons/ui-16px-1_bold-add.svg'
77
import SkillsQuestion from '../SkillsQuestion/SkillsQuestionBase'
88
import PositiveNumberInput from '../../../../components/PositiveNumberInput/PositiveNumberInput'
99
import SelectDropdown from 'appirio-tech-react-components/components/SelectDropdown/SelectDropdown'
10+
import DescriptionField from '../DescriptionField'
1011

1112
import styles from './JobPickerRow.scss'
1213

@@ -73,8 +74,8 @@ class JobPickerRow extends React.PureComponent {
7374
this.props.onChange(this.props.rowIndex, 'role', evt)
7475
}
7576

76-
handleDescriptionChange(evt) {
77-
this.props.onChange(this.props.rowIndex, 'description', evt.target.value)
77+
handleDescriptionChange(value) {
78+
this.props.onChange(this.props.rowIndex, 'description', value)
7879
}
7980

8081
resetDuration() {
@@ -229,20 +230,18 @@ class JobPickerRow extends React.PureComponent {
229230
/>
230231
</div>
231232
)
233+
232234
const descriptionColumn = (
233235
<div styleName="col col-skill-selection">
234236
<label className="tc-label" styleName="label">
235237
Job Description
236238
</label>
237-
<div styleName="job-description">
238-
<textarea
239-
className={`job-textarea ${isRowIncomplete && !value.description.trim() ? 'error' : 'empty'}`}
240-
onChange={this.handleDescriptionChange}
241-
placeholder="Job Description"
242-
type="text"
243-
value={value.description || ''}
244-
/>
245-
</div>
239+
<DescriptionField
240+
className={`${isRowIncomplete && !value.description.trim() ? 'error' : ''}`}
241+
onChange={this.handleDescriptionChange}
242+
placeholder="Job Description"
243+
value={value.description || ''}
244+
/>
246245
</div>
247246
)
248247

src/projects/detail/components/JobPickerRow/JobPickerRow.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@
4545
}
4646

4747
.col {
48+
:global {
49+
.error > .tui-editor-defaultUI {
50+
border: 1px solid #ff5b52
51+
}
52+
}
4853
padding: 4px 7.5px;
4954
flex-grow: 0;
5055
flex-shrink: 0;

0 commit comments

Comments
 (0)