Schema

对于复杂的 DOM 树,我们需要使用一套规则来约束 DOM 树结构,包括节点嵌套、节点属性、以及一些特定的行为。

我们可以对单个节点制定规则,当然也可以针对三种节点类型mark inline block制定全局规则。card 属于我们一种特殊类型,本质上他们也可以归纳为 inlineblock 类型

如果一个节点不在约束规则中,那么它将会被过滤掉,包括属性、样式,如果你需要这个属性,那么它一定要出现在规则中,否则都不会被保留

设置

单个规则类型:SchemaRule 全局规则类型:SchemaGlobal

一个规则的包含以下属性:

  • name DOM 节点名称,可选值
  • type 类型,mark inline block。在不制定节点名称情况下,将根据 type 设置全局规则。必须值
  • attributes 属性,设置节点属性规则,是一个对象。可选值
  • isVoid 是否是空节点,类似 br、img 等标签,是无法设置子节点的,包括文本。可选值

例子:

  1. //单个节点规则
  2. {
  3. name: 'p',
  4. type: 'block',
  5. },
  6. {
  7. name: 'span',
  8. type: 'mark',
  9. attributes: {
  10. style: {
  11. color: "@color"
  12. }
  13. }
  14. }
  15. //按类型全局规则
  16. {
  17. type: "block",
  18. attributes: {
  19. id: "*"
  20. }
  21. }

块级节点额外规则

在通用规则之外,我们还为块级节点额外定制了两个属性

  1. {
  2. ...
  3. allowIn?: Array<string>;
  4. canMerge?: boolean;
  5. }
  • allowIn 允许节点可以放入的块级节点名称,默认他们的值为 $root(编辑器根节点)。这通常在嵌套节点中使用,例如:ul li 无序列表下有 li 子节点,它也是独占一行的属于块级节点。如果一个块级节点没有指定可放入的块级节点,那么它将会被过滤掉
  • canMerge 相邻的两个块级节点是否可以合并。例如:引用插件 blockquote ,在两个 blockquote 节点处于相邻状态时,它们的子节点会被合并到一个 blockquote 节点下,因为它们相邻单独存在是没有意义的,反而还会增加文档的复杂性

类型:SchemaBlock

attributes 值

属性值类型 SchemaValue,它由 SchemaValueObjectSchemaValueBase 组成

  1. export type SchemaValueBase =
  2. | RegExp
  3. | Array<string>
  4. | string
  5. | ((propValue: string) => boolean)
  6. | '@number'
  7. | '@length'
  8. | '@color'
  9. | '@url'
  10. | '*';
  11. export type SchemaValueObject = {
  12. required: boolean;
  13. value: SchemaValueBase;
  14. };

我们可以看到属性值是可以很灵活配置的,支持:

  • 正则表达式
  • 数组
  • 单个字符
  • 函数自定义验证
  • @number 数量,数字
  • @length 长度,包括像素带单位的像素值,例如:10px
  • @color 可以判断该属性值是否是一个“颜色”。例如:#ffffff rgb(0,0,0,0)
  • @ulr 判断该属性值是否是一个链接
  • * 任意值,包括 undefined、null 等空值都可以通过效验

除了值的判定外,默认情况下,这些属性设置后都是可选属性,我们还可能需要为节点制定必要的属性,以区分相通名称节点之间的差别和所属插件类型判别,例如:

表示前景色的样式节点

  1. <span style="color:#ffffff">Hello</span>
  1. {
  2. name: "span",
  3. type: "mark",
  4. attributes: {
  5. style:{
  6. color: "@color"
  7. }
  8. }
  9. }

表示前景色和背景色的样式节点

  1. <span style="color:#ffffff;background-color:#000000">Hello</span>
  1. {
  2. name: "span",
  3. type: "mark",
  4. attributes: {
  5. style:{
  6. color: "@color",
  7. "background-color": "@color"
  8. }
  9. }
  10. }

这两个样式节点名称都是 span 而且都包含 color 样式,因为默认属性都是可选属性,所以我们在判定一个节点时会忽略这些可选属性,剩下的名称也是一样的,这样就会造成逻辑错误,出现很多意外情况。

所以这里我们需要使用 SchemaValueObject 类型的值,来表明这两个节点的唯一性,这些标明的属性也是节点最主要的特征点

  1. {
  2. name: "span",
  3. type: "mark",
  4. attributes: {
  5. style:{
  6. color: {
  7. required: true,
  8. value:"@color"
  9. }
  10. }
  11. }
  12. }
  13. {
  14. name: "span",
  15. type: "mark",
  16. attributes: {
  17. style:{
  18. color: {
  19. required: true,
  20. value:"@color"
  21. },
  22. "background-color": {
  23. required: true,
  24. value:"@color"
  25. }
  26. }
  27. }
  28. }

默认规则

引擎按照功能和特性对节点进行了划分 mark inline block card,为了满足这些划分后的节点正常工作以及引擎需要,我们制定了一些默认规则,这些规则会和我们自定义规则合并后一起使用,所以不建议自定义规则去覆盖它们

  1. import { SchemaGlobal, SchemaRule } from '../types';
  2. import { CARD_KEY, CARD_TYPE_KEY, CARD_VALUE_KEY } from './card';
  3. import { ANCHOR, CURSOR, FOCUS } from './selection';
  4. const defualtSchema: Array<SchemaRule | SchemaGlobal> = [
  5. {
  6. name: 'p',
  7. type: 'block',
  8. },
  9. {
  10. name: 'br',
  11. type: 'inline',
  12. isVoid: true,
  13. },
  14. {
  15. name: ANCHOR,
  16. type: 'inline',
  17. isVoid: true,
  18. },
  19. {
  20. name: FOCUS,
  21. type: 'inline',
  22. isVoid: true,
  23. },
  24. {
  25. name: CURSOR,
  26. type: 'inline',
  27. isVoid: true,
  28. },
  29. {
  30. type: 'block',
  31. attributes: {
  32. 'data-id': '*',
  33. },
  34. },
  35. {
  36. name: 'card',
  37. type: 'inline',
  38. attributes: {
  39. name: {
  40. required: true,
  41. value: /\w+/,
  42. },
  43. type: {
  44. required: true,
  45. value: 'inline',
  46. },
  47. value: '*',
  48. },
  49. },
  50. {
  51. name: 'span',
  52. type: 'inline',
  53. attributes: {
  54. [CARD_KEY]: {
  55. required: true,
  56. value: /\w+/,
  57. },
  58. [CARD_TYPE_KEY]: {
  59. required: true,
  60. value: 'inline',
  61. },
  62. [CARD_VALUE_KEY]: '*',
  63. class: '*',
  64. contenteditable: '*',
  65. },
  66. },
  67. {
  68. name: 'card',
  69. type: 'block',
  70. attributes: {
  71. name: {
  72. required: true,
  73. value: /\w+/,
  74. },
  75. type: {
  76. required: true,
  77. value: 'block',
  78. },
  79. value: '*',
  80. },
  81. },
  82. {
  83. name: 'div',
  84. type: 'block',
  85. attributes: {
  86. [CARD_KEY]: {
  87. required: true,
  88. value: /\w+/,
  89. },
  90. [CARD_TYPE_KEY]: {
  91. required: true,
  92. value: 'block',
  93. },
  94. [CARD_VALUE_KEY]: '*',
  95. class: '*',
  96. contenteditable: '*',
  97. },
  98. },
  99. ];
  100. export default defualtSchema;