8
0

codeblockediting.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. /**
  2. * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /**
  6. * @module code-block/codeblockediting
  7. */
  8. import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
  9. import ShiftEnter from '@ckeditor/ckeditor5-enter/src/shiftenter';
  10. import CodeBlockCommand from './codeblockcommand';
  11. /**
  12. * The editing part of the code block feature.
  13. *
  14. * Introduces the `'codeBlock'` command and the `'codeBlock'` model element.
  15. *
  16. * @extends module:core/plugin~Plugin
  17. */
  18. export default class CodeBlockEditing extends Plugin {
  19. /**
  20. * @inheritDoc
  21. */
  22. static get pluginName() {
  23. return 'CodeBlockEditing';
  24. }
  25. /**
  26. * @inheritDoc
  27. */
  28. static get requires() {
  29. return [ ShiftEnter ];
  30. }
  31. /**
  32. * @inheritDoc
  33. */
  34. init() {
  35. const editor = this.editor;
  36. const schema = editor.model.schema;
  37. // Command.
  38. editor.commands.add( 'codeBlock', new CodeBlockCommand( editor ) );
  39. // Schema.
  40. schema.register( 'codeBlock', { inheritAllFrom: '$block' } );
  41. // Disallow codeBlock in codeBlock.
  42. schema.addChildCheck( ( context, childDef ) => {
  43. if ( context.endsWith( 'codeBlock' ) && childDef.name === 'codeBlock' ) {
  44. return false;
  45. }
  46. } );
  47. // Disallow all attributes in `codeBlock`.
  48. schema.addAttributeCheck( context => {
  49. if ( context.endsWith( 'codeBlock' ) || context.endsWith( 'codeBlock $text' ) ) {
  50. return false;
  51. }
  52. } );
  53. // Conversion.
  54. editor.conversion.for( 'editingDowncast' ).elementToElement( { model: 'codeBlock', view: 'pre' } );
  55. editor.conversion.for( 'dataDowncast' ).elementToElement( { model: 'codeBlock', view: 'pre' } );
  56. editor.data.downcastDispatcher.on( 'insert:softBreak', dataModelViewSoftBreakInsertion( editor.model ), { priority: 'high' } );
  57. editor.data.upcastDispatcher.on( 'element:pre', dataViewModelPreInsertion( editor.data ) );
  58. }
  59. /**
  60. * @inheritDoc
  61. */
  62. afterInit() {
  63. const editor = this.editor;
  64. const viewDoc = editor.editing.view.document;
  65. this.listenTo( viewDoc, 'enter', ( evt, data ) => {
  66. const doc = editor.model.document;
  67. const positionParent = doc.selection.getLastPosition().parent;
  68. if ( positionParent.is( 'codeBlock' ) ) {
  69. editor.execute( 'shiftEnter' );
  70. data.preventDefault();
  71. evt.stop();
  72. }
  73. } );
  74. }
  75. }
  76. // A model-to-view converter for the new line separator.
  77. //
  78. // @param {module:engine/model/model~Model} model
  79. // @returns {Function} Returns a conversion callback.
  80. function dataModelViewSoftBreakInsertion( model ) {
  81. return ( evt, data, conversionApi ) => {
  82. if ( data.item.parent.name !== 'codeBlock' ) {
  83. return;
  84. }
  85. const { writer, mapper, consumable } = conversionApi;
  86. if ( !consumable.consume( data.item, 'insert' ) ) {
  87. return;
  88. }
  89. const position = mapper.toViewPosition( model.createPositionBefore( data.item ) );
  90. writer.insert( position, writer.createText( '\n' ) );
  91. };
  92. }
  93. // A view-to-model converter for `pre` tag.
  94. //
  95. // @param {module:engine/controller/datacontroller~DataController} dataController
  96. // @returns {Function} Returns a conversion callback.
  97. function dataViewModelPreInsertion( dataController ) {
  98. return ( evt, data, conversionApi ) => {
  99. const { consumable, writer } = conversionApi;
  100. if ( !consumable.consume( data.viewItem, { name: true } ) ) {
  101. return;
  102. }
  103. const modelItem = writer.createElement( 'codeBlock' );
  104. const stringifiedElement = dataController.processor.toData( data.viewItem );
  105. const textData = new RegExp( /^<pre>(.*)<\/pre>$/, 's' ).exec( stringifiedElement )[ 1 ];
  106. const textLines = textData.split( '\n' ).map( data => writer.createText( data ) );
  107. const lastLine = textLines[ textLines.length - 1 ];
  108. for ( const node of textLines ) {
  109. writer.append( node, modelItem );
  110. if ( node !== lastLine ) {
  111. writer.appendElement( 'softBreak', modelItem );
  112. }
  113. }
  114. writer.insert( modelItem, data.modelCursor );
  115. data.modelCursor = writer.createPositionAfter( modelItem );
  116. data.modelRange = writer.createRange( data.modelCursor );
  117. };
  118. }