contextualtoolbar.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* globals window, document, console:false */
  6. import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classic';
  7. import DomEventObserver from '@ckeditor/ckeditor5-engine/src/view/observer/domeventobserver';
  8. import Enter from '@ckeditor/ckeditor5-enter/src/enter';
  9. import Typing from '@ckeditor/ckeditor5-typing/src/typing';
  10. import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
  11. import Undo from '@ckeditor/ckeditor5-undo/src/undo';
  12. import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
  13. import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
  14. import Template from '../../../src/template';
  15. import ToolbarView from '../../../src/toolbar/toolbarview';
  16. import BalloonPanelView from '../../../src/balloonpanel/balloonpanelview';
  17. const arrowVOffset = BalloonPanelView.arrowVerticalOffset;
  18. const positions = {
  19. // [text range]
  20. // ^
  21. // +-----------------+
  22. // | Balloon |
  23. // +-----------------+
  24. forwardSelection: ( targetRect, balloonRect ) => ( {
  25. top: targetRect.bottom + arrowVOffset,
  26. left: targetRect.right - balloonRect.width / 2,
  27. name: 's'
  28. } ),
  29. // +-----------------+
  30. // | Balloon |
  31. // +-----------------+
  32. // V
  33. // [text range]
  34. backwardSelection: ( targetRect, balloonRect ) => ( {
  35. top: targetRect.top - balloonRect.height - arrowVOffset,
  36. left: targetRect.left - balloonRect.width / 2,
  37. name: 'n'
  38. } )
  39. };
  40. ClassicEditor.create( document.querySelector( '#editor' ), {
  41. plugins: [ Enter, Typing, Paragraph, Undo, Bold, Italic ],
  42. toolbar: [ 'bold', 'italic', 'undo', 'redo' ]
  43. } )
  44. .then( editor => {
  45. createContextualToolbar( editor );
  46. window.editor = editor;
  47. } )
  48. .catch( err => {
  49. console.error( err.stack );
  50. } );
  51. function createContextualToolbar( editor ) {
  52. // Create a plain toolbar instance.
  53. const toolbar = new ToolbarView();
  54. // Create a BalloonPanelView instance.
  55. const panel = new BalloonPanelView( editor.locale );
  56. Template.extend( panel.template, {
  57. attributes: {
  58. class: [
  59. 'ck-toolbar__container',
  60. ]
  61. }
  62. } );
  63. // Putting the toolbar inside of the balloon panel.
  64. panel.content.add( toolbar );
  65. editor.ui.view.body.add( panel ).then( () => {
  66. const editingView = editor.editing.view;
  67. // Fill the toolbar with some buttons. Simply copy default editor toolbar.
  68. for ( let name of editor.config.get( 'toolbar' ) ) {
  69. toolbar.items.add( editor.ui.componentFactory.create( name ) );
  70. }
  71. // Let the focusTracker know about new focusable UI element.
  72. editor.ui.focusTracker.add( panel.element );
  73. // Hide the panel when editor loses focus but no the other way around.
  74. panel.listenTo( editor.ui.focusTracker, 'change:isFocused', ( evt, name, is, was ) => {
  75. if ( was && !is ) {
  76. panel.hide();
  77. }
  78. } );
  79. // Add "mouseup" event observer. It's enought to use ClickObserver in Chrome
  80. // but Firefox requires "mouseup" to work properly.
  81. editingView.addObserver( class extends DomEventObserver {
  82. get domEventType() {
  83. return [ 'mouseup' ];
  84. }
  85. onDomEvent( domEvent ) {
  86. this.fire( domEvent.type, domEvent );
  87. }
  88. } );
  89. // Position the panel each time the user clicked in editable.
  90. editor.listenTo( editingView, 'mouseup', () => {
  91. // This implementation assumes that only non–collapsed selections gets the contextual toolbar.
  92. if ( !editingView.selection.isCollapsed ) {
  93. const isBackward = editingView.selection.isBackward;
  94. // getBoundingClientRect() makes no sense when the selection spans across number
  95. // of lines of text. Using getClientRects() allows us to browse micro–ranges
  96. // that would normally make up the bounding client rect.
  97. const rangeRects = editingView.domConverter.viewRangeToDom( editingView.selection.getFirstRange() ).getClientRects();
  98. // Select the proper range rect depending on the direction of the selection.
  99. const rangeRect = isBackward ? rangeRects.item( 0 ) : rangeRects.item( rangeRects.length - 1 );
  100. panel.attachTo( {
  101. target: rangeRect,
  102. positions: [
  103. positions[ isBackward ? 'backwardSelection' : 'forwardSelection' ]
  104. ]
  105. } );
  106. } else {
  107. panel.hide();
  108. }
  109. } );
  110. } );
  111. }