table-cell-refresh-post-fixer.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  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/converters/table-cell-refresh-post-fixer
  7. */
  8. /**
  9. * Injects a table cell post-fixer into the model which marks the table cell in the differ to have it re-rendered.
  10. *
  11. * Model `paragraph` inside a table cell can be rendered as `<span>` or `<p>`. It is rendered as `<span>` if this is the only block
  12. * element in that table cell and it does not have any attributes. It is rendered as `<p>` otherwise.
  13. *
  14. * When table cell content changes, for example a second `paragraph` element is added, we need to ensure that the first `paragraph` is
  15. * re-rendered so it changes from `<span>` to `<p>`. The easiest way to do it is to re-render the entire table cell.
  16. *
  17. * @param {module:engine/model/model~Model} model
  18. */
  19. export default function injectTableCellRefreshPostFixer( model ) {
  20. model.document.registerPostFixer( () => tableCellRefreshPostFixer( model ) );
  21. }
  22. function tableCellRefreshPostFixer( model ) {
  23. const differ = model.document.differ;
  24. const changesForCells = new Map();
  25. // 1. Gather all changes inside table cell.
  26. differ.getChanges().forEach( change => {
  27. const parent = change.type == 'attribute' ? change.range.start.parent : change.position.parent;
  28. if ( !parent.is( 'element', 'tableCell' ) ) {
  29. return;
  30. }
  31. if ( !changesForCells.has( parent ) ) {
  32. changesForCells.set( parent, [] );
  33. }
  34. changesForCells.get( parent ).push( change );
  35. } );
  36. // Stores cells to be refreshed, so the table cell will be refreshed once for multiple changes.
  37. const cellsToRefresh = new Set();
  38. // 2. For each table cell:
  39. for ( const [ tableCell, changes ] of changesForCells.entries() ) {
  40. // 2a. Count inserts/removes as diff and marks any attribute change.
  41. const { childDiff, attribute } = getChangesSummary( changes );
  42. // 2b. If we detect that number of children has changed...
  43. if ( childDiff !== 0 ) {
  44. const currentChildren = tableCell.childCount;
  45. const prevChildren = currentChildren - childDiff;
  46. // Might need refresh if previous children was different from 1. Eg.: it was 2 before, now is 1.
  47. if ( currentChildren === 1 && prevChildren !== 1 ) {
  48. cellsToRefresh.add( tableCell );
  49. }
  50. // Might need refresh if previous children was 1. Eg.: it was 1 before, now is 5.
  51. if ( currentChildren !== 1 && prevChildren === 1 ) {
  52. cellsToRefresh.add( tableCell );
  53. }
  54. }
  55. // ... 2c or some attribute has changed.
  56. if ( attribute ) {
  57. cellsToRefresh.add( tableCell );
  58. }
  59. }
  60. // Having cells to refresh we need to
  61. if ( cellsToRefresh.size ) {
  62. // @if CK_DEBUG_TABLE // console.log( `Post-fixing table: Checking table cell to refresh (${ cellsToRefresh.size }).` );
  63. // @if CK_DEBUG_TABLE // let paragraphsRefreshed = 0;
  64. for ( const tableCell of cellsToRefresh.values() ) {
  65. for ( const paragraph of [ ...tableCell.getChildren() ].filter( child => child.is( 'element', 'paragraph' ) ) ) {
  66. // @if CK_DEBUG_TABLE // console.log( `Post-fixing table: refreshing paragraph in table cell (${++paragraphsRefreshed}).` );
  67. differ.refreshItem( paragraph );
  68. }
  69. }
  70. }
  71. // Always return false to prevent the refresh post-fixer from re-running on the same set of changes and going into an infinite loop.
  72. // See https://github.com/ckeditor/ckeditor5/issues/1936.
  73. return false;
  74. }
  75. function updateSummaryFromChange( summary, change ) {
  76. if ( change.type === 'remove' ) {
  77. summary.childDiff--;
  78. }
  79. if ( change.type === 'insert' ) {
  80. summary.childDiff++;
  81. }
  82. if ( change.type === 'attribute' ) {
  83. summary.attribute = true;
  84. }
  85. return summary;
  86. }
  87. function getChangesSummary( changes ) {
  88. return changes.reduce( updateSummaryFromChange, { childDiff: 0, attribute: false } );
  89. }