eda96af3897322a35de54f51c913b5a695865530542b607b4cb02e799903903ee1ea5a5bd7e93b9bc87ada00c064cdc58be062197b5188dcf3da3bbb554bd0 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import Attributor from '../attributor/attributor.js';
  2. import AttributorStore from '../attributor/store.js';
  3. import Scope from '../scope.js';
  4. import type {
  5. Blot,
  6. BlotConstructor,
  7. Formattable,
  8. Parent,
  9. Root,
  10. } from './abstract/blot.js';
  11. import LeafBlot from './abstract/leaf.js';
  12. import ParentBlot from './abstract/parent.js';
  13. // Shallow object comparison
  14. function isEqual(
  15. obj1: Record<string, unknown>,
  16. obj2: Record<string, unknown>,
  17. ): boolean {
  18. if (Object.keys(obj1).length !== Object.keys(obj2).length) {
  19. return false;
  20. }
  21. for (const prop in obj1) {
  22. if (obj1[prop] !== obj2[prop]) {
  23. return false;
  24. }
  25. }
  26. return true;
  27. }
  28. class InlineBlot extends ParentBlot implements Formattable {
  29. public static allowedChildren: BlotConstructor[] = [InlineBlot, LeafBlot];
  30. public static blotName = 'inline';
  31. public static scope = Scope.INLINE_BLOT;
  32. public static tagName: string | string[] = 'SPAN';
  33. static create(value?: unknown) {
  34. return super.create(value) as HTMLElement;
  35. }
  36. public static formats(domNode: HTMLElement, scroll: Root): any {
  37. const match = scroll.query(InlineBlot.blotName);
  38. if (
  39. match != null &&
  40. domNode.tagName === (match as BlotConstructor).tagName
  41. ) {
  42. return undefined;
  43. } else if (typeof this.tagName === 'string') {
  44. return true;
  45. } else if (Array.isArray(this.tagName)) {
  46. return domNode.tagName.toLowerCase();
  47. }
  48. return undefined;
  49. }
  50. protected attributes: AttributorStore;
  51. constructor(scroll: Root, domNode: Node) {
  52. super(scroll, domNode);
  53. this.attributes = new AttributorStore(this.domNode);
  54. }
  55. public format(name: string, value: any): void {
  56. if (name === this.statics.blotName && !value) {
  57. this.children.forEach((child) => {
  58. if (!(child instanceof InlineBlot)) {
  59. child = child.wrap(InlineBlot.blotName, true);
  60. }
  61. this.attributes.copy(child as InlineBlot);
  62. });
  63. this.unwrap();
  64. } else {
  65. const format = this.scroll.query(name, Scope.INLINE);
  66. if (format == null) {
  67. return;
  68. }
  69. if (format instanceof Attributor) {
  70. this.attributes.attribute(format, value);
  71. } else if (
  72. value &&
  73. (name !== this.statics.blotName || this.formats()[name] !== value)
  74. ) {
  75. this.replaceWith(name, value);
  76. }
  77. }
  78. }
  79. public formats(): { [index: string]: any } {
  80. const formats = this.attributes.values();
  81. const format = this.statics.formats(this.domNode, this.scroll);
  82. if (format != null) {
  83. formats[this.statics.blotName] = format;
  84. }
  85. return formats;
  86. }
  87. public formatAt(
  88. index: number,
  89. length: number,
  90. name: string,
  91. value: any,
  92. ): void {
  93. if (
  94. this.formats()[name] != null ||
  95. this.scroll.query(name, Scope.ATTRIBUTE)
  96. ) {
  97. const blot = this.isolate(index, length) as InlineBlot;
  98. blot.format(name, value);
  99. } else {
  100. super.formatAt(index, length, name, value);
  101. }
  102. }
  103. public optimize(context: { [key: string]: any }): void {
  104. super.optimize(context);
  105. const formats = this.formats();
  106. if (Object.keys(formats).length === 0) {
  107. return this.unwrap(); // unformatted span
  108. }
  109. const next = this.next;
  110. if (
  111. next instanceof InlineBlot &&
  112. next.prev === this &&
  113. isEqual(formats, next.formats())
  114. ) {
  115. next.moveChildren(this);
  116. next.remove();
  117. }
  118. }
  119. public replaceWith(name: string | Blot, value?: any): Blot {
  120. const replacement = super.replaceWith(name, value) as InlineBlot;
  121. this.attributes.copy(replacement);
  122. return replacement;
  123. }
  124. public update(
  125. mutations: MutationRecord[],
  126. context: { [key: string]: any },
  127. ): void {
  128. super.update(mutations, context);
  129. const attributeChanged = mutations.some(
  130. (mutation) =>
  131. mutation.target === this.domNode && mutation.type === 'attributes',
  132. );
  133. if (attributeChanged) {
  134. this.attributes.build();
  135. }
  136. }
  137. public wrap(name: string | Parent, value?: any): Parent {
  138. const wrapper = super.wrap(name, value);
  139. if (wrapper instanceof InlineBlot) {
  140. this.attributes.move(wrapper);
  141. }
  142. return wrapper;
  143. }
  144. }
  145. export default InlineBlot;