bootstrap-ui-inner.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* globals $, window, console:false */
  6. // Basic classes to create an editor.
  7. import Editor from '@ckeditor/ckeditor5-core/src/editor/editor';
  8. import EditorUIView from '@ckeditor/ckeditor5-ui/src/editorui/editoruiview';
  9. import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
  10. import ComponentFactory from '@ckeditor/ckeditor5-ui/src/componentfactory';
  11. import InlineEditableUIView from '@ckeditor/ckeditor5-ui/src/editableui/inline/inlineeditableuiview';
  12. import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor';
  13. import ElementReplacer from '@ckeditor/ckeditor5-utils/src/elementreplacer';
  14. // Interfaces to extend basic Editor API.
  15. import DataApiMixin from '@ckeditor/ckeditor5-core/src/editor/utils/dataapimixin';
  16. import ElementApiMixin from '@ckeditor/ckeditor5-core/src/editor/utils/elementapimixin';
  17. // Helper function for adding interfaces to the Editor class.
  18. import mix from '@ckeditor/ckeditor5-utils/src/mix';
  19. // Helper function that binds editor with HTMLForm element.
  20. import attachToForm from '@ckeditor/ckeditor5-core/src/editor/utils/attachtoform';
  21. // Basic features that every editor should enable.
  22. import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
  23. import Enter from '@ckeditor/ckeditor5-enter/src/enter';
  24. import Typing from '@ckeditor/ckeditor5-typing/src/typing';
  25. import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
  26. import UndoEditing from '@ckeditor/ckeditor5-undo/src/undoediting';
  27. // Basic features to associated with the edited content.
  28. import BoldEditing from '@ckeditor/ckeditor5-basic-styles/src/bold/boldediting';
  29. import ItalicEditing from '@ckeditor/ckeditor5-basic-styles/src/italic/italicediting';
  30. import UnderlineEditing from '@ckeditor/ckeditor5-basic-styles/src/underline/underlineediting';
  31. import HeadingEditing from '@ckeditor/ckeditor5-heading/src/headingediting';
  32. // The easy image integration.
  33. import EasyImage from '@ckeditor/ckeditor5-easy-image/src/easyimage';
  34. import { TOKEN_URL } from '@ckeditor/ckeditor5-cloudservices/tests/_utils/cloudservices-config';
  35. // Extending the Editor class, which brings base editor API.
  36. export default class BootstrapEditor extends Editor {
  37. constructor( element, config ) {
  38. super( config );
  39. // Remember the element the editor is created with.
  40. this.element = element;
  41. // Use the HTML data processor in this editor.
  42. this.data.processor = new HtmlDataProcessor();
  43. // Create the ("main") root element of the model tree.
  44. this.model.document.createRoot();
  45. // The UI layer of the editor.
  46. this.ui = new BootstrapEditorUI( this );
  47. // When editor#element is a textarea inside a form element
  48. // then content of this textarea will be updated on form submit.
  49. attachToForm( this );
  50. // A helper to easily replace the editor#element with editor.editable#element.
  51. this._elementReplacer = new ElementReplacer();
  52. }
  53. destroy() {
  54. // When destroyed, editor sets the output of editor#getData() into editor#element...
  55. this.updateElement();
  56. // ...and restores the original editor#element...
  57. this._elementReplacer.restore();
  58. // ...and destroys the UI.
  59. this.ui.destroy();
  60. return super.destroy();
  61. }
  62. static create( element, config ) {
  63. return new Promise( resolve => {
  64. const editor = new this( element, config );
  65. const editable = editor.ui.view.editable;
  66. resolve(
  67. editor.initPlugins()
  68. .then( () => {
  69. // Initialize the UI first. See the BootstrapEditorUI class to learn more.
  70. editor.ui.init();
  71. // Replace the editor#element with editor.editable#element.
  72. editor._elementReplacer.replace( element, editable.element );
  73. // Tell the world that the UI of the editor is ready to use.
  74. editor.fire( 'uiReady' );
  75. } )
  76. // Bind the editor editing layer to the editable in DOM.
  77. .then( () => editor.editing.view.attachDomRoot( editable.element ) )
  78. // Fill the editable with the intial data.
  79. .then( () => editor.loadDataFromElement() )
  80. // Fire the events that announce that the editor is complete and ready to use.
  81. .then( () => {
  82. editor.fire( 'dataReady' );
  83. editor.fire( 'ready' );
  84. } )
  85. .then( () => editor )
  86. );
  87. } );
  88. }
  89. }
  90. // Mixing interfaces, which extends basic editor API.
  91. mix( BootstrapEditor, DataApiMixin );
  92. mix( BootstrapEditor, ElementApiMixin );
  93. // The class organizing the UI of the editor, binding it with existing Bootstrap elements in DOM.
  94. class BootstrapEditorUI {
  95. constructor( editor ) {
  96. this.editor = editor;
  97. // The global UI view of the editor. It aggregates various Bootstrap DOM elements.
  98. const view = this.view = new EditorUIView( editor.locale );
  99. // This is the main editor element in DOM.
  100. view.element = $( '.ck-editor' );
  101. // This is the editable view in DOM. It will replace the data container in DOM.
  102. view.editable = new InlineEditableUIView( editor.locale );
  103. // References to the dropdown elements for further usage. See #_setupBootstrapHeadingDropdown.
  104. view.dropdownMenu = view.element.find( '.dropdown-menu' );
  105. view.dropdownToggle = view.element.find( '.dropdown-toggle' );
  106. // References to the toolbar buttons for further usage. See #_setupBootstrapToolbarButtons.
  107. view.toolbarButtons = {};
  108. [ 'bold', 'italic', 'underline', 'undo', 'redo' ].forEach( name => {
  109. // Retrieve the jQuery object corresponding with the button in DOM.
  110. view.toolbarButtons[ name ] = view.element.find( `#${ name }` );
  111. } );
  112. // Mandatory EditorUI interface components.
  113. this.componentFactory = new ComponentFactory( editor );
  114. this.focusTracker = new FocusTracker();
  115. }
  116. init() {
  117. const editor = this.editor;
  118. const view = this.view;
  119. // Render the editable component in DOM first.
  120. view.editable.render();
  121. // Create an editing root in the editing layer. It will correspond with the
  122. // document root created in the constructor().
  123. const editingRoot = editor.editing.view.document.getRoot();
  124. // Bind the basic attributes of the editable in DOM with the editing layer.
  125. view.editable.bind( 'isReadOnly' ).to( editingRoot );
  126. view.editable.bind( 'isFocused' ).to( editor.editing.view.document );
  127. view.editable.name = editingRoot.rootName;
  128. // Setup the existing, external Bootstrap UI so it works with the rest of the editor.
  129. this._setupBootstrapToolbarButtons();
  130. this._setupBootstrapHeadingDropdown();
  131. }
  132. destroy() {
  133. this.view.editable.destroy();
  134. }
  135. // This method activates Bold, Italic, Underline, Undo and Redo buttons in the toolbar.
  136. _setupBootstrapToolbarButtons() {
  137. const editor = this.editor;
  138. for ( const name in this.view.toolbarButtons ) {
  139. // Retrieve the editor command corresponding with the id of the button in DOM.
  140. const command = editor.commands.get( name );
  141. const button = this.view.toolbarButtons[ name ];
  142. // Clicking on the buttons should execute the editor command...
  143. button.click( () => editor.execute( name ) );
  144. // ...but it should not steal the focus so the editing is uninterrupted.
  145. button.mousedown( evt => evt.preventDefault() );
  146. const onValueChange = () => {
  147. button.toggleClass( 'active', command.value );
  148. };
  149. const onIsEnabledChange = () => {
  150. button.attr( 'disabled', () => !command.isEnabled );
  151. };
  152. // Commands can become disabled, e.g. when the editor is read-only.
  153. // Make sure the buttons reflect this state change.
  154. command.on( 'change:isEnabled', onIsEnabledChange );
  155. onIsEnabledChange();
  156. // Bold, Italic and Underline commands have a value that changes
  157. // when the selection starts in an element the command creates.
  158. // The button should indicate that e.g. editing text which is already bold.
  159. if ( !new Set( [ 'undo', 'redo' ] ).has( name ) ) {
  160. command.on( 'change:value', onValueChange );
  161. onValueChange();
  162. }
  163. }
  164. }
  165. // This method activates the headings dropdown in the toolbar.
  166. _setupBootstrapHeadingDropdown() {
  167. const editor = this.editor;
  168. const dropdownMenu = this.view.dropdownMenu;
  169. const dropdownToggle = this.view.dropdownToggle;
  170. // Create a dropdown menu entry for each heading configuration option.
  171. editor.config.get( 'heading.options' ).map( option => {
  172. // Retrieve the editor command corresponding with the configuration option.
  173. const command = editor.commands.get( option.model );
  174. // Create the menu item DOM element.
  175. const menuItem = $(
  176. `<a href="#" class="dropdown-item heading-item_${ option.model }">` +
  177. `${ option.title }` +
  178. '</a>' );
  179. // Upon click, the dropdown menua item should execute the command and focus
  180. // the editing view to keep the editing process uninterrupted.
  181. menuItem.click( () => {
  182. editor.execute( option.model );
  183. editor.editing.view.focus();
  184. } );
  185. dropdownMenu.append( menuItem );
  186. // Make sure the dropdown and its items reflect the state of the
  187. // currently active command.
  188. command.on( 'change:value', onValueChange );
  189. onValueChange();
  190. // Heading commands can become disabled, e.g. when the editor is read-only.
  191. // Make sure the UI reflects this state change.
  192. command.on( 'change:isEnabled', onIsEnabledChange );
  193. onIsEnabledChange();
  194. function onValueChange() {
  195. if ( command.value ) {
  196. dropdownToggle.children( ':first' ).text( option.title );
  197. }
  198. menuItem.toggleClass( 'active', command.value );
  199. }
  200. function onIsEnabledChange() {
  201. dropdownToggle.attr( 'disabled', () => !command.isEnabled );
  202. }
  203. } );
  204. }
  205. }
  206. // Finally, create the BootstrapEditor instance with a selected set of features.
  207. BootstrapEditor
  208. .create( $( '#editor' ).get( 0 ), {
  209. plugins: [
  210. Clipboard, Enter, Typing, Paragraph, EasyImage,
  211. BoldEditing, ItalicEditing, UnderlineEditing, HeadingEditing, UndoEditing,
  212. ],
  213. cloudServices: {
  214. tokenUrl: TOKEN_URL
  215. }
  216. } )
  217. .then( editor => {
  218. window.editor = editor;
  219. $( '#toggle-readonly' ).on( 'click', () => {
  220. editor.isReadOnly = !editor.isReadOnly;
  221. } );
  222. } )
  223. .catch( err => {
  224. console.error( err.stack );
  225. } );