app_tree_planting/components/parser/parser.vue
itgaojian163 aae2813dd8 初始化
2023-01-11 19:31:59 +08:00

678 lines
20 KiB
Vue
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 主模块组件
github地址https://github.com/jin-yufeng/Parser
文档地址https://jin-yufeng.github.io/Parser
插件市场https://ext.dcloud.net.cn/plugin?id=805
authorJinYufeng
-->
<template>
<view style="display: inherit;">
<slot v-if="(!html||!html.length||(!html[0].name&&!html[0].type))&&!nodes.length"></slot>
<view class="_contain" :style="(selectable?'user-select:text;-webkit-user-select:text;':'')+(showWithAnimation?'opacity:0;':'')"
:animation="showAnimation" @tap="tap" @touchstart="touchstart" @touchmove="touchmove">
<!--#ifdef H5-->
<div :id="'rtf'+uid"></div>
<!--#endif-->
<!--#ifndef H5-->
<trees :nodes="nodes.length?nodes:(html&&html.length&&(html[0].name||html[0].type)?html:[])" :loadVideo="loadVideo" />
<!--#endif-->
</view>
</view>
</template>
<script>
// #ifndef H5
import trees from "./libs/trees"
var document; // document 补丁包,详见 https://jin-yufeng.github.io/Parser/#/instructions?id=document
const parseHtml = require('./libs/MpHtmlParser.js');
const cache = getApp().parserCache = {};
const CssHandler = require("./libs/CssHandler.js");
// 散列函数(计算 cache 的 key
function Hash(str) {
for (var i = str.length, hash = 5381; i--;)
hash += (hash << 5) + str.charCodeAt(i);
return hash;
};
// #endif
// 动画
const showAnimation = uni.createAnimation({
timingFunction: "ease"
}).opacity(1).step().export();
const config = require('./libs/config.js');
// #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU || MP-TOUTIAO
// 图片链接去重
function Deduplicate(src) {
if (src.indexOf("http") != 0) return src;
var newSrc = '';
for (var i = 0; i < src.length; i++) {
newSrc += (Math.random() >= 0.5 ? src[i].toUpperCase() : src[i].toLowerCase());
if (src[i] == '/' && src[i - 1] != '/' && src[i + 1] != '/') break;
}
newSrc += src.substring(i + 1);
return newSrc;
}
// #endif
export default {
name: 'parser',
data() {
return {
// #ifdef APP-PLUS
loadVideo: false,
// #endif
// #ifdef H5
uid: this._uid,
// #endif
showAnimation: '',
// #ifndef H5
controls: {},
// #endif
nodes: []
}
},
// #ifndef H5
components: {
trees
},
// #endif
props: {
"html": {
type: null,
default: null
},
// #ifndef MP-ALIPAY
"autopause": {
type: Boolean,
default: true
},
// #endif
"autosetTitle": {
type: Boolean,
default: true
},
"domain": {
type: String,
default: null
},
// #ifndef MP-BAIDU || MP-ALIPAY || APP-PLUS
"gestureZoom": {
type: Boolean,
default: false
},
// #endif
// #ifdef MP-WEIXIN || MP-QQ || H5 || APP-PLUS
"lazyLoad": {
type: Boolean,
default: false
},
// #endif
"selectable": {
type: Boolean,
default: false
},
"tagStyle": {
type: Object,
default: () => {
return {};
}
},
"showWithAnimation": {
type: Boolean,
default: false
},
"useAnchor": {
type: Boolean,
default: false
},
"useCache": {
type: Boolean,
default: false
}
},
watch: {
html(html) {
this.setContent(html, undefined, true);
}
},
mounted() {
this.imgList = [];
this.imgList.each = function(f) {
for (var i = 0; i < this.length; i++) {
// #ifdef MP-ALIPAY || APP-PLUS
this[i] = f(this[i], i, this) || this[i];
// #endif
// #ifndef MP-ALIPAY || APP-PLUS
var newSrc = f(this[i], i, this);
if (newSrc) {
if (this.includes(newSrc)) this[i] = Deduplicate(newSrc);
else this[i] = newSrc;
}
// #endif
}
}
this.setContent(this.html, undefined, true);
},
// #ifdef H5
beforeDestroy() {
if (this._observer) this._observer.disconnect();
},
// #endif
methods: {
// #ifdef H5
setContent(html, options, observed) {
if (typeof options == "object")
for (var key in options) {
key = key.replace(/-(\w)/g, function() {
return arguments[1].toUpperCase();
})
this[key] = options[key];
}
html = html || '';
if (!html) {
if (this.rtf) this.rtf.parentNode.removeChild(this.rtf);
return;
}
if (typeof html != "string") html = this.Dom2Str(html.nodes || html);
// 处理 rpx
if (html.includes("rpx"))
html = html.replace(/[0-9.]*rpx/g, function($) {
return parseFloat($) * config.rpx + "px";
})
// 处理 tag-style 和 userAgentStyles
var style = "<style>";
for (var item in config.userAgentStyles)
style += (item + '{' + config.userAgentStyles[item] + '}');
for (var item in this.tagStyle)
style += (item + '{' + this.tagStyle[item] + '}');
style += "</style>";
html = style + html;
if (this.rtf) this.rtf.parentNode.removeChild(this.rtf);
this.rtf = document.createElement('div');
this.rtf.innerHTML = html;
for (var style of this.rtf.getElementsByTagName("style")) {
style.innerHTML = style.innerHTML.replace(/\s*body/g, "#rtf" + this._uid);
style.setAttribute("scoped", "true");
}
// 懒加载
if (this.lazyLoad && IntersectionObserver) {
if (this._observer) this._observer.disconnect();
this._observer = new IntersectionObserver(changes => {
for (var change of changes) {
if (change.isIntersecting) {
change.target.src = change.target.getAttribute("data-src");
change.target.removeAttribute("data-src");
this._observer.unobserve(change.target);
}
}
}, {
rootMargin: "1000px 0px 1000px 0px"
})
}
var component = this;
// 获取标题
var title = this.rtf.getElementsByTagName("title");
if (title.length && this.autosetTitle)
uni.setNavigationBarTitle({
title: title[0].innerText
})
// 图片处理
this.imgList.length = 0;
var imgs = this.rtf.getElementsByTagName("img");
for (var i = 0; i < imgs.length; i++) {
var img = imgs[i];
img.style.maxWidth = "100%";
img.i = i;
if (this.domain && img.getAttribute("src")[0] == "/") {
if (img.getAttribute("src")[1] == "/")
img.src = (this.domain.includes("://") ? this.domain.split("://")[0] : "http") + ':' + img.getAttribute("src");
else img.src = this.domain + img.getAttribute("src");
}
component.imgList.push(img.src);
if (img.parentElement.nodeName != 'A') {
img.onclick = function() {
if (!this.hasAttribute('ignore')) {
var preview = true;
this.ignore = () => preview = false;
component.$emit('imgtap', this);
if (preview) {
uni.previewImage({
current: this.i,
urls: component.imgList
});
}
}
}
}
img.onerror = function() {
component.$emit('error', {
source: "img",
target: this
});
}
if (component.lazyLoad && this._observer) {
img.setAttribute("data-src", img.src);
img.removeAttribute("src");
this._observer.observe(img);
}
}
// 链接处理
var links = this.rtf.getElementsByTagName("a");
for (var link of links) {
link.onclick = function(e) {
var jump = true,
href = this.getAttribute("href");
component.$emit('linkpress', {
href,
ignore: () => jump = false
});
if (jump && href) {
if (href[0] == '#') {
if (component.useAnchor) {
component.navigateTo({
id: href.substring(1)
})
}
} else if (href.indexOf("http") == 0 || href.indexOf("//") == 0)
return true;
else {
uni.navigateTo({
url: href
})
}
}
return false;
}
}
// 视频处理
var videos = this.rtf.getElementsByTagName("video");
component.videoContexts = videos;
for (var video of videos) {
video.style.maxWidth = "100%";
video.onerror = function() {
component.$emit('error', {
source: "video",
target: this
});
}
video.onplay = function() {
if (component.autopause) {
for (var video of component.videoContexts) {
if (video != this)
video.pause();
}
}
}
}
// 音频处理
var audios = this.rtf.getElementsByTagName("audios");
for (var audio of audios) {
audio.onerror = function(e) {
component.$emit('error', {
source: "audio",
target: this
});
}
}
document.getElementById("rtf" + this._uid).appendChild(this.rtf);
if (this.showWithAnimation)
this.showAnimation = showAnimation;
if (!observed) this.nodes = [0];
this.$nextTick(() => {
var rect = this.rtf.getBoundingClientRect();
this.width = rect.width;
this.$emit("ready", rect);
})
},
Dom2Str(nodes) {
var str = "";
for (var node of nodes) {
if (node.type == "text")
str += node.text;
else {
str += ('<' + node.name);
for (var attr in node.attrs || {})
str += (' ' + attr + '="' + node.attrs[attr] + '"');
if (!node.children || !node.children.length) str += "/>";
else str += ('>' + this.Dom2Str(node.children) + "</" + node.name + '>');
}
}
return str;
},
getText(whiteSpace = true) {
if (!whiteSpace) return this.rtf.innerText.replace(/\s/g, '');
return this.rtf.innerText;
},
navigateTo(obj) {
obj.fail = obj.fail || (() => {});
if (!this.useAnchor)
return obj.fail({
errMsg: "Use-anchor attribute is disabled"
})
if (!obj.id) {
window.scrollTo(0, this.rtf.offsetTop);
return obj.success ? obj.success({
errMsg: "pageScrollTo:ok"
}) : null;
}
var target = document.getElementById(obj.id);
if (!target) return obj.fail({
errMsg: "Label not found"
});
uni.pageScrollTo({
scrollTop: this.rtf.offsetTop + target.offsetTop,
success: obj.success,
fail: obj.fail
});
},
// #endif
// #ifndef H5
setContent(html, options, observed) {
if (typeof options == "object")
for (var key in options) {
key = key.replace(/-(\w)/g, function($, $1) {
return $1.toUpperCase();
})
this[key] = options[key];
}
if (this.showWithAnimation)
this.showAnimation = showAnimation;
if (!html) {
if (observed) return;
else this.nodes = [];
} else if (typeof html == "string") {
// 缓存读取
if (this.useCache) {
var hash = Hash(html);
if (cache[hash])
this.nodes = cache[hash];
else {
this.nodes = parseHtml(html, this);
cache[hash] = this.nodes;
}
} else this.nodes = parseHtml(html, this);
this.$emit('parse', this.nodes);
} else if (Object.prototype.toString.call(html) == "[object Array]") {
if (!observed) this.nodes = html;
else this.nodes = [];
// 非本插件产生的 array 需要进行一些转换
if (html.length && html[0].PoweredBy != "Parser") {
const Parser = {
_imgNum: 0,
_videoNum: 0,
_audioNum: 0,
_domain: this.domain,
_protocol: this.domain ? (this.domain.includes("://") ? this.domain.split("://")[0] : "http") : undefined,
_STACK: [],
CssHandler: new CssHandler(this.tagStyle)
};
Parser.CssHandler.getStyle('');
function DFS(nodes) {
for (var i = 0, node; node = nodes[i++];) {
if (node.type == "text") continue;
node.attrs = node.attrs || {};
for (var item in node.attrs) {
if (!config.trustAttrs[item]) node.attrs[item] = undefined;
else if (typeof node.attrs[item] != "string") node.attrs[item] = node.attrs[item].toString();
}
config.LabelAttrsHandler(node, Parser);
if (config.blockTags[node.name]) node.name = 'div';
else if (!config.trustTags[node.name]) node.name = 'span';
if (node.children && node.children.length) {
Parser._STACK.push(node);
DFS(node.children);
Parser._STACK.pop();
} else node.children = undefined;
}
}
DFS(html);
this.nodes = html;
}
} else if (typeof html == 'object' && html.nodes) {
this.nodes = html.nodes;
console.warn("Parser 类型错误object 类型已废弃,请直接将 html 设置为 object.nodes array 类型)");
} else {
return this.$emit('error', {
source: "parse",
errMsg: "传入的nodes数组格式不正确应该传入的类型是array实际传入的类型是" + typeof html.nodes
});
}
// #ifdef APP-PLUS
this.loadVideo = false;
// #endif
if (document) this.document = new document("html", this.html || html, this);
this.$nextTick(() => {
this.imgList.length = 0;
this.videoContexts = [];
const getContext = (components) => {
for (var i = components.length; i--;) {
let component = components[i];
if (component.$options.name == "trees") {
var observered = false;
for (var j = component.nodes.length, item; item = component.nodes[--j];) {
if (item.c) continue;
if (item.name == 'img') {
if (item.attrs.src && item.attrs.i) {
// #ifndef MP-ALIPAY || APP-PLUS
if (this.imgList.indexOf(item.attrs.src) == -1)
this.imgList[item.attrs.i] = item.attrs.src;
else this.imgList[item.attrs.i] = Deduplicate(item.attrs.src);
// #endif
// #ifdef MP-ALIPAY || APP-PLUS
this.imgList[item.attrs.i] = item.attrs.src;
// #endif
}
// #ifndef MP-ALIPAY
if (!observered) {
observered = true;
if (this.lazyLoad && uni.createIntersectionObserver) {
if (component._observer) component._observer.disconnect();
component._observer = uni.createIntersectionObserver(component);
component._observer.relativeToViewport({
top: 1000,
bottom: 1000
}).observe('._img', res => {
component.imgLoad = true;
component._observer.disconnect();
component._observer = null;
})
} else
component.imgLoad = true;
}
// #endif
}
// #ifndef MP-ALIPAY
else if (item.name == 'video') {
var context = uni.createVideoContext(item.attrs.id, component);
context.id = item.attrs.id;
this.videoContexts.push(context);
}
// #endif
// #ifdef MP-WEIXIN
else if (item.name == 'audio' && item.attrs.autoplay)
wx.createAudioContext(item.attrs.id, component).play();
// #endif
// 设置标题
else if (item.name == "title" && this.autosetTitle && item.children[0].type == "text" && item.children[
0].text)
uni.setNavigationBarTitle({
title: item.children[0].text
})
// #ifdef MP-BAIDU || MP-ALIPAY || APP-PLUS
if (item.attrs && item.attrs.id) {
this.anchors = this.anchors || [];
this.anchors.push({
id: item.attrs.id,
node: component
})
}
// #endif
}
}
if (component.$children.length)
getContext(component.$children)
}
}
// #ifdef MP-TOUTIAO
setTimeout(() => {
// #endif
getContext(this.$children);
// #ifndef APP-PLUS
this.createSelectorQuery()
// #endif
// #ifdef APP-PLUS
uni.createSelectorQuery().in(this)
// #endif
.select("._contain").boundingClientRect().exec(res => {
this.width = (res[0] ? res[0] : res).width;
this.$emit("ready", res);
});
// #ifdef MP-TOUTIAO
}, 200)
// #endif
// #ifdef APP-PLUS
setTimeout(() => {
this.loadVideo = true;
}, 3000);
// #endif
})
},
getText(whiteSpace = true) {
var text = "";
const DFS = (node) => {
if (node.type == "text") return text += node.text;
else {
if (whiteSpace && (((node.name == 'p' || node.name == "div" || node.name == "tr" || node.name == "li" ||
/h[1-6]/.test(node.name)) && text && text[text.length - 1] != '\n') || node.name == "br"))
text += '\n';
if (node.children)
for (var i = 0; i < node.children.length; i++)
DFS(node.children[i]);
if (whiteSpace && (node.name == 'p' || node.name == "div" || node.name == "tr" || node.name == "li" || /h[1-6]/.test(
node.name)) && text && text[text.length - 1] != '\n')
text += '\n';
else if (whiteSpace && node.name == "td") text += '\t';
}
}
var nodes = ((this.nodes && this.nodes.length) ? this.nodes : (this.html[0] && (this.html[0].name || this.html[0].type) ?
this.html : []));
if (!nodes.length) return "";
for (var i = 0; i < this.data.html.length; i++) DFS(this.data.html[i]);
return text;
},
navigateTo(obj) {
obj.fail = obj.fail || (() => {});
if (!this.useAnchor)
return obj.fail({
errMsg: "Use-anchor attribute is disabled"
})
var Scroll = (selector, component) => {
const query = uni.createSelectorQuery().in(component ? component : this);
query.select(selector).boundingClientRect();
query.selectViewport().scrollOffset();
query.exec(res => {
if (!res || !res[0])
return obj.fail({
errMsg: "Label not found"
});
uni.pageScrollTo({
scrollTop: res[1].scrollTop + res[0].top,
success: obj.success,
fail: obj.fail
})
})
}
if (!obj.id) Scroll("._contain");
else {
// #ifndef MP-BAIDU || MP-ALIPAY || APP-PLUS
Scroll('._contain >>> #' + obj.id + ', ._contain >>> .' + obj.id);
// #endif
// #ifdef MP-BAIDU || MP-ALIPAY || APP-PLUS
for (var anchor of this.anchors) {
if (anchor.id == obj.id) {
Scroll("#" + obj.id + ", ." + obj.id, anchor.node);
}
}
// #endif
}
},
// #endif
tap(e) {
// #ifndef MP-BAIDU || MP-ALIPAY || APP-PLUS
if (this.gestureZoom && e.timeStamp - this.lastTime < 300) {
if (this.zoomIn) {
this.animation.translateX(0).scale(1).step();
uni.pageScrollTo({
scrollTop: (e.touches[0].pageY - e.currentTarget.offsetTop + this.initY) / 2 - e.touches[0].clientY,
duration: 400
})
} else {
var initX = e.touches[0].pageX - e.currentTarget.offsetLeft;
this.initY = e.touches[0].pageY - e.currentTarget.offsetTop;
this.animation = uni.createAnimation({
transformOrigin: initX + "px " + this.initY + "px 0",
timingFunction: "ease-in-out"
});
// #ifdef MP-TOUTIAO
this.animation.opacity(1);
// #endif
this.animation.scale(2).step();
this.translateMax = initX / 2;
this.translateMin = (initX - this.width) / 2;
this.translateX = 0;
}
this.zoomIn = !this.zoomIn;
this.showAnimation = this.animation.export();
}
this.lastTime = e.timeStamp;
// #endif
},
touchstart(e) {
// #ifndef MP-BAIDU || MP-ALIPAY || APP-PLUS
if (e.touches.length == 1)
this.initX = this.lastX = e.touches[0].pageX;
// #endif
},
touchmove(e) {
// #ifndef MP-BAIDU || MP-ALIPAY || APP-PLUS
var diff = e.touches[0].pageX - this.lastX;
if (this.zoomIn && e.touches.length == 1 && Math.abs(diff) > 20) {
this.lastX = e.touches[0].pageX;
if ((this.translateX <= this.translateMin && diff < 0) || (this.translateX >= this.translateMax && diff > 0))
return;
this.translateX += (diff * Math.abs(this.lastX - this.initX) * 0.05);
if (this.translateX < this.translateMin)
this.translateX = this.translateMin;
if (this.translateX > this.translateMax)
this.translateX = this.translateMax;
this.animation.translateX(this.translateX).step();
this.showAnimation = this.animation.export();
}
// #endif
},
getVideoContext(id) {
if (!id) return this.videoContexts;
else
for (var i = this.videoContexts.length; i--;)
if (this.videoContexts[i].id == id) return this.videoContexts[i];
return null;
}
}
}
</script>
<style>
/* #ifndef MP-WEIXIN || APP-PLUS */
:host {
display: block;
overflow: scroll;
-webkit-overflow-scrolling: touch;
}
._contain {
display: inherit;
}
/* #endif */
</style>