attributeelement.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module engine/view/attributeelement
  7. */
  8. import Element from './element';
  9. // Default attribute priority.
  10. const DEFAULT_PRIORITY = 10;
  11. /**
  12. * Attributes are elements which define document presentation. They are mostly elements like `<b>` or `<span>`.
  13. * Attributes can be broken and merged by the {@link module:engine/view/writer~Writer view writer}.
  14. *
  15. * Editing engine does not define fixed HTML DTD. This is why the type of the {@link module:engine/view/element~Element} need to
  16. * be defined by the feature developer. Creating an element you should use {@link module:engine/view/containerelement~ContainerElement}
  17. * class or `AttributeElement`.
  18. *
  19. * @extends module:engine/view/element~Element
  20. */
  21. export default class AttributeElement extends Element {
  22. /**
  23. * Creates a attribute element.
  24. *
  25. * @see module:engine/view/writer~Writer#createAttributeElement
  26. * @protected
  27. * @see module:engine/view/element~Element
  28. */
  29. constructor( name, attrs, children ) {
  30. super( name, attrs, children );
  31. /**
  32. * Element priority. Attributes have to have the same priority to be
  33. * {@link module:engine/view/element~Element#isSimilar similar}. Setting different priorities on similar
  34. * nodes may prevent merging, e.g. two `<abbr>` nodes next each other shouldn't be merged.
  35. *
  36. * @protected
  37. * @member {Number}
  38. */
  39. this._priority = DEFAULT_PRIORITY;
  40. /**
  41. * Returns block {@link module:engine/view/filler filler} offset or `null` if block filler is not needed.
  42. *
  43. * @method #getFillerOffset
  44. * @returns {Number|null} Block filler offset or `null` if block filler is not needed.
  45. */
  46. this.getFillerOffset = getFillerOffset;
  47. }
  48. /**
  49. * Priority of this element.
  50. *
  51. * @readonly
  52. * @return {Number}
  53. */
  54. get priority() {
  55. return this._priority;
  56. }
  57. /**
  58. * @inheritDoc
  59. */
  60. is( type, name = null ) {
  61. if ( !name ) {
  62. return type == 'attributeElement' || super.is( type );
  63. } else {
  64. return ( type == 'attributeElement' && name == this.name ) || super.is( type, name );
  65. }
  66. }
  67. /**
  68. * Checks if this element is similar to other element.
  69. * Both elements should have the same name, attributes and priority to be considered as similar.
  70. * Two similar elements can contain different set of children nodes.
  71. *
  72. * @param {module:engine/view/element~Element} otherElement
  73. * @returns {Boolean}
  74. */
  75. isSimilar( otherElement ) {
  76. return super.isSimilar( otherElement ) && this.priority == otherElement.priority;
  77. }
  78. /**
  79. * Clones provided element with priority.
  80. *
  81. * @protected
  82. * @param {Boolean} deep If set to `true` clones element and all its children recursively. When set to `false`,
  83. * element will be cloned without any children.
  84. * @returns {module:engine/view/attributeelement~AttributeElement} Clone of this element.
  85. */
  86. _clone( deep ) {
  87. const cloned = super._clone( deep );
  88. // Clone priority too.
  89. cloned._priority = this._priority;
  90. return cloned;
  91. }
  92. }
  93. /**
  94. * Default attribute priority.
  95. *
  96. * @member {Number} module:engine/view/attributeelement~AttributeElement.DEFAULT_PRIORITY
  97. */
  98. AttributeElement.DEFAULT_PRIORITY = DEFAULT_PRIORITY;
  99. // Returns block {@link module:engine/view/filler~Filler filler} offset or `null` if block filler is not needed.
  100. //
  101. // @returns {Number|null} Block filler offset or `null` if block filler is not needed.
  102. function getFillerOffset() {
  103. // <b>foo</b> does not need filler.
  104. if ( nonUiChildrenCount( this ) ) {
  105. return null;
  106. }
  107. let element = this.parent;
  108. // <p><b></b></p> needs filler -> <p><b><br></b></p>
  109. while ( element && element.is( 'attributeElement' ) ) {
  110. if ( nonUiChildrenCount( element ) > 1 ) {
  111. return null;
  112. }
  113. element = element.parent;
  114. }
  115. if ( !element || nonUiChildrenCount( element ) > 1 ) {
  116. return null;
  117. }
  118. // Render block filler at the end of element (after all ui elements).
  119. return this.childCount;
  120. }
  121. // Returns total count of children that are not {@link module:engine/view/uielement~UIElement UIElements}.
  122. //
  123. // @param {module:engine/view/element~Element} element
  124. // @return {Number}
  125. function nonUiChildrenCount( element ) {
  126. return Array.from( element.getChildren() ).filter( element => !element.is( 'uiElement' ) ).length;
  127. }