index.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. const {
  2. QuillDeltaToHtmlConverter: QuillDeltaToHtmlConverterBase,
  3. BlockGroup,
  4. ListGroup,
  5. BlotBlock,
  6. } = require('./core')
  7. const jsonRules = {
  8. list(list) {
  9. const firstItem = list.items[0];
  10. return {
  11. type: "list",
  12. data: {
  13. type: firstItem.item.op.attributes.list,
  14. items: list.items.map((item) => jsonRules.listItem(item)),
  15. },
  16. attributes: firstItem.item.op.attributes,
  17. class: attributes2class(firstItem.item.op.attributes, 'block'),
  18. style: attributes2style(firstItem.item.op.attributes, 'block'),
  19. };
  20. },
  21. listItem(listItem) {
  22. // listItem.item.op.attributes.indent = 0;
  23. const inlines = jsonRules.inlines(listItem.item.op, listItem.item.ops, false).map((v) => v.ops)
  24. return {
  25. data: inlines[0] || [],
  26. children: listItem.innerList ? jsonRules.list(listItem.innerList): [],
  27. }
  28. },
  29. block (op, ops) {
  30. const type = ops[0].insert.type
  31. if (type === 'text') {
  32. return jsonRules.inlines(op, ops)
  33. }
  34. return {
  35. type: ops[0].insert.type,
  36. data: ops.map(bop => jsonRules.inline(bop)),
  37. attributes: op.attributes,
  38. class: attributes2class(op.attributes, 'block'),
  39. style: attributes2style(op.attributes, 'block'),
  40. }
  41. },
  42. inlines(op = {}, ops, isInlineGroup = true) {
  43. const opsLen = ops.length - 1;
  44. const br = {
  45. type: 'br'
  46. }
  47. const texts = ops.reduce((acc, op, i) => {
  48. if (i > 0 && i === opsLen && op.isJustNewline()) {
  49. acc[acc.length - 1].op = op
  50. return acc;
  51. }
  52. if (!acc[acc.length - 1]) {
  53. acc.push({ops: [], op: {}});
  54. }
  55. if (op.isJustNewline()) {
  56. const nextOp = ops[i + 1];
  57. acc[acc.length - 1].op = op
  58. if (nextOp && nextOp.isJustNewline()) {
  59. acc.push({ops: [br], op: {}});
  60. } else {
  61. acc.push({ops: [], op: {}});
  62. }
  63. return acc;
  64. } else {
  65. acc[acc.length - 1].ops.push(jsonRules.inline(op));
  66. return acc;
  67. }
  68. }, []);
  69. if (!isInlineGroup) {
  70. return texts;
  71. }
  72. return texts.map((v) => {
  73. return {
  74. type: "paragraph",
  75. data: v.ops,
  76. class: attributes2class(op.attributes || v.op.attributes, 'block'),
  77. style: attributes2style(op.attributes || v.op.attributes, 'block'),
  78. };
  79. });
  80. },
  81. inline (op) {
  82. const data = {
  83. value: op.insert.value,
  84. attributes: op.attributes,
  85. class: attributes2class(op.attributes, 'inline'),
  86. style: attributes2style(op.attributes, 'inline'),
  87. }
  88. if (op.isCustomEmbed()) {
  89. return {
  90. type: op.insert.type,
  91. data
  92. }
  93. }
  94. if (op.isLink()) {
  95. return {
  96. type: 'link',
  97. data
  98. }
  99. }
  100. return {
  101. type: op.isImage() ? "image": "text",
  102. data
  103. }
  104. },
  105. blotBlock (op) {
  106. return {
  107. type: op.insert.type,
  108. data: {
  109. value: op.insert.value,
  110. attributes: op.attributes,
  111. class: attributes2class(op.attributes, 'block'),
  112. style: attributes2style(op.attributes, 'block'),
  113. }
  114. }
  115. }
  116. };
  117. function attributes2style(attributes, type) {
  118. if (!attributes) return ''
  119. // 定义允许的属性
  120. const allowAttr = {
  121. inline: ['align', 'color', 'background', 'font', 'fontSize', 'fontStyle', 'fontVariant', 'fontWeight', 'fontFamily', 'lineHeight', 'letterSpacing', 'textDecoration', 'textIndent', 'wordWrap', 'wordBreak', 'whiteSpace'],
  122. block: ['align', 'margin', 'marginTop', 'marginBottom', 'marginLeft', 'marginRight', 'padding', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight', 'lineHeight', 'textIndent']
  123. }[type]
  124. // 定义属性适配器
  125. const adp = {
  126. align: 'text-align',
  127. }
  128. // 如果不支持该类型的属性,则抛出错误
  129. if (!allowAttr) throw new Error('type not supported')
  130. // 将属性转换为style
  131. return allowAttr.reduce((res, item) => {
  132. // 如果属性为空,则返回空字符串
  133. if (!attributes) return res
  134. // 如果属性不为undefined,则将属性名和属性值添加到style列表中
  135. if (attributes[item] !== undefined) {
  136. // 如果属性适配器中存在该属性,则使用适配器中的属性名
  137. // 否则,将属性名转换为短横线连接的形式
  138. res.push(`${adp[item] || item.replace(/([A-Z])/g, (s, m) => `-${m.toLowerCase()}`)}: ${attributes[item]}`)
  139. }
  140. return res
  141. }, []).join(';')
  142. }
  143. function attributes2class(attributes, type) {
  144. if (!attributes) return ''
  145. // 定义允许的属性
  146. const allowAttr = {
  147. inline: ['bold', 'italic', 'underline', 'strike', 'ins', 'link'],
  148. block: ['header', 'list']
  149. }[type]
  150. // 如果不支持该类型的属性,则抛出错误
  151. if (!allowAttr) throw new Error('type not supported')
  152. // 将属性转换为class列表
  153. const classList = allowAttr.reduce((res, item) => {
  154. // 如果属性为空,则返回空字符串
  155. if (!attributes || item === "link") return res
  156. // 如果属性为true,则将属性名添加到class列表中
  157. if (attributes[item] === true) {
  158. res.push(item)
  159. // 如果属性不为undefined,则将属性名和属性值添加到class列表中
  160. } else if (attributes[item] !== undefined) {
  161. res.push(`${item}-${attributes[item]}`)
  162. }
  163. return res
  164. }, [])
  165. // 如果属性中包含link,则添加link类
  166. if ('link' in attributes) {
  167. classList.push('link')
  168. }
  169. return classList.join(' ')
  170. }
  171. class QuillDeltaToJSONConverter {
  172. constructor(deltaOps) {
  173. this.deltaPreHandler(deltaOps)
  174. this.deltaOps = deltaOps
  175. this.converter = new QuillDeltaToHtmlConverterBase(this.deltaOps, {
  176. multiLineParagraph: false,
  177. });
  178. }
  179. deltaPreHandler (deltaOps) {
  180. for (const op of deltaOps) {
  181. if (typeof op.insert === 'string') continue
  182. if (!op.attributes) {
  183. op.attributes = {}
  184. }
  185. const insertType = Object.keys(op.insert)
  186. const blockRenderList = ['divider', 'unlockContent', 'mediaVideo']
  187. if (insertType && insertType.length > 0 && blockRenderList.includes(insertType[0])) {
  188. op.attributes.renderAsBlock = true
  189. }
  190. }
  191. }
  192. convert () {
  193. const opsGroups = this.converter.getGroupedOps();
  194. return [].concat.apply(
  195. [],
  196. opsGroups.map((group) => {
  197. // console.log(JSON.stringify(group), '--------------------group------------------------------')
  198. switch (group.constructor) {
  199. case ListGroup:
  200. return jsonRules.list(group);
  201. case BlockGroup:
  202. return jsonRules.block(group.op, group.ops)
  203. case BlotBlock:
  204. return jsonRules.blotBlock(group.op, group.ops)
  205. default:
  206. return jsonRules.inlines(group.op, group.ops);
  207. }
  208. })
  209. ).filter(op => op.data instanceof Array ? op.data.length : op.data);
  210. }
  211. }
  212. class QuillDeltaToHtmlConverter extends QuillDeltaToHtmlConverterBase {
  213. constructor(deltaOps) {
  214. super(deltaOps, {
  215. multiLineParagraph: false,
  216. inlineStyles: true,
  217. });
  218. // this.renderCustomWith(function (customOp, contextOp) {
  219. // console.log(customOp, contextOp)
  220. // })
  221. }
  222. }
  223. exports.QuillDeltaToHtmlConverter = QuillDeltaToHtmlConverter
  224. exports.QuillDeltaToJSONConverter = QuillDeltaToJSONConverter