contextualtoolbar.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  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. toolbar.fillFromConfig( editor.config.get( 'toolbar' ), editor.ui.componentFactory );
  69. // Let the focusTracker know about new focusable UI element.
  70. editor.ui.focusTracker.add( panel.element );
  71. // Hide the panel when editor loses focus but no the other way around.
  72. panel.listenTo( editor.ui.focusTracker, 'change:isFocused', ( evt, name, is, was ) => {
  73. if ( was && !is ) {
  74. panel.hide();
  75. }
  76. } );
  77. // Add "mouseup" event observer. It's enought to use ClickObserver in Chrome
  78. // but Firefox requires "mouseup" to work properly.
  79. editingView.addObserver( class extends DomEventObserver {
  80. get domEventType() {
  81. return [ 'mouseup' ];
  82. }
  83. onDomEvent( domEvent ) {
  84. this.fire( domEvent.type, domEvent );
  85. }
  86. } );
  87. // Position the panel each time the user clicked in editable.
  88. editor.listenTo( editingView, 'mouseup', () => {
  89. // This implementation assumes that only non–collapsed selections gets the contextual toolbar.
  90. if ( !editingView.selection.isCollapsed ) {
  91. const isBackward = editingView.selection.isBackward;
  92. // getBoundingClientRect() makes no sense when the selection spans across number
  93. // of lines of text. Using getClientRects() allows us to browse micro–ranges
  94. // that would normally make up the bounding client rect.
  95. const rangeRects = editingView.domConverter.viewRangeToDom( editingView.selection.getFirstRange() ).getClientRects();
  96. // Select the proper range rect depending on the direction of the selection.
  97. const rangeRect = isBackward ? rangeRects.item( 0 ) : rangeRects.item( rangeRects.length - 1 );
  98. panel.attachTo( {
  99. target: rangeRect,
  100. positions: [
  101. positions[ isBackward ? 'backwardSelection' : 'forwardSelection' ]
  102. ]
  103. } );
  104. } else {
  105. panel.hide();
  106. }
  107. } );
  108. } );
  109. }