diff --git a/components/parser/libs/CssHandler.js b/components/parser/libs/CssHandler.js new file mode 100644 index 0000000..a238794 --- /dev/null +++ b/components/parser/libs/CssHandler.js @@ -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; +} \ No newline at end of file diff --git a/components/parser/libs/MpHtmlParser.js b/components/parser/libs/MpHtmlParser.js new file mode 100644 index 0000000..4108b34 --- /dev/null +++ b/components/parser/libs/MpHtmlParser.js @@ -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) 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; \ No newline at end of file diff --git a/components/parser/libs/config.js b/components/parser/libs/config.js new file mode 100644 index 0000000..fbb0666 --- /dev/null +++ b/components/parser/libs/config.js @@ -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; +} \ No newline at end of file diff --git a/components/parser/parser.js b/components/parser/parser.js new file mode 100644 index 0000000..38d22a7 --- /dev/null +++ b/components/parser/parser.js @@ -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) + } + } +}) \ No newline at end of file diff --git a/components/parser/parser.json b/components/parser/parser.json new file mode 100644 index 0000000..85e49c9 --- /dev/null +++ b/components/parser/parser.json @@ -0,0 +1,6 @@ +{ + "component": true, + "usingComponents": { + "trees": "./trees/trees" + } +} \ No newline at end of file diff --git a/components/parser/parser.wxml b/components/parser/parser.wxml new file mode 100644 index 0000000..aeeca92 --- /dev/null +++ b/components/parser/parser.wxml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/components/parser/parser.wxss b/components/parser/parser.wxss new file mode 100644 index 0000000..b24180f --- /dev/null +++ b/components/parser/parser.wxss @@ -0,0 +1,19 @@ +:host { + display: block; + overflow: scroll; + -webkit-overflow-scrolling: touch; +} + +.top { + display: inherit; +} + +@keyframes show { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} diff --git a/components/parser/trees/trees.js b/components/parser/trees/trees.js new file mode 100644 index 0000000..bfe4394 --- /dev/null +++ b/components/parser/trees/trees.js @@ -0,0 +1,122 @@ +const errorImg = require('../libs/config.js').errorImg; +Component({ + data: { + canIUse: !!wx.chooseMessageFile, + placeholder: "data:image/svg+xml;utf8,", + 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 + }) + } + } +}) \ No newline at end of file diff --git a/components/parser/trees/trees.json b/components/parser/trees/trees.json new file mode 100644 index 0000000..07cb773 --- /dev/null +++ b/components/parser/trees/trees.json @@ -0,0 +1,6 @@ +{ + "component": true, + "usingComponents": { + "trees": "./trees" + } +} \ No newline at end of file diff --git a/components/parser/trees/trees.wxml b/components/parser/trees/trees.wxml new file mode 100644 index 0000000..38752ee --- /dev/null +++ b/components/parser/trees/trees.wxml @@ -0,0 +1,67 @@ + + +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 + } +} + + + + + + + + + {{n.text}} + \n + + + + + + + + + + \ No newline at end of file diff --git a/components/parser/trees/trees.wxss b/components/parser/trees/trees.wxss new file mode 100644 index 0000000..25085ed --- /dev/null +++ b/components/parser/trees/trees.wxss @@ -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; +} diff --git a/project.config.json b/project.config.json index 4669972..0e8739b 100644 --- a/project.config.json +++ b/project.config.json @@ -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 + } + ] + } + } } \ No newline at end of file diff --git a/subpages/home/pages/newsDetail/newsDetail.json b/subpages/home/pages/newsDetail/newsDetail.json index 82a7cd2..0107cae 100644 --- a/subpages/home/pages/newsDetail/newsDetail.json +++ b/subpages/home/pages/newsDetail/newsDetail.json @@ -1,6 +1,7 @@ { "usingComponents": { - "completeInfo-dialog": "../../../../../components/completeInfoDialog/completeInfoDialog" + "completeInfo-dialog": "../../../../components/completeInfoDialog/completeInfoDialog", + "parser": "../../../../components/parser/parser" }, "navigationBarTitleText": "e锦水" } \ No newline at end of file diff --git a/subpages/home/pages/newsDetail/newsDetail.wxml b/subpages/home/pages/newsDetail/newsDetail.wxml index a7ad941..1d5d18c 100644 --- a/subpages/home/pages/newsDetail/newsDetail.wxml +++ b/subpages/home/pages/newsDetail/newsDetail.wxml @@ -15,7 +15,8 @@ - + + diff --git a/subpages/home/pages/noticeDetail/noticeDetail.json b/subpages/home/pages/noticeDetail/noticeDetail.json index 81a4e80..cc3da93 100644 --- a/subpages/home/pages/noticeDetail/noticeDetail.json +++ b/subpages/home/pages/noticeDetail/noticeDetail.json @@ -1,5 +1,6 @@ { "usingComponents": { + "parser": "../../../../components/parser/parser" }, "navigationBarTitleText": "" } \ No newline at end of file diff --git a/subpages/home/pages/noticeDetail/noticeDetail.wxml b/subpages/home/pages/noticeDetail/noticeDetail.wxml index cacf6f7..fbe4621 100644 --- a/subpages/home/pages/noticeDetail/noticeDetail.wxml +++ b/subpages/home/pages/noticeDetail/noticeDetail.wxml @@ -20,7 +20,8 @@ - + + diff --git a/subpages/oneKeyService/pages/noticeDetail/noticeDetail.json b/subpages/oneKeyService/pages/noticeDetail/noticeDetail.json index d8bbc60..cc3da93 100644 --- a/subpages/oneKeyService/pages/noticeDetail/noticeDetail.json +++ b/subpages/oneKeyService/pages/noticeDetail/noticeDetail.json @@ -1,4 +1,6 @@ { - "usingComponents": {}, + "usingComponents": { + "parser": "../../../../components/parser/parser" + }, "navigationBarTitleText": "" } \ No newline at end of file diff --git a/subpages/oneKeyService/pages/noticeDetail/noticeDetail.wxml b/subpages/oneKeyService/pages/noticeDetail/noticeDetail.wxml index 632d7db..41b2e8f 100644 --- a/subpages/oneKeyService/pages/noticeDetail/noticeDetail.wxml +++ b/subpages/oneKeyService/pages/noticeDetail/noticeDetail.wxml @@ -15,7 +15,8 @@ - + + diff --git a/subpages/understandJs/pages/noticeDetail/noticeDetail.json b/subpages/understandJs/pages/noticeDetail/noticeDetail.json index ed1ed57..a93c426 100644 --- a/subpages/understandJs/pages/noticeDetail/noticeDetail.json +++ b/subpages/understandJs/pages/noticeDetail/noticeDetail.json @@ -1,6 +1,7 @@ { "usingComponents": { - "no-data": "../../../../components/nodata/nodata" + "no-data": "../../../../components/nodata/nodata", + "parser": "../../../../components/parser/parser" }, "navigationBarTitleText": "" } \ No newline at end of file diff --git a/subpages/understandJs/pages/noticeDetail/noticeDetail.wxml b/subpages/understandJs/pages/noticeDetail/noticeDetail.wxml index 8afa456..8f74807 100644 --- a/subpages/understandJs/pages/noticeDetail/noticeDetail.wxml +++ b/subpages/understandJs/pages/noticeDetail/noticeDetail.wxml @@ -40,7 +40,8 @@ - + +