reactjs simple text editor

  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
原文地址:https://www.cnblogs.com/vaal-water/p/6047809.html