blockquoteediting.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  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 block-quote/blockquoteediting
  7. */
  8. import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
  9. import BlockQuoteCommand from './blockquotecommand';
  10. /**
  11. * The block quote editing.
  12. *
  13. * Introduces the `'blockQuote'` command and the `'blockQuote'` model element.
  14. *
  15. * @extends module:core/plugin~Plugin
  16. */
  17. export default class BlockQuoteEditing extends Plugin {
  18. /**
  19. * @inheritDoc
  20. */
  21. static get pluginName() {
  22. return 'BlockQuoteEditing';
  23. }
  24. /**
  25. * @inheritDoc
  26. */
  27. init() {
  28. const editor = this.editor;
  29. const schema = editor.model.schema;
  30. editor.commands.add( 'blockQuote', new BlockQuoteCommand( editor ) );
  31. schema.register( 'blockQuote', {
  32. allowWhere: '$block',
  33. allowContentOf: '$root'
  34. } );
  35. // Disallow blockQuote in blockQuote.
  36. schema.addChildCheck( ( ctx, childDef ) => {
  37. if ( ctx.endsWith( 'blockQuote' ) && childDef.name == 'blockQuote' ) {
  38. return false;
  39. }
  40. } );
  41. editor.conversion.elementToElement( { model: 'blockQuote', view: 'blockquote' } );
  42. // Postfixer which cleans incorrect model states connected with block quotes.
  43. editor.model.document.registerPostFixer( writer => {
  44. const changes = editor.model.document.differ.getChanges();
  45. for ( const entry of changes ) {
  46. if ( entry.type == 'insert' ) {
  47. const element = entry.position.nodeAfter;
  48. if ( !element ) {
  49. // We are inside a text node.
  50. continue;
  51. }
  52. if ( element.is( 'element', 'blockQuote' ) && element.isEmpty ) {
  53. // Added an empty blockQuote - remove it.
  54. writer.remove( element );
  55. return true;
  56. } else if ( element.is( 'element', 'blockQuote' ) && !schema.checkChild( entry.position, element ) ) {
  57. // Added a blockQuote in incorrect place - most likely inside another blockQuote. Unwrap it
  58. // so the content inside is not lost.
  59. writer.unwrap( element );
  60. return true;
  61. } else if ( element.is( 'element' ) ) {
  62. // Just added an element. Check its children to see if there are no nested blockQuotes somewhere inside.
  63. const range = writer.createRangeIn( element );
  64. for ( const child of range.getItems() ) {
  65. if (
  66. child.is( 'element', 'blockQuote' ) &&
  67. !schema.checkChild( writer.createPositionBefore( child ), child )
  68. ) {
  69. writer.unwrap( child );
  70. return true;
  71. }
  72. }
  73. }
  74. } else if ( entry.type == 'remove' ) {
  75. const parent = entry.position.parent;
  76. if ( parent.is( 'element', 'blockQuote' ) && parent.isEmpty ) {
  77. // Something got removed and now blockQuote is empty. Remove the blockQuote as well.
  78. writer.remove( parent );
  79. return true;
  80. }
  81. }
  82. }
  83. return false;
  84. } );
  85. }
  86. /**
  87. * @inheritDoc
  88. */
  89. afterInit() {
  90. const editor = this.editor;
  91. const command = editor.commands.get( 'blockQuote' );
  92. // Overwrite default Enter key behavior.
  93. // If Enter key is pressed with selection collapsed in empty block inside a quote, break the quote.
  94. // This listener is added in afterInit in order to register it after list's feature listener.
  95. // We can't use a priority for this, because 'low' is already used by the enter feature, unless
  96. // we'd use numeric priority in this case.
  97. this.listenTo( this.editor.editing.view.document, 'enter', ( evt, data ) => {
  98. const doc = this.editor.model.document;
  99. const positionParent = doc.selection.getLastPosition().parent;
  100. if ( doc.selection.isCollapsed && positionParent.isEmpty && command.value ) {
  101. this.editor.execute( 'blockQuote' );
  102. this.editor.editing.view.scrollToTheSelection();
  103. data.preventDefault();
  104. evt.stop();
  105. }
  106. } );
  107. }
  108. }