1 import React, { Component } from 'react'
2 import PubSub from 'pubsub'
3 import GlobalVars from 'globalVars'
4 import styles from './main.css'
5
6 // globalVars.runMode
7 class Text extends Component{
8 static defaultProps = {
9 text: '文案内容'
10
11 };
12 constructor(props, context) {
13 super(props)
14 this.state = {
15 'content' : props.content
16 ,'Styles' : props.Styles
17 ,'editMode' : false
18 }
19
20 PubSub.subscribe('textEditorBar' , (evt) => {
21 var workingEditor = evt.data
22 if (workingEditor != this.editorID){
23 this.hideEditor()
24 }
25 })
26 }
27 componentWillReceiveProps(nextProps){
28 this.setState({'Styles' : nextProps.Styles})
29 }
30
31 emitChange(evt ){
32 //https://github.com/sstur/react-rte/blob/master/src/SimpleRichTextEditor.js
33 var new_content = this.textInput.innerHTML
34 if (new_content != this.state.content){
35 this.setState({'content' : new_content})
36 }
37 }
38
39 showEditor(evt){
40 evt.preventDefault()
41 this.setState({'editMode' : {'palette' : false}})
42 }
43 hideEditor(evt){
44 this.setState({'editMode' : false})
45 }
46 saveSelection() {
47 //https://github.com/mindmup/bootstrap-wysiwyg/blob/master/bootstrap-wysiwyg.js
48
49 var sel = window.getSelection()
50 if (sel.getRangeAt && sel.rangeCount) {
51 this.selectedRange = sel.getRangeAt(0)
52 }
53 }
54 updateToolbar() {
55 var btns = document.querySelectorAll('.' + styles.textEditor + ' li[data-tag]')
56
57 for (var i=0;i< btns.length;i++){
58 var tag = btns[i].dataset.tag
59
60 if (document.queryCommandState(tag)){
61 btns[i].classList.add(styles.editActive)
62 }else {
63 btns[i].classList.remove(styles.editActive)
64 }
65 }
66 }
67 emitKeyUp() {
68 this.saveSelection()
69 this.updateToolbar()
70 }
71 emitPaste(evt) {
72 evt.preventDefault()
73 var content = this.formatContent(evt.clipboardData.getData('Text') , {'nl2br':true})
74 this.restoreSelection()
75 document.execCommand('insertHTML', false, content)
76 this.saveSelection()
77 }
78 formatContent(content , opt){
79 opt = opt || {}
80 if (!content) return ''
81 if (opt.nl2br){
82 //content = content.replace(/<(?:.|
)*?>/gm, '').replace(/
/g,'</br/>')
83 content = content.replace(/
/g,'</br/>')
84 }
85 return content
86 }
87
88 restoreSelection() {
89 var selection = window.getSelection()
90 if (this.selectedRange) {
91 try {
92 selection.removeAllRanges()
93 } catch (ex) {
94 document.body.createTextRange().select()
95 document.selection.empty()
96 }
97
98 selection.addRange(this.selectedRange)
99 }
100 }
101
102
103 setStyles(tag ,new_val){
104 this.restoreSelection()
105 document.execCommand(tag ,false , new_val || null)
106 this.saveSelection()
107 this.updateToolbar()
108 }
109 getSelectionHtml(){
110 var userSelection
111 if (window.getSelection) {
112 // W3C Ranges
113 userSelection = window.getSelection()
114 // Get the range:
115 if (userSelection.getRangeAt){
116 var range = userSelection.getRangeAt(0)
117 var container = range.commonAncestorContainer
118 if (container.nodeType == 3) {
119 container = container.parentNode
120 return container.outerHTML
121 }
122 //if (container.nodeName === "A") {alert ("Yes, it's an anchor!");}
123 var clonedSelection = range.cloneContents()
124 var div = document.createElement('div')
125 div.appendChild(clonedSelection)
126 return div.innerHTML
127 }
128 } else if (document.selection) {
129 // Explorer selection, return the HTML
130 userSelection = document.selection.createRange()
131 return userSelection.htmlText
132 } else {
133 return ''
134 }
135 }
136 setLink(){
137 this.restoreSelection()
138 var wSelf = this
139 var tmp = document.createElement('div')
140 tmp.innerHTML = this.getSelectionHtml()
141 var link = tmp.getElementsByTagName('a')
142 PubSub.publish(
143 'widgetEditLink'
144 , {'link' : link[0]
145 ,'cbk' : (new_val) => {
146 this.restoreSelection()
147 var sText = window.getSelection()
148 if (new_val.link){
149 document.execCommand('createlink', false, new_val.link)
150 //document.execCommand('insertHTML', false, '<a href="' + new_val.link + '" target="' + (new_val.target || '_blank') + '">' + sText + '</a>')
151 }else{
152 var range = window.getSelection().getRangeAt(0)
153 var container = range.commonAncestorContainer
154 if (container.nodeType == 3) {
155 container = container.parentNode
156 }
157 if (container.nodeName === "A") {
158 container.outerHTML = container.innerHTML
159 }
160
161 }
162 this.saveSelection()
163 }
164 }
165 ,this)
166
167 }
168 setWholeStyles(tag , val){
169 var Styles = {...this.state.Styles }|| {}
170 if (val){
171 Styles[tag] = val
172 } else {
173 delete Styles[tag]
174 }
175 this.setState({'Styles' : Styles})
176 }
177 shouldComponentUpdate(newProps, newState){
178 if (this.props.setProps) {
179 var state_clone = {...newState}
180 delete state_clone.editMode
181 this.props.setProps(state_clone)
182 }
183 return true
184 }
185 setFontSize(evt){
186 this.setWholeStyles('fontSize' , evt.target.value)
187 }
188 setForeColor(evt){
189 this.setWholeStyles('color' , evt.target.dataset.color)
190 }
191 palette(){
192 var {editMode} = this.state || {}
193 this.setState({'editMode' : {'palette': !editMode.palette}})
194 }
195
196 render(){
197 var {content ,Styles ,editMode} = this.state
198 Styles = Styles || {}
199 content = this.formatContent(content)
200
201 var fontsize_options = {
202 's' : styles.textSmall
203 ,'n' : styles.textNormal
204 , 'l' : styles.textLarge
205 }
206 var fontsize_state = (Styles.fontSize in fontsize_options) ? Styles.fontSize :'n'
207 var wrapper_cls = styles.textWrapper
208 var fontsize_cls = fontsize_options[fontsize_state]
209 if (fontsize_cls) wrapper_cls += ' ' + fontsize_cls
210
211
212 var StylesClone = {...Styles}
213 delete StylesClone.fontSize
214
215 if ('edit' == GlobalVars.runMode){
216 if (editMode) {
217 editMode = editMode || {}
218 //fontSize foreColor
219 var size_options = []
220 ;[{'txt' :'小' ,'val' : 's'}
221 ,{'txt' :'普通' ,'val' : 'n'}
222 ,{'txt' : '大' ,'val' : 'l'}].forEach((item , i) => {
223 size_options.push(<option key={i} value={item.val}>{item.txt}</option>)
224 })
225
226 var color_options = []
227 ;['#f00','#ccc' , '#0ff','#f69'].forEach((color , i) => {
228 color_options.push(<li key={i} onClick={this.setForeColor.bind(this)} data-color={color} style={{'color':color}}>{color}</li>)
229
230 })
231
232 var palette_style = {}
233 if (editMode.palette) {
234 palette_style.display = 'block'
235 }
236 var edit_btn = (
237 <ul className={styles.textEditor}>
238 <li data-tag='bold' onClick={this.setStyles.bind(this,'bold')}>B</li>
239 <li data-tag='italic' onClick={this.setStyles.bind(this,'italic')}>I</li>
240 <li data-tag='underline' onClick={this.setStyles.bind(this,'underline')}>U</li>
241
242 <li data-tag='justifyLeft' onClick={this.setStyles.bind(this,'justifyLeft')}>L</li>
243 <li data-tag='justifyCenter' onClick={this.setStyles.bind(this,'justifyCenter')}>C</li>
244 <li data-tag='justifyRight' onClick={this.setStyles.bind(this,'justifyRight')}>R</li>
245 <li data-tag='justifyFull' onClick={this.setStyles.bind(this,'justifyFull')}>F</li>
246
247
248 <li onClick={this.setLink.bind(this)}>Link</li>
249
250 <li onClick={this.palette.bind(this)} className={styles.textColorEditor}>
251 Color
252 <ul style={palette_style}>
253 <li onClick={this.setForeColor.bind(this)} data-color='' style={{'color':'grey'}}>默认</li>
254 {color_options}
255 </ul>
256 </li>
257 <li>
258 <select value={fontsize_state} onChange={this.setFontSize.bind(this)}>
259 {size_options}
260 </select>
261 </li>
262 </ul>)
263 }
264 var holder_cls = `${styles.textHolder} textEditorHolder`
265 this.editorID = uuid()
266 return (
267 <div className={holder_cls} data-editorid={this.editorID}>
268 <div
269 contentEditable
270 suppressContentEditableWarning
271 ref={(input) => this.textInput = input}
272 onInput={this.emitChange.bind(this)}
273 onBlur={this.emitChange.bind(this )}
274 onClick={this.showEditor.bind(this)}
275 onKeyUp={this.emitKeyUp.bind(this)}
276 onMouseUp={this.emitKeyUp.bind(this)}
277 onPaste={this.emitPaste.bind(this)}
278 style={StylesClone}
279 className={wrapper_cls}
280 dangerouslySetInnerHTML={{__html:content}}
281 ></div>
282 {edit_btn}
283 </div>
284 )
285
286 }else {
287 return (
288 <div style={StylesClone} className={wrapper_cls}>{content}</div>
289 )
290 }
291 }
292 }
293 export default Text