8
0

utils.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  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 table/utils
  7. */
  8. import { isWidget, toWidget } from '@ckeditor/ckeditor5-widget/src/utils';
  9. import { findAncestor } from './commands/utils';
  10. import TableWalker from './tablewalker';
  11. /**
  12. * Converts a given {@link module:engine/view/element~Element} to a table widget:
  13. * * Adds a {@link module:engine/view/element~Element#_setCustomProperty custom property} allowing to recognize the table widget element.
  14. * * Calls the {@link module:widget/utils~toWidget} function with the proper element's label creator.
  15. *
  16. * @param {module:engine/view/element~Element} viewElement
  17. * @param {module:engine/view/downcastwriter~DowncastWriter} writer An instance of the view writer.
  18. * @param {String} label The element's label. It will be concatenated with the table `alt` attribute if one is present.
  19. * @returns {module:engine/view/element~Element}
  20. */
  21. export function toTableWidget( viewElement, writer ) {
  22. writer.setCustomProperty( 'table', true, viewElement );
  23. return toWidget( viewElement, writer, { hasSelectionHandle: true } );
  24. }
  25. /**
  26. * Checks if a given view element is a table widget.
  27. *
  28. * @param {module:engine/view/element~Element} viewElement
  29. * @returns {Boolean}
  30. */
  31. export function isTableWidget( viewElement ) {
  32. return !!viewElement.getCustomProperty( 'table' ) && isWidget( viewElement );
  33. }
  34. /**
  35. * Returns a table widget editing view element if one is selected.
  36. *
  37. * @param {module:engine/view/selection~Selection|module:engine/view/documentselection~DocumentSelection} selection
  38. * @returns {module:engine/view/element~Element|null}
  39. */
  40. export function getSelectedTableWidget( selection ) {
  41. const viewElement = selection.getSelectedElement();
  42. if ( viewElement && isTableWidget( viewElement ) ) {
  43. return viewElement;
  44. }
  45. return null;
  46. }
  47. /**
  48. * Returns a table widget editing view element if one is among the selection's ancestors.
  49. *
  50. * @param {module:engine/view/selection~Selection|module:engine/view/documentselection~DocumentSelection} selection
  51. * @returns {module:engine/view/element~Element|null}
  52. */
  53. export function getTableWidgetAncestor( selection ) {
  54. const parentTable = findAncestor( 'table', selection.getFirstPosition() );
  55. if ( parentTable && isTableWidget( parentTable.parent ) ) {
  56. return parentTable.parent;
  57. }
  58. return null;
  59. }
  60. /**
  61. * Returns all model table cells that are fully selected (from the outside)
  62. * within the provided model selection's ranges.
  63. *
  64. * To obtain the cells selected from the inside, use
  65. * {@link module:table/utils~getTableCellsContainingSelection}.
  66. *
  67. * @param {module:engine/model/selection~Selection} selection
  68. * @returns {Array.<module:engine/model/element~Element>}
  69. */
  70. export function getSelectedTableCells( selection ) {
  71. const cells = [];
  72. for ( const range of sortRanges( selection.getRanges() ) ) {
  73. const element = range.getContainedElement();
  74. if ( element && element.is( 'tableCell' ) ) {
  75. cells.push( element );
  76. }
  77. }
  78. return cells;
  79. }
  80. /**
  81. * Returns all model table cells that the provided model selection's ranges
  82. * {@link module:engine/model/range~Range#start} inside.
  83. *
  84. * To obtain the cells selected from the outside, use
  85. * {@link module:table/utils~getSelectedTableCells}.
  86. *
  87. * @param {module:engine/model/selection~Selection} selection
  88. * @returns {Array.<module:engine/model/element~Element>}
  89. */
  90. export function getTableCellsContainingSelection( selection ) {
  91. const cells = [];
  92. for ( const range of selection.getRanges() ) {
  93. const cellWithSelection = findAncestor( 'tableCell', range.start );
  94. if ( cellWithSelection ) {
  95. cells.push( cellWithSelection );
  96. }
  97. }
  98. return cells;
  99. }
  100. /**
  101. * Returns all model table cells that are either completely selected
  102. * by selection ranges or host selection range
  103. * {@link module:engine/model/range~Range#start start positions} inside them.
  104. *
  105. * Combines {@link module:table/utils~getTableCellsContainingSelection} and
  106. * {@link module:table/utils~getSelectedTableCells}.
  107. *
  108. * @param {module:engine/model/selection~Selection} selection
  109. * @returns {Array.<module:engine/model/element~Element>}
  110. */
  111. export function getSelectionAffectedTableCells( selection ) {
  112. const selectedCells = getSelectedTableCells( selection );
  113. if ( selectedCells.length ) {
  114. return selectedCells;
  115. }
  116. return getTableCellsContainingSelection( selection );
  117. }
  118. /**
  119. * Returns an object with `first` and `last` row index contained in the given `tableCells`.
  120. *
  121. * const selectedTableCells = getSelectedTableCells( editor.model.document.selection );
  122. *
  123. * const { first, last } = getRowIndexes( selectedTableCells );
  124. *
  125. * console.log( `Selected rows: ${ first } to ${ last }` );
  126. *
  127. * @param {Array.<module:engine/model/element~Element>} tableCells
  128. * @returns {Object} Returns an object with `first` and `last` table row indexes.
  129. */
  130. export function getRowIndexes( tableCells ) {
  131. const indexes = tableCells.map( cell => cell.parent.index );
  132. return getFirstLastIndexesObject( indexes );
  133. }
  134. /**
  135. * Returns an object with `first` and `last` column index contained in the given `tableCells`.
  136. *
  137. * const selectedTableCells = getSelectedTableCells( editor.model.document.selection );
  138. *
  139. * const { first, last } = getColumnIndexes( selectedTableCells );
  140. *
  141. * console.log( `Selected columns: ${ first } to ${ last }` );
  142. *
  143. * @param {Array.<module:engine/model/element~Element>} tableCells
  144. * @returns {Object} Returns an object with `first` and `last` table column indexes.
  145. */
  146. export function getColumnIndexes( tableCells ) {
  147. const table = findAncestor( 'table', tableCells[ 0 ] );
  148. const tableMap = [ ...new TableWalker( table ) ];
  149. const indexes = tableMap
  150. .filter( entry => tableCells.includes( entry.cell ) )
  151. .map( entry => entry.column );
  152. return getFirstLastIndexesObject( indexes );
  153. }
  154. // Helper method to get an object with `first` and `last` indexes from an unsorted array of indexes.
  155. function getFirstLastIndexesObject( indexes ) {
  156. const allIndexesSorted = indexes.sort( ( indexA, indexB ) => indexA - indexB );
  157. const first = allIndexesSorted[ 0 ];
  158. const last = allIndexesSorted[ allIndexesSorted.length - 1 ];
  159. return { first, last };
  160. }
  161. function sortRanges( rangesIterator ) {
  162. return Array.from( rangesIterator ).sort( compareRangeOrder );
  163. }
  164. function compareRangeOrder( rangeA, rangeB ) {
  165. // Since table cell ranges are disjoint, it's enough to check their start positions.
  166. const posA = rangeA.start;
  167. const posB = rangeB.start;
  168. // Checking for equal position (returning 0) is not needed because this would be either:
  169. // a. Intersecting range (not allowed by model)
  170. // b. Collapsed range on the same position (allowed by model but should not happen).
  171. return posA.isBefore( posB ) ? -1 : 1;
  172. }