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(/<(?:.|\n)*?>/gm, '').replace(/\n/g,'</br/>') 83 content = content.replace(/\n/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