fakeselection.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* globals console:false, document, window */
  6. import ClassicEditor from '/ckeditor5/editor-classic/classic.js';
  7. import testUtils from '/tests/utils/_utils/utils.js';
  8. import Feature from '/ckeditor5/core/feature.js';
  9. import buildModelConverter from '/ckeditor5/engine/conversion/buildmodelconverter.js';
  10. import buildViewConverter from '/ckeditor5/engine/conversion/buildviewconverter.js';
  11. import ViewContainerElement from '/ckeditor5/engine/view/containerelement.js';
  12. import ViewRange from '/ckeditor5/engine/view/range.js';
  13. import ModelElement from '/ckeditor5/engine/model/element.js';
  14. import ModelRange from '/ckeditor5/engine/model/range.js';
  15. import ModelLiveRange from '/ckeditor5/engine/model/liverange.js';
  16. import ClickObserver from '/ckeditor5/engine/view/observer/clickobserver.js';
  17. let editor, editable, observer;
  18. class Test extends Feature {
  19. /**
  20. * @inheritDoc
  21. */
  22. init() {
  23. const editor = this.editor;
  24. const data = editor.data;
  25. const editing = editor.editing;
  26. const view = editing.view;
  27. const document = editor.document;
  28. const selected = new Set();
  29. // Handle clicks on view elements.
  30. view.addObserver( ClickObserver );
  31. editor.editing.view.on( 'click', ( eventInfo, domEventData ) => {
  32. const target = domEventData.target;
  33. if ( target.name == 'span' ) {
  34. const viewRange = ViewRange.createOn( target );
  35. const modelRange = ModelLiveRange.createFromRange( editing.mapper.toModelRange( viewRange ) );
  36. // Put selection around span element.
  37. // TODO: Do we need this inside enq changes? Batch needed?
  38. document.enqueueChanges( ( ) => {
  39. document.selection.setRanges( [ modelRange ] );
  40. } );
  41. }
  42. } );
  43. // Handle selection conversion.
  44. editing.modelToView.on( 'selection', ( evt, data, consumable, conversionApi ) => {
  45. const viewSelection = conversionApi.viewSelection;
  46. const range = data.selection.getFirstRange();
  47. const nodeAfterStart = range.start.nodeAfter;
  48. // remove selection from all selected widgets
  49. for ( let viewElement of selected ) {
  50. viewElement.setStyle( 'border', '2px solid yellow' );
  51. }
  52. // This could be just one element instead of set.
  53. selected.clear();
  54. if ( !data.selection.isCollapsed && nodeAfterStart && nodeAfterStart.name == 'span' && ModelRange.createOn( nodeAfterStart ).isEqual( range ) ) {
  55. viewSelection.setFake( true, { label: 'span fake selection' } );
  56. const viewElement = conversionApi.mapper.toViewElement( nodeAfterStart );
  57. viewElement.setStyle( 'border', '2px solid red' );
  58. selected.add( viewElement );
  59. }
  60. }, { priority: 'low' } );
  61. // Allow bold attribute on all inline nodes.
  62. // document.schema.allow( { name: '$inline', attributes: [ 'span' ] } );
  63. document.schema.registerItem( 'span', '$inline' );
  64. document.schema.allow( { name: 'span', inside: '$block' } );
  65. // Build converter from model to view for data and editing pipelines.
  66. buildModelConverter().for( data.modelToView, editing.modelToView )
  67. .fromElement( 'span' )
  68. // .toElement( new ViewContainerElement( 'span' ) );
  69. .toElement( () => {
  70. const element = new ViewContainerElement( 'span', {
  71. contenteditable: 'false',
  72. style: 'background-color: yellow; color: black; border: 2px solid yellow;'
  73. } );
  74. return widgetize( element );
  75. } );
  76. // Build converter from view to model for data pipeline.
  77. buildViewConverter().for( data.viewToModel )
  78. .fromElement( 'span' )
  79. .toElement( 'span' );
  80. }
  81. }
  82. class BasicImage extends Feature {
  83. /**
  84. * @inheritDoc
  85. */
  86. init() {
  87. const editor = this.editor;
  88. const data = editor.data;
  89. const editing = editor.editing;
  90. const view = editing.view;
  91. const document = editor.document;
  92. const selected = new Set();
  93. // Handle clicks on view elements.
  94. view.addObserver( ClickObserver );
  95. editor.editing.view.on( 'click', ( eventInfo, domEventData ) => {
  96. const target = domEventData.target;
  97. if ( target.name == 'img' ) {
  98. const viewRange = ViewRange.createOn( target );
  99. const modelRange = ModelLiveRange.createFromRange( editing.mapper.toModelRange( viewRange ) );
  100. document.enqueueChanges( ( ) => {
  101. document.selection.setRanges( [ modelRange ] );
  102. } );
  103. }
  104. } );
  105. // Handle selection conversion.
  106. editing.modelToView.on( 'selection', ( evt, data, consumable, conversionApi ) => {
  107. const viewSelection = conversionApi.viewSelection;
  108. const range = data.selection.getFirstRange();
  109. const nodeAfterStart = range.start.nodeAfter;
  110. // remove selection from all selected widgets
  111. for ( let viewElement of selected ) {
  112. viewElement.setStyle( 'border', '2px solid yellow' );
  113. }
  114. // This could be just one element instead of set.
  115. selected.clear();
  116. if ( !data.selection.isCollapsed && nodeAfterStart && nodeAfterStart.name == 'image' && ModelRange.createOn( nodeAfterStart ).isEqual( range ) ) {
  117. viewSelection.setFake( true, { label: 'image fake selection' } );
  118. const viewElement = conversionApi.mapper.toViewElement( nodeAfterStart );
  119. viewElement.setStyle( 'border', '2px solid red' );
  120. selected.add( viewElement );
  121. }
  122. }, { priority: 'low' } );
  123. // Allow bold attribute on all inline nodes.
  124. // document.schema.allow( { name: '$inline', attributes: [ 'span' ] } );
  125. document.schema.registerItem( 'image', '$block' );
  126. document.schema.allow( { name: 'image', attributes: [ 'src', 'alt' ] } );
  127. // Build converter from model to view for data and editing pipelines.
  128. buildModelConverter().for( data.modelToView, editing.modelToView )
  129. .fromElement( 'image' )
  130. .toElement( ( data ) => {
  131. return widgetize( new ViewContainerElement( 'img', {
  132. src: data.item.getAttribute( 'src' ),
  133. alt: data.item.getAttribute( 'alt' ),
  134. style: 'border: 2px solid yellow'
  135. } ) );
  136. } );
  137. // Build converter from view to model for data pipeline.
  138. buildViewConverter().for( data.viewToModel )
  139. .fromElement( 'img' ).consuming( { name: true, attributes: [ 'src', 'alt' ] } )
  140. .toElement( ( viewElement ) => new ModelElement( 'image', {
  141. src: viewElement.getAttribute( 'src' ),
  142. alt: viewElement.getAttribute( 'alt' )
  143. } ) );
  144. }
  145. }
  146. function initEditor() {
  147. ClassicEditor.create( document.querySelector( '#editor' ), {
  148. features: [ 'enter', 'typing', 'paragraph', 'undo', 'heading', Test, BasicImage ],
  149. toolbar: [ 'headings' ]
  150. } )
  151. .then( newEditor => {
  152. window.editor = editor = newEditor;
  153. window.editable = editable = editor.editing.view.getRoot();
  154. observer = testUtils.createObserver();
  155. observer.observe( 'Editable', editable, [ 'isFocused' ] );
  156. } )
  157. .catch( err => {
  158. console.error( err.stack );
  159. } );
  160. }
  161. initEditor();
  162. function widgetize( element ) {
  163. element.isWidget = true;
  164. return element;
  165. }