oxy-uni-ui 1.1.0 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/attributes.json +1 -1
  2. package/components/common/abstracts/variable.scss +59 -1
  3. package/components/common/path.ts +9 -0
  4. package/components/common/util.ts +42 -0
  5. package/components/composables/index.ts +1 -0
  6. package/components/composables/useGlobalLoading.ts +42 -0
  7. package/components/composables/useGlobalMessage.ts +48 -0
  8. package/components/composables/useGlobalToast.ts +84 -0
  9. package/components/composables/useVirtualScroll.ts +173 -0
  10. package/components/oxy-cell/oxy-cell.vue +15 -2
  11. package/components/oxy-cell/types.ts +4 -0
  12. package/components/oxy-checkbox/index.scss +1 -1
  13. package/components/oxy-checkbox/oxy-checkbox.vue +2 -2
  14. package/components/oxy-col-picker/oxy-col-picker.vue +3 -0
  15. package/components/oxy-col-picker/types.ts +5 -1
  16. package/components/oxy-corner/index.scss +121 -1
  17. package/components/oxy-corner/oxy-corner.vue +18 -5
  18. package/components/oxy-corner/types.ts +24 -3
  19. package/components/oxy-date-strip/index.scss +10 -0
  20. package/components/oxy-date-strip/oxy-date-strip.vue +198 -0
  21. package/components/oxy-date-strip/types.ts +98 -0
  22. package/components/oxy-date-strip/utils.ts +67 -0
  23. package/components/oxy-date-strip-item/index.scss +94 -0
  24. package/components/oxy-date-strip-item/oxy-date-strip-item.vue +102 -0
  25. package/components/oxy-date-strip-item/types.ts +53 -0
  26. package/components/oxy-datetime-picker/oxy-datetime-picker.vue +3 -1
  27. package/components/oxy-datetime-picker/types.ts +5 -1
  28. package/components/oxy-echarts/index.scss +17 -0
  29. package/components/oxy-echarts/index.ts +1 -0
  30. package/components/oxy-echarts/oxy-echarts.vue +32 -0
  31. package/components/oxy-echarts/types.ts +12 -0
  32. package/components/oxy-file-list/index.scss +26 -0
  33. package/components/oxy-file-list/oxy-file-list.vue +208 -34
  34. package/components/oxy-file-list/types.ts +58 -2
  35. package/components/oxy-global-loading/oxy-global-loading.vue +53 -0
  36. package/components/oxy-global-message/oxy-global-message.vue +64 -0
  37. package/components/oxy-global-toast/oxy-global-toast.vue +53 -0
  38. package/components/oxy-img-lazy/index.scss +17 -0
  39. package/components/oxy-img-lazy/oxy-img-lazy.vue +332 -0
  40. package/components/oxy-img-lazy/types.ts +69 -0
  41. package/components/oxy-link/index.scss +57 -0
  42. package/components/oxy-link/oxy-link.vue +130 -0
  43. package/components/oxy-link/types.ts +81 -0
  44. package/components/oxy-list/index.scss +8 -1
  45. package/components/oxy-list/oxy-list.vue +121 -40
  46. package/components/oxy-list/types.ts +3 -15
  47. package/components/oxy-picker/oxy-picker.vue +3 -0
  48. package/components/oxy-picker/types.ts +5 -1
  49. package/components/oxy-radio/index.scss +3 -3
  50. package/components/oxy-radio/oxy-radio.vue +1 -1
  51. package/components/oxy-rich-text/icon/emjio.svg +1 -0
  52. package/components/oxy-rich-text/icon/quote.svg +1 -0
  53. package/components/oxy-rich-text/icon/text.svg +1 -0
  54. package/components/oxy-rich-text/icon/title.svg +1 -0
  55. package/components/oxy-rich-text/index.scss +159 -0
  56. package/components/oxy-rich-text/mp-html/card/card.vue +122 -0
  57. package/components/oxy-rich-text/mp-html/card/index.js +7 -0
  58. package/components/oxy-rich-text/mp-html/editable/config.js +15 -0
  59. package/components/oxy-rich-text/mp-html/editable/index.js +553 -0
  60. package/components/oxy-rich-text/mp-html/emoji/index.js +203 -0
  61. package/components/oxy-rich-text/mp-html/highlight/config.js +5 -0
  62. package/components/oxy-rich-text/mp-html/highlight/index.js +96 -0
  63. package/components/oxy-rich-text/mp-html/highlight/prism.css +1 -0
  64. package/components/oxy-rich-text/mp-html/highlight/prism.min.js +7 -0
  65. package/components/oxy-rich-text/mp-html/img-cache/index.js +138 -0
  66. package/components/oxy-rich-text/mp-html/latex/index.js +80 -0
  67. package/components/oxy-rich-text/mp-html/latex/katex.css +1 -0
  68. package/components/oxy-rich-text/mp-html/latex/katex.min.js +1 -0
  69. package/components/oxy-rich-text/mp-html/markdown/index.js +50 -0
  70. package/components/oxy-rich-text/mp-html/markdown/marked.min.js +71 -0
  71. package/components/oxy-rich-text/mp-html/mp-html.d.ts +184 -0
  72. package/components/oxy-rich-text/mp-html/mp-html.vue +675 -0
  73. package/components/oxy-rich-text/mp-html/node/node.vue +1161 -0
  74. package/components/oxy-rich-text/mp-html/parser.js +1428 -0
  75. package/components/oxy-rich-text/mp-html/search/index.js +132 -0
  76. package/components/oxy-rich-text/mp-html/style/index.js +129 -0
  77. package/components/oxy-rich-text/mp-html/style/parser.js +175 -0
  78. package/components/oxy-rich-text/mp-html/template/index.js +67 -0
  79. package/components/oxy-rich-text/mp-html/txv-video/index.js +46 -0
  80. package/components/oxy-rich-text/oxy-rich-text.vue +642 -0
  81. package/components/oxy-rich-text/types.ts +71 -0
  82. package/components/oxy-select/index.scss +255 -0
  83. package/components/oxy-select/oxy-select.vue +421 -0
  84. package/components/oxy-select/types.ts +71 -0
  85. package/components/oxy-select-picker/oxy-select-picker.vue +3 -0
  86. package/components/oxy-select-picker/types.ts +5 -1
  87. package/components/oxy-stream-render/index.scss +6 -0
  88. package/components/oxy-stream-render/oxy-stream-render.vue +204 -0
  89. package/components/oxy-stream-render/types.ts +5 -0
  90. package/components/oxy-tree/index.scss +43 -5
  91. package/components/oxy-tree/oxy-tree.vue +233 -35
  92. package/components/oxy-tree/types.ts +54 -7
  93. package/components/oxy-tree/utils.ts +51 -0
  94. package/components/oxy-virtual-scroll/index.scss +1 -1
  95. package/components/oxy-virtual-scroll/oxy-virtual-scroll.vue +69 -110
  96. package/components/oxy-virtual-scroll/types.ts +95 -5
  97. package/components/oxy-waterfall/index.scss +18 -0
  98. package/components/oxy-waterfall/oxy-waterfall.vue +218 -0
  99. package/components/oxy-waterfall/types.ts +90 -0
  100. package/components/oxy-waterfall-item/index.scss +8 -0
  101. package/components/oxy-waterfall-item/oxy-waterfall-item.vue +89 -0
  102. package/components/oxy-waterfall-item/types.ts +16 -0
  103. package/global.d.ts +7 -0
  104. package/index.ts +3 -0
  105. package/locale/lang/en-US.ts +35 -9
  106. package/locale/lang/zh-CN.ts +31 -5
  107. package/oxy-uni-ui.zip +0 -0
  108. package/package.json +1 -1
  109. package/tags.json +1 -1
  110. package/uni-echarts/changelog.md +2 -0
  111. package/uni-echarts/components/index.js +1 -0
  112. package/uni-echarts/components/uni-echarts/events.js +95 -0
  113. package/uni-echarts/components/uni-echarts/types.d.ts +183 -0
  114. package/uni-echarts/components/uni-echarts/types.js +1 -0
  115. package/uni-echarts/components/uni-echarts/uni-echarts.vue +530 -0
  116. package/uni-echarts/components/uni-echarts/uni-echarts.vue.d.ts +19 -0
  117. package/uni-echarts/global.d.ts +7 -0
  118. package/uni-echarts/index.d.ts +440 -0
  119. package/uni-echarts/index.js +2 -0
  120. package/uni-echarts/package.json +105 -0
  121. package/uni-echarts/shared-core.d.ts +269 -0
  122. package/uni-echarts/shared-core.js +900 -0
  123. package/web-types.json +1 -1
