@gmcb/uv-ui 0.2.26

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