123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- /**
- * 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)
- }
- }
- })
|