20 changed files with 1459 additions and 149 deletions
@ -0,0 +1,97 @@ |
|||||
|
const cfg = require('./config.js'), |
||||
|
isLetter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); |
||||
|
|
||||
|
function CssHandler(tagStyle) { |
||||
|
var styles = Object.assign(Object.create(null), cfg.userAgentStyles); |
||||
|
for (var item in tagStyle) |
||||
|
styles[item] = (styles[item] ? styles[item] + ';' : '') + tagStyle[item]; |
||||
|
this.styles = styles; |
||||
|
} |
||||
|
CssHandler.prototype.getStyle = function (data) { |
||||
|
this.styles = new parser(data, this.styles).parse(); |
||||
|
} |
||||
|
CssHandler.prototype.match = function (name, attrs) { |
||||
|
var tmp, matched = (tmp = this.styles[name]) ? tmp + ';' : ''; |
||||
|
if (attrs.class) { |
||||
|
var items = attrs.class.split(' '); |
||||
|
for (var i = 0, item; item = items[i]; i++) |
||||
|
if (tmp = this.styles['.' + item]) |
||||
|
matched += tmp + ';'; |
||||
|
} |
||||
|
if (tmp = this.styles['#' + attrs.id]) |
||||
|
matched += tmp + ';'; |
||||
|
return matched; |
||||
|
} |
||||
|
module.exports = CssHandler; |
||||
|
|
||||
|
function parser(data, init) { |
||||
|
this.data = data; |
||||
|
this.floor = 0; |
||||
|
this.i = 0; |
||||
|
this.list = []; |
||||
|
this.res = init; |
||||
|
this.state = this.Space; |
||||
|
} |
||||
|
parser.prototype.parse = function () { |
||||
|
for (var c; c = this.data[this.i]; this.i++) |
||||
|
this.state(c); |
||||
|
return this.res; |
||||
|
} |
||||
|
parser.prototype.section = function () { |
||||
|
return this.data.substring(this.start, this.i); |
||||
|
} |
||||
|
// 状态机
|
||||
|
parser.prototype.Space = function (c) { |
||||
|
if (c == '.' || c == '#' || isLetter(c)) { |
||||
|
this.start = this.i; |
||||
|
this.state = this.Name; |
||||
|
} else if (c == '/' && this.data[this.i + 1] == '*') |
||||
|
this.Comment(); |
||||
|
else if (!cfg.blankChar[c] && c != ';') |
||||
|
this.state = this.Ignore; |
||||
|
} |
||||
|
parser.prototype.Comment = function () { |
||||
|
this.i = this.data.indexOf('*/', this.i) + 1; |
||||
|
if (!this.i) this.i = this.data.length; |
||||
|
this.state = this.Space; |
||||
|
} |
||||
|
parser.prototype.Ignore = function (c) { |
||||
|
if (c == '{') this.floor++; |
||||
|
else if (c == '}' && !--this.floor) this.state = this.Space; |
||||
|
} |
||||
|
parser.prototype.Name = function (c) { |
||||
|
if (cfg.blankChar[c]) { |
||||
|
this.list.push(this.section()); |
||||
|
this.state = this.NameSpace; |
||||
|
} else if (c == '{') { |
||||
|
this.list.push(this.section()); |
||||
|
this.Content(); |
||||
|
} else if (c == ',') { |
||||
|
this.list.push(this.section()); |
||||
|
this.Comma(); |
||||
|
} else if (!isLetter(c) && (c < '0' || c > '9') && c != '-' && c != '_') |
||||
|
this.state = this.Ignore; |
||||
|
} |
||||
|
parser.prototype.NameSpace = function (c) { |
||||
|
if (c == '{') this.Content(); |
||||
|
else if (c == ',') this.Comma(); |
||||
|
else if (!cfg.blankChar[c]) this.state = this.Ignore; |
||||
|
} |
||||
|
parser.prototype.Comma = function () { |
||||
|
while (cfg.blankChar[this.data[++this.i]]); |
||||
|
if (this.data[this.i] == '{') this.Content(); |
||||
|
else { |
||||
|
this.start = this.i--; |
||||
|
this.state = this.Name; |
||||
|
} |
||||
|
} |
||||
|
parser.prototype.Content = function () { |
||||
|
this.start = ++this.i; |
||||
|
if ((this.i = this.data.indexOf('}', this.i)) == -1) this.i = this.data.length; |
||||
|
var content = this.section(); |
||||
|
for (var i = 0, item; item = this.list[i++];) |
||||
|
if (this.res[item]) this.res[item] += ';' + content; |
||||
|
else this.res[item] = content; |
||||
|
this.list = []; |
||||
|
this.state = this.Space; |
||||
|
} |
||||
@ -0,0 +1,525 @@ |
|||||
|
/** |
||||
|
* html 解析器 |
||||
|
* @tutorial https://github.com/jin-yufeng/Parser
|
||||
|
* @version 20200719 |
||||
|
* @author JinYufeng |
||||
|
* @listens MIT |
||||
|
*/ |
||||
|
const cfg = require('./config.js'), |
||||
|
blankChar = cfg.blankChar, |
||||
|
CssHandler = require('./CssHandler.js'), |
||||
|
windowWidth = wx.getSystemInfoSync().windowWidth; |
||||
|
var emoji; |
||||
|
|
||||
|
function MpHtmlParser(data, options = {}) { |
||||
|
this.attrs = {}; |
||||
|
this.CssHandler = new CssHandler(options.tagStyle, windowWidth); |
||||
|
this.data = data; |
||||
|
this.domain = options.domain; |
||||
|
this.DOM = []; |
||||
|
this.i = this.start = this.audioNum = this.imgNum = this.videoNum = 0; |
||||
|
options.prot = (this.domain || '').includes('://') ? this.domain.split('://')[0] : 'http'; |
||||
|
this.options = options; |
||||
|
this.state = this.Text; |
||||
|
this.STACK = []; |
||||
|
// 工具函数
|
||||
|
this.bubble = () => { |
||||
|
for (var i = this.STACK.length, item; item = this.STACK[--i];) { |
||||
|
if (cfg.richOnlyTags[item.name]) { |
||||
|
if (item.name == 'table' && !Object.hasOwnProperty.call(item, 'c')) item.c = 1; |
||||
|
return false; |
||||
|
} |
||||
|
item.c = 1; |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
this.decode = (val, amp) => { |
||||
|
var i = -1, |
||||
|
j, en; |
||||
|
while (1) { |
||||
|
if ((i = val.indexOf('&', i + 1)) == -1) break; |
||||
|
if ((j = val.indexOf(';', i + 2)) == -1) break; |
||||
|
if (val[i + 1] == '#') { |
||||
|
en = parseInt((val[i + 2] == 'x' ? '0' : '') + val.substring(i + 2, j)); |
||||
|
if (!isNaN(en)) val = val.substr(0, i) + String.fromCharCode(en) + val.substr(j + 1); |
||||
|
} else { |
||||
|
en = val.substring(i + 1, j); |
||||
|
if (cfg.entities[en] || en == amp) |
||||
|
val = val.substr(0, i) + (cfg.entities[en] || '&') + val.substr(j + 1); |
||||
|
} |
||||
|
} |
||||
|
return val; |
||||
|
} |
||||
|
this.getUrl = url => { |
||||
|
if (url[0] == '/') { |
||||
|
if (url[1] == '/') url = this.options.prot + ':' + url; |
||||
|
else if (this.domain) url = this.domain + url; |
||||
|
} else if (this.domain && url.indexOf('data:') != 0 && !url.includes('://')) |
||||
|
url = this.domain + '/' + url; |
||||
|
return url; |
||||
|
} |
||||
|
this.isClose = () => this.data[this.i] == '>' || (this.data[this.i] == '/' && this.data[this.i + 1] == '>'); |
||||
|
this.section = () => this.data.substring(this.start, this.i); |
||||
|
this.parent = () => this.STACK[this.STACK.length - 1]; |
||||
|
this.siblings = () => this.STACK.length ? this.parent().children : this.DOM; |
||||
|
} |
||||
|
MpHtmlParser.prototype.parse = function () { |
||||
|
if (emoji) this.data = emoji.parseEmoji(this.data); |
||||
|
for (var c; c = this.data[this.i]; this.i++) |
||||
|
this.state(c); |
||||
|
if (this.state == this.Text) this.setText(); |
||||
|
while (this.STACK.length) this.popNode(this.STACK.pop()); |
||||
|
return this.DOM; |
||||
|
} |
||||
|
// 设置属性
|
||||
|
MpHtmlParser.prototype.setAttr = function () { |
||||
|
var name = this.attrName.toLowerCase(), |
||||
|
val = this.attrVal; |
||||
|
if (cfg.boolAttrs[name]) this.attrs[name] = 'T'; |
||||
|
else if (val) { |
||||
|
if (name == 'src' || (name == 'data-src' && !this.attrs.src)) this.attrs.src = this.getUrl(this.decode(val, 'amp')); |
||||
|
else if (name == 'href' || name == 'style') this.attrs[name] = this.decode(val, 'amp'); |
||||
|
else if (name.substr(0, 5) != 'data-') this.attrs[name] = val; |
||||
|
} |
||||
|
this.attrVal = ''; |
||||
|
while (blankChar[this.data[this.i]]) this.i++; |
||||
|
if (this.isClose()) this.setNode(); |
||||
|
else { |
||||
|
this.start = this.i; |
||||
|
this.state = this.AttrName; |
||||
|
} |
||||
|
} |
||||
|
// 设置文本节点
|
||||
|
MpHtmlParser.prototype.setText = function () { |
||||
|
var back, text = this.section(); |
||||
|
if (!text) return; |
||||
|
text = (cfg.onText && cfg.onText(text, () => back = true)) || text; |
||||
|
if (back) { |
||||
|
this.data = this.data.substr(0, this.start) + text + this.data.substr(this.i); |
||||
|
let j = this.start + text.length; |
||||
|
for (this.i = this.start; this.i < j; this.i++) this.state(this.data[this.i]); |
||||
|
return; |
||||
|
} |
||||
|
if (!this.pre) { |
||||
|
// 合并空白符
|
||||
|
var flag, tmp = []; |
||||
|
for (let i = text.length, c; c = text[--i];) |
||||
|
if (!blankChar[c]) { |
||||
|
tmp.unshift(c); |
||||
|
if (!flag) flag = 1; |
||||
|
} else { |
||||
|
if (tmp[0] != ' ') tmp.unshift(' '); |
||||
|
if (c == '\n' && flag == void 0) flag = 0; |
||||
|
} |
||||
|
if (flag == 0) return; |
||||
|
text = tmp.join(''); |
||||
|
} |
||||
|
this.siblings().push({ |
||||
|
type: 'text', |
||||
|
text: this.decode(text) |
||||
|
}); |
||||
|
} |
||||
|
// 设置元素节点
|
||||
|
MpHtmlParser.prototype.setNode = function () { |
||||
|
var node = { |
||||
|
name: this.tagName.toLowerCase(), |
||||
|
attrs: this.attrs |
||||
|
}, |
||||
|
close = cfg.selfClosingTags[node.name]; |
||||
|
this.attrs = {}; |
||||
|
if (!cfg.ignoreTags[node.name]) { |
||||
|
// 处理属性
|
||||
|
var attrs = node.attrs, |
||||
|
style = this.CssHandler.match(node.name, attrs, node) + (attrs.style || ''), |
||||
|
styleObj = {}; |
||||
|
if (attrs.id) { |
||||
|
if (this.options.compress & 1) attrs.id = void 0; |
||||
|
else if (this.options.useAnchor) this.bubble(); |
||||
|
} |
||||
|
if ((this.options.compress & 2) && attrs.class) attrs.class = void 0; |
||||
|
switch (node.name) { |
||||
|
case 'a': |
||||
|
case 'ad': |
||||
|
this.bubble(); |
||||
|
break; |
||||
|
case 'font': |
||||
|
if (attrs.color) { |
||||
|
styleObj['color'] = attrs.color; |
||||
|
attrs.color = void 0; |
||||
|
} |
||||
|
if (attrs.face) { |
||||
|
styleObj['font-family'] = attrs.face; |
||||
|
attrs.face = void 0; |
||||
|
} |
||||
|
if (attrs.size) { |
||||
|
var size = parseInt(attrs.size); |
||||
|
if (size < 1) size = 1; |
||||
|
else if (size > 7) size = 7; |
||||
|
var map = ['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large']; |
||||
|
styleObj['font-size'] = map[size - 1]; |
||||
|
attrs.size = void 0; |
||||
|
} |
||||
|
break; |
||||
|
case 'embed': |
||||
|
var src = node.attrs.src || '', |
||||
|
type = node.attrs.type || ''; |
||||
|
if (type.includes('video') || src.includes('.mp4') || src.includes('.3gp') || src.includes('.m3u8')) |
||||
|
node.name = 'video'; |
||||
|
else if (type.includes('audio') || src.includes('.m4a') || src.includes('.wav') || src.includes('.mp3') || src.includes('.aac')) |
||||
|
node.name = 'audio'; |
||||
|
else break; |
||||
|
if (node.attrs.autostart) |
||||
|
node.attrs.autoplay = 'T'; |
||||
|
node.attrs.controls = 'T'; |
||||
|
// falls through
|
||||
|
case 'video': |
||||
|
case 'audio': |
||||
|
if (!attrs.id) attrs.id = node.name + (++this[`${node.name}Num`]); |
||||
|
else this[`${node.name}Num`]++; |
||||
|
if (node.name == 'video') { |
||||
|
if (this.videoNum > 3) |
||||
|
node.lazyLoad = 1; |
||||
|
if (attrs.width) { |
||||
|
styleObj.width = parseFloat(attrs.width) + (attrs.width.includes('%') ? '%' : 'px'); |
||||
|
attrs.width = void 0; |
||||
|
} |
||||
|
if (attrs.height) { |
||||
|
styleObj.height = parseFloat(attrs.height) + (attrs.height.includes('%') ? '%' : 'px'); |
||||
|
attrs.height = void 0; |
||||
|
} |
||||
|
} |
||||
|
if (!attrs.controls && !attrs.autoplay) attrs.controls = 'T'; |
||||
|
attrs.source = []; |
||||
|
if (attrs.src) { |
||||
|
attrs.source.push(attrs.src); |
||||
|
attrs.src = void 0; |
||||
|
} |
||||
|
this.bubble(); |
||||
|
break; |
||||
|
case 'td': |
||||
|
case 'th': |
||||
|
if (attrs.colspan || attrs.rowspan) |
||||
|
for (var k = this.STACK.length, item; item = this.STACK[--k];) |
||||
|
if (item.name == 'table') { |
||||
|
item.c = void 0; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
if (attrs.align) { |
||||
|
styleObj['text-align'] = attrs.align; |
||||
|
attrs.align = void 0; |
||||
|
} |
||||
|
// 压缩 style
|
||||
|
var styles = style.split(';'); |
||||
|
style = ''; |
||||
|
for (var i = 0, len = styles.length; i < len; i++) { |
||||
|
var info = styles[i].split(':'); |
||||
|
if (info.length < 2) continue; |
||||
|
let key = info[0].trim().toLowerCase(), |
||||
|
value = info.slice(1).join(':').trim(); |
||||
|
if (value[0] == '-' || value.includes('safe')) |
||||
|
style += `;${key}:${value}`; |
||||
|
else if (!styleObj[key] || value.includes('import') || !styleObj[key].includes('import')) |
||||
|
styleObj[key] = value; |
||||
|
} |
||||
|
if (node.name == 'img') { |
||||
|
if (attrs.src && !attrs.ignore) { |
||||
|
if (this.bubble()) |
||||
|
attrs.i = (this.imgNum++).toString(); |
||||
|
else attrs.ignore = 'T'; |
||||
|
} |
||||
|
if (attrs.ignore) { |
||||
|
style += ';-webkit-touch-callout:none'; |
||||
|
styleObj['max-width'] = '100%'; |
||||
|
} |
||||
|
var width; |
||||
|
if (styleObj.width) width = styleObj.width; |
||||
|
else if (attrs.width) width = attrs.width.includes('%') ? attrs.width : attrs.width + 'px'; |
||||
|
if (width) { |
||||
|
styleObj.width = width; |
||||
|
attrs.width = '100%'; |
||||
|
if (parseInt(width) > windowWidth) { |
||||
|
styleObj.height = ''; |
||||
|
if (attrs.height) attrs.height = void 0; |
||||
|
} |
||||
|
} |
||||
|
if (styleObj.height) { |
||||
|
attrs.height = styleObj.height; |
||||
|
styleObj.height = ''; |
||||
|
} else if (attrs.height && !attrs.height.includes('%')) |
||||
|
attrs.height += 'px'; |
||||
|
} |
||||
|
for (var key in styleObj) { |
||||
|
var value = styleObj[key]; |
||||
|
if (!value) continue; |
||||
|
if (key.includes('flex') || key == 'order' || key == 'self-align') node.c = 1; |
||||
|
// 填充链接
|
||||
|
if (value.includes('url')) { |
||||
|
var j = value.indexOf('('); |
||||
|
if (j++ != -1) { |
||||
|
while (value[j] == '"' || value[j] == "'" || blankChar[value[j]]) j++; |
||||
|
value = value.substr(0, j) + this.getUrl(value.substr(j)); |
||||
|
} |
||||
|
} |
||||
|
// 转换 rpx
|
||||
|
else if (value.includes('rpx')) |
||||
|
value = value.replace(/[0-9.]+\s*rpx/g, $ => parseFloat($) * windowWidth / 750 + 'px'); |
||||
|
else if (key == 'white-space' && value.includes('pre') && !close) |
||||
|
this.pre = node.pre = true; |
||||
|
style += `;${key}:${value}`; |
||||
|
} |
||||
|
style = style.substr(1); |
||||
|
if (style) attrs.style = style; |
||||
|
if (!close) { |
||||
|
node.children = []; |
||||
|
if (node.name == 'pre' && cfg.highlight) { |
||||
|
this.remove(node); |
||||
|
this.pre = node.pre = true; |
||||
|
} |
||||
|
this.siblings().push(node); |
||||
|
this.STACK.push(node); |
||||
|
} else if (!cfg.filter || cfg.filter(node, this) != false) |
||||
|
this.siblings().push(node); |
||||
|
} else { |
||||
|
if (!close) this.remove(node); |
||||
|
else if (node.name == 'source') { |
||||
|
var parent = this.parent(); |
||||
|
if (parent && (parent.name == 'video' || parent.name == 'audio') && node.attrs.src) |
||||
|
parent.attrs.source.push(node.attrs.src); |
||||
|
} else if (node.name == 'base' && !this.domain) this.domain = node.attrs.href; |
||||
|
} |
||||
|
if (this.data[this.i] == '/') this.i++; |
||||
|
this.start = this.i + 1; |
||||
|
this.state = this.Text; |
||||
|
} |
||||
|
// 移除标签
|
||||
|
MpHtmlParser.prototype.remove = function (node) { |
||||
|
var name = node.name, |
||||
|
j = this.i; |
||||
|
// 处理 svg
|
||||
|
var handleSvg = () => { |
||||
|
var src = this.data.substring(j, this.i + 1); |
||||
|
if (!node.attrs.xmlns) src = ' xmlns="http://www.w3.org/2000/svg"' + src; |
||||
|
var i = j; |
||||
|
while (this.data[j] != '<') j--; |
||||
|
src = this.data.substring(j, i).replace("viewbox", "viewBox") + src; |
||||
|
var parent = this.parent(); |
||||
|
if (node.attrs.width == '100%' && parent && (parent.attrs.style || '').includes('inline')) |
||||
|
parent.attrs.style = 'width:300px;max-width:100%;' + parent.attrs.style; |
||||
|
this.siblings().push({ |
||||
|
name: 'img', |
||||
|
attrs: { |
||||
|
src: 'data:image/svg+xml;utf8,' + src.replace(/#/g, '%23'), |
||||
|
style: (/vertical[^;]+/.exec(node.attrs.style) || []).shift(), |
||||
|
ignore: 'T' |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
if (node.name == 'svg' && this.data[j] == '/') return handleSvg(this.i++); |
||||
|
while (1) { |
||||
|
if ((this.i = this.data.indexOf('</', this.i + 1)) == -1) { |
||||
|
if (name == 'pre' || name == 'svg') this.i = j; |
||||
|
else this.i = this.data.length; |
||||
|
return; |
||||
|
} |
||||
|
this.start = (this.i += 2); |
||||
|
while (!blankChar[this.data[this.i]] && !this.isClose()) this.i++; |
||||
|
if (this.section().toLowerCase() == name) { |
||||
|
// 代码块高亮
|
||||
|
if (name == 'pre') { |
||||
|
this.data = this.data.substr(0, j + 1) + cfg.highlight(this.data.substring(j + 1, this.i - 5), node.attrs) + this.data.substr(this.i - 5); |
||||
|
return this.i = j; |
||||
|
} else if (name == 'style') |
||||
|
this.CssHandler.getStyle(this.data.substring(j + 1, this.i - 7)); |
||||
|
else if (name == 'title') |
||||
|
this.DOM.title = this.data.substring(j + 1, this.i - 7); |
||||
|
if ((this.i = this.data.indexOf('>', this.i)) == -1) this.i = this.data.length; |
||||
|
if (name == 'svg') handleSvg(); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
// 节点出栈处理
|
||||
|
MpHtmlParser.prototype.popNode = function (node) { |
||||
|
// 空白符处理
|
||||
|
if (node.pre) { |
||||
|
node.pre = this.pre = void 0; |
||||
|
for (let i = this.STACK.length; i--;) |
||||
|
if (this.STACK[i].pre) |
||||
|
this.pre = true; |
||||
|
} |
||||
|
var siblings = this.siblings(), |
||||
|
len = siblings.length, |
||||
|
childs = node.children; |
||||
|
if (node.name == 'head' || (cfg.filter && cfg.filter(node, this) == false)) |
||||
|
return siblings.pop(); |
||||
|
var attrs = node.attrs; |
||||
|
// 替换一些标签名
|
||||
|
if (cfg.blockTags[node.name]) node.name = 'div'; |
||||
|
else if (!cfg.trustTags[node.name]) node.name = 'span'; |
||||
|
// 处理列表
|
||||
|
if (node.c && (node.name == 'ul' || node.name == 'ol')) { |
||||
|
if ((node.attrs.style || '').includes('list-style:none')) { |
||||
|
for (let i = 0, child; child = childs[i++];) |
||||
|
if (child.name == 'li') |
||||
|
child.name = 'div'; |
||||
|
} else if (node.name == 'ul') { |
||||
|
var floor = 1; |
||||
|
for (let i = this.STACK.length; i--;) |
||||
|
if (this.STACK[i].name == 'ul') floor++; |
||||
|
if (floor != 1) |
||||
|
for (let i = childs.length; i--;) |
||||
|
childs[i].floor = floor; |
||||
|
} else { |
||||
|
for (let i = 0, num = 1, child; child = childs[i++];) |
||||
|
if (child.name == 'li') { |
||||
|
child.type = 'ol'; |
||||
|
child.num = ((num, type) => { |
||||
|
if (type == 'a') return String.fromCharCode(97 + (num - 1) % 26); |
||||
|
if (type == 'A') return String.fromCharCode(65 + (num - 1) % 26); |
||||
|
if (type == 'i' || type == 'I') { |
||||
|
num = (num - 1) % 99 + 1; |
||||
|
var one = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'], |
||||
|
ten = ['X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'], |
||||
|
res = (ten[Math.floor(num / 10) - 1] || '') + (one[num % 10 - 1] || ''); |
||||
|
if (type == 'i') return res.toLowerCase(); |
||||
|
return res; |
||||
|
} |
||||
|
return num; |
||||
|
})(num++, attrs.type) + '.'; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
// 处理表格的边框
|
||||
|
if (node.name == 'table') { |
||||
|
var padding = attrs.cellpadding, |
||||
|
spacing = attrs.cellspacing, |
||||
|
border = attrs.border; |
||||
|
if (node.c) { |
||||
|
this.bubble(); |
||||
|
attrs.style = (attrs.style || '') + ';display:table'; |
||||
|
if (!padding) padding = 2; |
||||
|
if (!spacing) spacing = 2; |
||||
|
} |
||||
|
if (border) attrs.style = `border:${border}px solid gray;${attrs.style || ''}`; |
||||
|
if (spacing) attrs.style = `border-spacing:${spacing}px;${attrs.style || ''}`; |
||||
|
if (border || padding || node.c) |
||||
|
(function f(ns) { |
||||
|
for (var i = 0, n; n = ns[i]; i++) { |
||||
|
if (n.type == 'text') continue; |
||||
|
var style = n.attrs.style || ''; |
||||
|
if (node.c && n.name[0] == 't') { |
||||
|
n.c = 1; |
||||
|
style += ';display:table-' + (n.name == 'th' || n.name == 'td' ? 'cell' : (n.name == 'tr' ? 'row' : 'row-group')); |
||||
|
} |
||||
|
if (n.name == 'th' || n.name == 'td') { |
||||
|
if (border) style = `border:${border}px solid gray;${style}`; |
||||
|
if (padding) style = `padding:${padding}px;${style}`; |
||||
|
} else f(n.children || []); |
||||
|
if (style) n.attrs.style = style; |
||||
|
} |
||||
|
})(childs) |
||||
|
if (this.options.autoscroll) { |
||||
|
var table = Object.assign({}, node); |
||||
|
node.name = 'div'; |
||||
|
node.attrs = { |
||||
|
style: 'overflow:scroll' |
||||
|
} |
||||
|
node.children = [table]; |
||||
|
} |
||||
|
} |
||||
|
this.CssHandler.pop && this.CssHandler.pop(node); |
||||
|
// 自动压缩
|
||||
|
if (node.name == 'div' && !Object.keys(attrs).length && childs.length == 1 && childs[0].name == 'div') |
||||
|
siblings[len - 1] = childs[0]; |
||||
|
} |
||||
|
// 状态机
|
||||
|
MpHtmlParser.prototype.Text = function (c) { |
||||
|
if (c == '<') { |
||||
|
var next = this.data[this.i + 1], |
||||
|
isLetter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); |
||||
|
if (isLetter(next)) { |
||||
|
this.setText(); |
||||
|
this.start = this.i + 1; |
||||
|
this.state = this.TagName; |
||||
|
} else if (next == '/') { |
||||
|
this.setText(); |
||||
|
if (isLetter(this.data[++this.i + 1])) { |
||||
|
this.start = this.i + 1; |
||||
|
this.state = this.EndTag; |
||||
|
} else this.Comment(); |
||||
|
} else if (next == '!' || next == '?') { |
||||
|
this.setText(); |
||||
|
this.Comment(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
MpHtmlParser.prototype.Comment = function () { |
||||
|
var key; |
||||
|
if (this.data.substring(this.i + 2, this.i + 4) == '--') key = '-->'; |
||||
|
else if (this.data.substring(this.i + 2, this.i + 9) == '[CDATA[') key = ']]>'; |
||||
|
else key = '>'; |
||||
|
if ((this.i = this.data.indexOf(key, this.i + 2)) == -1) this.i = this.data.length; |
||||
|
else this.i += key.length - 1; |
||||
|
this.start = this.i + 1; |
||||
|
this.state = this.Text; |
||||
|
} |
||||
|
MpHtmlParser.prototype.TagName = function (c) { |
||||
|
if (blankChar[c]) { |
||||
|
this.tagName = this.section(); |
||||
|
while (blankChar[this.data[this.i]]) this.i++; |
||||
|
if (this.isClose()) this.setNode(); |
||||
|
else { |
||||
|
this.start = this.i; |
||||
|
this.state = this.AttrName; |
||||
|
} |
||||
|
} else if (this.isClose()) { |
||||
|
this.tagName = this.section(); |
||||
|
this.setNode(); |
||||
|
} |
||||
|
} |
||||
|
MpHtmlParser.prototype.AttrName = function (c) { |
||||
|
if (c == '=' || blankChar[c] || this.isClose()) { |
||||
|
this.attrName = this.section(); |
||||
|
if (blankChar[c]) |
||||
|
while (blankChar[this.data[++this.i]]); |
||||
|
if (this.data[this.i] == '=') { |
||||
|
while (blankChar[this.data[++this.i]]); |
||||
|
this.start = this.i--; |
||||
|
this.state = this.AttrValue; |
||||
|
} else this.setAttr(); |
||||
|
} |
||||
|
} |
||||
|
MpHtmlParser.prototype.AttrValue = function (c) { |
||||
|
if (c == '"' || c == "'") { |
||||
|
this.start++; |
||||
|
if ((this.i = this.data.indexOf(c, this.i + 1)) == -1) return this.i = this.data.length; |
||||
|
this.attrVal = this.section(); |
||||
|
this.i++; |
||||
|
} else { |
||||
|
for (; !blankChar[this.data[this.i]] && !this.isClose(); this.i++); |
||||
|
this.attrVal = this.section(); |
||||
|
} |
||||
|
this.setAttr(); |
||||
|
} |
||||
|
MpHtmlParser.prototype.EndTag = function (c) { |
||||
|
if (blankChar[c] || c == '>' || c == '/') { |
||||
|
var name = this.section().toLowerCase(); |
||||
|
for (var i = this.STACK.length; i--;) |
||||
|
if (this.STACK[i].name == name) break; |
||||
|
if (i != -1) { |
||||
|
var node; |
||||
|
while ((node = this.STACK.pop()).name != name) this.popNode(node); |
||||
|
this.popNode(node); |
||||
|
} else if (name == 'p' || name == 'br') |
||||
|
this.siblings().push({ |
||||
|
name, |
||||
|
attrs: {} |
||||
|
}); |
||||
|
this.i = this.data.indexOf('>', this.i); |
||||
|
this.start = this.i + 1; |
||||
|
if (this.i == -1) this.i = this.data.length; |
||||
|
else this.state = this.Text; |
||||
|
} |
||||
|
} |
||||
|
module.exports = MpHtmlParser; |
||||
@ -0,0 +1,63 @@ |
|||||
|
/* 配置文件 */ |
||||
|
const canIUse = wx.canIUse('editor'); // 高基础库标识,用于兼容
|
||||
|
module.exports = { |
||||
|
// 出错占位图
|
||||
|
errorImg: null, |
||||
|
// 过滤器函数
|
||||
|
filter: null, |
||||
|
// 代码高亮函数
|
||||
|
highlight: null, |
||||
|
// 文本处理函数
|
||||
|
onText: null, |
||||
|
// 实体编码列表
|
||||
|
entities: { |
||||
|
quot: '"', |
||||
|
apos: "'", |
||||
|
semi: ';', |
||||
|
nbsp: '\xA0', |
||||
|
ndash: '–', |
||||
|
mdash: '—', |
||||
|
middot: '·', |
||||
|
lsquo: '‘', |
||||
|
rsquo: '’', |
||||
|
ldquo: '“', |
||||
|
rdquo: '”', |
||||
|
bull: '•', |
||||
|
hellip: '…' |
||||
|
}, |
||||
|
blankChar: makeMap(' ,\xA0,\t,\r,\n,\f'), |
||||
|
boolAttrs: makeMap('autoplay,autostart,controls,ignore,loop,muted'), |
||||
|
// 块级标签,将被转为 div
|
||||
|
blockTags: makeMap('address,article,aside,body,caption,center,cite,footer,header,html,nav,section' + (canIUse ? '' : ',pre')), |
||||
|
// 将被移除的标签
|
||||
|
ignoreTags: makeMap('area,base,canvas,frame,iframe,input,link,map,meta,param,script,source,style,svg,textarea,title,track,wbr' + (canIUse ? ',rp' : '')), |
||||
|
// 只能被 rich-text 显示的标签
|
||||
|
richOnlyTags: makeMap('a,colgroup,fieldset,legend,table' + (canIUse ? ',bdi,bdo,rt,ruby' : '')), |
||||
|
// 自闭合的标签
|
||||
|
selfClosingTags: makeMap('area,base,br,col,circle,ellipse,embed,frame,hr,img,input,line,link,meta,param,path,polygon,rect,source,track,use,wbr'), |
||||
|
// 信任的标签
|
||||
|
trustTags: makeMap('a,abbr,ad,audio,b,blockquote,br,code,col,colgroup,dd,del,dl,dt,div,em,fieldset,h1,h2,h3,h4,h5,h6,hr,i,img,ins,label,legend,li,ol,p,q,source,span,strong,sub,sup,table,tbody,td,tfoot,th,thead,tr,title,ul,video' + (canIUse ? ',bdi,bdo,caption,pre,rt,ruby' : '')), |
||||
|
// 默认的标签样式
|
||||
|
userAgentStyles: { |
||||
|
address: 'font-style:italic', |
||||
|
big: 'display:inline;font-size:1.2em', |
||||
|
blockquote: 'background-color:#f6f6f6;border-left:3px solid #dbdbdb;color:#6c6c6c;padding:5px 0 5px 10px', |
||||
|
caption: 'display:table-caption;text-align:center', |
||||
|
center: 'text-align:center', |
||||
|
cite: 'font-style:italic', |
||||
|
dd: 'margin-left:40px', |
||||
|
mark: 'background-color:yellow', |
||||
|
pre: 'font-family:monospace;white-space:pre;overflow:scroll', |
||||
|
s: 'text-decoration:line-through', |
||||
|
small: 'display:inline;font-size:0.8em', |
||||
|
u: 'text-decoration:underline' |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function makeMap(str) { |
||||
|
var map = Object.create(null), |
||||
|
list = str.split(','); |
||||
|
for (var i = list.length; i--;) |
||||
|
map[list[i]] = true; |
||||
|
return map; |
||||
|
} |
||||
@ -0,0 +1,213 @@ |
|||||
|
/** |
||||
|
* Parser 富文本组件 |
||||
|
* @tutorial https://github.com/jin-yufeng/Parser
|
||||
|
* @version 20200728 |
||||
|
* @author JinYufeng |
||||
|
* @listens MIT |
||||
|
*/ |
||||
|
var cache = {}, |
||||
|
Parser = require('./libs/MpHtmlParser.js'), |
||||
|
fs = wx.getFileSystemManager && wx.getFileSystemManager(); |
||||
|
var dom; |
||||
|
var search; |
||||
|
// 计算 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); |
||||
|
if (search) this.search = args => search(this, args); |
||||
|
}, |
||||
|
detached() { |
||||
|
// 删除暂存
|
||||
|
this.imgList.each(src => { |
||||
|
if (src && src.includes(wx.env.USER_DATA_PATH) && fs) |
||||
|
fs.unlink({ |
||||
|
filePath: src |
||||
|
}) |
||||
|
}) |
||||
|
clearInterval(this._timer); |
||||
|
}, |
||||
|
methods: { |
||||
|
// 锚点跳转
|
||||
|
in (obj) { |
||||
|
if (obj.page && obj.selector && obj.scrollTop) this._in = obj; |
||||
|
}, |
||||
|
navigateTo(obj) { |
||||
|
if (!this.data.useAnchor) return obj.fail && obj.fail('Anchor is disabled'); |
||||
|
var selector = (this._in ? this._in.page : this).createSelectorQuery().select((this._in ? this._in.selector : '.top') + (obj.id ? '>>>#' + obj.id : '')).boundingClientRect(); |
||||
|
if (this._in) selector.select(this._in.selector).fields({ |
||||
|
rect: true, |
||||
|
scrollOffset: true |
||||
|
}); |
||||
|
else selector.selectViewport().scrollOffset(); |
||||
|
selector.exec(res => { |
||||
|
if (!res[0]) return this.group ? this.group.navigateTo(this.i, obj) : obj.fail && obj.fail('Label not found'); |
||||
|
var scrollTop = res[1].scrollTop + res[0].top - (res[1].top || 0) + (obj.offset || 0); |
||||
|
if (this._in) { |
||||
|
var data = {}; |
||||
|
data[this._in.scrollTop] = scrollTop; |
||||
|
this._in.page.setData(data); |
||||
|
} else wx.pageScrollTo({ |
||||
|
scrollTop |
||||
|
}) |
||||
|
obj.success && obj.success(); |
||||
|
}) |
||||
|
}, |
||||
|
// 获取文本
|
||||
|
getText(ns = this.data.nodes) { |
||||
|
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) |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"component": true, |
||||
|
"usingComponents": { |
||||
|
"trees": "./trees/trees" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<!--parser 主组件--> |
||||
|
<slot wx:if="{{!nodes.length}}" /> |
||||
|
<trees class="top" style="{{selectable?'user-select:text;-webkit-user-select:text;':''}}{{showAm}}" lazy-load="{{lazyLoad}}" loading="{{loadingImg}}" nodes="{{nodes}}" /> |
||||
@ -0,0 +1,19 @@ |
|||||
|
:host { |
||||
|
display: block; |
||||
|
overflow: scroll; |
||||
|
-webkit-overflow-scrolling: touch; |
||||
|
} |
||||
|
|
||||
|
.top { |
||||
|
display: inherit; |
||||
|
} |
||||
|
|
||||
|
@keyframes show { |
||||
|
0% { |
||||
|
opacity: 0; |
||||
|
} |
||||
|
|
||||
|
100% { |
||||
|
opacity: 1; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,122 @@ |
|||||
|
const errorImg = require('../libs/config.js').errorImg; |
||||
|
Component({ |
||||
|
data: { |
||||
|
canIUse: !!wx.chooseMessageFile, |
||||
|
placeholder: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='300' height='225'/>", |
||||
|
ctrl: [] |
||||
|
}, |
||||
|
properties: { |
||||
|
nodes: Array, |
||||
|
lazyLoad: Boolean, |
||||
|
loading: String |
||||
|
}, |
||||
|
methods: { |
||||
|
// 视频播放事件
|
||||
|
play(e) { |
||||
|
this.top.group && this.top.group.pause(this.top.i); |
||||
|
if (this.top.videoContexts.length > 1 && this.top.data.autopause) |
||||
|
for (var i = this.top.videoContexts.length; i--;) |
||||
|
if (this.top.videoContexts[i].id != e.currentTarget.id) |
||||
|
this.top.videoContexts[i].pause(); |
||||
|
}, |
||||
|
// 图片事件
|
||||
|
imgtap(e) { |
||||
|
var attrs = e.currentTarget.dataset.attrs; |
||||
|
if (!attrs.ignore) { |
||||
|
var preview = true; |
||||
|
this.top.triggerEvent('imgtap', { |
||||
|
id: e.currentTarget.id, |
||||
|
src: attrs.src, |
||||
|
ignore: () => preview = false |
||||
|
}) |
||||
|
if (preview) { |
||||
|
if (this.top.group) return this.top.group.preview(this.top.i, attrs.i); |
||||
|
var urls = this.top.imgList, |
||||
|
current = urls[attrs.i] ? urls[attrs.i] : (urls = [attrs.src], attrs.src); |
||||
|
wx.previewImage({ |
||||
|
current, |
||||
|
urls |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
loadImg(e) { |
||||
|
var i = e.target.dataset.i; |
||||
|
if (this.data.lazyLoad && !this.data.ctrl[i]) |
||||
|
this.setData({ |
||||
|
[`ctrl[${i}]`]: 1 |
||||
|
}) |
||||
|
else if (this.data.loading && this.data.ctrl[i] != 2) |
||||
|
this.setData({ |
||||
|
[`ctrl[${i}]`]: 2 |
||||
|
}) |
||||
|
}, |
||||
|
// 链接点击事件
|
||||
|
linkpress(e) { |
||||
|
var jump = true, |
||||
|
attrs = e.currentTarget.dataset.attrs; |
||||
|
attrs.ignore = () => jump = false; |
||||
|
this.top.triggerEvent('linkpress', attrs); |
||||
|
if (jump) { |
||||
|
if (attrs['app-id']) |
||||
|
wx.navigateToMiniProgram({ |
||||
|
appId: attrs['app-id'], |
||||
|
path: attrs.path |
||||
|
}) |
||||
|
else if (attrs.href) { |
||||
|
if (attrs.href[0] == '#') |
||||
|
this.top.navigateTo({ |
||||
|
id: attrs.href.substring(1) |
||||
|
}) |
||||
|
else if (attrs.href.indexOf('http') == 0 || attrs.href.indexOf('//') == 0) |
||||
|
wx.setClipboardData({ |
||||
|
data: attrs.href, |
||||
|
success: () => |
||||
|
wx.showToast({ |
||||
|
title: '链接已复制' |
||||
|
}) |
||||
|
}) |
||||
|
else |
||||
|
wx.navigateTo({ |
||||
|
url: attrs.href, |
||||
|
fail() { |
||||
|
wx.switchTab({ |
||||
|
url: attrs.href, |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
// 错误事件
|
||||
|
error(e) { |
||||
|
var source = e.target.dataset.source, |
||||
|
i = e.target.dataset.i, |
||||
|
node = this.data.nodes[i]; |
||||
|
if (source == 'video' || source == 'audio') { |
||||
|
// 加载其他 source
|
||||
|
var index = (node.i || 0) + 1; |
||||
|
if (index < node.attrs.source.length) |
||||
|
return this.setData({ |
||||
|
[`nodes[${i}].i`]: index |
||||
|
}) |
||||
|
} else if (source == 'img' && errorImg) { |
||||
|
this.top.imgList.setItem(e.target.dataset.index, errorImg); |
||||
|
this.setData({ |
||||
|
[`nodes[${i}].attrs.src`]: errorImg |
||||
|
}) |
||||
|
} |
||||
|
this.top && this.top.triggerEvent('error', { |
||||
|
source, |
||||
|
target: e.target, |
||||
|
errMsg: e.detail.errMsg |
||||
|
}) |
||||
|
}, |
||||
|
// 加载视频
|
||||
|
loadVideo(e) { |
||||
|
this.setData({ |
||||
|
[`nodes[${e.target.dataset.i}].attrs.autoplay`]: true |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"component": true, |
||||
|
"usingComponents": { |
||||
|
"trees": "./trees" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,67 @@ |
|||||
|
<!--trees 递归子组件--> |
||||
|
<wxs module="handler"> |
||||
|
var inline = { |
||||
|
abbr: 1, |
||||
|
b: 1, |
||||
|
big: 1, |
||||
|
code: 1, |
||||
|
del: 1, |
||||
|
em: 1, |
||||
|
i: 1, |
||||
|
ins: 1, |
||||
|
label: 1, |
||||
|
q: 1, |
||||
|
small: 1, |
||||
|
span: 1, |
||||
|
strong: 1, |
||||
|
sub: 1, |
||||
|
sup: 1 |
||||
|
} |
||||
|
module.exports = { |
||||
|
visited: function (e, owner) { |
||||
|
if (!e.instance.hasClass('_visited')) |
||||
|
e.instance.addClass('_visited') |
||||
|
owner.callMethod('linkpress', e) |
||||
|
}, |
||||
|
use: function (item) { |
||||
|
return !item.c && !inline[item.name] && (item.attrs.style || '').indexOf('display:inline') == -1 |
||||
|
} |
||||
|
} |
||||
|
</wxs> |
||||
|
<block wx:for="{{nodes}}" wx:for-item="n" wx:for-index="i" wx:key="i"> |
||||
|
<!--图片--> |
||||
|
<view wx:if="{{n.name=='img'}}" id="{{n.attrs.id}}" class="_img {{n.attrs.class}}" style="{{n.attrs.style}}" data-attrs="{{n.attrs}}" bindtap="imgtap"> |
||||
|
<rich-text nodes="{{[{attrs:{src:loading&&ctrl[i]!=2?loading:(lazyLoad&&!ctrl[i]?placeholder:n.attrs.src||''),alt:n.attrs.alt||'',width:n.attrs.width||'',style:'-webkit-touch-callout:none;max-width:100%;display:block'+(n.attrs.height?';height:'+n.attrs.height:'')},name:'img'}]}}" /> |
||||
|
<image class="_image" src="{{lazyLoad&&!ctrl[i]?placeholder:n.attrs.src}}" lazy-load="{{lazyLoad}}" show-menu-by-longpress="{{!n.attrs.ignore}}" data-i="{{i}}" data-index="{{n.attrs.i}}" data-source="img" bindload="loadImg" binderror="error" /> |
||||
|
</view> |
||||
|
<!--文本--> |
||||
|
<text wx:elif="{{n.type=='text'}}" decode>{{n.text}}</text> |
||||
|
<text wx:elif="{{n.name=='br'}}">\n</text> |
||||
|
<!--链接--> |
||||
|
<view wx:elif="{{n.name=='a'}}" id="{{n.attrs.id}}" class="_a {{n.attrs.class}}" hover-class="_hover" style="{{n.attrs.style}}" data-attrs="{{n.attrs}}" bindtap="{{canIUse?handler.visited:'linkpress'}}"> |
||||
|
<trees class="_node" nodes="{{n.children}}" /> |
||||
|
</view> |
||||
|
<!--视频--> |
||||
|
<block wx:elif="{{n.name=='video'}}"> |
||||
|
<view wx:if="{{n.lazyLoad&&!n.attrs.autoplay}}" id="{{n.attrs.id}}" class="_video {{n.attrs.class}}" style="{{n.attrs.style}}" data-i="{{i}}" bindtap="loadVideo" /> |
||||
|
<video wx:else id="{{n.attrs.id}}" class="{{n.attrs.class}}" style="{{n.attrs.style}}" autoplay="{{n.attrs.autoplay}}" controls="{{n.attrs.controls}}" loop="{{n.attrs.loop}}" muted="{{n.attrs.muted}}" poster="{{n.attrs.poster}}" src="{{n.attrs.source[n.i||0]}}" unit-id="{{n.attrs['unit-id']}}" data-i="{{i}}" data-source="video" binderror="error" bindplay="play" /> |
||||
|
</block> |
||||
|
<!--音频--> |
||||
|
<audio wx:elif="{{n.name=='audio'}}" id="{{n.attrs.id}}" class="{{n.attrs.class}}" style="{{n.attrs.style}}" author="{{n.attrs.author}}" autoplay="{{n.attrs.autoplay}}" controls="{{n.attrs.controls}}" loop="{{n.attrs.loop}}" name="{{n.attrs.name}}" poster="{{n.attrs.poster}}" src="{{n.attrs.source[n.i||0]}}" data-i="{{i}}" data-source="audio" binderror="error" bindplay="play" /> |
||||
|
<!--广告--> |
||||
|
<ad wx:elif="{{n.name=='ad'}}" class="{{n.attrs.class}}" style="{{n.attrs.style}}" unit-id="{{n.attrs['unit-id']}}" data-source="ad" binderror="error" /> |
||||
|
<!--列表--> |
||||
|
<view wx:elif="{{n.name=='li'}}" id="{{n.attrs.id}}" class="{{n.attrs.class}}" style="{{n.attrs.style}};display:flex"> |
||||
|
<view wx:if="{{n.type=='ol'}}" class="_ol-bef">{{n.num}}</view> |
||||
|
<view wx:else class="_ul-bef"> |
||||
|
<view wx:if="{{n.floor%3==0}}" class="_ul-p1">█</view> |
||||
|
<view wx:elif="{{n.floor%3==2}}" class="_ul-p2" /> |
||||
|
<view wx:else class="_ul-p1" style="border-radius:50%">█</view> |
||||
|
</view> |
||||
|
<trees class="_node _li" lazyLoad="{{lazyLoad}}" loading="{{loading}}" nodes="{{n.children}}" /> |
||||
|
</view> |
||||
|
<!--富文本--> |
||||
|
<rich-text wx:elif="{{handler.use(n)}}" id="{{n.attrs.id}}" class="_p __{{n.name}}" nodes="{{[n]}}" /> |
||||
|
<!--继续递归--> |
||||
|
<trees wx:else id="{{n.attrs.id}}" class="_node _{{n.name}} {{n.attrs.class}}" style="{{n.attrs.style}}" lazyLoad="{{lazyLoad}}" loading="{{loading}}" nodes="{{n.children}}" /> |
||||
|
</block> |
||||
@ -0,0 +1,180 @@ |
|||||
|
/* 在这里引入自定义样式 */ |
||||
|
|
||||
|
/* 链接和图片效果 */ |
||||
|
._a { |
||||
|
display: inline; |
||||
|
padding: 1.5px 0 1.5px 0; |
||||
|
color: #366092; |
||||
|
word-break: break-all; |
||||
|
} |
||||
|
|
||||
|
._hover { |
||||
|
text-decoration: underline; |
||||
|
opacity: 0.7; |
||||
|
} |
||||
|
|
||||
|
._visited { |
||||
|
color: #551a8b; |
||||
|
} |
||||
|
|
||||
|
._img { |
||||
|
display: inline-block; |
||||
|
max-width: 100%; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
/* 内部样式 */ |
||||
|
:host { |
||||
|
display: inline; |
||||
|
} |
||||
|
|
||||
|
._blockquote, |
||||
|
._div, |
||||
|
._p, |
||||
|
._ul, |
||||
|
._ol, |
||||
|
._li { |
||||
|
display: block; |
||||
|
} |
||||
|
|
||||
|
._b, |
||||
|
._strong { |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
._code { |
||||
|
font-family: monospace; |
||||
|
} |
||||
|
|
||||
|
._del { |
||||
|
text-decoration: line-through; |
||||
|
} |
||||
|
|
||||
|
._em, |
||||
|
._i { |
||||
|
font-style: italic; |
||||
|
} |
||||
|
|
||||
|
._h1 { |
||||
|
font-size: 2em; |
||||
|
} |
||||
|
|
||||
|
._h2 { |
||||
|
font-size: 1.5em; |
||||
|
} |
||||
|
|
||||
|
._h3 { |
||||
|
font-size: 1.17em; |
||||
|
} |
||||
|
|
||||
|
._h5 { |
||||
|
font-size: 0.83em; |
||||
|
} |
||||
|
|
||||
|
._h6 { |
||||
|
font-size: 0.67em; |
||||
|
} |
||||
|
|
||||
|
._h1, |
||||
|
._h2, |
||||
|
._h3, |
||||
|
._h4, |
||||
|
._h5, |
||||
|
._h6 { |
||||
|
display: block; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
._image { |
||||
|
display: block; |
||||
|
width: 100%; |
||||
|
height: 360px; |
||||
|
margin-top: -360px; |
||||
|
opacity: 0; |
||||
|
} |
||||
|
|
||||
|
._ins { |
||||
|
text-decoration: underline; |
||||
|
} |
||||
|
|
||||
|
._li { |
||||
|
flex: 1; |
||||
|
width: 0; |
||||
|
} |
||||
|
|
||||
|
._ol-bef { |
||||
|
width: 36px; |
||||
|
margin-right: 5px; |
||||
|
text-align: right; |
||||
|
} |
||||
|
|
||||
|
._ul-bef { |
||||
|
margin: 0 12px 0 23px; |
||||
|
line-height: normal; |
||||
|
} |
||||
|
|
||||
|
._ol-bef, |
||||
|
._ul-bef { |
||||
|
flex: none; |
||||
|
user-select: none; |
||||
|
} |
||||
|
|
||||
|
._ul-p1 { |
||||
|
display: inline-block; |
||||
|
width: 0.3em; |
||||
|
height: 0.3em; |
||||
|
overflow: hidden; |
||||
|
line-height: 0.3em; |
||||
|
} |
||||
|
|
||||
|
._ul-p2 { |
||||
|
display: inline-block; |
||||
|
width: 0.23em; |
||||
|
height: 0.23em; |
||||
|
border: 0.05em solid black; |
||||
|
border-radius: 50%; |
||||
|
} |
||||
|
|
||||
|
._q::before { |
||||
|
content: '"'; |
||||
|
} |
||||
|
|
||||
|
._q::after { |
||||
|
content: '"'; |
||||
|
} |
||||
|
|
||||
|
._sub { |
||||
|
font-size: smaller; |
||||
|
vertical-align: sub; |
||||
|
} |
||||
|
|
||||
|
._sup { |
||||
|
font-size: smaller; |
||||
|
vertical-align: super; |
||||
|
} |
||||
|
|
||||
|
.__bdi, |
||||
|
.__bdo, |
||||
|
.__ruby, |
||||
|
.__rt { |
||||
|
display: inline-block; |
||||
|
} |
||||
|
|
||||
|
._video { |
||||
|
position: relative; |
||||
|
display: inline-block; |
||||
|
width: 300px; |
||||
|
height: 225px; |
||||
|
background-color: black; |
||||
|
} |
||||
|
|
||||
|
._video::after { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
margin: -15px 0 0 -15px; |
||||
|
content: ''; |
||||
|
border-color: transparent transparent transparent white; |
||||
|
border-style: solid; |
||||
|
border-width: 15px 0 15px 30px; |
||||
|
} |
||||
@ -1,144 +1,144 @@ |
|||||
{ |
{ |
||||
"description": "项目配置文件", |
"description": "项目配置文件", |
||||
"packOptions": { |
"packOptions": { |
||||
"ignore": [] |
"ignore": [] |
||||
}, |
}, |
||||
"setting": { |
"setting": { |
||||
"urlCheck": false, |
"urlCheck": false, |
||||
"es6": true, |
"es6": true, |
||||
"postcss": true, |
"postcss": true, |
||||
"preloadBackgroundData": false, |
"preloadBackgroundData": false, |
||||
"minified": true, |
"minified": true, |
||||
"newFeature": true, |
"newFeature": true, |
||||
"coverView": true, |
"coverView": true, |
||||
"autoAudits": false, |
"autoAudits": false, |
||||
"showShadowRootInWxmlPanel": true, |
"showShadowRootInWxmlPanel": true, |
||||
"scopeDataCheck": false, |
"scopeDataCheck": false, |
||||
"checkInvalidKey": true, |
"checkInvalidKey": true, |
||||
"checkSiteMap": true, |
"checkSiteMap": true, |
||||
"uploadWithSourceMap": true, |
"uploadWithSourceMap": true, |
||||
"compileHotReLoad": false, |
"compileHotReLoad": false, |
||||
"babelSetting": { |
"babelSetting": { |
||||
"ignore": [], |
"ignore": [], |
||||
"disablePlugins": [], |
"disablePlugins": [], |
||||
"outputPath": "" |
"outputPath": "" |
||||
}, |
}, |
||||
"useIsolateContext": true, |
"useIsolateContext": true, |
||||
"useCompilerModule": false, |
"useCompilerModule": false, |
||||
"userConfirmedUseCompilerModuleSwitch": false |
"userConfirmedUseCompilerModuleSwitch": false |
||||
}, |
}, |
||||
"compileType": "miniprogram", |
"compileType": "miniprogram", |
||||
"libVersion": "2.8.2", |
"libVersion": "2.8.2", |
||||
"appid": "wx6dcf544cdae7d4ec", |
"appid": "wx6dcf544cdae7d4ec", |
||||
"projectname": "%E7%B2%BE%E8%87%B4%E9%94%A6%E6%B0%B4-%E5%B1%85%E6%B0%9 1%E7%AB%AF", |
"projectname": "%E7%B2%BE%E8%87%B4%E9%94%A6%E6%B0%B4-%E5%B1%85%E6%B0%9 1%E7%AB%AF", |
||||
"debugOptions": { |
"debugOptions": { |
||||
"hidedInDevtools": [] |
"hidedInDevtools": [] |
||||
}, |
}, |
||||
"isGameTourist": false, |
"isGameTourist": false, |
||||
"simulatorType": "wechat", |
"simulatorType": "wechat", |
||||
"simulatorPluginLibVersion": {}, |
"simulatorPluginLibVersion": {}, |
||||
"condition": { |
"condition": { |
||||
"search": { |
"search": { |
||||
"current": -1, |
"current": -1, |
||||
"list": [] |
"list": [] |
||||
}, |
}, |
||||
"conversation": { |
"conversation": { |
||||
"current": -1, |
"current": -1, |
||||
"list": [] |
"list": [] |
||||
}, |
}, |
||||
"plugin": { |
"plugin": { |
||||
"current": -1, |
"current": -1, |
||||
"list": [] |
"list": [] |
||||
}, |
}, |
||||
"game": { |
"game": { |
||||
"currentL": -1, |
"currentL": -1, |
||||
"list": [] |
"list": [] |
||||
}, |
}, |
||||
"gamePlugin": { |
"gamePlugin": { |
||||
"current": -1, |
"current": -1, |
||||
"list": [] |
"list": [] |
||||
}, |
}, |
||||
"miniprogram": { |
"miniprogram": { |
||||
"current": -1, |
"current": -1, |
||||
"list": [ |
"list": [ |
||||
{ |
{ |
||||
"id": 0, |
"id": 0, |
||||
"name": "pages/formid/formid", |
"name": "pages/formid/formid", |
||||
"pathName": "pages/formid/formid", |
"pathName": "pages/formid/formid", |
||||
"query": "gid=1233592630168813569", |
"query": "gid=1233592630168813569", |
||||
"scene": 1011 |
"scene": 1011 |
||||
}, |
}, |
||||
{ |
{ |
||||
"id": 1, |
"id": 1, |
||||
"name": "pages/indexNew/indexNew", |
"name": "pages/indexNew/indexNew", |
||||
"pathName": "pages/indexNew/indexNew", |
"pathName": "pages/indexNew/indexNew", |
||||
"query": "scene=1233592247862198274", |
"query": "scene=1233592247862198274", |
||||
"scene": 1011 |
"scene": 1011 |
||||
}, |
}, |
||||
{ |
{ |
||||
"id": 2, |
"id": 2, |
||||
"name": "网格长注册", |
"name": "网格长注册", |
||||
"pathName": "pages/index/index", |
"pathName": "pages/index/index", |
||||
"query": "scene=gridLeader", |
"query": "scene=gridLeader", |
||||
"scene": 1011 |
"scene": 1011 |
||||
}, |
}, |
||||
{ |
{ |
||||
"id": 3, |
"id": 3, |
||||
"name": "subpages/home/pages/newsDetail/newsDetail", |
"name": "subpages/home/pages/newsDetail/newsDetail", |
||||
"pathName": "subpages/home/pages/newsDetail/newsDetail", |
"pathName": "subpages/home/pages/newsDetail/newsDetail", |
||||
"query": "id=7c8bc749ff4b6380bf1d902c0bde0ba&defaultGridId=1233592630168813569", |
"query": "id=7c8bc749ff4b6380bf1d902c0bde0ba&defaultGridId=1233592630168813569", |
||||
"scene": null |
"scene": null |
||||
}, |
}, |
||||
{ |
{ |
||||
"id": -1, |
"id": -1, |
||||
"name": "社群列表", |
"name": "社群列表", |
||||
"pathName": "subpages/associationNew/pages/associationlist/associationlist", |
"pathName": "subpages/associationNew/pages/associationlist/associationlist", |
||||
"query": "", |
"query": "", |
||||
"scene": null |
"scene": null |
||||
}, |
}, |
||||
{ |
{ |
||||
"id": 5, |
"id": 5, |
||||
"name": "数据端跳转", |
"name": "数据端跳转", |
||||
"pathName": "pages/indexNew/indexNew", |
"pathName": "pages/indexNew/indexNew", |
||||
"query": "scene=1277169327606366209&from=analysis", |
"query": "scene=1277169327606366209&from=analysis", |
||||
"scene": null |
"scene": null |
||||
}, |
}, |
||||
{ |
{ |
||||
"id": 6, |
"id": 6, |
||||
"name": "工作端跳转", |
"name": "工作端跳转", |
||||
"pathName": "pages/indexNew/indexNew", |
"pathName": "pages/indexNew/indexNew", |
||||
"query": "scene=1280737901335838721&from=work", |
"query": "scene=1280737901335838721&from=work", |
||||
"scene": null |
"scene": null |
||||
}, |
}, |
||||
{ |
{ |
||||
"id": 7, |
"id": 7, |
||||
"name": "我有事说", |
"name": "我有事说", |
||||
"pathName": "subpages/discussion/pages/addIssue/addIssue", |
"pathName": "subpages/discussion/pages/addIssue/addIssue", |
||||
"query": "", |
"query": "", |
||||
"scene": null |
"scene": null |
||||
}, |
}, |
||||
{ |
{ |
||||
"id": 8, |
"id": 8, |
||||
"name": "搜索页面", |
"name": "搜索页面", |
||||
"pathName": "subpages/oneKeyService/pages/search/search", |
"pathName": "subpages/oneKeyService/pages/search/search", |
||||
"query": "", |
"query": "", |
||||
"scene": null |
"scene": null |
||||
}, |
}, |
||||
{ |
{ |
||||
"id": -1, |
"id": -1, |
||||
"name": "注册页面", |
"name": "注册页面", |
||||
"pathName": "pages/toRegister/toRegister", |
"pathName": "pages/toRegister/toRegister", |
||||
"query": "", |
"query": "", |
||||
"scene": null |
"scene": null |
||||
}, |
}, |
||||
{ |
{ |
||||
"id": -1, |
"id": -1, |
||||
"name": "subpages/heart/pages/leaderboardNew/leaderboardNew", |
"name": "subpages/heart/pages/leaderboardNew/leaderboardNew", |
||||
"pathName": "subpages/heart/pages/leaderboardNew/leaderboardNew", |
"pathName": "subpages/heart/pages/leaderboardNew/leaderboardNew", |
||||
"query": "", |
"query": "", |
||||
"scene": null |
"scene": null |
||||
} |
} |
||||
] |
] |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
@ -1,6 +1,7 @@ |
|||||
{ |
{ |
||||
"usingComponents": { |
"usingComponents": { |
||||
"completeInfo-dialog": "../../../../../components/completeInfoDialog/completeInfoDialog" |
"completeInfo-dialog": "../../../../components/completeInfoDialog/completeInfoDialog", |
||||
|
"parser": "../../../../components/parser/parser" |
||||
}, |
}, |
||||
"navigationBarTitleText": "e锦水" |
"navigationBarTitleText": "e锦水" |
||||
} |
} |
||||
@ -1,5 +1,6 @@ |
|||||
{ |
{ |
||||
"usingComponents": { |
"usingComponents": { |
||||
|
"parser": "../../../../components/parser/parser" |
||||
}, |
}, |
||||
"navigationBarTitleText": "" |
"navigationBarTitleText": "" |
||||
} |
} |
||||
@ -1,4 +1,6 @@ |
|||||
{ |
{ |
||||
"usingComponents": {}, |
"usingComponents": { |
||||
|
"parser": "../../../../components/parser/parser" |
||||
|
}, |
||||
"navigationBarTitleText": "" |
"navigationBarTitleText": "" |
||||
} |
} |
||||
@ -1,6 +1,7 @@ |
|||||
{ |
{ |
||||
"usingComponents": { |
"usingComponents": { |
||||
"no-data": "../../../../components/nodata/nodata" |
"no-data": "../../../../components/nodata/nodata", |
||||
|
"parser": "../../../../components/parser/parser" |
||||
}, |
}, |
||||
"navigationBarTitleText": "" |
"navigationBarTitleText": "" |
||||
} |
} |
||||
Loading…
Reference in new issue