attributeelement.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  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/element~Element
  26. */
  27. constructor( name, attrs, children ) {
  28. super( name, attrs, children );
  29. /**
  30. * Element priority. Attributes have to have the same priority to be
  31. * {@link module:engine/view/element~Element#isSimilar similar}. Setting different priorities on similar
  32. * nodes may prevent merging, e.g. two `<abbr>` nodes next each other shouldn't be merged.
  33. *
  34. * @protected
  35. * @member {Number}
  36. */
  37. this._priority = DEFAULT_PRIORITY;
  38. /**
  39. * Returns block {@link module:engine/view/filler filler} offset or `null` if block filler is not needed.
  40. *
  41. * @method #getFillerOffset
  42. * @returns {Number|null} Block filler offset or `null` if block filler is not needed.
  43. */
  44. this.getFillerOffset = getFillerOffset;
  45. }
  46. /**
  47. * Priority of this element.
  48. *
  49. * @readonly
  50. * @return {Number}
  51. */
  52. get priority() {
  53. return this._priority;
  54. }
  55. /**
  56. * @inheritDoc
  57. */
  58. is( type, name = null ) {
  59. if ( !name ) {
  60. return type == 'attributeElement' || super.is( type );
  61. } else {
  62. return ( type == 'attributeElement' && name == this.name ) || super.is( type, name );
  63. }
  64. }
  65. /**
  66. * Clones provided element with priority.
  67. *
  68. * @param {Boolean} deep If set to `true` clones element and all its children recursively. When set to `false`,
  69. * element will be cloned without any children.
  70. * @returns {module:engine/view/attributeelement~AttributeElement} Clone of this element.
  71. */
  72. clone( deep ) {
  73. const cloned = super.clone( deep );
  74. // Clone priority too.
  75. cloned._priority = this._priority;
  76. return cloned;
  77. }
  78. /**
  79. * Checks if this element is similar to other element.
  80. * Both elements should have the same name, attributes and priority to be considered as similar.
  81. * Two similar elements can contain different set of children nodes.
  82. *
  83. * @param {module:engine/view/element~Element} otherElement
  84. * @returns {Boolean}
  85. */
  86. isSimilar( otherElement ) {
  87. return super.isSimilar( otherElement ) && this.priority == otherElement.priority;
  88. }
  89. }
  90. /**
  91. * Default attribute priority.
  92. *
  93. * @member {Number} module:engine/view/attributeelement~AttributeElement.DEFAULT_PRIORITY
  94. */
  95. AttributeElement.DEFAULT_PRIORITY = DEFAULT_PRIORITY;
  96. // Returns block {@link module:engine/view/filler~Filler filler} offset or `null` if block filler is not needed.
  97. //
  98. // @returns {Number|null} Block filler offset or `null` if block filler is not needed.
  99. function getFillerOffset() {
  100. // <b>foo</b> does not need filler.
  101. if ( nonUiChildrenCount( this ) ) {
  102. return null;
  103. }
  104. let element = this.parent;
  105. // <p><b></b></p> needs filler -> <p><b><br></b></p>
  106. while ( element && element.is( 'attributeElement' ) ) {
  107. if ( nonUiChildrenCount( element ) > 1 ) {
  108. return null;
  109. }
  110. element = element.parent;
  111. }
  112. if ( !element || nonUiChildrenCount( element ) > 1 ) {
  113. return null;
  114. }
  115. // Render block filler at the end of element (after all ui elements).
  116. return this.childCount;
  117. }
  118. // Returns total count of children that are not {@link module:engine/view/uielement~UIElement UIElements}.
  119. //
  120. // @param {module:engine/view/element~Element} element
  121. // @return {Number}
  122. function nonUiChildrenCount( element ) {
  123. return Array.from( element.getChildren() ).filter( element => !element.is( 'uiElement' ) ).length;
  124. }