heading.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module heading/heading
  7. */
  8. import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
  9. import HeadingEngine from './headingengine';
  10. import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
  11. import Model from '@ckeditor/ckeditor5-ui/src/model';
  12. import createListDropdown from '@ckeditor/ckeditor5-ui/src/dropdown/list/createlistdropdown';
  13. import Collection from '@ckeditor/ckeditor5-utils/src/collection';
  14. import '../theme/theme.scss';
  15. /**
  16. * The headings feature. It introduces the `headings` drop-down list and the `heading` command which allow
  17. * to convert paragraphs into headings.
  18. *
  19. * @extends module:core/plugin~Plugin
  20. */
  21. export default class Heading extends Plugin {
  22. /**
  23. * @inheritDoc
  24. */
  25. static get requires() {
  26. return [ Paragraph, HeadingEngine ];
  27. }
  28. /**
  29. * @inheritDoc
  30. */
  31. init() {
  32. const editor = this.editor;
  33. const dropdownItems = new Collection();
  34. const options = this._getLocalizedOptions();
  35. const commands = [];
  36. let defaultOption;
  37. for ( let option of options ) {
  38. const command = editor.commands.get( option.modelElement );
  39. const itemModel = new Model( {
  40. commandName: option.modelElement,
  41. label: option.title,
  42. class: option.class
  43. } );
  44. itemModel.bind( 'isActive' ).to( command, 'value' );
  45. // Add the option to the collection.
  46. dropdownItems.add( itemModel );
  47. commands.push( command );
  48. if ( !defaultOption && option.modelElement == 'paragraph' ) {
  49. defaultOption = option;
  50. }
  51. }
  52. // Create dropdown model.
  53. const dropdownModel = new Model( {
  54. withText: true,
  55. items: dropdownItems
  56. } );
  57. dropdownModel.bind( 'isEnabled' ).to(
  58. // Bind to #isEnabled of each command...
  59. ...getCommandsBindingTargets( commands, 'isEnabled' ),
  60. // ...and set it true if any command #isEnabled is true.
  61. ( ...areEnabled ) => areEnabled.some( isEnabled => isEnabled )
  62. );
  63. dropdownModel.bind( 'label' ).to(
  64. // Bind to #value of each command...
  65. ...getCommandsBindingTargets( commands, 'value' ),
  66. // ...and chose the title of the first one which #value is true.
  67. ( ...areActive ) => {
  68. const index = areActive.findIndex( value => value );
  69. // If none of the commands is active, display the first one.
  70. return ( options[ index ] || defaultOption ).title;
  71. }
  72. );
  73. // Register UI component.
  74. editor.ui.componentFactory.add( 'headings', ( locale ) => {
  75. const dropdown = createListDropdown( dropdownModel, locale );
  76. // Execute command when an item from the dropdown is selected.
  77. this.listenTo( dropdown, 'execute', ( evt ) => {
  78. editor.execute( evt.source.commandName );
  79. editor.editing.view.focus();
  80. } );
  81. return dropdown;
  82. } );
  83. }
  84. /**
  85. * Returns heading options as defined in `config.heading.options` but processed to consider
  86. * editor localization, i.e. to display {@link module:heading/headingcommand~HeadingOption}
  87. * in the correct language.
  88. *
  89. * Note: The reason behind this method is that there's no way to use {@link module:utils/locale~Locale#t}
  90. * when the user config is defined because the editor does not exist yet.
  91. *
  92. * @private
  93. * @returns {Array.<module:heading/headingcommand~HeadingOption>}.
  94. */
  95. _getLocalizedOptions() {
  96. const editor = this.editor;
  97. const t = editor.t;
  98. const localizedTitles = {
  99. Paragraph: t( 'Paragraph' ),
  100. 'Heading 1': t( 'Heading 1' ),
  101. 'Heading 2': t( 'Heading 2' ),
  102. 'Heading 3': t( 'Heading 3' )
  103. };
  104. return editor.config.get( 'heading.options' ).map( option => {
  105. const title = localizedTitles[ option.title ];
  106. if ( title && title != option.title ) {
  107. // Clone the option to avoid altering the original `config.heading.options`.
  108. option = Object.assign( {}, option, { title } );
  109. }
  110. return option;
  111. } );
  112. }
  113. }
  114. // Returns an array of binding components for
  115. // {@link module:utils/observablemixin~Observable#bind} from a set of iterable
  116. // commands.
  117. //
  118. // @private
  119. // @param {Iterable.<module:core/command/command~Command>} commands
  120. // @param {String} attribute
  121. // @returns {Array.<String>}
  122. function getCommandsBindingTargets( commands, attribute ) {
  123. return Array.prototype.concat( ...commands.map( c => [ c, attribute ] ) );
  124. }