headingcommand.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /**
  6. * @module heading/headingcommand
  7. */
  8. import Command from '@ckeditor/ckeditor5-core/src/command';
  9. import first from '@ckeditor/ckeditor5-utils/src/first';
  10. /**
  11. * The heading command. It is used by the {@link module:heading/heading~Heading heading feature} to apply headings.
  12. *
  13. * @extends module:core/command~Command
  14. */
  15. export default class HeadingCommand extends Command {
  16. /**
  17. * Creates an instance of the command.
  18. *
  19. * @param {module:core/editor/editor~Editor} editor Editor instance.
  20. * @param {Array.<String>} modelElements Names of the element which this command can apply in the model.
  21. */
  22. constructor( editor, modelElements ) {
  23. super( editor );
  24. /**
  25. * If the selection starts in a heading (which {@link #modelElements is supported by this command})
  26. * the value is set to the name of that heading model element.
  27. * It is set to `false` otherwise.
  28. *
  29. * @observable
  30. * @readonly
  31. * @member {Boolean|String} #value
  32. */
  33. /**
  34. * Set of defined model's elements names that this command support.
  35. * See {@link module:heading/heading~HeadingOption}.
  36. *
  37. * @readonly
  38. * @member {Array.<String>}
  39. */
  40. this.modelElements = modelElements;
  41. }
  42. /**
  43. * @inheritDoc
  44. */
  45. refresh() {
  46. const block = first( this.editor.model.document.selection.getSelectedBlocks() );
  47. this.value = !!block && this.modelElements.includes( block.name ) && block.name;
  48. this.isEnabled = !!block && this.modelElements.some( heading => checkCanBecomeHeading( block, heading, this.editor.model.schema ) );
  49. }
  50. /**
  51. * Executes the command. Applies the heading to the selected blocks or, if the first selected
  52. * block is a heading already, turns selected headings (of this level only) to paragraphs.
  53. *
  54. * @param {Object} options
  55. * @param {String} options.value Name of the element which this command will apply in the model.
  56. * @fires execute
  57. */
  58. execute( options ) {
  59. const model = this.editor.model;
  60. const document = model.document;
  61. const modelElement = options.value;
  62. model.change( writer => {
  63. const blocks = Array.from( document.selection.getSelectedBlocks() )
  64. .filter( block => {
  65. return checkCanBecomeHeading( block, modelElement, model.schema );
  66. } );
  67. for ( const block of blocks ) {
  68. if ( !block.is( 'element', modelElement ) ) {
  69. writer.rename( block, modelElement );
  70. }
  71. }
  72. } );
  73. }
  74. }
  75. // Checks whether the given block can be replaced by a specific heading.
  76. //
  77. // @private
  78. // @param {module:engine/model/element~Element} block A block to be tested.
  79. // @param {module:heading/headingcommand~HeadingCommand#modelElement} heading Command element name in the model.
  80. // @param {module:engine/model/schema~Schema} schema The schema of the document.
  81. // @returns {Boolean}
  82. function checkCanBecomeHeading( block, heading, schema ) {
  83. return schema.checkChild( block.parent, heading ) && !schema.isObject( block );
  84. }