b2c0382b9c7ffee21ad5d4712cc72ee0ba4872000ed95c5cda91a0a49721e98e6181f86b7b49b3821fc7ce6c1055d5be482842103fb28ca8fd8bdfa523e8b5 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import Attributor from './attributor/attributor.js';
  2. import {
  3. type Blot,
  4. type BlotConstructor,
  5. type Root,
  6. } from './blot/abstract/blot.js';
  7. import ParchmentError from './error.js';
  8. import Scope from './scope.js';
  9. export type RegistryDefinition = Attributor | BlotConstructor;
  10. export interface RegistryInterface {
  11. create(scroll: Root, input: Node | string | Scope, value?: any): Blot;
  12. query(query: string | Node | Scope, scope: Scope): RegistryDefinition | null;
  13. register(...definitions: any[]): any;
  14. }
  15. export default class Registry implements RegistryInterface {
  16. public static blots = new WeakMap<Node, Blot>();
  17. public static find(node?: Node | null, bubble = false): Blot | null {
  18. if (node == null) {
  19. return null;
  20. }
  21. if (this.blots.has(node)) {
  22. return this.blots.get(node) || null;
  23. }
  24. if (bubble) {
  25. let parentNode: Node | null = null;
  26. try {
  27. parentNode = node.parentNode;
  28. } catch (err) {
  29. // Probably hit a permission denied error.
  30. // A known case is in Firefox, event targets can be anonymous DIVs
  31. // inside an input element.
  32. // https://bugzilla.mozilla.org/show_bug.cgi?id=208427
  33. return null;
  34. }
  35. return this.find(parentNode, bubble);
  36. }
  37. return null;
  38. }
  39. private attributes: { [key: string]: Attributor } = {};
  40. private classes: { [key: string]: BlotConstructor } = {};
  41. private tags: { [key: string]: BlotConstructor } = {};
  42. private types: { [key: string]: RegistryDefinition } = {};
  43. public create(scroll: Root, input: Node | string | Scope, value?: any): Blot {
  44. const match = this.query(input);
  45. if (match == null) {
  46. throw new ParchmentError(`Unable to create ${input} blot`);
  47. }
  48. const blotClass = match as BlotConstructor;
  49. const node =
  50. // @ts-expect-error Fix me later
  51. input instanceof Node || input.nodeType === Node.TEXT_NODE
  52. ? input
  53. : blotClass.create(value);
  54. const blot = new blotClass(scroll, node as Node, value);
  55. Registry.blots.set(blot.domNode, blot);
  56. return blot;
  57. }
  58. public find(node: Node | null, bubble = false): Blot | null {
  59. return Registry.find(node, bubble);
  60. }
  61. public query(
  62. query: string | Node | Scope,
  63. scope: Scope = Scope.ANY,
  64. ): RegistryDefinition | null {
  65. let match;
  66. if (typeof query === 'string') {
  67. match = this.types[query] || this.attributes[query];
  68. // @ts-expect-error Fix me later
  69. } else if (query instanceof Text || query.nodeType === Node.TEXT_NODE) {
  70. match = this.types.text;
  71. } else if (typeof query === 'number') {
  72. if (query & Scope.LEVEL & Scope.BLOCK) {
  73. match = this.types.block;
  74. } else if (query & Scope.LEVEL & Scope.INLINE) {
  75. match = this.types.inline;
  76. }
  77. } else if (query instanceof Element) {
  78. const names = (query.getAttribute('class') || '').split(/\s+/);
  79. names.some((name) => {
  80. match = this.classes[name];
  81. if (match) {
  82. return true;
  83. }
  84. return false;
  85. });
  86. match = match || this.tags[query.tagName];
  87. }
  88. if (match == null) {
  89. return null;
  90. }
  91. if (
  92. 'scope' in match &&
  93. scope & Scope.LEVEL & match.scope &&
  94. scope & Scope.TYPE & match.scope
  95. ) {
  96. return match;
  97. }
  98. return null;
  99. }
  100. public register(...definitions: RegistryDefinition[]): RegistryDefinition[] {
  101. return definitions.map((definition) => {
  102. const isBlot = 'blotName' in definition;
  103. const isAttr = 'attrName' in definition;
  104. if (!isBlot && !isAttr) {
  105. throw new ParchmentError('Invalid definition');
  106. } else if (isBlot && definition.blotName === 'abstract') {
  107. throw new ParchmentError('Cannot register abstract class');
  108. }
  109. const key = isBlot
  110. ? definition.blotName
  111. : isAttr
  112. ? definition.attrName
  113. : (undefined as never); // already handled by above checks
  114. this.types[key] = definition;
  115. if (isAttr) {
  116. if (typeof definition.keyName === 'string') {
  117. this.attributes[definition.keyName] = definition;
  118. }
  119. } else if (isBlot) {
  120. if (definition.className) {
  121. this.classes[definition.className] = definition;
  122. }
  123. if (definition.tagName) {
  124. if (Array.isArray(definition.tagName)) {
  125. definition.tagName = definition.tagName.map((tagName: string) => {
  126. return tagName.toUpperCase();
  127. });
  128. } else {
  129. definition.tagName = definition.tagName.toUpperCase();
  130. }
  131. const tagNames = Array.isArray(definition.tagName)
  132. ? definition.tagName
  133. : [definition.tagName];
  134. tagNames.forEach((tag: string) => {
  135. if (this.tags[tag] == null || definition.className == null) {
  136. this.tags[tag] = definition;
  137. }
  138. });
  139. }
  140. }
  141. return definition;
  142. });
  143. }
  144. }