8
0

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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  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. // Stores cells to be refreshed so the table cell will be refreshed once for multiple changes.
  25. const cellsToRefresh = new Set();
  26. // Counting the paragraph inserts to verify if it increased to more than one paragraph in the current differ.
  27. let insertCount = 0;
  28. for ( const change of differ.getChanges() ) {
  29. const parent = change.type == 'insert' || change.type == 'remove' ? change.position.parent : change.range.start.parent;
  30. if ( !parent.is( 'tableCell' ) ) {
  31. continue;
  32. }
  33. if ( change.type == 'insert' ) {
  34. insertCount++;
  35. }
  36. if ( checkRefresh( parent, change.type, insertCount ) ) {
  37. cellsToRefresh.add( parent );
  38. }
  39. }
  40. if ( cellsToRefresh.size ) {
  41. for ( const tableCell of cellsToRefresh.values() ) {
  42. differ.refreshItem( tableCell );
  43. }
  44. return true;
  45. }
  46. return false;
  47. }
  48. // Checks if the model table cell requires refreshing to be re-rendered to a proper state in the view.
  49. //
  50. // This method detects changes that will require renaming `<span>` to `<p>` (or vice versa) in the view.
  51. //
  52. // This method is a simple heuristic that checks only a single change and will sometimes give a false positive result when multiple changes
  53. // will result in a state that does not require renaming in the view (but will be seen as requiring a refresh).
  54. //
  55. // For instance: A `<span>` should be renamed to `<p>` when adding an attribute to a `<paragraph>`.
  56. // But adding one attribute and removing another one will result in a false positive: the check for an added attribute will see one
  57. // attribute on a paragraph and will falsely qualify such change as adding an attribute to a paragraph without any attribute.
  58. //
  59. // @param {module:engine/model/element~Element} tableCell The table cell to check.
  60. // @param {String} type Type of change.
  61. // @param {Number} insertCount The number of inserts in differ.
  62. function checkRefresh( tableCell, type, insertCount ) {
  63. const hasInnerParagraph = Array.from( tableCell.getChildren() ).some( child => child.is( 'paragraph' ) );
  64. // If there is no paragraph in table cell then the view doesn't require refreshing.
  65. //
  66. // Why? What we really want to achieve is to make all the old paragraphs (which weren't added in this batch) to be
  67. // converted once again, so that the paragraph-in-table-cell converter can correctly create a `<p>` or a `<span>` element.
  68. // If there are no paragraphs in the table cell, we don't care.
  69. if ( !hasInnerParagraph ) {
  70. return false;
  71. }
  72. // For attribute change we only refresh if there is a single paragraph as in this case we may want to change existing `<span>` to `<p>`.
  73. if ( type == 'attribute' ) {
  74. const attributesCount = Array.from( tableCell.getChild( 0 ).getAttributeKeys() ).length;
  75. return tableCell.childCount === 1 && attributesCount < 2;
  76. }
  77. // For other changes (insert, remove) the `<span>` to `<p>` change is needed when:
  78. //
  79. // - another element is added to a single paragraph (childCount becomes >= 2)
  80. // - another element is removed and a single paragraph is left (childCount == 1)
  81. //
  82. // Change is not needed if there were multiple blocks before change.
  83. return tableCell.childCount <= ( type == 'insert' ? insertCount + 1 : 1 );
  84. }