· 6 years ago · Apr 30, 2020, 04:32 PM
1import React , {useState , useEffect, useRef} from 'react';
2import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3import {Editor, EditorState , RichUtils , CompositeDecorator , SelectionState} from 'draft-js';
4
5function RichTextEditor(props)
6 {
7
8 const url_ref =useRef();
9
10 const editor_ref =useRef();
11
12
13
14
15 const checkIcon = (props) => {
16
17 return <FontAwesomeIcon
18 className={'check'}
19 icon="check" color="white" size="lg" />;
20 }
21
22 const unique = (elem, index, array) => {
23 for (var i = 0; i < index; i++) {
24 if (array[i] === elem) return false;
25 }
26 return true;
27 };
28
29
30 const Link = (props) => {
31 const {url} = props.contentState.getEntity(props.entityKey).getData();
32 return (
33 <a href={url} className={'link_url_editor'}>
34 {props.children}
35 </a>
36 );
37 };
38
39 const finaloutput = (s) => {
40
41 console.log("final: "+s);
42
43 };
44
45
46 const WordError= (props) => {
47
48 let str = props.contentState.getBlockForKey(props.blockKey).getText();
49
50 let last = str.length-1;
51
52 let positionRate = (props.start / last)*100
53
54 console.log(positionRate);
55
56 // const classRatePos = positionRate >=92 ? 'finalpos' : '';
57 const classRatePos = '';
58
59 let el = React.useRef();
60 // console.log(el.getBoundingClientRect());
61
62
63
64 return (
65 <span className={`wrong_word ${classRatePos}`}>
66 {props.children}
67
68 <span className={"wrong_word_menu"}>
69
70 <span>{checkIcon()} Medico</span>
71 <span>{checkIcon()}Medicina</span>
72 <span>{checkIcon()}Mediocridade</span>
73 <span>{checkIcon()}Ignore</span>
74
75 </span>
76
77 </span>
78 );
79 };
80
81
82
83
84 //decorator for links
85 const decorator = new CompositeDecorator([
86 {
87 strategy: findLinkEntities,
88 component: Link,
89 },
90
91
92 {
93 strategy: findBadWords,
94 component: WordError
95
96 },
97
98 ]);
99
100
101 /*start states declaration */
102
103 const [editorState, setEditorState] = useState(EditorState.createEmpty(decorator));
104 const [url_box_visible, setUrlBoxVisible] =useState(false);
105 const [url_box, setUrlBox] = useState('');
106 const [forbiddenWords, setForbiddenWords] = useState(['madrid','lisboa','teste']);
107
108
109 /* end states declaration */
110
111
112
113 /* current unique words */
114
115 const unique_words =
116 editorState.getCurrentContent().getPlainText().toLowerCase().replace(/\W/g, ' ').split(" ").filter(unique);
117
118 /* end current unique words */
119
120
121
122 /*start effects declaration */
123
124 useEffect(() => {
125
126 //when the box ulr is visible focus is on
127
128 if(url_box_visible)
129 {
130
131 setTimeout(() => url_ref.current.focus(), 0);
132
133 }
134
135
136
137
138 },[url_box_visible]);
139
140 /* end effects declaration */
141
142
143
144
145 function toggleShowUrlBox()
146 {
147
148 //clean input
149 setUrlBox('');
150
151 setUrlBoxVisible(!url_box_visible);
152
153
154 }
155
156
157
158 function linkExists()
159 {
160
161 const selection = editorState.getSelection();
162 if (!selection.isCollapsed())
163 {
164
165
166 const startKey = editorState.getSelection().getStartKey();
167 const startOffset = editorState.getSelection().getStartOffset();
168 const blockWithLinkAtBeginning = contentState.getBlockForKey(startKey);
169 const linkKey = blockWithLinkAtBeginning.getEntityAt(startOffset);
170
171
172
173 if(linkKey)
174 return true;
175 }
176
177 return false;
178
179 }
180
181
182 function textSelected()
183 {
184
185 const selection = editorState.getSelection();
186 if (!selection.isCollapsed())
187 {
188 return true;
189 }
190
191 return false;
192
193 }
194
195
196
197 function showLinkBox(e) {
198 e.preventDefault();
199
200 if(e.button!==0)
201 return;
202
203
204 /*
205 if(!url_box_visible){
206 return;
207 }
208*/
209
210
211 const selection = editorState.getSelection();
212 if (!selection.isCollapsed()) {
213 const contentState = editorState.getCurrentContent();
214 const startKey = editorState.getSelection().getStartKey();
215 const startOffset = editorState.getSelection().getStartOffset();
216 const blockWithLinkAtBeginning = contentState.getBlockForKey(startKey);
217 const linkKey = blockWithLinkAtBeginning.getEntityAt(startOffset);
218
219 let url = '';
220 if (linkKey) {
221 const linkInstance = contentState.getEntity(linkKey);
222 url = linkInstance.getData().url;
223 }
224
225 setUrlBox(url);
226 setUrlBoxVisible(true);
227
228
229 /*
230 this.setState({
231 showURLInput: true,
232 urlValue: url,
233 }, () => {
234 setTimeout(() => this.refs.url.focus(), 0);
235 });*/
236
237
238
239
240
241
242
243
244 }
245
246
247
248 }
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269 //array with the ignored words
270 const [ignored_wrong_words, setIgnoredWrongWords] = React.useState([
271
272 { entityId: '' , word: '' }
273
274 ]);
275
276
277
278
279
280 //mock up of a fake api spell language request
281const dictionary = ['portugal' , 'espanha'];
282
283
284const INLINE_STYLES = [
285 {
286 label: 'Bold',
287 style: 'BOLD'
288 }, {
289 label: 'Italic',
290 style: 'ITALIC'
291 }, {
292 label: 'Underline',
293 style: 'UNDERLINE'
294 }
295 ];
296
297
298
299
300
301
302 // place holder hack
303 const contentState = editorState.getCurrentContent();
304 let showPlaceholder = false;
305 if (!contentState.hasText()) {
306
307 if (contentState.getBlockMap().first().getType() === 'unstyled') {
308
309 showPlaceholder = true;
310
311 }
312
313 }
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333 //start buttons events
334
335
336 function onInlineStyleClick(command)
337 {
338
339 const newState = RichUtils.toggleInlineStyle(editorState,command);
340 setEditorState(newState);
341
342
343
344 }
345
346
347
348 function getBlockStyle(block) {
349 switch (block.getType()) {
350 case 'blockquote':
351 return 'RichEditor-blockquote';
352 default:
353 return null;
354 }
355 }
356
357
358 function onBlockStyleClick(e)
359 {
360 e.preventDefault();
361
362 if(e.button!==0)
363 return;
364
365
366 let block = e.currentTarget.getAttribute('data-block');
367 setEditorState(RichUtils.toggleBlockType(editorState, block));
368
369
370
371
372
373 }
374
375
376 //end buttons events
377
378
379
380
381 function updateEditorState(newState)
382 {
383
384
385 setEditorState(newState);
386
387 props.onChange(newState);
388
389
390 }
391
392
393
394
395
396
397 //handle key command
398 function handleKeyCommand (command,editorState)
399 {
400
401
402 console.log(command);
403
404 const newState = RichUtils.handleKeyCommand(editorState,command);
405
406
407 if(newState)
408 {
409
410 setEditorState(newState);
411
412 return "handled";
413
414
415 }
416
417 return "not-handled";
418
419
420
421 }
422
423
424
425 const inStyle = editorState.getCurrentInlineStyle();
426 const blockType = RichUtils.getCurrentBlockType(editorState);
427
428
429
430
431
432
433
434
435
436
437
438
439 function findLinkEntities(contentBlock, callback, contentState) {
440 contentBlock.findEntityRanges(
441 (character) => {
442 const entityKey = character.getEntity();
443 return (
444 entityKey !== null &&
445 contentState.getEntity(entityKey).getType() === 'LINK'
446 );
447 },
448 callback
449 );
450
451
452
453
454 }
455
456
457
458
459 function findBadWords(contentBlock, callback, contentState) {
460
461
462
463 const _text = contentBlock.getText().toLowerCase().replace(/\W/g, ' ');
464 console.log(_text);
465 // console.log(_text);
466 let _word , start;
467
468 for(let i=0;i<forbiddenWords.length;i++)
469 {
470
471
472
473 _word = forbiddenWords[i];
474 //let regex = /`_word`/g;
475
476 let regex=new RegExp("\\b"+_word+"\\b","g");
477
478 // start = _text.indexOf(_word);
479 let matchArr;
480
481
482
483 while ((matchArr = regex.exec(_text)) !== null) {
484
485 start = matchArr.index;
486 callback(start, start + matchArr[0].length);
487
488 }
489
490
491
492 }
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512 }
513
514
515
516
517
518
519
520
521
522 function _confirmLink(e) {
523 if(e) e.preventDefault();
524 //const {editorState, urlValue} = this.state;
525
526
527 if(!url_box)
528 {
529
530 _removeLink(null);
531 setUrlBox('');
532 setUrlBoxVisible(false);
533 return;
534
535 }
536
537 const contentState = editorState.getCurrentContent();
538
539 const contentStateWithEntity = contentState.createEntity(
540 'LINK',
541 'MUTABLE',
542 {url: url_box}
543 );
544
545
546 const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
547 const newEditorState = EditorState.set(editorState, { currentContent: contentStateWithEntity });
548
549
550
551 setEditorState(RichUtils.toggleLink(
552 newEditorState,
553 newEditorState.getSelection(),
554 entityKey
555 ));
556
557
558
559 setUrlBox('');
560
561 setUrlBoxVisible(false);
562
563 focus();
564
565
566
567
568
569 }
570
571
572
573
574
575
576
577 function _onLinkInputKeyDown(e) {
578 if (e.which === 13) {
579 this._confirmLink(e);
580 }
581 }
582
583
584
585
586
587 function _removeLink(e) {
588
589 if(e){
590
591 e.preventDefault();
592
593 if(e.button!==0)
594 return;
595
596 }
597
598
599
600 const selection = editorState.getSelection();
601
602 if (!selection.isCollapsed()) {
603
604 setEditorState(RichUtils.toggleLink(editorState, selection, null));
605
606 }
607
608
609 }
610
611
612 function _onKeyDown(e){
613 e.stopPropagation(); // <------ Here is the magic
614
615
616 if (e.key === 'Enter') {
617
618 _confirmLink(null);
619 }
620
621 if(e.keyCode === 27) {
622 //Do whatever when esc is pressed
623
624
625
626 setUrlBoxVisible(false);
627 focus();
628
629
630
631
632
633
634
635 }
636
637 }
638
639
640
641 function focus()
642 {
643
644 setTimeout(() => editor_ref.current.focus(),0);
645
646 }
647
648
649
650 function spellCheck()
651 {
652
653 alert("spell check");
654
655 }
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711return (
712
713
714<div id="professional_experience_container" className={`container-fluid ${!showPlaceholder ? 'hide-placeholder' : ''}`}>
715
716
717
718
719<div className="row title_container">
720
721 <div className="col-lg-12 col-sm-12 col-xs-12">
722<h3>What's your professional experience?</h3>
723 </div>
724
725</div>
726
727
728
729
730
731<div className="row">
732<div className="col-lg-12 col-sm-12 col-xs-12">
733
734<div className="editor-container">
735
736
737
738{url_box_visible && <div
739
740
741className="link_box_container">
742<div className="link_box">
743
744<p>Enter the URL of the link</p>
745
746<input onKeyDown={(e) => _onKeyDown(e)} ref={url_ref} type="text" value={url_box} onChange={(e) => setUrlBox(e.target.value)} />
747
748<span className='buttons_link'>
749
750<span onMouseDown={ () => toggleShowUrlBox() } className="link_l">Cancel</span>
751<span onMouseDown={ (e) => _confirmLink(e) }className="link_r">Confirm</span>
752
753</span>
754
755</div>
756</div>
757}
758
759
760<div className="editor_buttons">
761
762
763 <span className="left">
764
765
766
767
768 {INLINE_STYLES.map(
769 type => <FontAwesomeIcon key={type.style} className={ inStyle.has(type.style) ? 'selected' : ''} onMouseDown={(e) => {
770 e.preventDefault();
771
772
773
774 if(e.button!==0)
775 return;
776
777
778
779 onInlineStyleClick(type.style);
780
781 }
782
783 }
784 icon={type.style.toLowerCase()} color="white" size="lg" />
785 )}
786
787
788
789
790 {/* we use onmousedown so we dont loose the focus cursor */}
791
792 <FontAwesomeIcon
793 className={blockType === 'unordered-list-item' ? 'selected' : ''}
794 data-block="unordered-list-item" onMouseDown={(e) => onBlockStyleClick(e)} icon="list-ul" color="white" size="lg" />
795
796
797 <FontAwesomeIcon
798
799 className={blockType === 'ordered-list-item' ? 'selected' : ''}
800 data-block="ordered-list-item" onMouseDown={(e) => onBlockStyleClick(e)} icon="list-ol" color="white" size="lg" />
801
802 <FontAwesomeIcon
803 className={blockType === 'blockquote' ? 'selected' : ''}
804 data-block="blockquote" onMouseDown={(e) => onBlockStyleClick(e)} icon="indent" color="white" size="lg" />
805
806
807
808
809 <FontAwesomeIcon data-block=""
810 className={`${!textSelected() ? "no-hover" : ''} `}
811 onMouseDown={(e) => showLinkBox(e)} icon="link" color="white" size="lg" />
812
813
814
815 {linkExists() &&
816 <FontAwesomeIcon
817 className={"removelink"}
818 onMouseDown={(e) => _removeLink(e)} icon="unlink" color="white" size="lg" />
819 }
820
821
822 </span>
823
824
825
826
827
828
829
830 <span className="right">
831 <FontAwesomeIcon onClick={() => spellCheck()} icon="spell-check" color="white" size="lg" />
832 </span>
833
834
835</div>
836
837
838<Editor
839
840placeholder="Soy elizeu..."
841
842className={`rich_editor`}
843
844editorState={editorState}
845
846blockStyleFn={(b) =>getBlockStyle(b)}
847
848onChange={(editorState) => updateEditorState(editorState) }
849
850handleKeyCommand={handleKeyCommand}
851
852ref={editor_ref}
853
854/>
855
856
857<div>
858 {
859
860
861 /* unique_words.join(" ") */
862 }
863</div>
864
865</div>
866
867</div>
868</div>
869
870
871</div>
872
873);
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891}
892
893
894
895
896
897export default RichTextEditor;