24644632d83e60f981f8e813a3dc3a3a405c1996a2c1635f60ddf5489bb8329cfef550d23c38f1e953315eebb42c906ae2163557e02da9686e65b6e7b1d0ca 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import ParchmentError from '../../error.js';
  2. import Registry from '../../registry.js';
  3. import Scope from '../../scope.js';
  4. import type {
  5. Blot,
  6. BlotConstructor,
  7. Formattable,
  8. Parent,
  9. Root,
  10. } from './blot.js';
  11. class ShadowBlot implements Blot {
  12. public static blotName = 'abstract';
  13. public static className: string;
  14. public static requiredContainer: BlotConstructor;
  15. public static scope: Scope;
  16. public static tagName: string | string[];
  17. public static create(rawValue?: unknown): Node {
  18. if (this.tagName == null) {
  19. throw new ParchmentError('Blot definition missing tagName');
  20. }
  21. let node: HTMLElement;
  22. let value: string | number | undefined;
  23. if (Array.isArray(this.tagName)) {
  24. if (typeof rawValue === 'string') {
  25. value = rawValue.toUpperCase();
  26. if (parseInt(value, 10).toString() === value) {
  27. value = parseInt(value, 10);
  28. }
  29. } else if (typeof rawValue === 'number') {
  30. value = rawValue;
  31. }
  32. if (typeof value === 'number') {
  33. node = document.createElement(this.tagName[value - 1]);
  34. } else if (value && this.tagName.indexOf(value) > -1) {
  35. node = document.createElement(value);
  36. } else {
  37. node = document.createElement(this.tagName[0]);
  38. }
  39. } else {
  40. node = document.createElement(this.tagName);
  41. }
  42. if (this.className) {
  43. node.classList.add(this.className);
  44. }
  45. return node;
  46. }
  47. public prev: Blot | null;
  48. public next: Blot | null;
  49. // @ts-expect-error Fix me later
  50. public parent: Parent;
  51. // Hack for accessing inherited static methods
  52. get statics(): any {
  53. return this.constructor;
  54. }
  55. constructor(
  56. public scroll: Root,
  57. public domNode: Node,
  58. ) {
  59. Registry.blots.set(domNode, this);
  60. this.prev = null;
  61. this.next = null;
  62. }
  63. public attach(): void {
  64. // Nothing to do
  65. }
  66. public clone(): Blot {
  67. const domNode = this.domNode.cloneNode(false);
  68. return this.scroll.create(domNode);
  69. }
  70. public detach(): void {
  71. if (this.parent != null) {
  72. this.parent.removeChild(this);
  73. }
  74. Registry.blots.delete(this.domNode);
  75. }
  76. public deleteAt(index: number, length: number): void {
  77. const blot = this.isolate(index, length);
  78. blot.remove();
  79. }
  80. public formatAt(
  81. index: number,
  82. length: number,
  83. name: string,
  84. value: any,
  85. ): void {
  86. const blot = this.isolate(index, length);
  87. if (this.scroll.query(name, Scope.BLOT) != null && value) {
  88. blot.wrap(name, value);
  89. } else if (this.scroll.query(name, Scope.ATTRIBUTE) != null) {
  90. const parent = this.scroll.create(this.statics.scope) as Parent &
  91. Formattable;
  92. blot.wrap(parent);
  93. parent.format(name, value);
  94. }
  95. }
  96. public insertAt(index: number, value: string, def?: any): void {
  97. const blot =
  98. def == null
  99. ? this.scroll.create('text', value)
  100. : this.scroll.create(value, def);
  101. const ref = this.split(index);
  102. this.parent.insertBefore(blot, ref || undefined);
  103. }
  104. public isolate(index: number, length: number): Blot {
  105. const target = this.split(index);
  106. if (target == null) {
  107. throw new Error('Attempt to isolate at end');
  108. }
  109. target.split(length);
  110. return target;
  111. }
  112. public length(): number {
  113. return 1;
  114. }
  115. public offset(root: Blot = this.parent): number {
  116. if (this.parent == null || this === root) {
  117. return 0;
  118. }
  119. return this.parent.children.offset(this) + this.parent.offset(root);
  120. }
  121. public optimize(_context?: { [key: string]: any }): void {
  122. if (
  123. this.statics.requiredContainer &&
  124. !(this.parent instanceof this.statics.requiredContainer)
  125. ) {
  126. this.wrap(this.statics.requiredContainer.blotName);
  127. }
  128. }
  129. public remove(): void {
  130. if (this.domNode.parentNode != null) {
  131. this.domNode.parentNode.removeChild(this.domNode);
  132. }
  133. this.detach();
  134. }
  135. public replaceWith(name: string | Blot, value?: any): Blot {
  136. const replacement =
  137. typeof name === 'string' ? this.scroll.create(name, value) : name;
  138. if (this.parent != null) {
  139. this.parent.insertBefore(replacement, this.next || undefined);
  140. this.remove();
  141. }
  142. return replacement;
  143. }
  144. public split(index: number, _force?: boolean): Blot | null {
  145. return index === 0 ? this : this.next;
  146. }
  147. public update(
  148. _mutations: MutationRecord[],
  149. _context: { [key: string]: any },
  150. ): void {
  151. // Nothing to do by default
  152. }
  153. public wrap(name: string | Parent, value?: any): Parent {
  154. const wrapper =
  155. typeof name === 'string'
  156. ? (this.scroll.create(name, value) as Parent)
  157. : name;
  158. if (this.parent != null) {
  159. this.parent.insertBefore(wrapper, this.next || undefined);
  160. }
  161. if (typeof wrapper.appendChild !== 'function') {
  162. throw new ParchmentError(`Cannot wrap ${name}`);
  163. }
  164. wrapper.appendChild(this);
  165. return wrapper;
  166. }
  167. }
  168. export default ShadowBlot;