examtest_mp/components/parser/parser.js

312 lines
10 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
parser 主组件
githubhttps://github.com/jin-yufeng/Parser
docshttps://jin-yufeng.github.io/Parser
authorJinYufeng
update2020/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(/&nbsp;/g, '\u00A0').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/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()
})
}
}
}
})