/** * Parser 富文本组件 * @tutorial https://github.com/jin-yufeng/Parser * @version 20200630 * @author JinYufeng * @listens MIT */ var cache = {}, Parser = require('./libs/MpHtmlParser.js'), fs = wx.getFileSystemManager && wx.getFileSystemManager(); var dom; // 计算 cache 的 key function hash(str) { for (var i = str.length, val = 5381; i--;) val += (val << 5) + str.charCodeAt(i); return val; } Component({ options: { pureDataPattern: /^[acdgtu]|W/ }, data: { nodes: [] }, properties: { html: { type: String, observer(html) { this.setContent(html); } }, autopause: { type: Boolean, value: true }, autoscroll: Boolean, autosetTitle: { type: Boolean, value: true }, compress: Number, domain: String, lazyLoad: Boolean, loadingImg: String, selectable: Boolean, tagStyle: Object, showWithAnimation: Boolean, useAnchor: Boolean, useCache: Boolean }, relations: { '../parser-group/parser-group': { type: 'ancestor' } }, created() { // 图片数组 this.imgList = []; this.imgList.setItem = function(i, src) { if (!i || !src) return; // 去重 if (src.indexOf('http') == 0 && this.includes(src)) { var newSrc = ''; for (var j = 0, c; c = src[j]; j++) { if (c == '/' && src[j - 1] != '/' && src[j + 1] != '/') break; newSrc += Math.random() > 0.5 ? c.toUpperCase() : c; } newSrc += src.substr(j); return this[i] = newSrc; } this[i] = src; // 暂存 data src if (src.includes('data:image')) { var info = src.match(/data:image\/(\S+?);(\S+?),(.+)/); if (!info) return; var filePath = `${wx.env.USER_DATA_PATH}/${Date.now()}.${info[1]}`; fs && fs.writeFile({ filePath, data: info[3], encoding: info[2], success: () => this[i] = filePath }) } } this.imgList.each = function(f) { for (var i = 0, len = this.length; i < len; i++) this.setItem(i, f(this[i], i, this)); } if (dom) this.document = new dom(this); }, detached() { // 删除暂存 this.imgList.each(src => { if (src && src.includes(wx.env.USER_DATA_PATH) && fs) fs.unlink({ filePath: src }) }) clearInterval(this._timer); }, methods: { // 锚点跳转 navigateTo(obj) { if (!this.data.useAnchor) return obj.fail && obj.fail({ errMsg: 'Anchor is disabled' }) this.createSelectorQuery() .select('.top' + (obj.id ? '>>>#' + obj.id : '')).boundingClientRect() .selectViewport().scrollOffset().exec(res => { if (!res[0]) return this.group ? this.group.navigateTo(this.i, obj) : obj.fail && obj.fail({ errMsg: 'Label not found' }); obj.scrollTop = res[1].scrollTop + res[0].top + (obj.offset || 0); wx.pageScrollTo(obj); }) }, // 获取文本 getText(ns = this.data.html) { var txt = ''; for (var i = 0, n; n = ns[i++];) { if (n.type == 'text') txt += n.text.replace(/ /g, '\u00A0').replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&'); else if (n.type == 'br') txt += '\n'; else { // 块级标签前后加换行 var br = n.name == 'p' || n.name == 'div' || n.name == 'tr' || n.name == 'li' || (n.name[0] == 'h' && n.name[1] > '0' && n.name[1] < '7'); if (br && txt && txt[txt.length - 1] != '\n') txt += '\n'; if (n.children) txt += this.getText(n.children); if (br && txt[txt.length - 1] != '\n') txt += '\n'; else if (n.name == 'td' || n.name == 'th') txt += '\t'; } } return txt; }, // 获取视频 context getVideoContext(id) { if (!id) return this.videoContexts; for (var i = this.videoContexts.length; i--;) if (this.videoContexts[i].id == id) return this.videoContexts[i]; }, // 渲染富文本 setContent(html, append) { var nodes, parser = new Parser(html, this.data); // 缓存读取 if (this.data.useCache) { var hashVal = hash(html); if (cache[hashVal]) nodes = cache[hashVal]; else cache[hashVal] = nodes = parser.parse(); } else nodes = parser.parse(); this.triggerEvent('parse', nodes); var data = {}; if (append) for (let i = this.data.nodes.length, j = nodes.length; j--;) data[`nodes[${i + j}]`] = nodes[j]; else data.nodes = nodes; if (this.showWithAnimation) data.showAm = 'animation: show .5s'; this.setData(data, () => { this.triggerEvent('load') }); // 设置标题 if (nodes.title && this.data.autosetTitle) wx.setNavigationBarTitle({ title: nodes.title }) this.imgList.length = 0; this.videoContexts = []; var ns = this.selectAllComponents('.top,.top>>>._node'); for (let i = 0, n; n = ns[i++];) { n.top = this; for (let j = 0, item; item = n.data.nodes[j++];) { if (item.c) continue; // 获取图片列表 if (item.name == 'img') this.imgList.setItem(item.attrs.i, item.attrs.src); // 音视频控制 else if (item.name == 'video' || item.name == 'audio') { var ctx; if (item.name == 'video') ctx = wx.createVideoContext(item.attrs.id, n); else ctx = n.selectComponent('#' + item.attrs.id); if (ctx) { ctx.id = item.attrs.id; this.videoContexts.push(ctx); } } } } var height; clearInterval(this._timer); this._timer = setInterval(() => { this.createSelectorQuery().select('.top').boundingClientRect(res => { if (!res) return; this.rect = res; if (res.height == height) { this.triggerEvent('ready', res) clearInterval(this._timer); } height = res.height; }).exec(); }, 350) } } })