5 import Portal from 'react-portal' |
5 import Portal from 'react-portal' |
6 import Immutable from 'immutable' |
6 import Immutable from 'immutable' |
7 import HtmlSerializer from '../HtmlSerializer' |
7 import HtmlSerializer from '../HtmlSerializer' |
8 import AnnotationPlugin from '../AnnotationPlugin' |
8 import AnnotationPlugin from '../AnnotationPlugin' |
9 import CategoriesTooltip from './CategoriesTooltip' |
9 import CategoriesTooltip from './CategoriesTooltip' |
|
10 // import './SlateEditor.css'; |
10 import { now } from '../utils'; |
11 import { now } from '../utils'; |
11 import { defaultAnnotationsCategories } from '../constants'; |
12 import { defaultAnnotationsCategories } from '../constants'; |
12 |
13 |
13 const plugins = []; |
14 const plugins = []; |
14 |
15 |
24 * @type {Object} |
25 * @type {Object} |
25 */ |
26 */ |
26 // TODO Check if we can move this to the plugin using the schema option |
27 // TODO Check if we can move this to the plugin using the schema option |
27 // https://docs.slatejs.org/reference/plugins/plugin.html#schema |
28 // https://docs.slatejs.org/reference/plugins/plugin.html#schema |
28 const schema = { |
29 const schema = { |
|
30 |
29 nodes: { |
31 nodes: { |
30 'bulleted-list': props => <ul {...props.attributes}>{props.children}</ul>, |
32 'bulleted-list': props => <ul {...props.attributes}>{props.children}</ul>, |
31 'list-item': props => <li {...props.attributes}>{props.children}</li>, |
33 'list-item': props => <li {...props.attributes}>{props.children}</li>, |
32 'numbered-list': props => <ol {...props.attributes}>{props.children}</ol>, |
34 'numbered-list': props => <ol {...props.attributes}>{props.children}</ol>, |
33 }, |
35 }, |
88 const annotationPlugin = AnnotationPlugin({ |
91 const annotationPlugin = AnnotationPlugin({ |
89 onChange: (text, start, end) => { |
92 onChange: (text, start, end) => { |
90 this.setState({ |
93 this.setState({ |
91 currentSelectionText: text, |
94 currentSelectionText: text, |
92 currentSelectionStart: start, |
95 currentSelectionStart: start, |
93 currentSelectionEnd: end |
96 currentSelectionEnd: end, |
94 }); |
97 }); |
95 } |
98 } |
96 }); |
99 }); |
97 |
100 |
98 plugins.push(annotationPlugin); |
101 plugins.push(annotationPlugin); |
99 |
102 |
100 |
103 |
101 this.state = { |
104 this.state = { |
102 value: props.note ? initialValue : Plain.deserialize(''), |
105 value: props.note ? Value.fromJSON(initialValue) : Plain.deserialize(''), |
103 startedAt: null, |
106 startedAt: null, |
104 finishedAt: null, |
107 finishedAt: null, |
105 currentSelectionText: '', |
108 currentSelectionText: '', |
106 currentSelectionStart: 0, |
109 currentSelectionStart: 0, |
107 currentSelectionEnd: 0, |
110 currentSelectionEnd: 0, |
108 hoveringMenu: null, |
111 hoveringMenu: null, |
109 isPortalOpen: false, |
112 isPortalOpen: false, |
110 categories: Immutable.List([]), |
113 categories: Immutable.List([]), |
111 isCheckboxChecked: false, |
114 isCheckboxChecked: false, |
|
115 enterKeyValue: 0, |
112 }; |
116 }; |
113 } |
117 } |
114 |
118 |
115 componentDidMount = () => { |
119 componentDidMount = () => { |
116 this.updateMenu(); |
120 this.updateMenu(); |
210 |
214 |
211 focus = () => { |
215 focus = () => { |
212 this.refs.editor.focus(); |
216 this.refs.editor.focus(); |
213 } |
217 } |
214 |
218 |
215 /** |
|
216 * On key down, if it's a formatting command toggle a mark. |
|
217 * |
|
218 * @param {Event} e |
|
219 * @param {Object} data |
|
220 * @param {Change} change |
|
221 * @return {Change} |
|
222 */ |
|
223 |
|
224 onKeyDown = (e, change) => { |
|
225 // if (data.key === 'enter' && this.props.isChecked && typeof this.props.onEnterKeyDown === 'function') { |
|
226 |
|
227 // e.preventDefault(); |
|
228 // this.props.onEnterKeyDown(); |
|
229 |
|
230 // return change; |
|
231 // } |
|
232 |
|
233 // if (!data.isMod) return |
|
234 if (!e.ctrlKey) return |
|
235 // Decide what to do based on the key code... |
|
236 switch (e.key) { |
|
237 // When "B" is pressed, add a "bold" mark to the text. |
|
238 case 'b': { |
|
239 e.preventDefault() |
|
240 change.toggleMark('bold') |
|
241 |
|
242 return true |
|
243 } |
|
244 case 'i': { |
|
245 // When "U" is pressed, add an "italic" mark to the text. |
|
246 e.preventDefault() |
|
247 change.toggleMark('italic') |
|
248 |
|
249 return true |
|
250 } |
|
251 case 'u': { |
|
252 // When "U" is pressed, add an "underline" mark to the text. |
|
253 e.preventDefault() |
|
254 change.toggleMark('underlined') |
|
255 |
|
256 return true |
|
257 } |
|
258 } |
|
259 } |
|
260 |
|
261 /** |
219 /** |
262 * When a mark button is clicked, toggle the current mark. |
220 * When a mark button is clicked, toggle the current mark. |
263 * |
221 * |
264 * @param {Event} e |
222 * @param {Event} e |
265 * @param {String} type |
223 * @param {String} type |
266 */ |
224 */ |
267 |
225 |
268 onClickMark = (e, type) => { |
226 onClickMark = (e, type) => { |
269 |
227 |
270 e.preventDefault() |
228 e.preventDefault() |
271 const { value } = this.state |
229 const { value } = this.state |
272 let { categories } = this.state |
230 let { categories } = this.state |
273 |
231 |
274 let isPortalOpen = false; |
232 let isPortalOpen = false; |
275 |
233 |
396 }, |
354 }, |
397 color: category.color, |
355 color: category.color, |
398 key: category.key |
356 key: category.key |
399 } |
357 } |
400 }) |
358 }) |
401 this.onChange(change) |
|
402 |
359 |
403 Object.assign(category, { |
360 Object.assign(category, { |
404 text: currentSelectionText, |
361 text: currentSelectionText, |
405 selection: { |
362 selection: { |
406 start: currentSelectionStart, |
363 start: currentSelectionStart, |
407 end: currentSelectionEnd, |
364 end: currentSelectionEnd, |
408 }, |
365 }, |
409 }); |
366 }); |
410 categories = categories.push(category); |
367 categories = categories.push(category); |
411 |
368 |
|
369 this.onChange(change) |
|
370 |
412 this.setState({ |
371 this.setState({ |
413 value: value, |
|
414 isPortalOpen: false, |
372 isPortalOpen: false, |
415 categories: categories |
373 categories: categories |
416 }); |
374 }); |
417 } |
375 } |
418 |
376 |
424 |
382 |
425 onCheckboxChange = (e) => { |
383 onCheckboxChange = (e) => { |
426 if (typeof this.props.onCheckboxChange === 'function') { |
384 if (typeof this.props.onCheckboxChange === 'function') { |
427 this.props.onCheckboxChange(e); |
385 this.props.onCheckboxChange(e); |
428 } |
386 } |
|
387 } |
|
388 |
|
389 /** |
|
390 * On key down, if it's a formatting command toggle a mark. |
|
391 * |
|
392 * @param {Event} e |
|
393 * @param {Change} change |
|
394 * @return {Change} |
|
395 */ |
|
396 |
|
397 onKeyDown = (e, change) => { |
|
398 |
|
399 const {value} = this.state; |
|
400 |
|
401 // if (e.key === 'Enter' && value.document.text === '') { |
|
402 // change.removeChild() |
|
403 // } |
|
404 |
|
405 if (e.key === 'Enter' && value.document.text !== '') { |
|
406 this.setState({enterKeyValue: 1}) |
|
407 } |
|
408 |
|
409 if (e.key !== 'Enter') { |
|
410 this.setState({ |
|
411 enterKeyValue: 0, |
|
412 }) |
|
413 |
|
414 } |
|
415 |
|
416 if (e.key === 'Enter' && !this.props.isChecked && this.state.enterKeyValue === 1 && typeof this.props.onEnterKeyDown === 'function') { |
|
417 e.preventDefault(); |
|
418 this.props.onEnterKeyDown(); |
|
419 this.setState({ |
|
420 enterKeyValue: 0, |
|
421 }) |
|
422 |
|
423 |
|
424 return change |
|
425 } |
|
426 |
|
427 else if (e.key === 'Enter' && value.document.text !== '' && this.props.isChecked && typeof this.props.onEnterKeyDown === 'function') { |
|
428 |
|
429 e.preventDefault(); |
|
430 this.props.onEnterKeyDown(); |
|
431 |
|
432 return change |
|
433 } |
|
434 |
|
435 if (!e.ctrlKey) return |
|
436 // Decide what to do based on the key code... |
|
437 switch (e.key) { |
|
438 default: { |
|
439 break; |
|
440 } |
|
441 // When "B" is pressed, add a "bold" mark to the text. |
|
442 case 'b': { |
|
443 e.preventDefault() |
|
444 change.toggleMark('bold') |
|
445 |
|
446 return true |
|
447 } |
|
448 case 'i': { |
|
449 // When "U" is pressed, add an "italic" mark to the text. |
|
450 e.preventDefault() |
|
451 change.toggleMark('italic') |
|
452 |
|
453 return true |
|
454 } |
|
455 case 'u': { |
|
456 // When "U" is pressed, add an "underline" mark to the text. |
|
457 e.preventDefault() |
|
458 change.toggleMark('underlined') |
|
459 |
|
460 return true |
|
461 } |
|
462 case 'Enter': { |
|
463 // When "ENTER" is pressed, autosubmit the note. |
|
464 if (value.document.text !== '' && typeof this.props.onEnterKeyDown === 'function') { |
|
465 e.preventDefault() |
|
466 this.props.onEnterKeyDown(); |
|
467 this.setState({ |
|
468 enterKeyValue: 0, |
|
469 }) |
|
470 |
|
471 return true |
|
472 } |
|
473 } |
|
474 } |
429 } |
475 } |
430 |
476 |
431 /** |
477 /** |
432 * Render. |
478 * Render. |
433 * |
479 * |
451 * @return {Element} |
497 * @return {Element} |
452 */ |
498 */ |
453 |
499 |
454 renderToolbar = () => { |
500 renderToolbar = () => { |
455 return ( |
501 return ( |
456 <div className="menu toolbar-menu d-flex bg-secondary"> |
502 <div className="menu toolbar-menu d-flex sticky-top bg-secondary"> |
457 {this.renderMarkButton('bold', 'format_bold')} |
503 {this.renderMarkButton('bold', 'format_bold')} |
458 {this.renderMarkButton('italic', 'format_italic')} |
504 {this.renderMarkButton('italic', 'format_italic')} |
459 {this.renderMarkButton('underlined', 'format_underlined')} |
505 {this.renderMarkButton('underlined', 'format_underlined')} |
460 {this.renderMarkButton('category', 'label')} |
506 {this.renderMarkButton('category', 'label')} |
461 |
507 |
476 </label> |
522 </label> |
477 </div> |
523 </div> |
478 ) |
524 ) |
479 } |
525 } |
480 |
526 |
|
527 renderSaveButton = () => { |
|
528 if (this.props.note) { |
|
529 return <button type="button" id="btn-editor" className="btn btn-primary btn-sm text-secondary font-weight-bold mr-2" disabled={this.props.isButtonDisabled} onClick={this.onButtonClick}> |
|
530 Sauvegarder</button> |
|
531 } |
|
532 } |
|
533 |
481 renderToolbarButtons = () => { |
534 renderToolbarButtons = () => { |
482 return ( |
535 return ( |
483 <div> |
536 <div> |
484 <button type="button" className="btn btn-primary btn-sm text-secondary float-right mr-5" disabled={this.props.isButtonDisabled} onClick={this.onButtonClick}> |
537 {/* <button type="button" id="btn-editor" className="btn btn-primary btn-sm text-secondary font-weight-bold float-right" disabled={this.props.isButtonDisabled} onClick={this.onButtonClick}> */} |
485 { this.props.note ? 'Save note' : 'Ajouter' } |
538 {/* { this.props.note ? 'Sauvegarder' : 'Ajouter' } */} |
486 </button> |
539 {this.renderSaveButton()} |
|
540 {/* </button> */} |
487 { !this.props.note && this.renderToolbarCheckbox() } |
541 { !this.props.note && this.renderToolbarCheckbox() } |
488 </div> |
542 </div> |
489 ); |
543 ); |
490 } |
544 } |
491 |
545 |
498 */ |
552 */ |
499 |
553 |
500 renderMarkButton = (type, icon) => { |
554 renderMarkButton = (type, icon) => { |
501 const isActive = this.hasMark(type) |
555 const isActive = this.hasMark(type) |
502 const onMouseDown = e => this.onClickMark(e, type) |
556 const onMouseDown = e => this.onClickMark(e, type) |
503 |
557 const markActivation = "button sticky-top" + ((!isActive)?" text-primary":" text-dark"); |
504 |
558 |
505 return ( |
559 return ( |
506 <span className="button text-primary" onMouseDown={onMouseDown} data-active={isActive}> |
560 // <span className="button text-primary" onMouseDown={onMouseDown} data-active={isActive}> |
|
561 <span className={markActivation} onMouseDown={onMouseDown} data-active={isActive}> |
|
562 |
507 <span className="material-icons">{icon}</span> |
563 <span className="material-icons">{icon}</span> |
508 </span> |
564 </span> |
509 ) |
565 ) |
510 } |
566 } |
511 |
567 |
513 |
569 |
514 renderMark = props => { |
570 renderMark = props => { |
515 const { children, mark, attributes } = props |
571 const { children, mark, attributes } = props |
516 |
572 |
517 switch (mark.type) { |
573 switch (mark.type) { |
|
574 default: { |
|
575 break; |
|
576 } |
518 case 'bold': |
577 case 'bold': |
519 return <strong {...attributes}>{children}</strong> |
578 return <strong {...attributes}>{children}</strong> |
520 case 'code': |
579 case 'code': |
521 return <code {...attributes}>{children}</code> |
580 return <code {...attributes}>{children}</code> |
522 case 'italic': |
581 case 'italic': |
540 const { value } = this.state |
599 const { value } = this.state |
541 const parent = value.document.getParent(value.blocks.first().key) |
600 const parent = value.document.getParent(value.blocks.first().key) |
542 isActive = this.hasBlock('list-item') && parent && parent.type === type |
601 isActive = this.hasBlock('list-item') && parent && parent.type === type |
543 } |
602 } |
544 const onMouseDown = e => this.onClickBlock(e, type) |
603 const onMouseDown = e => this.onClickBlock(e, type) |
|
604 const blockActivation = "button sticky-top" + ((!isActive)?" text-primary":" text-dark"); |
545 |
605 |
546 return ( |
606 return ( |
547 <span className="button text-primary" onMouseDown={onMouseDown} data-active={isActive}> |
607 <span className={blockActivation} onMouseDown={onMouseDown} data-active={isActive}> |
548 <span className="material-icons">{icon}</span> |
608 <span className="material-icons">{icon}</span> |
549 </span> |
609 </span> |
550 ) |
610 ) |
551 } |
611 } |
552 |
612 |
553 renderNode = props => { |
613 renderNode = props => { |
554 const { attributes, children, node } = props |
614 const { attributes, children, node } = props |
555 |
615 |
556 switch (node.type) { |
616 switch (node.type) { |
|
617 default: { |
|
618 break; |
|
619 } |
557 case 'block-quote': |
620 case 'block-quote': |
558 return <blockquote {...attributes}>{children}</blockquote> |
621 return <blockquote {...attributes}>{children}</blockquote> |
559 case 'bulleted-list': |
622 case 'bulleted-list': |
560 return <ul {...attributes}>{children}</ul> |
623 return <ul {...attributes}>{children}</ul> |
561 case 'heading-one': |
624 case 'heading-one': |
575 * @return {Element} |
638 * @return {Element} |
576 */ |
639 */ |
577 |
640 |
578 renderEditor = () => { |
641 renderEditor = () => { |
579 return ( |
642 return ( |
580 <div className="editor-wrapper sticky-bottom p-2"> |
643 <div className="editor-slatejs p-2"> |
581 <div className="editor-slatejs"> |
|
582 {this.renderHoveringMenu()} |
644 {this.renderHoveringMenu()} |
583 <Editor |
645 <Editor |
584 ref="editor" |
646 ref="editor" |
585 spellCheck |
647 spellCheck |
586 autoFocus |
|
587 placeholder={'Votre espace de prise de note...'} |
648 placeholder={'Votre espace de prise de note...'} |
588 schema={schema} |
649 schema={schema} |
589 plugins={plugins} |
650 plugins={plugins} |
590 value={this.state.value} |
651 value={this.state.value} |
591 onChange={this.onChange} |
652 onChange={this.onChange} |
592 onKeyDown={this.onKeyDown} |
653 onKeyDown={this.onKeyDown} |
593 renderMark={this.renderMark} |
654 renderMark={this.renderMark} |
594 renderNode = {this.renderNode} |
655 renderNode = {this.renderNode} |
595 /> |
656 /> |
596 </div> |
|
597 </div> |
657 </div> |
598 ) |
658 ) |
599 } |
659 } |
600 |
660 |
601 renderHoveringMenu = () => { |
661 renderHoveringMenu = () => { |