@@ -0,0 +1,1428 @@
1
+ /**
2
+ * @fileoverview html 解析器
3
+ */
4
+
5
+ // 配置
6
+ const config = {
7
+ // 信任的标签(保持标签名不变)
8
+ trustTags: makeMap('a,abbr,ad,audio,b,blockquote,br,code,col,colgroup,dd,del,dl,dt,div,em,fieldset,h1,h2,h3,h4,h5,h6,hr,i,img,ins,label,legend,li,ol,p,q,ruby,rt,source,span,strong,sub,sup,table,tbody,td,tfoot,th,thead,tr,title,ul,video'),
9
+
10
+ // 块级标签(转为 div,其他的非信任标签转为 span)
11
+ blockTags: makeMap('address,article,aside,body,caption,center,cite,footer,header,html,nav,pre,section'),
12
+
13
+ // #ifdef (MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE3
14
+ // 行内标签
15
+ inlineTags: makeMap('abbr,b,big,code,del,em,i,ins,label,q,small,span,strong,sub,sup'),
16
+ // #endif
17
+
18
+ // 要移除的标签
19
+ ignoreTags: makeMap('area,base,canvas,embed,frame,head,iframe,input,link,map,meta,param,rp,script,source,style,textarea,title,track,wbr'),
20
+
21
+ // 自闭合的标签
22
+ voidTags: makeMap('area,base,br,col,circle,ellipse,embed,frame,hr,img,input,line,link,meta,param,path,polygon,rect,source,track,use,wbr'),
23
+
24
+ // html 实体
25
+ entities: {
26
+ lt: '<',
27
+ gt: '>',
28
+ quot: '"',
29
+ apos: "'",
30
+ ensp: '\u2002',
31
+ emsp: '\u2003',
32
+ nbsp: '\xA0',
33
+ semi: ';',
34
+ ndash: '–',
35
+ mdash: '—',
36
+ middot: '·',
37
+ lsquo: '‘',
38
+ rsquo: '’',
39
+ ldquo: '“',
40
+ rdquo: '”',
41
+ bull: '•',
42
+ hellip: '…',
43
+ larr: '←',
44
+ uarr: '↑',
45
+ rarr: '→',
46
+ darr: '↓'
47
+ },
48
+
49
+ // 默认的标签样式
50
+ tagStyle: {
51
+ // #ifndef APP-PLUS-NVUE
52
+ address: 'font-style:italic',
53
+ big: 'display:inline;font-size:1.2em',
54
+ caption: 'display:table-caption;text-align:center',
55
+ center: 'text-align:center',
56
+ cite: 'font-style:italic',
57
+ dd: 'margin-left:40px',
58
+ mark: 'background-color:yellow',
59
+ pre: 'font-family:monospace;white-space:pre',
60
+ s: 'text-decoration:line-through',
61
+ small: 'display:inline;font-size:0.8em',
62
+ strike: 'text-decoration:line-through',
63
+ u: 'text-decoration:underline'
64
+ // #endif
65
+ },
66
+
67
+ // svg 大小写对照表
68
+ svgDict: {
69
+ animatetransform: 'animateTransform',
70
+ lineargradient: 'linearGradient',
71
+ viewbox: 'viewBox',
72
+ attributename: 'attributeName',
73
+ repeatcount: 'repeatCount',
74
+ repeatdur: 'repeatDur',
75
+ foreignobject: 'foreignObject'
76
+ }
77
+ }
78
+ const tagSelector={}
79
+ let windowWidth, system
80
+ // #ifdef MP-WEIXIN
81
+ if (uni.canIUse('getWindowInfo')) {
82
+ windowWidth = uni.getWindowInfo().windowWidth
83
+ system = uni.getDeviceInfo().system
84
+ } else {
85
+ // #endif
86
+ const systemInfo = uni.getSystemInfoSync()
87
+ windowWidth = systemInfo.windowWidth
88
+ // #ifdef MP-WEIXIN
89
+ system = systemInfo.system
90
+ }
91
+ // #endif
92
+ const blankChar = makeMap(' ,\r,\n,\t,\f')
93
+ let idIndex = 0
94
+
95
+ // #ifdef H5 || APP-PLUS
96
+ config.ignoreTags.iframe = undefined
97
+ config.trustTags.iframe = true
98
+ config.ignoreTags.embed = undefined
99
+ config.trustTags.embed = true
100
+ // #endif
101
+ // #ifdef APP-PLUS-NVUE
102
+ config.ignoreTags.source = undefined
103
+ config.ignoreTags.style = undefined
104
+ // #endif
105
+
106
+ /**
107
+ * @description 创建 map
108
+ * @param {String} str 逗号分隔
109
+ */
110
+ function makeMap (str) {
111
+ const map = Object.create(null)
112
+ const list = str.split(',')
113
+ for (let i = list.length; i--;) {
114
+ map[list[i]] = true
115
+ }
116
+ return map
117
+ }
118
+
119
+ /**
120
+ * @description 解码 html 实体
121
+ * @param {String} str 要解码的字符串
122
+ * @param {Boolean} amp 要不要解码 &amp;
123
+ * @returns {String} 解码后的字符串
124
+ */
125
+ function decodeEntity (str, amp) {
126
+ let i = str.indexOf('&')
127
+ while (i !== -1) {
128
+ const j = str.indexOf(';', i + 3)
129
+ let code
130
+ if (j === -1) break
131
+ if (str[i + 1] === '#') {
132
+ // &#123; 形式的实体
133
+ code = parseInt((str[i + 2] === 'x' ? '0' : '') + str.substring(i + 2, j))
134
+ if (!isNaN(code)) {
135
+ str = str.substr(0, i) + String.fromCharCode(code) + str.substr(j + 1)
136
+ }
137
+ } else {
138
+ // &nbsp; 形式的实体
139
+ code = str.substring(i + 1, j)
140
+ if (config.entities[code] || (code === 'amp' && amp)) {
141
+ str = str.substr(0, i) + (config.entities[code] || '&') + str.substr(j + 1)
142
+ }
143
+ }
144
+ i = str.indexOf('&', i + 1)
145
+ }
146
+ return str
147
+ }
148
+
149
+ /**
150
+ * @description 合并多个块级标签,加快长内容渲染
151
+ * @param {Array} nodes 要合并的标签数组
152
+ */
153
+ function mergeNodes (nodes) {
154
+ let i = nodes.length - 1
155
+ for (let j = i; j >= -1; j--) {
156
+ if (j === -1 || nodes[j].c || !nodes[j].name || (nodes[j].name !== 'div' && nodes[j].name !== 'p' && nodes[j].name[0] !== 'h') || (nodes[j].attrs.style || '').includes('inline')) {
157
+ if (i - j >= 5) {
158
+ nodes.splice(j + 1, i - j, {
159
+ name: 'div',
160
+ attrs: {},
161
+ children: nodes.slice(j + 1, i + 1)
162
+ })
163
+ }
164
+ i = j - 1
165
+ }
166
+ }
167
+ }
168
+
169
+ /**
170
+ * @description html 解析器
171
+ * @param {Object} vm 组件实例
172
+ */
173
+ function Parser (vm) {
174
+ this.options = vm || {}
175
+ this.tagStyle = Object.assign({}, config.tagStyle, this.options.tagStyle)
176
+ this.imgList = vm.imgList || []
177
+ this.imgList._unloadimgs = 0
178
+ this.plugins = vm.plugins || []
179
+ this.attrs = Object.create(null)
180
+ this.stack = []
181
+ this.nodes = []
182
+ this.pre = (this.options.containerStyle || '').includes('white-space') && this.options.containerStyle.includes('pre') ? 2 : 0
183
+ }
184
+
185
+ /**
186
+ * @description 执行解析
187
+ * @param {String} content 要解析的文本
188
+ */
189
+ Parser.prototype.parse = function (content) {
190
+ // 插件处理
191
+ for (let i = this.plugins.length; i--;) {
192
+ if (this.plugins[i].onUpdate) {
193
+ content = this.plugins[i].onUpdate(content, config) || content
194
+ }
195
+ }
196
+
197
+ new Lexer(this).parse(content)
198
+ // 出栈未闭合的标签
199
+ while (this.stack.length) {
200
+ this.popNode()
201
+ }
202
+ if (this.nodes.length > 50) {
203
+ mergeNodes(this.nodes)
204
+ }
205
+ return this.nodes
206
+ }
207
+
208
+ /**
209
+ * @description 将标签暴露出来(不被 rich-text 包含)
210
+ */
211
+ Parser.prototype.expose = function () {
212
+ // #ifndef APP-PLUS-NVUE
213
+ for (let i = this.stack.length; i--;) {
214
+ const item = this.stack[i]
215
+ if (item.c || item.name === 'a' || item.name === 'video' || item.name === 'audio') return
216
+ item.c = 1
217
+ }
218
+ // #endif
219
+ }
220
+
221
+ /**
222
+ * @description 处理插件
223
+ * @param {Object} node 要处理的标签
224
+ * @returns {Boolean} 是否要移除此标签
225
+ */
226
+ Parser.prototype.hook = function (node) {
227
+ for (let i = this.plugins.length; i--;) {
228
+ if (this.plugins[i].onParse && this.plugins[i].onParse(node, this) === false) {
229
+ return false
230
+ }
231
+ }
232
+ return true
233
+ }
234
+
235
+ /**
236
+ * @description 将链接拼接上主域名
237
+ * @param {String} url 需要拼接的链接
238
+ * @returns {String} 拼接后的链接
239
+ */
240
+ Parser.prototype.getUrl = function (url) {
241
+ const domain = this.options.domain
242
+ if (url[0] === '/') {
243
+ if (url[1] === '/') {
244
+ // // 开头的补充协议名
245
+ url = (domain ? domain.split('://')[0] : 'http') + ':' + url
246
+ } else if (domain) {
247
+ // 否则补充整个域名
248
+ url = domain + url
249
+ } /* #ifdef APP-PLUS */ else {
250
+ url = plus.io.convertLocalFileSystemURL(url)
251
+ } /* #endif */
252
+ } else if (!url.includes('data:') && !url.includes('://')) {
253
+ if (domain) {
254
+ url = domain + '/' + url
255
+ } /* #ifdef APP-PLUS */ else {
256
+ url = plus.io.convertLocalFileSystemURL(url)
257
+ } /* #endif */
258
+ }
259
+ return url
260
+ }
261
+
262
+ /**
263
+ * @description 解析样式表
264
+ * @param {Object} node 标签
265
+ * @returns {Object}
266
+ */
267
+ Parser.prototype.parseStyle = function (node) {
268
+ const attrs = node.attrs
269
+ const list = (this.tagStyle[node.name] || '').split(';').concat((attrs.style || '').split(';'))
270
+ const styleObj = {}
271
+ let tmp = ''
272
+
273
+ if (this.stack.length > 0) {
274
+ const parent = this.stack[this.stack.length - 1]
275
+ const nestedKey = `${parent.name}>${node.name}`
276
+ const nestedStyle = this.tagStyle[nestedKey]
277
+ if (nestedStyle) {
278
+ const nestedList = nestedStyle.split(';')
279
+ for (let i = 0, len = nestedList.length; i < len; i++) {
280
+ const info = nestedList[i].split(':')
281
+ if (info.length >= 2) {
282
+ const key = info.shift().trim().toLowerCase()
283
+ const value = info.join(':').trim()
284
+ if (value) {
285
+ styleObj[key] = value
286
+ }
287
+ }
288
+ }
289
+ }
290
+ }
291
+
292
+ if (attrs.id && !this.xml) {
293
+ // 暴露锚点
294
+ if (this.options.useAnchor) {
295
+ this.expose()
296
+ } else if (node.name !== 'img' && node.name !== 'a' && node.name !== 'video' && node.name !== 'audio') {
297
+ attrs.id = undefined
298
+ }
299
+ }
300
+
301
+ // 转换 width 和 height 属性
302
+ if (attrs.width) {
303
+ styleObj.width = parseFloat(attrs.width) + (attrs.width.includes('%') ? '%' : 'px')
304
+ attrs.width = undefined
305
+ }
306
+ if (attrs.height) {
307
+ styleObj.height = parseFloat(attrs.height) + (attrs.height.includes('%') ? '%' : 'px')
308
+ attrs.height = undefined
309
+ }
310
+
311
+ for (let i = 0, len = list.length; i < len; i++) {
312
+ const info = list[i].split(':')
313
+ if (info.length < 2) continue
314
+ const key = info.shift().trim().toLowerCase()
315
+ let value = info.join(':').trim()
316
+ if ((value[0] === '-' && value.lastIndexOf('-') > 0) || value.includes('safe')) {
317
+ // 兼容性的 css 不压缩
318
+ tmp += `;${key}:${value}`
319
+ } else if (!styleObj[key] || value.includes('import') || !styleObj[key].includes('import')) {
320
+ // 重复的样式进行覆盖
321
+ if (value.includes('url')) {
322
+ // 填充链接
323
+ let j = value.indexOf('(') + 1
324
+ if (j) {
325
+ while (value[j] === '"' || value[j] === "'" || blankChar[value[j]]) {
326
+ j++
327
+ }
328
+ value = value.substr(0, j) + this.getUrl(value.substr(j))
329
+ }
330
+ } else if (value.includes('rpx')) {
331
+ // 转换 rpx(rich-text 内部不支持 rpx)
332
+ value = value.replace(/[0-9.]+\s*rpx/g, $ => parseFloat($) * windowWidth / 750 + 'px')
333
+ }
334
+ styleObj[key] = value
335
+ }
336
+ }
337
+
338
+ node.attrs.style = tmp
339
+ return styleObj
340
+ }
341
+
342
+ /**
343
+ * @description 解析到标签名
344
+ * @param {String} name 标签名
345
+ * @private
346
+ */
347
+ Parser.prototype.onTagName = function (name) {
348
+ this.tagName = this.xml ? name : name.toLowerCase()
349
+ if (this.tagName === 'svg') {
350
+ this.xml = (this.xml || 0) + 1 // svg 标签内大小写敏感
351
+ config.ignoreTags.style = undefined // svg 标签内 style 可用
352
+ }
353
+ }
354
+
355
+ /**
356
+ * @description 解析到属性名
357
+ * @param {String} name 属性名
358
+ * @private
359
+ */
360
+ Parser.prototype.onAttrName = function (name) {
361
+ name = this.xml ? name : name.toLowerCase()
362
+ // #ifdef (VUE3 && (H5 || APP-PLUS)) || APP-PLUS-NVUE
363
+ if (name.includes('?') || name.includes(';')) {
364
+ this.attrName = undefined
365
+ return
366
+ }
367
+ // #endif
368
+ if (name.substr(0, 5) === 'data-') {
369
+ if (name === 'data-src' && !this.attrs.src) {
370
+ // data-src 自动转为 src
371
+ this.attrName = 'src'
372
+ } else if (this.tagName === 'img' || this.tagName === 'a') {
373
+ // a 和 img 标签保留 data- 的属性,可以在 imgtap 和 linktap 事件中使用
374
+ this.attrName = name
375
+ } else {
376
+ // 剩余的移除以减小大小
377
+ this.attrName = undefined
378
+ }
379
+ } else {
380
+ this.attrName = name
381
+ this.attrs[name] = 'T' // boolean 型属性缺省设置
382
+ }
383
+ }
384
+
385
+ /**
386
+ * @description 解析到属性值
387
+ * @param {String} val 属性值
388
+ * @private
389
+ */
390
+ Parser.prototype.onAttrVal = function (val) {
391
+ const name = this.attrName || ''
392
+ if (name === 'style' || name === 'href') {
393
+ // 部分属性进行实体解码
394
+ this.attrs[name] = decodeEntity(val, true)
395
+ } else if (name.includes('src')) {
396
+ // 拼接主域名
397
+ this.attrs[name] = this.getUrl(decodeEntity(val, true))
398
+ } else if (name) {
399
+ this.attrs[name] = val
400
+ }
401
+ }
402
+
403
+ /**
404
+ * @description 解析到标签开始
405
+ * @param {Boolean} selfClose 是否有自闭合标识 />
406
+ * @private
407
+ */
408
+ Parser.prototype.onOpenTag = function (selfClose) {
409
+ // 拼装 node
410
+ const node = Object.create(null)
411
+ node.name = this.tagName
412
+ node.attrs = this.attrs
413
+ // 避免因为自动 diff 使得 type 被设置为 null 导致部分内容不显示
414
+ if (this.options.nodes.length) {
415
+ node.type = 'node'
416
+ }
417
+ this.attrs = Object.create(null)
418
+
419
+ const attrs = node.attrs
420
+ const parent = this.stack[this.stack.length - 1]
421
+ const siblings = parent ? parent.children : this.nodes
422
+ const close = this.xml ? selfClose : config.voidTags[node.name]
423
+
424
+ // 替换标签名选择器
425
+ if (tagSelector[node.name]) {
426
+ attrs.class = tagSelector[node.name] + (attrs.class ? ' ' + attrs.class : '')
427
+ }
428
+
429
+ // 转换 embed 标签
430
+ if (node.name === 'embed') {
431
+ // #ifndef H5 || APP-PLUS
432
+ const src = attrs.src || ''
433
+ // 按照后缀名和 type 将 embed 转为 video 或 audio
434
+ if (src.includes('.mp4') || src.includes('.3gp') || src.includes('.m3u8') || (attrs.type || '').includes('video')) {
435
+ node.name = 'video'
436
+ } else if (src.includes('.mp3') || src.includes('.wav') || src.includes('.aac') || src.includes('.m4a') || (attrs.type || '').includes('audio')) {
437
+ node.name = 'audio'
438
+ }
439
+ if (attrs.autostart) {
440
+ attrs.autoplay = 'T'
441
+ }
442
+ attrs.controls = 'T'
443
+ // #endif
444
+ // #ifdef H5 || APP-PLUS
445
+ this.expose()
446
+ // #endif
447
+ }
448
+
449
+ // #ifndef APP-PLUS-NVUE
450
+ // 处理音视频
451
+ if (node.name === 'video' || node.name === 'audio') {
452
+ // 设置 id 以便获取 context
453
+ if (node.name === 'video' && !attrs.id) {
454
+ attrs.id = 'v' + idIndex++
455
+ }
456
+ // 没有设置 controls 也没有设置 autoplay 的自动设置 controls
457
+ if (!attrs.controls && !attrs.autoplay) {
458
+ attrs.controls = 'T'
459
+ }
460
+ // 用数组存储所有可用的 source
461
+ node.src = []
462
+ if (attrs.src) {
463
+ node.src.push(attrs.src)
464
+ attrs.src = undefined
465
+ }
466
+ this.expose()
467
+ }
468
+ // #endif
469
+
470
+ // 处理自闭合标签
471
+ if (close) {
472
+ if (!this.hook(node) || config.ignoreTags[node.name]) {
473
+ // 通过 base 标签设置主域名
474
+ if (node.name === 'base' && !this.options.domain) {
475
+ this.options.domain = attrs.href
476
+ } /* #ifndef APP-PLUS-NVUE */ else if (node.name === 'source' && parent && (parent.name === 'video' || parent.name === 'audio') && attrs.src) {
477
+ // 设置 source 标签(仅父节点为 video 或 audio 时有效)
478
+ parent.src.push(attrs.src)
479
+ } /* #endif */
480
+ return
481
+ }
482
+
483
+ // 解析 style
484
+ const styleObj = this.parseStyle(node)
485
+
486
+ // 处理图片
487
+ if (node.name === 'img') {
488
+ if (attrs.src) {
489
+ // 标记 webp
490
+ if (attrs.src.includes('webp')) {
491
+ node.webp = 'T'
492
+ }
493
+ // data url 图片如果没有设置 original-src 默认为不可预览的小图片
494
+ if (attrs.src.includes('data:') && this.options.previewImg !== 'all' && !attrs['original-src']) {
495
+ attrs.ignore = 'T'
496
+ }
497
+ if (!attrs.ignore || node.webp || attrs.src.includes('cloud://')) {
498
+ for (let i = this.stack.length; i--;) {
499
+ const item = this.stack[i]
500
+ if (item.name === 'a') {
501
+ node.a = item.attrs
502
+ }
503
+ if (item.name === 'table' && !node.webp && !attrs.src.includes('cloud://')) {
504
+ if (!styleObj.display || styleObj.display.includes('inline')) {
505
+ node.t = 'inline-block'
506
+ } else {
507
+ node.t = styleObj.display
508
+ }
509
+ styleObj.display = undefined
510
+ }
511
+ // #ifndef H5 || APP-PLUS
512
+ const style = item.attrs.style || ''
513
+ if (style.includes('flex:') && !style.includes('flex:0') && !style.includes('flex: 0') && (!styleObj.width || parseInt(styleObj.width) > 100)) {
514
+ styleObj.width = '100% !important'
515
+ styleObj.height = ''
516
+ for (let j = i + 1; j < this.stack.length; j++) {
517
+ this.stack[j].attrs.style = (this.stack[j].attrs.style || '').replace('inline-', '')
518
+ }
519
+ } else if (style.includes('flex') && styleObj.width === '100%') {
520
+ for (let j = i + 1; j < this.stack.length; j++) {
521
+ const style = this.stack[j].attrs.style || ''
522
+ if (!style.includes(';width') && !style.includes(' width') && style.indexOf('width') !== 0) {
523
+ styleObj.width = ''
524
+ break
525
+ }
526
+ }
527
+ } else if (style.includes('inline-block')) {
528
+ if (styleObj.width && styleObj.width[styleObj.width.length - 1] === '%') {
529
+ item.attrs.style += ';max-width:' + styleObj.width
530
+ styleObj.width = ''
531
+ } else {
532
+ item.attrs.style += ';max-width:100%'
533
+ }
534
+ }
535
+ // #endif
536
+ item.c = 1
537
+ }
538
+ attrs.i = this.imgList.length.toString()
539
+ let src = attrs['original-src'] || attrs.src
540
+ // #ifndef H5 || MP-ALIPAY || APP-PLUS || MP-360
541
+ if (this.imgList.includes(src)) {
542
+ // 如果有重复的链接则对域名进行随机大小写变换避免预览时错位
543
+ let i = src.indexOf('://')
544
+ if (i !== -1) {
545
+ i += 3
546
+ let newSrc = src.substr(0, i)
547
+ for (; i < src.length; i++) {
548
+ if (src[i] === '/') break
549
+ newSrc += Math.random() > 0.5 ? src[i].toUpperCase() : src[i]
550
+ }
551
+ newSrc += src.substr(i)
552
+ src = newSrc
553
+ }
554
+ }
555
+ // #endif
556
+ this.imgList.push(src)
557
+ if (!node.t) {
558
+ this.imgList._unloadimgs += 1
559
+ }
560
+ // #ifdef H5 || APP-PLUS
561
+ if (this.options.lazyLoad) {
562
+ attrs['data-src'] = attrs.src
563
+ attrs.src = undefined
564
+ }
565
+ // #endif
566
+ }
567
+ }
568
+ if (styleObj.display === 'inline') {
569
+ styleObj.display = ''
570
+ }
571
+ // #ifndef APP-PLUS-NVUE
572
+ if (attrs.ignore) {
573
+ styleObj['max-width'] = styleObj['max-width'] || '100%'
574
+ attrs.style += ';-webkit-touch-callout:none'
575
+ }
576
+ // #endif
577
+ // 设置的宽度超出屏幕,为避免变形,高度转为自动
578
+ if (parseInt(styleObj.width) > windowWidth) {
579
+ styleObj.height = undefined
580
+ }
581
+ // 记录是否设置了宽高
582
+ if (!isNaN(parseInt(styleObj.width))) {
583
+ node.w = 'T'
584
+ }
585
+ if (!isNaN(parseInt(styleObj.height)) && (!styleObj.height.includes('%') || (parent && (parent.attrs.style || '').includes('height')))) {
586
+ node.h = 'T'
587
+ }
588
+ if (node.w && node.h && styleObj['object-fit']) {
589
+ if (styleObj['object-fit'] === 'contain') {
590
+ node.m = 'aspectFit'
591
+ } else if (styleObj['object-fit'] === 'cover') {
592
+ node.m = 'aspectFill'
593
+ }
594
+ }
595
+ } else if (node.name === 'svg') {
596
+ siblings.push(node)
597
+ this.stack.push(node)
598
+ this.popNode()
599
+ return
600
+ }
601
+ for (const key in styleObj) {
602
+ if (styleObj[key]) {
603
+ attrs.style += `;${key}:${styleObj[key].replace(' !important', '')}`
604
+ }
605
+ }
606
+ attrs.style = attrs.style.substr(1) || undefined
607
+ // #ifdef (MP-WEIXIN || MP-QQ) && VUE3
608
+ if (!attrs.style) {
609
+ delete attrs.style
610
+ }
611
+ // #endif
612
+ } else {
613
+ if ((node.name === 'pre' || ((attrs.style || '').includes('white-space') && attrs.style.includes('pre'))) && this.pre !== 2) {
614
+ this.pre = node.pre = 1
615
+ }
616
+ node.children = []
617
+ this.stack.push(node)
618
+ }
619
+
620
+ // 加入节点树
621
+ siblings.push(node)
622
+ }
623
+
624
+ /**
625
+ * @description 解析到标签结束
626
+ * @param {String} name 标签名
627
+ * @private
628
+ */
629
+ Parser.prototype.onCloseTag = function (name) {
630
+ // 依次出栈到匹配为止
631
+ name = this.xml ? name : name.toLowerCase()
632
+ let i
633
+ for (i = this.stack.length; i--;) {
634
+ if (this.stack[i].name === name) break
635
+ }
636
+ if (i !== -1) {
637
+ while (this.stack.length > i) {
638
+ this.popNode()
639
+ }
640
+ } else if (name === 'p' || name === 'br') {
641
+ const siblings = this.stack.length ? this.stack[this.stack.length - 1].children : this.nodes
642
+ siblings.push({
643
+ name,
644
+ attrs: {
645
+ class: tagSelector[name] || '',
646
+ style: this.tagStyle[name] || ''
647
+ }
648
+ })
649
+ }
650
+ }
651
+
652
+ /**
653
+ * @description 处理标签出栈
654
+ * @private
655
+ */
656
+ Parser.prototype.popNode = function () {
657
+ const editable = this.options.editable
658
+ const node = this.stack.pop()
659
+ let attrs = node.attrs
660
+ const children = node.children
661
+ const parent = this.stack[this.stack.length - 1]
662
+ const siblings = parent ? parent.children : this.nodes
663
+
664
+ if (!this.hook(node) || config.ignoreTags[node.name]) {
665
+ // 获取标题
666
+ if (node.name === 'title' && children.length && children[0].type === 'text' && this.options.setTitle) {
667
+ uni.setNavigationBarTitle({
668
+ title: children[0].text
669
+ })
670
+ }
671
+ siblings.pop()
672
+ return
673
+ }
674
+
675
+ if (node.pre && this.pre !== 2) {
676
+ // 是否合并空白符标识
677
+ this.pre = node.pre = undefined
678
+ for (let i = this.stack.length; i--;) {
679
+ if (this.stack[i].pre) {
680
+ this.pre = 1
681
+ }
682
+ }
683
+ }
684
+
685
+ const styleObj = {}
686
+
687
+ // 转换 svg
688
+ if (node.name === 'svg') {
689
+ if (this.xml > 1) {
690
+ // 多层 svg 嵌套
691
+ this.xml--
692
+ return
693
+ }
694
+ // #ifdef APP-PLUS-NVUE
695
+ (function traversal (node) {
696
+ if (node.name) {
697
+ // 调整 svg 的大小写
698
+ node.name = config.svgDict[node.name] || node.name
699
+ for (const item in node.attrs) {
700
+ if (config.svgDict[item]) {
701
+ node.attrs[config.svgDict[item]] = node.attrs[item]
702
+ node.attrs[item] = undefined
703
+ }
704
+ }
705
+ for (let i = 0; i < (node.children || []).length; i++) {
706
+ traversal(node.children[i])
707
+ }
708
+ }
709
+ })(node)
710
+ // #endif
711
+ // #ifndef APP-PLUS-NVUE
712
+ let src = ''
713
+ const style = attrs.style
714
+ attrs.style = ''
715
+ attrs.xmlns = 'http://www.w3.org/2000/svg';
716
+ (function traversal (node) {
717
+ if (node.type === 'text') {
718
+ src += node.text
719
+ return
720
+ }
721
+ const name = config.svgDict[node.name] || node.name
722
+ if (name === 'foreignObject') {
723
+ for (const child of (node.children || [])) {
724
+ if (child.attrs && !child.attrs.xmlns) {
725
+ child.attrs.xmlns = 'http://www.w3.org/1999/xhtml'
726
+ break
727
+ }
728
+ }
729
+ }
730
+ src += '<' + name
731
+ for (const item in node.attrs) {
732
+ const val = node.attrs[item]
733
+ if (val) {
734
+ src += ` ${config.svgDict[item] || item}="${val.replace(/"/g, '')}"`
735
+ }
736
+ }
737
+ if (!node.children) {
738
+ src += '/>'
739
+ } else {
740
+ src += '>'
741
+ for (let i = 0; i < node.children.length; i++) {
742
+ traversal(node.children[i])
743
+ }
744
+ src += '</' + name + '>'
745
+ }
746
+ })(node)
747
+ node.name = 'img'
748
+ node.attrs = {
749
+ src: 'data:image/svg+xml;utf8,' + src.replace(/#/g, '%23'),
750
+ style,
751
+ ignore: 'T'
752
+ }
753
+ node.children = undefined
754
+ // #endif
755
+ this.xml = false
756
+ config.ignoreTags.style = true
757
+ return
758
+ }
759
+
760
+ // #ifndef APP-PLUS-NVUE
761
+ // 转换 align 属性
762
+ if (attrs.align) {
763
+ if (node.name === 'table') {
764
+ if (attrs.align === 'center') {
765
+ styleObj['margin-inline-start'] = styleObj['margin-inline-end'] = 'auto'
766
+ } else {
767
+ styleObj.float = attrs.align
768
+ }
769
+ } else {
770
+ styleObj['text-align'] = attrs.align
771
+ }
772
+ attrs.align = undefined
773
+ }
774
+
775
+ // 转换 dir 属性
776
+ if (attrs.dir) {
777
+ styleObj.direction = attrs.dir
778
+ attrs.dir = undefined
779
+ }
780
+
781
+ // 转换 font 标签的属性
782
+ if (node.name === 'font') {
783
+ if (attrs.color) {
784
+ styleObj.color = attrs.color
785
+ attrs.color = undefined
786
+ }
787
+ if (attrs.face) {
788
+ styleObj['font-family'] = attrs.face
789
+ attrs.face = undefined
790
+ }
791
+ if (attrs.size) {
792
+ let size = parseInt(attrs.size)
793
+ if (!isNaN(size)) {
794
+ if (size < 1) {
795
+ size = 1
796
+ } else if (size > 7) {
797
+ size = 7
798
+ }
799
+ styleObj['font-size'] = ['x-small', 'small', 'medium', 'large', 'x-large', 'xx-large', 'xxx-large'][size - 1]
800
+ }
801
+ attrs.size = undefined
802
+ }
803
+ }
804
+ // #endif
805
+
806
+ // 一些编辑器的自带 class
807
+ if ((attrs.class || '').includes('align-center')) {
808
+ styleObj['text-align'] = 'center'
809
+ }
810
+
811
+ Object.assign(styleObj, this.parseStyle(node))
812
+
813
+ if (node.name !== 'table' && parseInt(styleObj.width) > windowWidth) {
814
+ styleObj['max-width'] = '100%'
815
+ styleObj['box-sizing'] = 'border-box'
816
+ }
817
+
818
+ // #ifndef APP-PLUS-NVUE
819
+ if (config.blockTags[node.name]) {
820
+ if (!editable) {
821
+ node.name = 'div'
822
+ }
823
+ } else if (!config.trustTags[node.name] && !this.xml) {
824
+ // 未知标签转为 span,避免无法显示
825
+ node.name = 'span'
826
+ }
827
+
828
+ if (node.name === 'a' || node.name === 'ad'
829
+ // #ifdef H5 || APP-PLUS
830
+ || node.name === 'iframe' // eslint-disable-line
831
+ // #endif
832
+ ) {
833
+ this.expose()
834
+ } else if (node.name === 'video') {
835
+ if ((styleObj.height || '').includes('auto')) {
836
+ styleObj.height = undefined
837
+ }
838
+ /* #ifdef APP-PLUS */
839
+ let str = '<video style="width:100%;height:100%"'
840
+ if (editable) {
841
+ attrs.controls = ''
842
+ }
843
+ for (const item in attrs) {
844
+ if (attrs[item]) {
845
+ str += ' ' + item + '="' + attrs[item] + '"'
846
+ }
847
+ }
848
+ if (this.options.pauseVideo) {
849
+ str += ' onplay="this.dispatchEvent(new CustomEvent(\'vplay\',{bubbles:!0}));for(var e=document.getElementsByTagName(\'video\'),t=0;t<e.length;t++)e[t]!=this&&e[t].pause()"'
850
+ }
851
+ str += '>'
852
+ for (let i = 0; i < node.src.length; i++) {
853
+ str += '<source src="' + node.src[i] + '">'
854
+ }
855
+ str += '</video>'
856
+ node.html = str
857
+ /* #endif */
858
+ } else if ((node.name === 'ul' || node.name === 'ol') && (node.c || editable)) {
859
+ // 列表处理
860
+ const types = {
861
+ a: 'lower-alpha',
862
+ A: 'upper-alpha',
863
+ i: 'lower-roman',
864
+ I: 'upper-roman'
865
+ }
866
+ if (types[attrs.type]) {
867
+ attrs.style += ';list-style-type:' + types[attrs.type]
868
+ attrs.type = undefined
869
+ }
870
+ for (let i = children.length; i--;) {
871
+ if (children[i].name === 'li') {
872
+ children[i].c = 1
873
+ }
874
+ }
875
+ } else if (node.name === 'table') {
876
+ // 表格处理
877
+ // cellpadding、cellspacing、border 这几个常用表格属性需要通过转换实现
878
+ let padding = parseFloat(attrs.cellpadding)
879
+ let spacing = parseFloat(attrs.cellspacing)
880
+ const border = parseFloat(attrs.border)
881
+ const bordercolor = styleObj['border-color']
882
+ const borderstyle = styleObj['border-style']
883
+ if ((node.c || editable)) {
884
+ // padding 和 spacing 默认 2
885
+ if (isNaN(padding)) {
886
+ padding = 2
887
+ }
888
+ if (isNaN(spacing)) {
889
+ spacing = 2
890
+ }
891
+ }
892
+ if (border) {
893
+ attrs.style += `;border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'}`
894
+ }
895
+ if (node.flag && (node.c || editable)) {
896
+ // 有 colspan 或 rowspan 且含有链接的表格通过 grid 布局实现
897
+ styleObj.display = 'grid'
898
+ if (styleObj['border-collapse'] === 'collapse') {
899
+ styleObj['border-collapse'] = undefined
900
+ spacing = 0
901
+ }
902
+ if (spacing) {
903
+ styleObj['grid-gap'] = spacing + 'px'
904
+ styleObj.padding = spacing + 'px'
905
+ } else if (border) {
906
+ // 无间隔的情况下避免边框重叠
907
+ attrs.style += ';border-left:0;border-top:0'
908
+ }
909
+
910
+ const width = [] // 表格的列宽
911
+ const trList = [] // tr 列表
912
+ const cells = [] // 保存新的单元格
913
+ const map = {}; // 被合并单元格占用的格子
914
+
915
+ (function traversal (nodes) {
916
+ for (let i = 0; i < nodes.length; i++) {
917
+ if (nodes[i].name === 'tr') {
918
+ trList.push(nodes[i])
919
+ } else if (nodes[i].name === 'colgroup') {
920
+ let colI = 1
921
+ for (const col of (nodes[i].children || [])) {
922
+ if (col.name === 'col') {
923
+ const style = col.attrs.style || ''
924
+ const start = style.indexOf('width') ? style.indexOf(';width') : 0
925
+ // 提取出宽度
926
+ if (start !== -1) {
927
+ let end = style.indexOf(';', start + 6)
928
+ if (end === -1) {
929
+ end = style.length
930
+ }
931
+ width[colI] = style.substring(start ? start + 7 : 6, end)
932
+ }
933
+ colI += 1
934
+ }
935
+ }
936
+ } else {
937
+ traversal(nodes[i].children || [])
938
+ }
939
+ }
940
+ })(children)
941
+
942
+ for (let row = 1; row <= trList.length; row++) {
943
+ let col = 1
944
+ for (let j = 0; j < trList[row - 1].children.length; j++) {
945
+ const td = trList[row - 1].children[j]
946
+ if (td.name === 'td' || td.name === 'th') {
947
+ // 这个格子被上面的单元格占用,则列号++
948
+ while (map[row + '.' + col]) {
949
+ col++
950
+ }
951
+ if (editable) {
952
+ td.r = row
953
+ }
954
+ let style = td.attrs.style || ''
955
+ let start = style.indexOf('width') ? style.indexOf(';width') : 0
956
+ // 提取出 td 的宽度
957
+ if (start !== -1) {
958
+ let end = style.indexOf(';', start + 6)
959
+ if (end === -1) {
960
+ end = style.length
961
+ }
962
+ if (!td.attrs.colspan) {
963
+ width[col] = style.substring(start ? start + 7 : 6, end)
964
+ }
965
+ style = style.substr(0, start) + style.substr(end)
966
+ }
967
+ // 设置竖直对齐
968
+ style += ';display:flex'
969
+ start = style.indexOf('vertical-align')
970
+ if (start !== -1) {
971
+ const val = style.substr(start + 15, 10)
972
+ if (val.includes('middle')) {
973
+ style += ';align-items:center'
974
+ } else if (val.includes('bottom')) {
975
+ style += ';align-items:flex-end'
976
+ }
977
+ } else {
978
+ style += ';align-items:center'
979
+ }
980
+ // 设置水平对齐
981
+ start = style.indexOf('text-align')
982
+ if (start !== -1) {
983
+ const val = style.substr(start + 11, 10)
984
+ if (val.includes('center')) {
985
+ style += ';justify-content: center'
986
+ } else if (val.includes('right')) {
987
+ style += ';justify-content: right'
988
+ }
989
+ }
990
+ style = (border ? `;border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'}` + (spacing ? '' : ';border-right:0;border-bottom:0') : '') + (padding ? `;padding:${padding}px` : '') + ';' + style
991
+ // 处理列合并
992
+ if (td.attrs.colspan) {
993
+ style += `;grid-column-start:${col};grid-column-end:${col + parseInt(td.attrs.colspan)}`
994
+ if (!td.attrs.rowspan) {
995
+ style += `;grid-row-start:${row};grid-row-end:${row + 1}`
996
+ }
997
+ col += parseInt(td.attrs.colspan) - 1
998
+ }
999
+ // 处理行合并
1000
+ if (td.attrs.rowspan) {
1001
+ style += `;grid-row-start:${row};grid-row-end:${row + parseInt(td.attrs.rowspan)}`
1002
+ if (!td.attrs.colspan) {
1003
+ style += `;grid-column-start:${col};grid-column-end:${col + 1}`
1004
+ }
1005
+ // 记录下方单元格被占用
1006
+ for (let rowspan = 1; rowspan < td.attrs.rowspan; rowspan++) {
1007
+ for (let colspan = 0; colspan < (td.attrs.colspan || 1); colspan++) {
1008
+ map[(row + rowspan) + '.' + (col - colspan)] = 1
1009
+ }
1010
+ }
1011
+ }
1012
+ if (style) {
1013
+ td.attrs.style = style
1014
+ }
1015
+ cells.push(td)
1016
+ col++
1017
+ }
1018
+ }
1019
+ if (row === 1) {
1020
+ let temp = ''
1021
+ for (let i = 1; i < col; i++) {
1022
+ temp += (width[i] ? width[i] : 'auto') + ' '
1023
+ }
1024
+ styleObj['grid-template-columns'] = temp
1025
+ }
1026
+ }
1027
+ node.children = cells
1028
+ } else {
1029
+ // 没有使用合并单元格的表格通过 table 布局实现
1030
+ if ((node.c || editable)) {
1031
+ styleObj.display = 'table'
1032
+ }
1033
+ if (!isNaN(spacing)) {
1034
+ styleObj['border-spacing'] = spacing + 'px'
1035
+ }
1036
+ if (border || padding) {
1037
+ // 遍历
1038
+ (function traversal (nodes) {
1039
+ for (let i = 0; i < nodes.length; i++) {
1040
+ const td = nodes[i]
1041
+ if (td.name === 'th' || td.name === 'td') {
1042
+ if (border) {
1043
+ td.attrs.style = `border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'};${td.attrs.style || ''}`
1044
+ }
1045
+ if (padding) {
1046
+ td.attrs.style = `padding:${padding}px;${td.attrs.style || ''}`
1047
+ }
1048
+ } else if (td.children) {
1049
+ traversal(td.children)
1050
+ }
1051
+ }
1052
+ })(children)
1053
+ }
1054
+ }
1055
+ // 给表格添加一个单独的横向滚动层
1056
+ if (this.options.scrollTable && !(attrs.style || '').includes('inline')) {
1057
+ const table = Object.assign({}, node)
1058
+ node.name = 'div'
1059
+ node.attrs = {
1060
+ style: 'overflow:auto'
1061
+ }
1062
+ node.children = [table]
1063
+ attrs = table.attrs
1064
+ }
1065
+ } else if ((node.name === 'tbody' || node.name === 'tr') && node.flag && (node.c || editable)) {
1066
+ node.flag = undefined;
1067
+ (function traversal (nodes) {
1068
+ for (let i = 0; i < nodes.length; i++) {
1069
+ if (nodes[i].name === 'td') {
1070
+ // 颜色样式设置给单元格避免丢失
1071
+ for (const style of ['color', 'background', 'background-color']) {
1072
+ if (styleObj[style]) {
1073
+ nodes[i].attrs.style = style + ':' + styleObj[style] + ';' + (nodes[i].attrs.style || '')
1074
+ }
1075
+ }
1076
+ } else {
1077
+ traversal(nodes[i].children || [])
1078
+ }
1079
+ }
1080
+ })(children)
1081
+ } else if ((node.name === 'td' || node.name === 'th') && (attrs.colspan || attrs.rowspan)) {
1082
+ for (let i = this.stack.length; i--;) {
1083
+ if (this.stack[i].name === 'table' || this.stack[i].name === 'tbody' || this.stack[i].name === 'tr') {
1084
+ this.stack[i].flag = 1 // 指示含有合并单元格
1085
+ }
1086
+ }
1087
+ } else if (node.name === 'ruby') {
1088
+ // 转换 ruby
1089
+ node.name = 'span'
1090
+ for (let i = 0; i < children.length - 1; i++) {
1091
+ if (children[i].type === 'text' && children[i + 1].name === 'rt') {
1092
+ children[i] = {
1093
+ name: 'div',
1094
+ attrs: {
1095
+ style: 'display:inline-block;text-align:center'
1096
+ },
1097
+ children: [{
1098
+ name: 'div',
1099
+ attrs: {
1100
+ style: 'font-size:50%;' + (children[i + 1].attrs.style || '')
1101
+ },
1102
+ children: children[i + 1].children
1103
+ }, children[i]]
1104
+ }
1105
+ children.splice(i + 1, 1)
1106
+ }
1107
+ }
1108
+ } else if (!editable && node.c ) {
1109
+ (function traversal (node) {
1110
+ node.c = 2
1111
+ for (let i = node.children.length; i--;) {
1112
+ const child = node.children[i]
1113
+ // #ifdef (MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE3
1114
+ if (child.name && (config.inlineTags[child.name] || ((child.attrs.style || '').includes('inline') && child.children)) && !child.c) {
1115
+ traversal(child)
1116
+ }
1117
+ // #endif
1118
+ if (!child.c || child.name === 'table') {
1119
+ node.c = 1
1120
+ }
1121
+ }
1122
+ })(node)
1123
+ }
1124
+
1125
+ if ((styleObj.display || '').includes('flex') && !(node.c || editable)) {
1126
+ for (let i = children.length; i--;) {
1127
+ const item = children[i]
1128
+ if (item.f) {
1129
+ item.attrs.style = (item.attrs.style || '') + item.f
1130
+ item.f = undefined
1131
+ }
1132
+ }
1133
+ }
1134
+ // flex 布局时部分样式需要提取到 rich-text 外层
1135
+ const flex = parent && ((parent.attrs.style || '').includes('flex') || (parent.attrs.style || '').includes('grid'))
1136
+ // #ifdef MP-WEIXIN
1137
+ // 检查基础库版本 virtualHost 是否可用
1138
+ && !((node.c || editable) && wx.getNFCAdapter) // eslint-disable-line
1139
+ // #endif
1140
+ // #ifndef MP-WEIXIN || MP-QQ || MP-BAIDU || MP-TOUTIAO
1141
+ && !node.c // eslint-disable-line
1142
+ // #endif
1143
+ if (flex) {
1144
+ node.f = ';max-width:100%'
1145
+ }
1146
+
1147
+ if (children.length >= 50 && (node.c || editable) && !(styleObj.display || '').includes('flex')) {
1148
+ mergeNodes(children)
1149
+ }
1150
+ // #endif
1151
+
1152
+ for (const key in styleObj) {
1153
+ if (styleObj[key]) {
1154
+ const val = `;${key}:${styleObj[key].replace(' !important', '')}`
1155
+ /* #ifndef APP-PLUS-NVUE */
1156
+ if (flex && ((key.includes('flex') && key !== 'flex-direction') || key === 'align-self' || key.includes('grid') || styleObj[key][0] === '-' || (key.includes('width') && val.includes('%')))) {
1157
+ node.f += val
1158
+ if (key === 'width') {
1159
+ attrs.style += ';width:100%'
1160
+ }
1161
+ } else /* #endif */ {
1162
+ attrs.style += val
1163
+ }
1164
+ }
1165
+ }
1166
+ attrs.style = attrs.style.substr(1) || undefined
1167
+ // #ifdef (MP-WEIXIN || MP-QQ) && VUE3
1168
+ for (const key in attrs) {
1169
+ if (!attrs[key]) {
1170
+ delete attrs[key]
1171
+ }
1172
+ }
1173
+ // #endif
1174
+ }
1175
+
1176
+ /**
1177
+ * @description 解析到文本
1178
+ * @param {String} text 文本内容
1179
+ */
1180
+ Parser.prototype.onText = function (text) {
1181
+ if (!this.pre) {
1182
+ // 合并空白符
1183
+ let trim = ''
1184
+ let flag
1185
+ for (let i = 0, len = text.length; i < len; i++) {
1186
+ if (!blankChar[text[i]]) {
1187
+ trim += text[i]
1188
+ } else {
1189
+ if (trim[trim.length - 1] !== ' ') {
1190
+ trim += ' '
1191
+ }
1192
+ if (text[i] === '\n' && !flag) {
1193
+ flag = true
1194
+ }
1195
+ }
1196
+ }
1197
+ // 去除含有换行符的空串
1198
+ if (trim === ' ') {
1199
+ if (flag) return
1200
+ // #ifdef VUE3
1201
+ else {
1202
+ const parent = this.stack[this.stack.length - 1]
1203
+ if (parent && parent.name[0] === 't') return
1204
+ }
1205
+ // #endif
1206
+ }
1207
+ text = trim
1208
+ }
1209
+ const node = Object.create(null)
1210
+ node.type = 'text'
1211
+ // #ifdef (MP-BAIDU || MP-ALIPAY || MP-TOUTIAO) && VUE3
1212
+ node.attrs = {}
1213
+ // #endif
1214
+ node.text = decodeEntity(text)
1215
+ if (this.hook(node)) {
1216
+ // #ifdef MP-WEIXIN
1217
+ if (this.options.selectable === 'force' && system.includes('iOS') && !uni.canIUse('rich-text.user-select')) {
1218
+ this.expose()
1219
+ }
1220
+ // #endif
1221
+ const siblings = this.stack.length ? this.stack[this.stack.length - 1].children : this.nodes
1222
+ siblings.push(node)
1223
+ }
1224
+ }
1225
+
1226
+ /**
1227
+ * @description html 词法分析器
1228
+ * @param {Object} handler 高层处理器
1229
+ */
1230
+ function Lexer (handler) {
1231
+ this.handler = handler
1232
+ }
1233
+
1234
+ /**
1235
+ * @description 执行解析
1236
+ * @param {String} content 要解析的文本
1237
+ */
1238
+ Lexer.prototype.parse = function (content) {
1239
+ this.content = content || ''
1240
+ this.i = 0 // 标记解析位置
1241
+ this.start = 0 // 标记一个单词的开始位置
1242
+ this.state = this.text // 当前状态
1243
+ for (let len = this.content.length; this.i !== -1 && this.i < len;) {
1244
+ this.state()
1245
+ }
1246
+ }
1247
+
1248
+ /**
1249
+ * @description 检查标签是否闭合
1250
+ * @param {String} method 如果闭合要进行的操作
1251
+ * @returns {Boolean} 是否闭合
1252
+ * @private
1253
+ */
1254
+ Lexer.prototype.checkClose = function (method) {
1255
+ const selfClose = this.content[this.i] === '/'
1256
+ if (this.content[this.i] === '>' || (selfClose && this.content[this.i + 1] === '>')) {
1257
+ if (method) {
1258
+ this.handler[method](this.content.substring(this.start, this.i))
1259
+ }
1260
+ this.i += selfClose ? 2 : 1
1261
+ this.start = this.i
1262
+ this.handler.onOpenTag(selfClose)
1263
+ if (this.handler.tagName === 'script') {
1264
+ this.i = this.content.indexOf('</', this.i)
1265
+ if (this.i !== -1) {
1266
+ this.i += 2
1267
+ this.start = this.i
1268
+ }
1269
+ this.state = this.endTag
1270
+ } else {
1271
+ this.state = this.text
1272
+ }
1273
+ return true
1274
+ }
1275
+ return false
1276
+ }
1277
+
1278
+ /**
1279
+ * @description 文本状态
1280
+ * @private
1281
+ */
1282
+ Lexer.prototype.text = function () {
1283
+ this.i = this.content.indexOf('<', this.i) // 查找最近的标签
1284
+ if (this.i === -1) {
1285
+ // 没有标签了
1286
+ if (this.start < this.content.length) {
1287
+ this.handler.onText(this.content.substring(this.start, this.content.length))
1288
+ }
1289
+ return
1290
+ }
1291
+ const c = this.content[this.i + 1]
1292
+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
1293
+ // 标签开头
1294
+ if (this.start !== this.i) {
1295
+ this.handler.onText(this.content.substring(this.start, this.i))
1296
+ }
1297
+ this.start = ++this.i
1298
+ this.state = this.tagName
1299
+ } else if (c === '/' || c === '!' || c === '?') {
1300
+ if (this.start !== this.i) {
1301
+ this.handler.onText(this.content.substring(this.start, this.i))
1302
+ }
1303
+ const next = this.content[this.i + 2]
1304
+ if (c === '/' && ((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) {
1305
+ // 标签结尾
1306
+ this.i += 2
1307
+ this.start = this.i
1308
+ this.state = this.endTag
1309
+ return
1310
+ }
1311
+ // 处理注释
1312
+ let end = '-->'
1313
+ if (c !== '!' || this.content[this.i + 2] !== '-' || this.content[this.i + 3] !== '-') {
1314
+ end = '>'
1315
+ }
1316
+ this.i = this.content.indexOf(end, this.i)
1317
+ if (this.i !== -1) {
1318
+ this.i += end.length
1319
+ this.start = this.i
1320
+ }
1321
+ } else {
1322
+ this.i++
1323
+ }
1324
+ }
1325
+
1326
+ /**
1327
+ * @description 标签名状态
1328
+ * @private
1329
+ */
1330
+ Lexer.prototype.tagName = function () {
1331
+ if (blankChar[this.content[this.i]]) {
1332
+ // 解析到标签名
1333
+ this.handler.onTagName(this.content.substring(this.start, this.i))
1334
+ while (blankChar[this.content[++this.i]]);
1335
+ if (this.i < this.content.length && !this.checkClose()) {
1336
+ this.start = this.i
1337
+ this.state = this.attrName
1338
+ }
1339
+ } else if (!this.checkClose('onTagName')) {
1340
+ this.i++
1341
+ }
1342
+ }
1343
+
1344
+ /**
1345
+ * @description 属性名状态
1346
+ * @private
1347
+ */
1348
+ Lexer.prototype.attrName = function () {
1349
+ let c = this.content[this.i]
1350
+ if (blankChar[c] || c === '=') {
1351
+ // 解析到属性名
1352
+ this.handler.onAttrName(this.content.substring(this.start, this.i))
1353
+ let needVal = c === '='
1354
+ const len = this.content.length
1355
+ while (++this.i < len) {
1356
+ c = this.content[this.i]
1357
+ if (!blankChar[c]) {
1358
+ if (this.checkClose()) return
1359
+ if (needVal) {
1360
+ // 等号后遇到第一个非空字符
1361
+ this.start = this.i
1362
+ this.state = this.attrVal
1363
+ return
1364
+ }
1365
+ if (this.content[this.i] === '=') {
1366
+ needVal = true
1367
+ } else {
1368
+ this.start = this.i
1369
+ this.state = this.attrName
1370
+ return
1371
+ }
1372
+ }
1373
+ }
1374
+ } else if (!this.checkClose('onAttrName')) {
1375
+ this.i++
1376
+ }
1377
+ }
1378
+
1379
+ /**
1380
+ * @description 属性值状态
1381
+ * @private
1382
+ */
1383
+ Lexer.prototype.attrVal = function () {
1384
+ const c = this.content[this.i]
1385
+ const len = this.content.length
1386
+ if (c === '"' || c === "'") {
1387
+ // 有冒号的属性
1388
+ this.start = ++this.i
1389
+ this.i = this.content.indexOf(c, this.i)
1390
+ if (this.i === -1) return
1391
+ this.handler.onAttrVal(this.content.substring(this.start, this.i))
1392
+ } else {
1393
+ // 没有冒号的属性
1394
+ for (; this.i < len; this.i++) {
1395
+ if (blankChar[this.content[this.i]]) {
1396
+ this.handler.onAttrVal(this.content.substring(this.start, this.i))
1397
+ break
1398
+ } else if (this.checkClose('onAttrVal')) return
1399
+ }
1400
+ }
1401
+ while (blankChar[this.content[++this.i]]);
1402
+ if (this.i < len && !this.checkClose()) {
1403
+ this.start = this.i
1404
+ this.state = this.attrName
1405
+ }
1406
+ }
1407
+
1408
+ /**
1409
+ * @description 结束标签状态
1410
+ * @returns {String} 结束的标签名
1411
+ * @private
1412
+ */
1413
+ Lexer.prototype.endTag = function () {
1414
+ const c = this.content[this.i]
1415
+ if (blankChar[c] || c === '>' || c === '/') {
1416
+ this.handler.onCloseTag(this.content.substring(this.start, this.i))
1417
+ if (c !== '>') {
1418
+ this.i = this.content.indexOf('>', this.i)
1419
+ if (this.i === -1) return
1420
+ }
1421
+ this.start = ++this.i
1422
+ this.state = this.text
1423
+ } else {
1424
+ this.i++
1425
+ }
1426
+ }
1427
+
1428
+ export default Parser