8
0

headingengine.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module heading/headingengine
  7. */
  8. import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
  9. import buildModelConverter from '@ckeditor/ckeditor5-engine/src/conversion/buildmodelconverter';
  10. import buildViewConverter from '@ckeditor/ckeditor5-engine/src/conversion/buildviewconverter';
  11. import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
  12. import HeadingCommand from './headingcommand';
  13. const defaultModelElement = 'paragraph';
  14. /**
  15. * The headings engine feature. It handles switching between block formats – headings and paragraph.
  16. * This class represents the engine part of the heading feature. See also {@link module:heading/heading~Heading}.
  17. *
  18. * @extends modules:core/plugin~Plugin
  19. */
  20. export default class HeadingEngine extends Plugin {
  21. /**
  22. * @inheritDoc
  23. */
  24. constructor( editor ) {
  25. super( editor );
  26. // TODO: This needs proper documentation, i.e. why paragraph entry does not need
  27. // more properties (https://github.com/ckeditor/ckeditor5/issues/403).
  28. // TODO: Document CSS classes as well.
  29. editor.config.define( 'heading', {
  30. options: [
  31. { modelElement: 'paragraph', title: 'Paragraph', class: 'ck-heading_paragraph' },
  32. { modelElement: 'heading1', viewElement: 'h2', title: 'Heading 1', class: 'ck-heading_heading1' },
  33. { modelElement: 'heading2', viewElement: 'h3', title: 'Heading 2', class: 'ck-heading_heading2' },
  34. { modelElement: 'heading3', viewElement: 'h4', title: 'Heading 3', class: 'ck-heading_heading3' }
  35. ]
  36. } );
  37. }
  38. /**
  39. * @inheritDoc
  40. */
  41. static get requires() {
  42. return [ Paragraph ];
  43. }
  44. /**
  45. * @inheritDoc
  46. */
  47. init() {
  48. const editor = this.editor;
  49. const data = editor.data;
  50. const editing = editor.editing;
  51. const options = editor.config.get( 'heading.options' );
  52. for ( let option of options ) {
  53. // Skip paragraph - it is defined in required Paragraph feature.
  54. if ( option.modelElement !== defaultModelElement ) {
  55. // Schema.
  56. editor.document.schema.registerItem( option.modelElement, '$block' );
  57. // Build converter from model to view for data and editing pipelines.
  58. buildModelConverter().for( data.modelToView, editing.modelToView )
  59. .fromElement( option.modelElement )
  60. .toElement( option.viewElement );
  61. // Build converter from view to model for data pipeline.
  62. buildViewConverter().for( data.viewToModel )
  63. .fromElement( option.viewElement )
  64. .toElement( option.modelElement );
  65. // Register the heading command for this option.
  66. editor.commands.set( option.modelElement, new HeadingCommand( editor, option ) );
  67. }
  68. }
  69. }
  70. /**
  71. * @inheritDoc
  72. */
  73. afterInit() {
  74. // If the enter command is added to the editor, alter its behavior.
  75. // Enter at the end of a heading element should create a paragraph.
  76. const editor = this.editor;
  77. const enterCommand = editor.commands.get( 'enter' );
  78. const options = editor.config.get( 'heading.options' );
  79. if ( enterCommand ) {
  80. this.listenTo( enterCommand, 'afterExecute', ( evt, data ) => {
  81. const positionParent = editor.document.selection.getFirstPosition().parent;
  82. const batch = data.batch;
  83. const isHeading = options.some( option => positionParent.is( option.modelElement ) );
  84. if ( isHeading && !positionParent.is( defaultModelElement ) && positionParent.childCount === 0 ) {
  85. batch.rename( positionParent, defaultModelElement );
  86. }
  87. } );
  88. }
  89. }
  90. }