312 lines
10 KiB
JavaScript
312 lines
10 KiB
JavaScript
/*
|
||
parser 主组件
|
||
github:https://github.com/jin-yufeng/Parser
|
||
docs:https://jin-yufeng.github.io/Parser
|
||
author:JinYufeng
|
||
update:2020/04/25
|
||
*/
|
||
var cache = {},
|
||
Parser = require('./libs/MpHtmlParser.js'),
|
||
fs = wx.getFileSystemManager && wx.getFileSystemManager();
|
||
try {
|
||
var dom = require('./libs/document.js');
|
||
} catch (e) {}
|
||
// 计算 cache 的 key
|
||
function hash(str) {
|
||
for (var i = str.length, val = 5381; i--;)
|
||
val += (val << 5) + str.charCodeAt(i);
|
||
return val;
|
||
}
|
||
Component({
|
||
options: {
|
||
pureDataPattern: /^[acdgtux]|W/
|
||
},
|
||
properties: {
|
||
'html': {
|
||
type: null,
|
||
observer(html) {
|
||
if (this._refresh) this._refresh = false;
|
||
else this.setContent(html, false, true);
|
||
}
|
||
},
|
||
'autosetTitle': {
|
||
type: Boolean,
|
||
value: true
|
||
},
|
||
'autopause': {
|
||
type: Boolean,
|
||
value: true
|
||
},
|
||
'compress': Number,
|
||
'domain': String,
|
||
'gestureZoom': Boolean,
|
||
'lazyLoad': Boolean,
|
||
'selectable': Boolean,
|
||
'tagStyle': Object,
|
||
'showWithAnimation': Boolean,
|
||
'useAnchor': Boolean,
|
||
'useCache': Boolean,
|
||
'xml': 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, _watch) {
|
||
var data = {};
|
||
if (!html) {
|
||
if (_watch || append) return;
|
||
data.html = '';
|
||
} else if (typeof html == 'string') {
|
||
let parser = new Parser(html, this.data);
|
||
// 缓存读取
|
||
if (this.data.useCache) {
|
||
var hashVal = hash(html);
|
||
if (cache[hashVal]) data.html = cache[hashVal];
|
||
else {
|
||
data.html = parser.parse();
|
||
cache[hashVal] = data.html;
|
||
}
|
||
} else data.html = parser.parse();
|
||
this._refresh = true;
|
||
this.triggerEvent('parse', data.html);
|
||
} else if (html.constructor == Array) {
|
||
// 转换不符合格式的 array
|
||
if (html.length && html[0].PoweredBy != 'Parser') {
|
||
let parser = new Parser('', this.data);
|
||
(function f(ns) {
|
||
for (var i = 0, n; n = ns[i]; i++) {
|
||
if (n.type == 'text') continue;
|
||
n.attrs = n.attrs || {};
|
||
for (var key in n.attrs)
|
||
if (typeof n.attrs[key] != 'string') n.attrs[key] = n.attrs[key].toString();
|
||
parser.matchAttr(n);
|
||
if (n.children) {
|
||
parser.STACK.push(n);
|
||
f(n.children);
|
||
parser.popNode(parser.STACK.pop());
|
||
}
|
||
}
|
||
})(html);
|
||
data.html = html;
|
||
}
|
||
if (!_watch) data.html = html;
|
||
} else if (typeof html == 'object' && html.nodes) {
|
||
data.html = html.nodes;
|
||
console.warn('错误的 html 类型:object 类型已废弃');
|
||
} else
|
||
return console.warn('错误的 html 类型:' + typeof html);
|
||
if (append) {
|
||
this._refresh = true;
|
||
data.html = (this.data.html || []).concat(data.html);
|
||
} else if (this.data.showWithAnimation) data.showAm = 'animation: show .5s';
|
||
if (data.html || data.showAm) this.setData(data);
|
||
// 设置标题
|
||
if (this.data.html.length && this.data.html[0].title && this.data.autosetTitle)
|
||
wx.setNavigationBarTitle({
|
||
title: this.data.html[0].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 (var 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);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
(wx.nextTick || setTimeout)(() => this.triggerEvent('load'), 50);
|
||
var height;
|
||
clearInterval(this._timer);
|
||
this._timer = setInterval(() => {
|
||
this.createSelectorQuery().select('.top').boundingClientRect(res => {
|
||
this.rect = res;
|
||
if (res.height == height) {
|
||
this.triggerEvent('ready', res)
|
||
clearInterval(this._timer);
|
||
}
|
||
height = res.height;
|
||
}).exec();
|
||
}, 350)
|
||
},
|
||
// 预加载
|
||
preLoad(html, num) {
|
||
if (typeof html == 'string') {
|
||
var id = hash(html);
|
||
html = new Parser(html, this.data).parse();
|
||
cache[id] = html;
|
||
}
|
||
var imgs, wait = [];
|
||
(function f(ns) {
|
||
for (var i = 0, n; n = ns[i++];) {
|
||
if (n.name == 'img' && n.attrs.src && !wait.includes(n.attrs.src))
|
||
wait.push(n.attrs.src);
|
||
f(n.children || []);
|
||
}
|
||
})(html);
|
||
if (num) wait = wait.slice(0, num);
|
||
this._wait = (this._wait || []).concat(wait);
|
||
if (!this.data.imgs) imgs = this._wait.splice(0, 15);
|
||
else if (this.data.imgs.length < 15)
|
||
imgs = this.data.imgs.concat(this._wait.splice(0, 15 - this.data.imgs.length));
|
||
imgs && this.setData({
|
||
imgs
|
||
});
|
||
},
|
||
_load(e) {
|
||
if (this._wait.length)
|
||
this.setData({
|
||
[`imgs[${e.target.id}]`]: this._wait.shift()
|
||
})
|
||
},
|
||
// 事件处理
|
||
_tap(e) {
|
||
if (this.data.gestureZoom && e.timeStamp - this._lastT < 300) {
|
||
var initY = e.detail.y - e.currentTarget.offsetTop;
|
||
if (this._zoom) {
|
||
this._scaleAm.translateX(0).scale(1).step();
|
||
wx.pageScrollTo({
|
||
scrollTop: (initY + this._initY) / 2 - e.touches[0].clientY,
|
||
duration: 400
|
||
})
|
||
} else {
|
||
var initX = e.detail.x - e.currentTarget.offsetLeft;
|
||
this._initY = initY;
|
||
this._scaleAm = wx.createAnimation({
|
||
transformOrigin: `${initX}px ${this._initY}px 0`,
|
||
timingFunction: 'ease-in-out'
|
||
});
|
||
this._scaleAm.scale(2).step();
|
||
this._tMax = initX / 2;
|
||
this._tMin = (initX - this.rect.width) / 2;
|
||
this._tX = 0;
|
||
}
|
||
this._zoom = !this._zoom;
|
||
this.setData({
|
||
scaleAm: this._scaleAm.export()
|
||
})
|
||
}
|
||
this._lastT = e.timeStamp;
|
||
},
|
||
_touchstart(e) {
|
||
if (e.touches.length == 1)
|
||
this._initX = this._lastX = e.touches[0].pageX;
|
||
},
|
||
_touchmove(e) {
|
||
var diff = e.touches[0].pageX - this._lastX;
|
||
if (this._zoom && e.touches.length == 1 && Math.abs(diff) > 20) {
|
||
this._lastX = e.touches[0].pageX;
|
||
if ((this._tX <= this._tMin && diff < 0) || (this._tX >= this._tMax && diff > 0)) return;
|
||
this._tX += diff * Math.abs(this._lastX - this._initX) * 0.05;
|
||
if (this._tX < this._tMin) this._tX = this._tMin;
|
||
if (this._tX > this._tMax) this._tX = this._tMax;
|
||
this._scaleAm.translateX(this._tX).step();
|
||
this.setData({
|
||
scaleAm: this._scaleAm.export()
|
||
})
|
||
}
|
||
}
|
||
}
|
||
}) |