10
10
*/
11
11
import '../vendor/vaadin-quill.js' ;
12
12
import { timeOut } from '@vaadin/component-base/src/async.js' ;
13
- import { isFirefox } from '@vaadin/component-base/src/browser-utils.js' ;
14
13
import { Debouncer } from '@vaadin/component-base/src/debounce.js' ;
15
14
import { I18nMixin } from '@vaadin/component-base/src/i18n-mixin.js' ;
16
15
17
16
const Quill = window . Quill ;
18
17
19
- // Workaround for text disappearing when accepting spellcheck suggestion
20
- // See https://github.com/quilljs/quill/issues/2096#issuecomment-399576957
21
- const Inline = Quill . import ( 'blots/inline' ) ;
22
-
23
- class CustomColor extends Inline {
24
- constructor ( domNode , value ) {
25
- super ( domNode , value ) ;
26
-
27
- // Map <font> properties
28
- domNode . style . color = domNode . color ;
29
-
30
- const span = this . replaceWith ( new Inline ( Inline . create ( ) ) ) ;
31
-
32
- span . children . forEach ( ( child ) => {
33
- if ( child . attributes ) child . attributes . copy ( span ) ;
34
- if ( child . unwrap ) child . unwrap ( ) ;
35
- } ) ;
36
-
37
- this . remove ( ) ;
38
-
39
- return span ; // eslint-disable-line no-constructor-return
18
+ // Fix to add `spellcheck="false"` on the `<pre>` tag removed by Quill
19
+ // TODO: Quill also removes `<code>` tag from the output, should add it?
20
+ const QuillCodeBlockContainer = Quill . import ( 'formats/code-block-container' ) ;
21
+
22
+ class CodeBlockContainer extends QuillCodeBlockContainer {
23
+ html ( index , length ) {
24
+ const markup = super . html ( index , length ) ;
25
+ const tempDiv = document . createElement ( 'div' ) ;
26
+ tempDiv . innerHTML = markup ;
27
+ const preTag = tempDiv . querySelector ( 'pre' ) ;
28
+ if ( preTag ) {
29
+ preTag . setAttribute ( 'spellcheck' , 'false' ) ;
30
+ return preTag . outerHTML ;
31
+ }
32
+ return markup ; // fallback
40
33
}
41
34
}
42
35
43
- CustomColor . blotName = 'customColor' ;
44
- CustomColor . tagName = 'FONT' ;
45
-
46
- Quill . register ( CustomColor , true ) ;
36
+ Quill . register ( 'formats/code-block-container' , CodeBlockContainer , true ) ;
47
37
48
38
const HANDLERS = [
49
39
'bold' ,
@@ -70,8 +60,6 @@ const STATE = {
70
60
CLICKED : 2 ,
71
61
} ;
72
62
73
- const TAB_KEY = 9 ;
74
-
75
63
const DEFAULT_I18N = {
76
64
undo : 'undo' ,
77
65
redo : 'redo' ,
@@ -352,11 +340,6 @@ export const RichTextEditorMixin = (superClass) =>
352
340
this . __patchToolbar ( ) ;
353
341
this . __patchKeyboard ( ) ;
354
342
355
- /* c8 ignore next 3 */
356
- if ( isFirefox ) {
357
- this . __patchFirefoxFocus ( ) ;
358
- }
359
-
360
343
this . __setDirection ( this . __dir ) ;
361
344
362
345
const editorContent = editor . querySelector ( '.ql-editor' ) ;
@@ -380,23 +363,21 @@ export const RichTextEditorMixin = (superClass) =>
380
363
}
381
364
} ) ;
382
365
383
- const TAB_KEY = 9 ;
384
-
385
366
editorContent . addEventListener ( 'keydown' , ( e ) => {
386
367
if ( e . key === 'Escape' ) {
387
368
if ( ! this . __tabBindings ) {
388
- this . __tabBindings = this . _editor . keyboard . bindings [ TAB_KEY ] ;
389
- this . _editor . keyboard . bindings [ TAB_KEY ] = null ;
369
+ this . __tabBindings = this . _editor . keyboard . bindings . Tab ;
370
+ this . _editor . keyboard . bindings . Tab = null ;
390
371
}
391
372
} else if ( this . __tabBindings ) {
392
- this . _editor . keyboard . bindings [ TAB_KEY ] = this . __tabBindings ;
373
+ this . _editor . keyboard . bindings . Tab = this . __tabBindings ;
393
374
this . __tabBindings = null ;
394
375
}
395
376
} ) ;
396
377
397
378
editorContent . addEventListener ( 'blur' , ( ) => {
398
379
if ( this . __tabBindings ) {
399
- this . _editor . keyboard . bindings [ TAB_KEY ] = this . __tabBindings ;
380
+ this . _editor . keyboard . bindings . Tab = this . __tabBindings ;
400
381
this . __tabBindings = null ;
401
382
}
402
383
} ) ;
@@ -483,7 +464,7 @@ export const RichTextEditorMixin = (superClass) =>
483
464
buttons [ index ] . focus ( ) ;
484
465
}
485
466
// Esc and Tab focuses the content
486
- if ( e . keyCode === 27 || ( e . keyCode === TAB_KEY && ! e . shiftKey ) ) {
467
+ if ( e . keyCode === 27 || ( e . key === 'Tab' && ! e . shiftKey ) ) {
487
468
e . preventDefault ( ) ;
488
469
this . _editor . focus ( ) ;
489
470
}
@@ -529,52 +510,6 @@ export const RichTextEditorMixin = (superClass) =>
529
510
return elem ;
530
511
}
531
512
532
- /** @private */
533
- __patchFirefoxFocus ( ) {
534
- // In Firefox 63+ with native Shadow DOM, when moving focus out of
535
- // contenteditable and back again within same shadow root, cursor
536
- // disappears. See https://bugzilla.mozilla.org/show_bug.cgi?id=1496769
537
- const editorContent = this . shadowRoot . querySelector ( '.ql-editor' ) ;
538
- let isFake = false ;
539
-
540
- const focusFake = ( ) => {
541
- isFake = true ;
542
- this . __fakeTarget = this . __createFakeFocusTarget ( ) ;
543
- document . body . appendChild ( this . __fakeTarget ) ;
544
- // Let the focus step out of shadow root!
545
- this . __fakeTarget . focus ( ) ;
546
- return new Promise ( ( resolve ) => {
547
- setTimeout ( resolve ) ;
548
- } ) ;
549
- } ;
550
-
551
- const focusBack = ( offsetNode , offset ) => {
552
- this . _editor . focus ( ) ;
553
- if ( offsetNode ) {
554
- this . _editor . selection . setNativeRange ( offsetNode , offset ) ;
555
- }
556
- document . body . removeChild ( this . __fakeTarget ) ;
557
- delete this . __fakeTarget ;
558
- isFake = false ;
559
- } ;
560
-
561
- editorContent . addEventListener ( 'mousedown' , ( e ) => {
562
- if ( ! this . _editor . hasFocus ( ) ) {
563
- const { x, y } = e ;
564
- const { offset, offsetNode } = document . caretPositionFromPoint ( x , y ) ;
565
- focusFake ( ) . then ( ( ) => {
566
- focusBack ( offsetNode , offset ) ;
567
- } ) ;
568
- }
569
- } ) ;
570
-
571
- editorContent . addEventListener ( 'focusin' , ( ) => {
572
- if ( isFake === false ) {
573
- focusFake ( ) . then ( ( ) => focusBack ( ) ) ;
574
- }
575
- } ) ;
576
- }
577
-
578
513
/** @private */
579
514
__patchToolbar ( ) {
580
515
const toolbar = this . _editor . getModule ( 'toolbar' ) ;
@@ -602,19 +537,19 @@ export const RichTextEditorMixin = (superClass) =>
602
537
this . _toolbar . querySelector ( 'button:not([tabindex])' ) . focus ( ) ;
603
538
} ;
604
539
605
- const keyboard = this . _editor . getModule ( ' keyboard' ) ;
606
- const bindings = keyboard . bindings [ TAB_KEY ] ;
540
+ const keyboard = this . _editor . keyboard ;
541
+ const bindings = keyboard . bindings . Tab ;
607
542
608
543
// Exclude Quill shift-tab bindings, except for code block,
609
544
// as some of those are breaking when on a newline in the list
610
545
// https://github.com/vaadin/vaadin-rich-text-editor/issues/67
611
546
const originalBindings = bindings . filter ( ( b ) => ! b . shiftKey || ( b . format && b . format [ 'code-block' ] ) ) ;
612
- const moveFocusBinding = { key : TAB_KEY , shiftKey : true , handler : focusToolbar } ;
547
+ const moveFocusBinding = { key : 'Tab' , shiftKey : true , handler : focusToolbar } ;
613
548
614
- keyboard . bindings [ TAB_KEY ] = [ ...originalBindings , moveFocusBinding ] ;
549
+ keyboard . bindings . Tab = [ ...originalBindings , moveFocusBinding ] ;
615
550
616
551
// Alt-f10 focuses a toolbar button
617
- keyboard . addBinding ( { key : 121 , altKey : true , handler : focusToolbar } ) ;
552
+ keyboard . addBinding ( { key : 'F10' , altKey : true , handler : focusToolbar } ) ;
618
553
}
619
554
620
555
/** @private */
@@ -653,6 +588,7 @@ export const RichTextEditorMixin = (superClass) =>
653
588
_applyLink ( link ) {
654
589
if ( link ) {
655
590
this . _markToolbarClicked ( ) ;
591
+ this . _editor . focus ( ) ;
656
592
this . _editor . format ( 'link' , link , SOURCE . USER ) ;
657
593
this . _editor . getModule ( 'toolbar' ) . update ( this . _editor . selection . savedRange ) ;
658
594
}
@@ -735,6 +671,7 @@ export const RichTextEditorMixin = (superClass) =>
735
671
const color = event . detail . color ;
736
672
this . _colorValue = color === '#000000' ? null : color ;
737
673
this . _markToolbarClicked ( ) ;
674
+ this . _editor . focus ( ) ;
738
675
this . _editor . format ( 'color' , this . _colorValue , SOURCE . USER ) ;
739
676
this . _toolbar . style . setProperty ( '--_color-value' , this . _colorValue ) ;
740
677
this . _colorEditing = false ;
@@ -750,15 +687,19 @@ export const RichTextEditorMixin = (superClass) =>
750
687
const color = event . detail . color ;
751
688
this . _backgroundValue = color === '#ffffff' ? null : color ;
752
689
this . _markToolbarClicked ( ) ;
690
+ this . _editor . focus ( ) ;
753
691
this . _editor . format ( 'background' , this . _backgroundValue , SOURCE . USER ) ;
754
692
this . _toolbar . style . setProperty ( '--_background-value' , this . _backgroundValue ) ;
755
693
this . _backgroundEditing = false ;
756
694
}
757
695
758
696
/** @private */
759
697
__updateHtmlValue ( ) {
760
- const editor = this . shadowRoot . querySelector ( '.ql-editor' ) ;
761
- let content = editor . innerHTML ;
698
+ // We have to use this instead of `innerHTML` to get correct tags like `<pre>` etc.
699
+ let content = this . _editor . getSemanticHTML ( ) ;
700
+
701
+ // TODO there are some issues e.g. `spellcheck="false"` not preserved
702
+ // See https://github.com/slab/quill/issues/4289
762
703
763
704
// Remove Quill classes, e.g. ql-syntax, except for align
764
705
content = content . replace ( / c l a s s = " ( [ ^ " ] * ) " / gu, ( _match , group1 ) => {
@@ -827,7 +768,7 @@ export const RichTextEditorMixin = (superClass) =>
827
768
htmlValue = htmlValue . replaceAll ( / > [ ^ < ] * < / gu, ( match ) => match . replaceAll ( character , replacement ) ) ; // NOSONAR
828
769
} ) ;
829
770
830
- const deltaFromHtml = this . _editor . clipboard . convert ( htmlValue ) ;
771
+ const deltaFromHtml = this . _editor . clipboard . convert ( { html : htmlValue } ) ;
831
772
832
773
// Restore whitespace characters after the conversion
833
774
Object . entries ( whitespaceCharacters ) . forEach ( ( [ character , replacement ] ) => {
0 commit comments