model-selection-to-view-converters.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import ViewRange from '../view/range';
  6. /**
  7. * Contains {@link module:engine/model/selection~Selection model selection} to
  8. * {@link module:engine/view/selection~Selection view selection} converters for
  9. * {@link module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher}.
  10. *
  11. * @module engine/conversion/model-selection-to-view-converters
  12. */
  13. /**
  14. * Function factory, creates a converter that converts non-collapsed {@link module:engine/model/selection~Selection model selection} to
  15. * {@link module:engine/view/selection~Selection view selection}. The converter consumes appropriate value from `consumable` object
  16. * and maps model positions from selection to view positions.
  17. *
  18. * modelDispatcher.on( 'selection', convertRangeSelection() );
  19. *
  20. * @returns {Function} Selection converter.
  21. */
  22. export function convertRangeSelection() {
  23. return ( evt, data, consumable, conversionApi ) => {
  24. const selection = data.selection;
  25. if ( selection.isCollapsed ) {
  26. return;
  27. }
  28. if ( !consumable.consume( selection, 'selection' ) ) {
  29. return;
  30. }
  31. conversionApi.viewSelection.removeAllRanges();
  32. for ( const range of selection.getRanges() ) {
  33. const viewRange = conversionApi.mapper.toViewRange( range );
  34. conversionApi.viewSelection.addRange( viewRange, selection.isBackward );
  35. }
  36. };
  37. }
  38. /**
  39. * Function factory, creates a converter that converts collapsed {@link module:engine/model/selection~Selection model selection} to
  40. * {@link module:engine/view/selection~Selection view selection}. The converter consumes appropriate value from `consumable` object,
  41. * maps model selection position to view position and breaks {@link module:engine/view/attributeelement~AttributeElement attribute elements}
  42. * at the selection position.
  43. *
  44. * modelDispatcher.on( 'selection', convertCollapsedSelection() );
  45. *
  46. * Example of view state before and after converting collapsed selection:
  47. *
  48. * <p><strong>f^oo<strong>bar</p>
  49. * -> <p><strong>f</strong>^<strong>oo</strong>bar</p>
  50. *
  51. * By breaking attribute elements like `<strong>`, selection is in correct element. Then, when selection attribute is
  52. * converted, the broken attributes might be merged again, or the position where the selection is may be wrapped
  53. * in different, appropriate attribute elements.
  54. *
  55. * See also {@link module:engine/conversion/model-selection-to-view-converters~clearAttributes} which does a clean-up
  56. * by merging attributes.
  57. *
  58. * @returns {Function} Selection converter.
  59. */
  60. export function convertCollapsedSelection() {
  61. return ( evt, data, consumable, conversionApi ) => {
  62. const selection = data.selection;
  63. if ( !selection.isCollapsed ) {
  64. return;
  65. }
  66. if ( !consumable.consume( selection, 'selection' ) ) {
  67. return;
  68. }
  69. const modelPosition = selection.getFirstPosition();
  70. const viewPosition = conversionApi.mapper.toViewPosition( modelPosition );
  71. const brokenPosition = conversionApi.writer.breakAttributes( viewPosition );
  72. conversionApi.viewSelection.removeAllRanges();
  73. conversionApi.viewSelection.addRange( new ViewRange( brokenPosition, brokenPosition ) );
  74. };
  75. }
  76. /**
  77. * Function factory, creates a converter that clears artifacts after the previous
  78. * {@link module:engine/model/selection~Selection model selection} conversion. It removes all empty
  79. * {@link module:engine/view/attributeelement~AttributeElement view attribute elements} and merge sibling attributes at all start and end
  80. * positions of all ranges.
  81. *
  82. * <p><strong>^</strong></p>
  83. * -> <p>^</p>
  84. *
  85. * <p><strong>foo</strong>^<strong>bar</strong>bar</p>
  86. * -> <p><strong>foo^bar<strong>bar</p>
  87. *
  88. * <p><strong>foo</strong><em>^</em><strong>bar</strong>bar</p>
  89. * -> <p><strong>foo^bar<strong>bar</p>
  90. *
  91. * This listener should be assigned before any converter for the new selection:
  92. *
  93. * modelDispatcher.on( 'selection', clearAttributes() );
  94. *
  95. * See {@link module:engine/conversion/model-selection-to-view-converters~convertCollapsedSelection}
  96. * which do the opposite by breaking attributes in the selection position.
  97. *
  98. * @returns {Function} Selection converter.
  99. */
  100. export function clearAttributes() {
  101. return ( evt, data, consumable, conversionApi ) => {
  102. for ( const range of conversionApi.viewSelection.getRanges() ) {
  103. // Not collapsed selection should not have artifacts.
  104. if ( range.isCollapsed ) {
  105. // Position might be in the node removed by the view writer.
  106. if ( range.end.parent.document ) {
  107. conversionApi.writer.mergeAttributes( range.start );
  108. }
  109. }
  110. }
  111. conversionApi.viewSelection.removeAllRanges();
  112. };
  113. }
  114. /**
  115. * Function factory, creates a converter that clears fake selection marking after the previous
  116. * {@link module:engine/model/selection~Selection model selection} conversion.
  117. */
  118. export function clearFakeSelection() {
  119. return ( evt, data, consumable, conversionApi ) => conversionApi.viewSelection.setFake( false );
  120. }