├── .gitignore ├── App.vue ├── README ├── colorui ├── animation.css ├── components │ └── cu-custom.vue ├── icon.css └── main.css ├── components └── jyf-parser │ ├── jyf-parser.vue │ └── libs │ ├── CssHandler.js │ ├── MpHtmlParser.js │ ├── config.js │ ├── handler.sjs │ ├── handler.wxs │ └── trees.vue ├── main.js ├── manifest.json ├── package.json ├── pages.json ├── pages ├── dynamic │ └── dynamic.vue ├── index │ ├── about.vue │ ├── feedback.vue │ └── index.vue ├── rss │ ├── edit.vue │ ├── index.vue │ ├── main.vue │ ├── rsslist.vue │ └── subscribe.vue └── user │ ├── index.vue │ ├── login.vue │ ├── register.vue │ ├── setting.vue │ ├── update.vue │ └── updatePwd.vue ├── plugin └── tools.js ├── static ├── avatar.svg ├── bg.png ├── logo.png └── user.jpeg ├── store └── index.js ├── utils ├── api.js ├── database.js ├── refreshRss.js └── request.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /unpackage 3 | -------------------------------------------------------------------------------- /App.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 72 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyml/MTRR/eb9bdc7b228d680c0ae8fac19600fe9043585e2b/README -------------------------------------------------------------------------------- /colorui/animation.css: -------------------------------------------------------------------------------- 1 | /* 2 | Animation 微动画 3 | 基于ColorUI组建库的动画模块 by 文晓港 2019年3月26日19:52:28 4 | */ 5 | 6 | /* css 滤镜 控制黑白底色gif的 */ 7 | .gif-black{ 8 | mix-blend-mode: screen; 9 | } 10 | .gif-white{ 11 | mix-blend-mode: multiply; 12 | } 13 | 14 | 15 | /* Animation css */ 16 | [class*=animation-] { 17 | animation-duration: .5s; 18 | animation-timing-function: ease-out; 19 | animation-fill-mode: both 20 | } 21 | 22 | .animation-fade { 23 | animation-name: fade; 24 | animation-duration: .8s; 25 | animation-timing-function: linear 26 | } 27 | 28 | .animation-scale-up { 29 | animation-name: scale-up 30 | } 31 | 32 | .animation-scale-down { 33 | animation-name: scale-down 34 | } 35 | 36 | .animation-slide-top { 37 | animation-name: slide-top 38 | } 39 | 40 | .animation-slide-bottom { 41 | animation-name: slide-bottom 42 | } 43 | 44 | .animation-slide-left { 45 | animation-name: slide-left 46 | } 47 | 48 | .animation-slide-right { 49 | animation-name: slide-right 50 | } 51 | 52 | .animation-shake { 53 | animation-name: shake 54 | } 55 | 56 | .animation-reverse { 57 | animation-direction: reverse 58 | } 59 | 60 | @keyframes fade { 61 | 0% { 62 | opacity: 0 63 | } 64 | 65 | 100% { 66 | opacity: 1 67 | } 68 | } 69 | 70 | @keyframes scale-up { 71 | 0% { 72 | opacity: 0; 73 | transform: scale(.2) 74 | } 75 | 76 | 100% { 77 | opacity: 1; 78 | transform: scale(1) 79 | } 80 | } 81 | 82 | @keyframes scale-down { 83 | 0% { 84 | opacity: 0; 85 | transform: scale(1.8) 86 | } 87 | 88 | 100% { 89 | opacity: 1; 90 | transform: scale(1) 91 | } 92 | } 93 | 94 | @keyframes slide-top { 95 | 0% { 96 | opacity: 0; 97 | transform: translateY(-100%) 98 | } 99 | 100 | 100% { 101 | opacity: 1; 102 | transform: translateY(0) 103 | } 104 | } 105 | 106 | @keyframes slide-bottom { 107 | 0% { 108 | opacity: 0; 109 | transform: translateY(100%) 110 | } 111 | 112 | 100% { 113 | opacity: 1; 114 | transform: translateY(0) 115 | } 116 | } 117 | 118 | @keyframes shake { 119 | 120 | 0%, 121 | 100% { 122 | transform: translateX(0) 123 | } 124 | 125 | 10% { 126 | transform: translateX(-9px) 127 | } 128 | 129 | 20% { 130 | transform: translateX(8px) 131 | } 132 | 133 | 30% { 134 | transform: translateX(-7px) 135 | } 136 | 137 | 40% { 138 | transform: translateX(6px) 139 | } 140 | 141 | 50% { 142 | transform: translateX(-5px) 143 | } 144 | 145 | 60% { 146 | transform: translateX(4px) 147 | } 148 | 149 | 70% { 150 | transform: translateX(-3px) 151 | } 152 | 153 | 80% { 154 | transform: translateX(2px) 155 | } 156 | 157 | 90% { 158 | transform: translateX(-1px) 159 | } 160 | } 161 | 162 | @keyframes slide-left { 163 | 0% { 164 | opacity: 0; 165 | transform: translateX(-100%) 166 | } 167 | 168 | 100% { 169 | opacity: 1; 170 | transform: translateX(0) 171 | } 172 | } 173 | 174 | @keyframes slide-right { 175 | 0% { 176 | opacity: 0; 177 | transform: translateX(100%) 178 | } 179 | 180 | 100% { 181 | opacity: 1; 182 | transform: translateX(0) 183 | } 184 | } -------------------------------------------------------------------------------- /colorui/components/cu-custom.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 62 | 63 | 66 | -------------------------------------------------------------------------------- /components/jyf-parser/jyf-parser.vue: -------------------------------------------------------------------------------- 1 | 8 | 22 | 23 | 613 | 614 | 638 | -------------------------------------------------------------------------------- /components/jyf-parser/libs/CssHandler.js: -------------------------------------------------------------------------------- 1 | /* 2 | 解析和匹配 Css 的选择器 3 | github:https://github.com/jin-yufeng/Parser 4 | docs:https://jin-yufeng.github.io/Parser 5 | author:JinYufeng 6 | */ 7 | const config = require("./config.js"); 8 | class CssHandler { 9 | constructor(tagStyle = {}) { 10 | this.styles = Object.assign({}, tagStyle); 11 | }; 12 | getStyle(data) { 13 | var style = ''; 14 | data = data.replace(/<[sS][tT][yY][lL][eE][\s\S]*?>([\s\S]*?)<\/[sS][tT][yY][lL][eE][\s\S]*?>/g, function($, $1) { 15 | style += $1; 16 | return ''; 17 | }) 18 | this.styles = new CssParser(style, this.styles).parse(); 19 | return data; 20 | }; 21 | match(name, attrs) { 22 | var tmp, matched = (tmp = this.styles[name]) ? tmp + ';' : ''; 23 | if (attrs.class) { 24 | var classes = attrs.class.split(' '); 25 | for (var i = 0; i < classes.length; i++) 26 | if (tmp = this.styles['.' + classes[i]]) 27 | matched += tmp + ';'; 28 | } 29 | if (tmp = this.styles['#' + attrs.id]) 30 | matched += tmp + ';'; 31 | return matched; 32 | }; 33 | } 34 | module.exports = CssHandler; 35 | class CssParser { 36 | constructor(data, tagStyle) { 37 | this.data = data; 38 | this.res = tagStyle; 39 | for (var item in config.userAgentStyles) { 40 | if (tagStyle[item]) tagStyle[item] = config.userAgentStyles[item] + ';' + tagStyle[item]; 41 | else tagStyle[item] = config.userAgentStyles[item]; 42 | } 43 | this._comma = false; 44 | this._floor = 0; 45 | this._i = 0; 46 | this._list = []; 47 | this._start = 0; 48 | this._state = this.Space; 49 | }; 50 | parse() { 51 | for (; this._i < this.data.length; this._i++) 52 | this._state(this.data[this._i]); 53 | return this.res; 54 | }; 55 | // 状态机 56 | Space(c) { 57 | if (c == '.' || c == '#' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { 58 | this._start = this._i; 59 | this._state = this.StyleName; 60 | } else if (c == '/' && this.data[this._i + 1] == '*') 61 | this.Comment(); 62 | else if (!config.blankChar[c] && c != ';') 63 | this._state = this.Ignore; 64 | }; 65 | Comment() { 66 | this._i = this.data.indexOf("*/", this._i) + 1; 67 | if (!this._i) this._i = this.data.length; 68 | this._state = this.Space; 69 | }; 70 | Ignore(c) { 71 | if (c == '{') this._floor++; 72 | else if (c == '}' && !--this._floor) { 73 | this._list = []; 74 | this._state = this.Space; 75 | } 76 | }; 77 | StyleName(c) { 78 | if (config.blankChar[c]) { 79 | if (this._start != this._i) 80 | this._list.push(this.data.substring(this._start, this._i)); 81 | this._state = this.NameSpace; 82 | } else if (c == '{') { 83 | this._list.push(this.data.substring(this._start, this._i)); 84 | this._start = this._i + 1; 85 | this.Content(); 86 | } else if (c == ',') { 87 | this._list.push(this.data.substring(this._start, this._i)); 88 | this._start = this._i + 1; 89 | this._comma = true; 90 | } else if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') && c != '.' && c != '#' && c != '-' && 91 | c != '_') 92 | this._state = this.Ignore; 93 | }; 94 | NameSpace(c) { 95 | if (c == '{') { 96 | this._start = this._i + 1; 97 | this.Content(); 98 | } else if (c == ',') { 99 | this._comma = true; 100 | this._start = this._i + 1; 101 | this._state = this.StyleName; 102 | } else if (!config.blankChar[c]) { 103 | if (this._comma) { 104 | this._state = this.StyleName; 105 | this._start = this._i--; 106 | this._comma = false; 107 | } else this._state = this.Ignore; 108 | } 109 | }; 110 | Content() { 111 | this._i = this.data.indexOf('}', this._i); 112 | if (this._i == -1) this._i = this.data.length; 113 | var content = this.data.substring(this._start, this._i); 114 | for (var i = this._list.length; i--;) 115 | this.res[this._list[i]] = (this.res[this._list[i]] || '') + content; 116 | this._list = []; 117 | this._state = this.Space; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /components/jyf-parser/libs/MpHtmlParser.js: -------------------------------------------------------------------------------- 1 | /* 2 | 将 html 解析为适用于小程序 rich-text 的 DOM 结构 3 | github:https://github.com/jin-yufeng/Parser 4 | docs:https://jin-yufeng.github.io/Parser 5 | author:JinYufeng 6 | */ 7 | const config = require("./config.js"); 8 | const blankChar = config.blankChar; 9 | const CssHandler = require("./CssHandler.js"); 10 | var emoji; // emoji 补丁包 https://jin-yufeng.github.io/Parser/#/instructions?id=emoji 11 | class MpHtmlParser { 12 | constructor(data, options = {}) { 13 | this.CssHandler = new CssHandler(options.tagStyle); 14 | this.data = data; 15 | this.DOM = []; 16 | this._attrName = ''; 17 | this._attrValue = ''; 18 | this._attrs = {}; 19 | this._domain = options.domain; 20 | this._protocol = this._domain && this._domain.includes("://") ? this._domain.split("://")[0] : "http"; 21 | this._i = 0; 22 | this._start = 0; 23 | this._state = this.Text; 24 | this._STACK = []; 25 | this._tagName = ''; 26 | this._audioNum = 0; 27 | this._imgNum = 0; 28 | this._videoNum = 0; 29 | this._useAnchor = options.useAnchor; 30 | this._pre = false; 31 | }; 32 | parse() { 33 | if (emoji) this.data = emoji.parseEmoji(this.data); 34 | // 高亮处理 35 | if (config.highlight) 36 | this.data = this.data.replace(/<[pP][rR][eE]([\s\S]*?)>([\s\S]+?)<\/[pP][rR][eE][\s\S]*?>/g, function($, $1, $2) { 37 | return `${config.highlight($2, $1)}`; 38 | }) 39 | this.data = this.CssHandler.getStyle(this.data); 40 | for (var len = this.data.length; this._i < len; this._i++) 41 | this._state(this.data[this._i]); 42 | if (this._state == this.Text) this.setText(); 43 | while (this._STACK.length) this.popNode(this._STACK.pop()); 44 | // #ifdef MP-BAIDU || MP-TOUTIAO 45 | // 将顶层标签的一些样式提取出来给 rich-text 46 | function setContain(nodes) { 47 | for (var i = nodes.length, element; element = nodes[--i];) { 48 | if (element.type == "text") continue; 49 | if (!element.c) { 50 | var style = element.attrs.style; 51 | if (style) { 52 | var j, k, res = ""; 53 | if ((j = style.indexOf("display")) != -1) 54 | res = style.substring(j, (k = style.indexOf(';', j)) == -1 ? style.length : k); 55 | if (style.indexOf("flex") != -1) res += ';' + style.match(getRegExp("flex[:-][^;]+/g")).join(';'); 56 | element.attrs.containStyle = res; 57 | } 58 | } else setContain(element.children); 59 | } 60 | }; 61 | setContain(this.DOM); 62 | // #endif 63 | if (this.DOM.length) this.DOM[0].PoweredBy = "Parser"; 64 | return this.DOM; 65 | }; 66 | // 设置属性 67 | setAttr() { 68 | if (config.trustAttrs[this._attrName]) { 69 | if (this._attrName == "src" && this._attrValue[0] == '/') { 70 | if (this._attrValue[1] == '/') this._attrValue = this._protocol + ':' + this._attrValue; 71 | else if (this._domain) this._attrValue = this._domain + this._attrValue; 72 | } 73 | this._attrs[this._attrName] = (this._attrValue ? this._attrValue : (this._attrName == "src" || this._attrName == 74 | "alt" ? '' : 'T')); 75 | } 76 | this._attrValue = ''; 77 | while (blankChar[this.data[this._i]]) this._i++; 78 | if (this.checkClose()) this.setNode(); 79 | else this._state = this.AttrName; 80 | }; 81 | // 设置文本节点 82 | setText() { 83 | var text = this.getSelection(); 84 | if (!text) return; 85 | if (!this._pre) { 86 | // 移除空白符 87 | for (var tmp = [], i = text.length, has = false, c; c = text[--i];) 88 | if ((!blankChar[c] && (has = true)) || (!blankChar[tmp[0]] && (c = ' '))) tmp.unshift(c); 89 | if (!has) return; 90 | text = tmp.join(''); 91 | } 92 | // 处理实体 93 | // #ifdef MP-BAIDU || MP-ALIPAY || MP-TOUTIAO 94 | var entities = { 95 | lt: "<", 96 | gt: ">", 97 | amp: "&", 98 | quot: '"', 99 | apos: "'", 100 | nbsp: "\u00A0", 101 | ensp: "\u2002", 102 | emsp: "\u2003", 103 | ndash: "–", 104 | mdash: "—", 105 | middot: "·", 106 | lsquo: "‘", 107 | rsquo: "’", 108 | ldquo: "“", 109 | rdquo: "”", 110 | bull: "•", 111 | hellip: "…", 112 | permil: "‰", 113 | copy: "©", 114 | reg: "®", 115 | trade: "™", 116 | times: "×", 117 | divide: "÷", 118 | cent: "¢", 119 | pound: "£", 120 | yen: "¥", 121 | euro: "€", 122 | sect: "§" 123 | }; 124 | // #endif 125 | var i = text.indexOf('&'), 126 | j, u, decode; 127 | while (i != -1) { 128 | j = text.indexOf(';', i + 2); 129 | if (j == -1) break; 130 | if (text[i + 1] == '#') { 131 | u = parseInt((text[i + 2] == 'x' ? '0' : '') + text.substring(i + 2, j)); 132 | if (!isNaN(u)) text = text.substring(0, i) + String.fromCharCode(u) + text.substring(j + 1); 133 | } else { 134 | u = text.substring(i + 1, j); 135 | // #ifdef MP-WEIXIN || MP-QQ || APP-PLUS 136 | if (u == "nbsp") text = text.substring(0, i) + '\u00A0' + text.substring(j + 1); // 解决连续   失效的问题 137 | else if (u != "lt" && u != "gt" && u != "amp" && u != "ensp" && u != "emsp") decode = true; 138 | // #endif 139 | // #ifdef MP-BAIDU || MP-ALIPAY || MP-TOUTIAO 140 | if (entities[u]) text = text.substring(0, i) + entities[u] + text.substring(j + 1); 141 | // #endif 142 | } 143 | i = text.indexOf('&', i + 1); 144 | } 145 | var slibings = this._STACK.length ? this._STACK[this._STACK.length - 1].children : this.DOM; 146 | if (slibings.length && slibings[slibings.length - 1].type == "text") { 147 | slibings[slibings.length - 1].text += text; 148 | if (decode) slibings[slibings.length - 1].decode = true; 149 | } else 150 | slibings.push({ 151 | type: "text", 152 | text, 153 | decode 154 | }) 155 | }; 156 | // 设置元素节点 157 | setNode() { 158 | var slibings = this._STACK.length ? this._STACK[this._STACK.length - 1].children : this.DOM; 159 | var node = { 160 | name: this._tagName.toLowerCase(), 161 | attrs: this._attrs 162 | } 163 | config.LabelHandler(node, this); 164 | this._attrs = {}; 165 | if (this.data[this._i] == '>') { 166 | if (!config.selfClosingTags[this._tagName]) { 167 | if (config.ignoreTags[node.name]) { 168 | var j = this._i; 169 | // 处理要被移除的标签 170 | while (this._i < this.data.length) { 171 | (this._i = this.data.indexOf("' && this.data[this._i] != '/') this._i++; 175 | if (this.data.substring(this._start, this._i).toLowerCase() == node.name) { 176 | this._i = this.data.indexOf('>', this._i); 177 | if (this._i == -1) this._i = this.data.length; 178 | else this._start = this._i + 1; 179 | this._state = this.Text; 180 | // 处理 svg 181 | if (node.name == "svg") { 182 | var src = this.data.substring(j, this._i + 1); 183 | if (!node.attrs.xmlns) src = " xmlns=\"http://www.w3.org/2000/svg\"" + src; 184 | this._i = j; 185 | while (this.data[j] != '<') j--; 186 | src = this.data.substring(j, this._i) + src; 187 | this._i = this._start - 1; 188 | node.name = "img"; 189 | node.attrs = { 190 | src: "data:image/svg+xml;utf8," + src.replace(/#/g, "%23"), 191 | ignore: 'T' 192 | } 193 | slibings.push(node); 194 | } 195 | break; 196 | } 197 | } 198 | return; 199 | } else this._STACK.push(node); 200 | node.children = []; 201 | } 202 | } else this._i++; 203 | this._start = this._i + 1; 204 | this._state = this.Text; 205 | if (!config.ignoreTags[node.name]) { 206 | // 检查空白符是否有效 207 | if (node.name == "pre" || (node.attrs.style && node.attrs.style.includes("white-space") && node.attrs.style.includes( 208 | "pre"))) { 209 | this._pre = true; 210 | node.pre = true; 211 | } 212 | slibings.push(node); 213 | } 214 | }; 215 | // 标签出栈处理 216 | popNode(node) { 217 | // 替换一些标签名 218 | if (node.name == "picture") { 219 | node.name = "img"; 220 | if (!node.attrs.src && node.children.length && node.children[0].name == "img") 221 | node.attrs.src = node.children[0].attrs.src; 222 | if (node.attrs.src && !node.attrs.ignore) 223 | node.attrs.i = (this._imgNum++).toString(); 224 | return node.children = void 0; 225 | } 226 | if (config.blockTags[node.name]) node.name = "div"; 227 | else if (!config.trustTags[node.name]) node.name = "span"; 228 | // 空白符处理 229 | if (node.pre) { 230 | this._pre = false; 231 | node.pre = undefined; 232 | for (var i = this._STACK.length; i--;) 233 | if (this._STACK[i].pre) 234 | this._pre = true; 235 | } 236 | // 处理列表 237 | if (node.c) { 238 | if (node.name == "ul") { 239 | var floor = 1; 240 | for (var i = this._STACK.length; i--;) 241 | if (this._STACK[i].name == "ul") floor++; 242 | if (floor != 1) 243 | for (i = node.children.length; i--;) 244 | node.children[i].floor = floor; 245 | } else if (node.name == "ol") { 246 | function convert(num, type) { 247 | if (type == 'a') return String.fromCharCode(97 + (num - 1) % 26); 248 | if (type == 'A') return String.fromCharCode(65 + (num - 1) % 26); 249 | if (type == 'i' || type == 'I') { 250 | num = (num - 1) % 99 + 1; 251 | var one = ['I', "II", "III", "IV", 'V', "VI", "VII", "VIII", "IX"], 252 | ten = ['X', "XX", "XXX", "XL", 'L', "LX", "LXX", "LXXX", "XC"], 253 | res = (ten[Math.floor(num / 10) - 1] || '') + (one[num % 10 - 1] || ''); 254 | if (type == 'i') return res.toLowerCase(); 255 | return res; 256 | } 257 | return num; 258 | } 259 | for (var i = 0, num = 1, child; child = node.children[i++];) 260 | if (child.name == "li") { 261 | child.type = "ol"; 262 | child.num = convert(num++, node.attrs.type) + '.'; 263 | } 264 | } 265 | } 266 | // 处理表格的边框 267 | if (node.name == "table") { 268 | if (node.attrs.border) 269 | node.attrs.style = `border:${node.attrs.border}px solid gray;${node.attrs.style || ''}`; 270 | if (node.attrs.hasOwnProperty("cellspacing")) 271 | node.attrs.style = `border-spacing:${node.attrs.cellspacing}px;${node.attrs.style || ''}`; 272 | 273 | function setBorder(elem) { 274 | if (elem.name == "th" || elem.name == "td") { 275 | if (node.attrs.border) 276 | elem.attrs.style = `border:${node.attrs.border}px solid gray;${elem.attrs.style || ''}`; 277 | if (node.attrs.hasOwnProperty("cellpadding")) 278 | elem.attrs.style = `padding:${node.attrs.cellpadding}px;${elem.attrs.style || ''}`; 279 | return; 280 | } 281 | if (elem.type == "text") return; 282 | for (var i = 0; i < (elem.children || []).length; i++) 283 | setBorder(elem.children[i]); 284 | } 285 | if (node.attrs.border || node.attrs.hasOwnProperty("cellpadding")) 286 | for (var i = 0; i < node.children.length; i++) 287 | setBorder(node.children[i]); 288 | } 289 | // 后代选择器处理 290 | this.CssHandler.pop && this.CssHandler.pop(node); 291 | }; 292 | // 工具函数 293 | checkClose() { 294 | if (this.data[this._i] == '>' || (this.data[this._i] == '/' && this.data[this._i + 1] == '>')) 295 | return true; 296 | return false; 297 | }; 298 | getSelection(trim) { 299 | var str = (this._start == this._i ? '' : this.data.substring(this._start, this._i)); 300 | while (trim && (blankChar[this.data[++this._i]] || (this._i--, false))); 301 | this._start = this._i + 1; 302 | return str; 303 | }; 304 | // 状态机 305 | Text(c) { 306 | if (c == '<') { 307 | var next = this.data[this._i + 1]; 308 | if ((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z')) { 309 | this.setText(); 310 | this._state = this.TagName; 311 | } else if (next == '/') { 312 | this.setText(); 313 | this._i++; 314 | next = this.data[this._i + 1]; 315 | if ((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z')) { 316 | this._start = this._i + 1; 317 | this._state = this.EndTag; 318 | } else 319 | this._state = this.Comment; 320 | } else if (next == '!') { 321 | this.setText(); 322 | this._state = this.Comment; 323 | } 324 | } 325 | }; 326 | Comment() { 327 | if (this.data.substring(this._i + 1, this._i + 3) == "--" || this.data.substring(this._i + 1, this._i + 7) == 328 | "[CDATA[") { 329 | this._i = this.data.indexOf("-->", this._i + 1); 330 | if (this._i == -1) return this._i = this.data.length; 331 | else this._i = this._i + 2; 332 | } else 333 | (this._i = this.data.indexOf('>', this._i + 1)) == -1 ? this._i = this.data.length : null; 334 | this._start = this._i + 1; 335 | this._state = this.Text; 336 | }; 337 | TagName(c) { 338 | if (blankChar[c]) { 339 | this._tagName = this.getSelection(true); 340 | if (this.checkClose()) this.setNode(); 341 | else this._state = this.AttrName; 342 | } else if (this.checkClose()) { 343 | this._tagName = this.getSelection(); 344 | this.setNode(); 345 | } 346 | }; 347 | AttrName(c) { 348 | if (blankChar[c]) { 349 | this._attrName = this.getSelection(true).toLowerCase(); 350 | if (this.data[this._i] == '=') { 351 | while (blankChar[this.data[++this._i]]); 352 | this._start = this._i--; 353 | this._state = this.AttrValue; 354 | } else this.setAttr(); 355 | } else if (c == '=') { 356 | this._attrName = this.getSelection().toLowerCase(); 357 | while (blankChar[this.data[++this._i]]); 358 | this._start = this._i--; 359 | this._state = this.AttrValue; 360 | } else if (this.checkClose()) { 361 | this._attrName = this.getSelection().toLowerCase(); 362 | this.setAttr(); 363 | } 364 | }; 365 | AttrValue(c) { 366 | if (c == '"' || c == "'") { 367 | this._start++; 368 | if ((this._i = this.data.indexOf(c, this._i + 1)) == -1) return this._i = this.data.length; 369 | } else 370 | for (; !blankChar[this.data[this._i]] && this.data[this._i] != '>'; this._i++); 371 | this._attrValue = this.getSelection(); 372 | while (this._attrValue.includes(""")) this._attrValue = this._attrValue.replace(""", '"'); 373 | this.setAttr(); 374 | }; 375 | EndTag(c) { 376 | if (blankChar[c] || c == '>' || c == '/') { 377 | var name = this.getSelection().toLowerCase(); 378 | var flag = false; 379 | for (var i = this._STACK.length; i--;) 380 | if (this._STACK[i].name == name) { 381 | flag = true; 382 | break; 383 | } 384 | if (flag) { 385 | var node; 386 | while (flag) { 387 | node = this._STACK.pop(); 388 | if (node.name == name) flag = false; 389 | this.popNode(node); 390 | } 391 | } else if (name == 'p' || name == "br") { 392 | var slibings = this._STACK.length ? this._STACK[this._STACK.length - 1].children : this.DOM; 393 | slibings.push({ 394 | name, 395 | attrs: {} 396 | }); 397 | } 398 | this._i = this.data.indexOf('>', this._i); 399 | if (this._i == -1) this._i = this.data.length; 400 | else this._state = this.Text; 401 | } 402 | }; 403 | }; 404 | module.exports = MpHtmlParser; 405 | -------------------------------------------------------------------------------- /components/jyf-parser/libs/config.js: -------------------------------------------------------------------------------- 1 | /* 配置文件 */ 2 | function makeMap(str, obj = {}) { 3 | var map = obj, 4 | list = str.split(','); 5 | for (var i = list.length; i--;) 6 | map[list[i]] = true; 7 | return map; 8 | } 9 | // 信任的属性列表,不在列表中的属性将被移除 10 | const trustAttrs = makeMap( 11 | "align,allowfullscreen,alt,app-id,appid,apid,author,autoplay,border,cellpadding,cellspacing,class,color,colspan,controls,data-src,dir,face,frameborder,height,href,id,ignore,loop,media,muted,name,path,poster,rowspan,size,span,src,start,style,type,unit-id,unitId,width,xmlns" 12 | ); 13 | // 信任的标签,将保持标签名不变 14 | const trustTags = makeMap( 15 | "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,u,ul,video" 16 | // #ifdef APP-PLUS 17 | + 18 | ",embed,iframe" 19 | // #endif 20 | ); 21 | // 块级标签,将被转为 div 22 | const blockTags = makeMap("address,article,aside,body,center,cite,footer,header,html,nav,pre,section"); 23 | // 被移除的标签(其中 svg 系列标签会被转为图片) 24 | const ignoreTags = makeMap( 25 | "area,base,basefont,canvas,circle,command,ellipse,frame,head,input,isindex,keygen,line,link,map,meta,param,path,polygon,rect,script,source,svg,textarea,track,use,wbr" 26 | // #ifndef APP-PLUS 27 | + 28 | ",embed,iframe" 29 | // #endif 30 | ); 31 | // 只能用 rich-text 显示的标签(其中图片不能预览、不能显示视频、音频等) 32 | const richOnlyTags = makeMap("a,colgroup,fieldset,legend,picture,table,tbody,td,tfoot,th,thead,tr"); 33 | // 自闭合标签 34 | const selfClosingTags = makeMap( 35 | "area,base,basefont,br,col,circle,ellipse,embed,frame,hr,img,input,isindex,keygen,line,link,meta,param,path,polygon,rect,source,track,use,wbr" 36 | ); 37 | // 空白字符 38 | const blankChar = makeMap(" ,\u00A0,\t,\r,\n,\f"); 39 | // 默认的标签样式 40 | var userAgentStyles = { 41 | a: "color:#366092;word-break:break-all;padding:1.5px 0 1.5px 0", 42 | address: "font-style:italic", 43 | big: "display:inline;font-size:1.2em", 44 | blockquote: "background-color:#f6f6f6;border-left:3px solid #dbdbdb;color:#6c6c6c;padding:5px 0 5px 10px", 45 | center: "text-align:center", 46 | cite: "font-style:italic", 47 | dd: "margin-left:40px", 48 | img: "max-width:100%", 49 | mark: "background-color:yellow", 50 | picture: "max-width:100%", 51 | pre: "font-family:monospace;white-space:pre;overflow:scroll", 52 | s: "text-decoration:line-through", 53 | small: "display:inline;font-size:0.8em", 54 | u: "text-decoration:underline" 55 | }; 56 | const screenWidth = wx.getSystemInfoSync().screenWidth; 57 | // #ifdef MP-WEIXIN 58 | // 版本兼容 59 | if (wx.canIUse("editor")) { 60 | makeMap("bdi,bdo,caption,rt,ruby,pre", trustTags); 61 | makeMap("bdi,bdo,caption,rt,ruby,pre", richOnlyTags); 62 | ignoreTags.rp = true; 63 | blockTags.pre = undefined; 64 | } else 65 | // #endif 66 | blockTags.caption = true; 67 | 68 | function bubbling(Parser) { 69 | for (var i = Parser._STACK.length; i--;) { 70 | if (!richOnlyTags[Parser._STACK[i].name]) 71 | Parser._STACK[i].c = 1; 72 | else return false; 73 | } 74 | return true; 75 | } 76 | module.exports = { 77 | // 高亮处理函数 78 | highlight: null, 79 | // 处理标签的属性,需要通过组件递归方式显示的标签需要调用 bubbling(Parser) 80 | LabelHandler(node, Parser) { 81 | var attrs = node.attrs; 82 | attrs.style = Parser.CssHandler.match(node.name, attrs, node) + (attrs.style || ''); 83 | switch (node.name) { 84 | case "div": 85 | case 'p': 86 | if (attrs.align) { 87 | attrs.style = `text-align:${attrs.align};${attrs.style}`; 88 | attrs.align = void 0; 89 | } 90 | break; 91 | case "img": 92 | if (attrs["data-src"]) { 93 | attrs.src = attrs.src || attrs["data-src"]; 94 | attrs["data-src"] = void 0; 95 | } 96 | if (attrs.width && parseInt(attrs.width) > screenWidth) 97 | attrs.style += ";height:auto !important"; 98 | if (attrs.src && !attrs.ignore) { 99 | if (bubbling(Parser)) attrs.i = (Parser._imgNum++).toString(); 100 | else attrs.ignore = 'T'; 101 | } 102 | break; 103 | case 'a': 104 | case "ad": 105 | // #ifdef APP-PLUS 106 | case "iframe": 107 | case "embed": 108 | // #endif 109 | bubbling(Parser); 110 | break; 111 | case "font": 112 | if (attrs.color) { 113 | attrs.style = `color:${attrs.color};${attrs.style}`; 114 | attrs.color = void 0; 115 | } 116 | if (attrs.face) { 117 | attrs.style = `font-family:${attrs.face};${attrs.style}`; 118 | attrs.face = void 0; 119 | } 120 | if (attrs.size) { 121 | var size = parseInt(attrs.size); 122 | if (size < 1) size = 1; 123 | else if (size > 7) size = 7; 124 | var map = ["xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large"]; 125 | attrs.style = `font-size:${map[size - 1]};${attrs.style}`; 126 | attrs.size = void 0; 127 | } 128 | break; 129 | case "video": 130 | case "audio": 131 | if (attrs.id) Parser[`_${node.name}Num`]++; 132 | else attrs.id = (node.name + (++Parser[`_${node.name}Num`])); 133 | if (node.name == "video") { 134 | attrs.style = attrs.style || ''; 135 | if (attrs.width) { 136 | attrs.style = `width:${parseFloat(attrs.width) + attrs.width.includes('%') ? '%' : "px"};${attrs.style}`; 137 | attrs.width = void 0; 138 | } 139 | if (attrs.height) { 140 | attrs.style = `height:${parseFloat(attrs.height) + attrs.height.includes('%') ? '%' : "px"};${attrs.style}`; 141 | attrs.height = void 0; 142 | } 143 | if (Parser._videoNum > 3) node.lazyLoad = true; 144 | } 145 | attrs.source = []; 146 | if (attrs.src) attrs.source.push(attrs.src); 147 | if (!attrs.controls && !attrs.autoplay) 148 | console.warn(`存在没有 controls 属性的 ${node.name} 标签,可能导致无法播放`, node); 149 | bubbling(Parser); 150 | break; 151 | case "source": 152 | var i, parent = Parser._STACK[Parser._STACK.length - 1]; 153 | if (!parent || !attrs.src) break; 154 | if (parent.name == "video" || parent.name == "audio") 155 | parent.attrs.source.push(attrs.src); 156 | else { 157 | var i, media = attrs.media; 158 | if (parent.name == "picture" && !parent.attrs.src && (!media || (media.includes("px") && 159 | (((i = media.indexOf("min-width")) != -1 && (i = media.indexOf(':', i + 8)) != -1 && 160 | screenWidth > parseInt(media.substring(i + 1))) || 161 | ((i = media.indexOf("max-width")) != -1 && (i = media.indexOf(':', i + 8)) != -1 && 162 | screenWidth < parseInt(media.substring(i + 1))))))) 163 | parent.attrs.src = attrs.src; 164 | } 165 | } 166 | // 压缩 style 167 | var styles = attrs.style.split(';'), 168 | compressed = {}; 169 | attrs.style = ""; 170 | for (var i = 0, len = styles.length; i < len; i++) { 171 | var info = styles[i].split(':'); 172 | if (info.length < 2) continue; 173 | var key = info[0].trim().toLowerCase(), 174 | value = info.slice(1).join(':').trim(); 175 | // 填充链接 176 | if (value.includes("url")) { 177 | var j = value.indexOf('('); 178 | if (j++ != -1) { 179 | while (value[j] == '"' || value[j] == "'" || blankChar[value[j]]) j++; 180 | if (value[j] == '/') { 181 | if (value[j + 1] == '/') value = value.substring(0, j) + Parser._protocol + ':' + value.substring(j); 182 | else if (Parser._domain) value = value.substring(0, j) + Parser._domain + value.substring(j); 183 | } 184 | } 185 | } 186 | // 转换 rpx 187 | else if (value.includes("rpx")) 188 | value = value.replace(/[0-9.]*rpx/g, function($) { 189 | return parseFloat($) * screenWidth / 750 + "px"; 190 | }) 191 | if (value.includes("-webkit") || value.includes("-moz") || value.includes("-ms") || value.includes("-o") || value.includes( 192 | "safe")) 193 | attrs.style += `;${key}:${value}`; 194 | else if (!compressed[key] || value.includes("import") || !compressed[key].includes("import")) 195 | compressed[key] = value; 196 | } 197 | if (node.name == "img" && compressed.width && compressed.width.includes("%") && parseInt(compressed.width) > 198 | screenWidth) 199 | compressed.height = "auto !important"; 200 | for (var key in compressed) 201 | attrs.style += `;${key}:${compressed[key]}`; 202 | attrs.style = attrs.style.substr(1); 203 | if (!attrs.style) attrs.style = void 0; 204 | if (Parser._useAnchor && attrs.id) bubbling(Parser); 205 | }, 206 | trustAttrs, 207 | trustTags, 208 | blockTags, 209 | ignoreTags, 210 | selfClosingTags, 211 | blankChar, 212 | userAgentStyles, 213 | screenWidth 214 | } 215 | -------------------------------------------------------------------------------- /components/jyf-parser/libs/handler.sjs: -------------------------------------------------------------------------------- 1 | var inlineTags = { 2 | abbr: 1, 3 | b: 1, 4 | big: 1, 5 | code: 1, 6 | del: 1, 7 | em: 1, 8 | i: 1, 9 | ins: 1, 10 | label: 1, 11 | q: 1, 12 | small: 1, 13 | span: 1, 14 | strong: 1 15 | } 16 | export default { 17 | // 从 rich-text 顶层标签的样式中取出一些给 rich-text 18 | getStyle: function(style, display) { 19 | if (style) { 20 | var i, j, res = ""; 21 | if ((i = style.indexOf("display")) != -1) 22 | res = style.substring(i, (j = style.indexOf(';', i)) == -1 ? style.length : j); 23 | else res = "display:" + display; 24 | if (style.indexOf("flex") != -1) res += ';' + style.match(getRegExp("flex[:-][^;]+/g")).join(';'); 25 | return res; 26 | } else return "display:" + display; 27 | }, 28 | getNode: function(item) { 29 | return [item]; 30 | }, 31 | // 是否通过 rich-text 显示 32 | useRichText: function(item) { 33 | // rich-text 不支持 inline 34 | if (item.c || inlineTags[item.name] || (item.attrs.style && item.attrs.style.indexOf("display:inline") != -1)) 35 | return false; 36 | return true; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /components/jyf-parser/libs/handler.wxs: -------------------------------------------------------------------------------- 1 | var inlineTags = { 2 | abbr: 1, 3 | b: 1, 4 | big: 1, 5 | code: 1, 6 | del: 1, 7 | em: 1, 8 | i: 1, 9 | ins: 1, 10 | label: 1, 11 | q: 1, 12 | small: 1, 13 | span: 1, 14 | strong: 1 15 | } 16 | module.exports = { 17 | // 从 rich-text 顶层标签的样式中取出一些给 rich-text 18 | getStyle: function(style, display) { 19 | if (style) { 20 | var i, j, res = ""; 21 | if ((i = style.indexOf("display")) != -1) 22 | res = style.substring(i, (j = style.indexOf(';', i)) == -1 ? style.length : j); 23 | else res = "display:" + display; 24 | if (style.indexOf("flex") != -1) res += ';' + style.match(getRegExp("flex[:-][^;]+/g")).join(';'); 25 | return res; 26 | } else return "display:" + display; 27 | }, 28 | // 处理懒加载 29 | getNode: function(item, imgLoad) { 30 | if (!imgLoad) { 31 | var img = { 32 | name: "img", 33 | attrs: JSON.parse(JSON.stringify(item.attrs)) 34 | } 35 | delete img.attrs.src; 36 | img.attrs.style += ";width:20px !important;height:20px !important"; 37 | return [img]; 38 | } else return [item]; 39 | }, 40 | // 是否通过 rich-text 显示 41 | useRichText: function(item) { 42 | // rich-text 不支持 inline 43 | if (item.c || inlineTags[item.name] || (item.attrs.style && item.attrs.style.indexOf("display:inline") != -1)) 44 | return false; 45 | return true; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /components/jyf-parser/libs/trees.vue: -------------------------------------------------------------------------------- 1 | 8 |