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": "项目配置文件", |
|||
"packOptions": { |
|||
"ignore": [] |
|||
}, |
|||
"setting": { |
|||
"urlCheck": false, |
|||
"es6": true, |
|||
"postcss": true, |
|||
"preloadBackgroundData": false, |
|||
"minified": true, |
|||
"newFeature": true, |
|||
"coverView": true, |
|||
"autoAudits": false, |
|||
"showShadowRootInWxmlPanel": true, |
|||
"scopeDataCheck": false, |
|||
"checkInvalidKey": true, |
|||
"checkSiteMap": true, |
|||
"uploadWithSourceMap": true, |
|||
"compileHotReLoad": false, |
|||
"babelSetting": { |
|||
"ignore": [], |
|||
"disablePlugins": [], |
|||
"outputPath": "" |
|||
}, |
|||
"useIsolateContext": true, |
|||
"useCompilerModule": false, |
|||
"userConfirmedUseCompilerModuleSwitch": false |
|||
}, |
|||
"compileType": "miniprogram", |
|||
"libVersion": "2.8.2", |
|||
"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", |
|||
"debugOptions": { |
|||
"hidedInDevtools": [] |
|||
}, |
|||
"isGameTourist": false, |
|||
"simulatorType": "wechat", |
|||
"simulatorPluginLibVersion": {}, |
|||
"condition": { |
|||
"search": { |
|||
"current": -1, |
|||
"list": [] |
|||
}, |
|||
"conversation": { |
|||
"current": -1, |
|||
"list": [] |
|||
}, |
|||
"plugin": { |
|||
"current": -1, |
|||
"list": [] |
|||
}, |
|||
"game": { |
|||
"currentL": -1, |
|||
"list": [] |
|||
}, |
|||
"gamePlugin": { |
|||
"current": -1, |
|||
"list": [] |
|||
}, |
|||
"miniprogram": { |
|||
"current": -1, |
|||
"list": [ |
|||
{ |
|||
"id": 0, |
|||
"name": "pages/formid/formid", |
|||
"pathName": "pages/formid/formid", |
|||
"query": "gid=1233592630168813569", |
|||
"scene": 1011 |
|||
}, |
|||
{ |
|||
"id": 1, |
|||
"name": "pages/indexNew/indexNew", |
|||
"pathName": "pages/indexNew/indexNew", |
|||
"query": "scene=1233592247862198274", |
|||
"scene": 1011 |
|||
}, |
|||
{ |
|||
"id": 2, |
|||
"name": "网格长注册", |
|||
"pathName": "pages/index/index", |
|||
"query": "scene=gridLeader", |
|||
"scene": 1011 |
|||
}, |
|||
{ |
|||
"id": 3, |
|||
"name": "subpages/home/pages/newsDetail/newsDetail", |
|||
"pathName": "subpages/home/pages/newsDetail/newsDetail", |
|||
"query": "id=7c8bc749ff4b6380bf1d902c0bde0ba&defaultGridId=1233592630168813569", |
|||
"scene": null |
|||
}, |
|||
{ |
|||
"id": -1, |
|||
"name": "社群列表", |
|||
"pathName": "subpages/associationNew/pages/associationlist/associationlist", |
|||
"query": "", |
|||
"scene": null |
|||
}, |
|||
{ |
|||
"id": 5, |
|||
"name": "数据端跳转", |
|||
"pathName": "pages/indexNew/indexNew", |
|||
"query": "scene=1277169327606366209&from=analysis", |
|||
"scene": null |
|||
}, |
|||
{ |
|||
"id": 6, |
|||
"name": "工作端跳转", |
|||
"pathName": "pages/indexNew/indexNew", |
|||
"query": "scene=1280737901335838721&from=work", |
|||
"scene": null |
|||
}, |
|||
{ |
|||
"id": 7, |
|||
"name": "我有事说", |
|||
"pathName": "subpages/discussion/pages/addIssue/addIssue", |
|||
"query": "", |
|||
"scene": null |
|||
}, |
|||
{ |
|||
"id": 8, |
|||
"name": "搜索页面", |
|||
"pathName": "subpages/oneKeyService/pages/search/search", |
|||
"query": "", |
|||
"scene": null |
|||
}, |
|||
{ |
|||
"id": -1, |
|||
"name": "注册页面", |
|||
"pathName": "pages/toRegister/toRegister", |
|||
"query": "", |
|||
"scene": null |
|||
}, |
|||
{ |
|||
"id": -1, |
|||
"name": "subpages/heart/pages/leaderboardNew/leaderboardNew", |
|||
"pathName": "subpages/heart/pages/leaderboardNew/leaderboardNew", |
|||
"query": "", |
|||
"scene": null |
|||
} |
|||
] |
|||
} |
|||
} |
|||
"description": "项目配置文件", |
|||
"packOptions": { |
|||
"ignore": [] |
|||
}, |
|||
"setting": { |
|||
"urlCheck": false, |
|||
"es6": true, |
|||
"postcss": true, |
|||
"preloadBackgroundData": false, |
|||
"minified": true, |
|||
"newFeature": true, |
|||
"coverView": true, |
|||
"autoAudits": false, |
|||
"showShadowRootInWxmlPanel": true, |
|||
"scopeDataCheck": false, |
|||
"checkInvalidKey": true, |
|||
"checkSiteMap": true, |
|||
"uploadWithSourceMap": true, |
|||
"compileHotReLoad": false, |
|||
"babelSetting": { |
|||
"ignore": [], |
|||
"disablePlugins": [], |
|||
"outputPath": "" |
|||
}, |
|||
"useIsolateContext": true, |
|||
"useCompilerModule": false, |
|||
"userConfirmedUseCompilerModuleSwitch": false |
|||
}, |
|||
"compileType": "miniprogram", |
|||
"libVersion": "2.8.2", |
|||
"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", |
|||
"debugOptions": { |
|||
"hidedInDevtools": [] |
|||
}, |
|||
"isGameTourist": false, |
|||
"simulatorType": "wechat", |
|||
"simulatorPluginLibVersion": {}, |
|||
"condition": { |
|||
"search": { |
|||
"current": -1, |
|||
"list": [] |
|||
}, |
|||
"conversation": { |
|||
"current": -1, |
|||
"list": [] |
|||
}, |
|||
"plugin": { |
|||
"current": -1, |
|||
"list": [] |
|||
}, |
|||
"game": { |
|||
"currentL": -1, |
|||
"list": [] |
|||
}, |
|||
"gamePlugin": { |
|||
"current": -1, |
|||
"list": [] |
|||
}, |
|||
"miniprogram": { |
|||
"current": -1, |
|||
"list": [ |
|||
{ |
|||
"id": 0, |
|||
"name": "pages/formid/formid", |
|||
"pathName": "pages/formid/formid", |
|||
"query": "gid=1233592630168813569", |
|||
"scene": 1011 |
|||
}, |
|||
{ |
|||
"id": 1, |
|||
"name": "pages/indexNew/indexNew", |
|||
"pathName": "pages/indexNew/indexNew", |
|||
"query": "scene=1233592247862198274", |
|||
"scene": 1011 |
|||
}, |
|||
{ |
|||
"id": 2, |
|||
"name": "网格长注册", |
|||
"pathName": "pages/index/index", |
|||
"query": "scene=gridLeader", |
|||
"scene": 1011 |
|||
}, |
|||
{ |
|||
"id": 3, |
|||
"name": "subpages/home/pages/newsDetail/newsDetail", |
|||
"pathName": "subpages/home/pages/newsDetail/newsDetail", |
|||
"query": "id=7c8bc749ff4b6380bf1d902c0bde0ba&defaultGridId=1233592630168813569", |
|||
"scene": null |
|||
}, |
|||
{ |
|||
"id": -1, |
|||
"name": "社群列表", |
|||
"pathName": "subpages/associationNew/pages/associationlist/associationlist", |
|||
"query": "", |
|||
"scene": null |
|||
}, |
|||
{ |
|||
"id": 5, |
|||
"name": "数据端跳转", |
|||
"pathName": "pages/indexNew/indexNew", |
|||
"query": "scene=1277169327606366209&from=analysis", |
|||
"scene": null |
|||
}, |
|||
{ |
|||
"id": 6, |
|||
"name": "工作端跳转", |
|||
"pathName": "pages/indexNew/indexNew", |
|||
"query": "scene=1280737901335838721&from=work", |
|||
"scene": null |
|||
}, |
|||
{ |
|||
"id": 7, |
|||
"name": "我有事说", |
|||
"pathName": "subpages/discussion/pages/addIssue/addIssue", |
|||
"query": "", |
|||
"scene": null |
|||
}, |
|||
{ |
|||
"id": 8, |
|||
"name": "搜索页面", |
|||
"pathName": "subpages/oneKeyService/pages/search/search", |
|||
"query": "", |
|||
"scene": null |
|||
}, |
|||
{ |
|||
"id": -1, |
|||
"name": "注册页面", |
|||
"pathName": "pages/toRegister/toRegister", |
|||
"query": "", |
|||
"scene": null |
|||
}, |
|||
{ |
|||
"id": -1, |
|||
"name": "subpages/heart/pages/leaderboardNew/leaderboardNew", |
|||
"pathName": "subpages/heart/pages/leaderboardNew/leaderboardNew", |
|||
"query": "", |
|||
"scene": null |
|||
} |
|||
] |
|||
} |
|||
} |
|||
} |
@ -1,6 +1,7 @@ |
|||
{ |
|||
"usingComponents": { |
|||
"completeInfo-dialog": "../../../../../components/completeInfoDialog/completeInfoDialog" |
|||
"completeInfo-dialog": "../../../../components/completeInfoDialog/completeInfoDialog", |
|||
"parser": "../../../../components/parser/parser" |
|||
}, |
|||
"navigationBarTitleText": "e锦水" |
|||
} |
@ -1,5 +1,6 @@ |
|||
{ |
|||
"usingComponents": { |
|||
"parser": "../../../../components/parser/parser" |
|||
}, |
|||
"navigationBarTitleText": "" |
|||
} |
@ -1,4 +1,6 @@ |
|||
{ |
|||
"usingComponents": {}, |
|||
"usingComponents": { |
|||
"parser": "../../../../components/parser/parser" |
|||
}, |
|||
"navigationBarTitleText": "" |
|||
} |
@ -1,6 +1,7 @@ |
|||
{ |
|||
"usingComponents": { |
|||
"no-data": "../../../../components/nodata/nodata" |
|||
"no-data": "../../../../components/nodata/nodata", |
|||
"parser": "../../../../components/parser/parser" |
|||
}, |
|||
"navigationBarTitleText": "" |
|||
} |
Loading…
Reference in new issue