Repository: tumobi/nideshop-mini-program Branch: master Commit: 0ae5a3d0c620 Files: 152 Total size: 373.5 KB Directory structure: gitextract_vsm6etqj/ ├── .gitignore ├── README.md ├── app.js ├── app.json ├── app.wxss ├── config/ │ └── api.js ├── lib/ │ └── wxParse/ │ ├── html2json.js │ ├── htmlparser.js │ ├── showdown.js │ ├── wxDiscode.js │ ├── wxParse.js │ ├── wxParse.wxml │ └── wxParse.wxss ├── pages/ │ ├── auth/ │ │ ├── login/ │ │ │ ├── login.js │ │ │ ├── login.json │ │ │ ├── login.wxml │ │ │ └── login.wxss │ │ ├── register/ │ │ │ ├── register.js │ │ │ ├── register.json │ │ │ ├── register.wxml │ │ │ └── register.wxss │ │ └── reset/ │ │ ├── reset.js │ │ ├── reset.json │ │ ├── reset.wxml │ │ └── reset.wxss │ ├── brand/ │ │ ├── brand.js │ │ ├── brand.json │ │ ├── brand.wxml │ │ └── brand.wxss │ ├── brandDetail/ │ │ ├── brandDetail.js │ │ ├── brandDetail.json │ │ ├── brandDetail.wxml │ │ └── brandDetail.wxss │ ├── cart/ │ │ ├── cart.js │ │ ├── cart.json │ │ ├── cart.wxml │ │ └── cart.wxss │ ├── catalog/ │ │ ├── catalog.js │ │ ├── catalog.json │ │ ├── catalog.wxml │ │ └── catalog.wxss │ ├── category/ │ │ ├── category.js │ │ ├── category.json │ │ ├── category.wxml │ │ └── category.wxss │ ├── comment/ │ │ ├── comment.js │ │ ├── comment.json │ │ ├── comment.wxml │ │ └── comment.wxss │ ├── commentPost/ │ │ ├── commentPost.js │ │ ├── commentPost.json │ │ ├── commentPost.wxml │ │ └── commentPost.wxss │ ├── goods/ │ │ ├── goods.js │ │ ├── goods.json │ │ ├── goods.wxml │ │ └── goods.wxss │ ├── hotGoods/ │ │ ├── hotGoods.js │ │ ├── hotGoods.json │ │ ├── hotGoods.wxml │ │ └── hotGoods.wxss │ ├── index/ │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ ├── logs/ │ │ ├── logs.js │ │ ├── logs.json │ │ ├── logs.wxml │ │ └── logs.wxss │ ├── newGoods/ │ │ ├── newGoods.js │ │ ├── newGoods.json │ │ ├── newGoods.wxml │ │ └── newGoods.wxss │ ├── pay/ │ │ ├── pay.js │ │ ├── pay.json │ │ ├── pay.wxml │ │ └── pay.wxss │ ├── payResult/ │ │ ├── payResult.js │ │ ├── payResult.json │ │ ├── payResult.wxml │ │ └── payResult.wxss │ ├── search/ │ │ ├── search.js │ │ ├── search.json │ │ ├── search.wxml │ │ └── search.wxss │ ├── shopping/ │ │ ├── address/ │ │ │ ├── address.js │ │ │ ├── address.json │ │ │ ├── address.wxml │ │ │ └── address.wxss │ │ ├── addressAdd/ │ │ │ ├── addressAdd.js │ │ │ ├── addressAdd.json │ │ │ ├── addressAdd.wxml │ │ │ └── addressAdd.wxss │ │ └── checkout/ │ │ ├── checkout.js │ │ ├── checkout.json │ │ ├── checkout.wxml │ │ └── checkout.wxss │ ├── topic/ │ │ ├── topic.js │ │ ├── topic.json │ │ ├── topic.wxml │ │ └── topic.wxss │ ├── topicComment/ │ │ ├── topicComment.js │ │ ├── topicComment.json │ │ ├── topicComment.wxml │ │ └── topicComment.wxss │ ├── topicDetail/ │ │ ├── topicDetail.js │ │ ├── topicDetail.json │ │ ├── topicDetail.wxml │ │ └── topicDetail.wxss │ └── ucenter/ │ ├── address/ │ │ ├── address.js │ │ ├── address.json │ │ ├── address.wxml │ │ └── address.wxss │ ├── addressAdd/ │ │ ├── addressAdd.js │ │ ├── addressAdd.json │ │ ├── addressAdd.wxml │ │ └── addressAdd.wxss │ ├── collect/ │ │ ├── collect.js │ │ ├── collect.json │ │ ├── collect.wxml │ │ └── collect.wxss │ ├── coupon/ │ │ ├── coupon.js │ │ ├── coupon.json │ │ ├── coupon.wxml │ │ └── coupon.wxss │ ├── express/ │ │ ├── express.js │ │ ├── express.json │ │ ├── express.wxml │ │ └── express.wxss │ ├── feedback/ │ │ ├── feedback.js │ │ ├── feedback.json │ │ ├── feedback.wxml │ │ └── feedback.wxss │ ├── footprint/ │ │ ├── footprint.js │ │ ├── footprint.json │ │ ├── footprint.wxml │ │ └── footprint.wxss │ ├── index/ │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ ├── order/ │ │ ├── order.js │ │ ├── order.json │ │ ├── order.wxml │ │ └── order.wxss │ └── orderDetail/ │ ├── orderDetail.js │ ├── orderDetail.json │ ├── orderDetail.wxml │ └── orderDetail.wxss ├── services/ │ ├── pay.js │ └── user.js └── utils/ └── util.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ project.config.json ================================================ FILE: README.md ================================================ ### NideShop商城(微信小程序端) + 界面高仿网易严选商城(主要是2016年wap版) + 测试数据采集自网易严选商城 + 功能和数据库参考ecshop + 服务端api基于Node.js+ThinkJS+MySQL + 计划添加基于Vue.js的后台管理系统、PC版、Wap版 **注意:当前版本功能还未完善,请勿商用。** 本项目需要配合NideShop商城服务端使用,GitHub: [https://github.com/tumobi/nideshop](https://github.com/tumobi/nideshop) [云服务器ECS-云主机优惠-2折上云](https://www.aliyun.com/daily-act/ecs/activity_selection?userCode=3grpysgf) ### 项目截图 ![首页](http://upload-images.jianshu.io/upload_images/3985656-c543b937ac6e79bb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/320) ![专题](http://upload-images.jianshu.io/upload_images/3985656-bd606aac3b5491c2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/320) ![分类](http://upload-images.jianshu.io/upload_images/3985656-fa9565158376d439.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/320) ![商品列表](http://upload-images.jianshu.io/upload_images/3985656-788b7fd2c4a558d0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/320) ![商品详情](http://upload-images.jianshu.io/upload_images/3985656-99a6e0a57778d85f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/320) ![购物车](http://upload-images.jianshu.io/upload_images/3985656-60ff2307d81f6bb2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/320) ![订单中心](http://upload-images.jianshu.io/upload_images/3985656-dff837e6b2ec87b3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/320) ### 功能列表 + 首页 + 分类首页、分类商品、新品首发、人气推荐商品页面 + 商品详情页面,包含加入购物车、收藏商品、商品评论功能 + 搜索功能 + 专题功能 + 品牌功能 + 完整的购物流程,商品的加入、编辑、删除、批量选择,收货地址的选择,下单支付 + 会员中心(订单、收藏、足迹、收货地址、意见反馈) .... ### 项目结构 ``` ├─config ├─lib │ └─wxParse    ├─pages │ ├─auth │ │ ├─login │ │ ├─register │ │ └─reset │ ├─brand │ ├─brandDetail │ ├─cart │ ├─catalog │ ├─category │ ├─comment │ ├─goods │ ├─hotGoods │ ├─index │ ├─logs │ ├─newGoods │ ├─pay │ ├─search │ ├─shopping │ │ ├─address │ │ ├─addressAdd │ │ └─checkout │ ├─topic │ ├─topicDetail │ └─ucenter │ ├─address │ ├─addressAdd │ ├─collect │ ├─coupon │ ├─feedback │ ├─footprint │ ├─index │ ├─order │ └─orderDetail ├─static │ └─images └─utils ``` ### 服务端api 项目地址:https://github.com/tumobi/nideshop ### 交流 喜欢别忘了 Star,有问题可通过微信、公众号、QQ 群联系我,谢谢您的关注。 ![联系方式](http://nideshop-static.childsay.com/nideshop-qrcode.png) ================================================ FILE: app.js ================================================ App({ onLaunch: function () { try { this.globalData.userInfo = JSON.parse(wx.getStorageSync('userInfo')); this.globalData.token = wx.getStorageSync('token'); } catch (e) { console.log(e); } }, globalData: { userInfo: { nickname: '点击登录', avatar: 'http://yanxuan.nosdn.127.net/8945ae63d940cc42406c3f67019c5cb6.png' }, token: '', } }) ================================================ FILE: app.json ================================================ { "pages": [ "pages/index/index", "pages/catalog/catalog", "pages/newGoods/newGoods", "pages/hotGoods/hotGoods", "pages/ucenter/address/address", "pages/ucenter/addressAdd/addressAdd", "pages/ucenter/footprint/footprint", "pages/ucenter/order/order", "pages/ucenter/orderDetail/orderDetail", "pages/ucenter/express/express", "pages/ucenter/feedback/feedback", "pages/ucenter/coupon/coupon", "pages/ucenter/collect/collect", "pages/auth/login/login", "pages/auth/register/register", "pages/auth/reset/reset", "pages/pay/pay", "pages/payResult/payResult", "pages/ucenter/index/index", "pages/topic/topic", "pages/comment/comment", "pages/commentPost/commentPost", "pages/topicComment/topicComment", "pages/brand/brand", "pages/brandDetail/brandDetail", "pages/search/search", "pages/category/category", "pages/cart/cart", "pages/shopping/checkout/checkout", "pages/shopping/address/address", "pages/shopping/addressAdd/addressAdd", "pages/goods/goods", "pages/topicDetail/topicDetail" ], "window": { "backgroundTextStyle": "dark", "navigationBarBackgroundColor": "#fff", "navigationBarTitleText": "仿网易严选", "navigationBarTextStyle": "black", "enablePullDownRefresh": true }, "tabBar": { "backgroundColor": "#fafafa", "borderStyle": "white", "selectedColor": "#b4282d", "color": "#666", "list": [ { "pagePath": "pages/index/index", "iconPath": "static/images/ic_menu_choice_nor.png", "selectedIconPath": "static/images/ic_menu_choice_pressed.png", "text": "首页" }, { "pagePath": "pages/topic/topic", "iconPath": "static/images/ic_menu_topic_nor.png", "selectedIconPath": "static/images/ic_menu_topic_pressed.png", "text": "专题" }, { "pagePath": "pages/catalog/catalog", "iconPath": "static/images/ic_menu_sort_nor.png", "selectedIconPath": "static/images/ic_menu_sort_pressed.png", "text": "分类" }, { "pagePath": "pages/cart/cart", "iconPath": "static/images/ic_menu_shoping_nor.png", "selectedIconPath": "static/images/ic_menu_shoping_pressed.png", "text": "购物车" }, { "pagePath": "pages/ucenter/index/index", "iconPath": "static/images/ic_menu_me_nor.png", "selectedIconPath": "static/images/ic_menu_me_pressed.png", "text": "我的" } ] }, "networkTimeout": { "request": 10000, "downloadFile": 10000 }, "debug": true } ================================================ FILE: app.wxss ================================================ /**app.wxss**/ .container { box-sizing: border-box; background-color: #f4f4f4; font-family: PingFangSC-Light,helvetica,'Heiti SC'; } view,image,text,navigator{ box-sizing: border-box; padding:0; margin:0; } view,text{ font-family: PingFangSC-Light,helvetica,'Heiti SC'; font-size: 29rpx; color: #333; } ================================================ FILE: config/api.js ================================================ const ApiRootUrl = 'http://127.0.0.1:8360/api/'; module.exports = { IndexUrl: ApiRootUrl + 'index/index', //首页数据接口 CatalogList: ApiRootUrl + 'catalog/index', //分类目录全部分类数据接口 CatalogCurrent: ApiRootUrl + 'catalog/current', //分类目录当前分类数据接口 AuthLoginByWeixin: ApiRootUrl + 'auth/loginByWeixin', //微信登录 GoodsCount: ApiRootUrl + 'goods/count', //统计商品总数 GoodsList: ApiRootUrl + 'goods/list', //获得商品列表 GoodsCategory: ApiRootUrl + 'goods/category', //获得分类数据 GoodsDetail: ApiRootUrl + 'goods/detail', //获得商品的详情 GoodsNew: ApiRootUrl + 'goods/new', //新品 GoodsHot: ApiRootUrl + 'goods/hot', //热门 GoodsRelated: ApiRootUrl + 'goods/related', //商品详情页的关联商品(大家都在看) BrandList: ApiRootUrl + 'brand/list', //品牌列表 BrandDetail: ApiRootUrl + 'brand/detail', //品牌详情 CartList: ApiRootUrl + 'cart/index', //获取购物车的数据 CartAdd: ApiRootUrl + 'cart/add', // 添加商品到购物车 CartUpdate: ApiRootUrl + 'cart/update', // 更新购物车的商品 CartDelete: ApiRootUrl + 'cart/delete', // 删除购物车的商品 CartChecked: ApiRootUrl + 'cart/checked', // 选择或取消选择商品 CartGoodsCount: ApiRootUrl + 'cart/goodscount', // 获取购物车商品件数 CartCheckout: ApiRootUrl + 'cart/checkout', // 下单前信息确认 OrderSubmit: ApiRootUrl + 'order/submit', // 提交订单 PayPrepayId: ApiRootUrl + 'pay/prepay', //获取微信统一下单prepay_id CollectList: ApiRootUrl + 'collect/list', //收藏列表 CollectAddOrDelete: ApiRootUrl + 'collect/addordelete', //添加或取消收藏 CommentList: ApiRootUrl + 'comment/list', //评论列表 CommentCount: ApiRootUrl + 'comment/count', //评论总数 CommentPost: ApiRootUrl + 'comment/post', //发表评论 TopicList: ApiRootUrl + 'topic/list', //专题列表 TopicDetail: ApiRootUrl + 'topic/detail', //专题详情 TopicRelated: ApiRootUrl + 'topic/related', //相关专题 SearchIndex: ApiRootUrl + 'search/index', //搜索页面数据 SearchResult: ApiRootUrl + 'search/result', //搜索数据 SearchHelper: ApiRootUrl + 'search/helper', //搜索帮助 SearchClearHistory: ApiRootUrl + 'search/clearhistory', //搜索帮助 AddressList: ApiRootUrl + 'address/list', //收货地址列表 AddressDetail: ApiRootUrl + 'address/detail', //收货地址详情 AddressSave: ApiRootUrl + 'address/save', //保存收货地址 AddressDelete: ApiRootUrl + 'address/delete', //保存收货地址 RegionList: ApiRootUrl + 'region/list', //获取区域列表 OrderList: ApiRootUrl + 'order/list', //订单列表 OrderDetail: ApiRootUrl + 'order/detail', //订单详情 OrderCancel: ApiRootUrl + 'order/cancel', //取消订单 OrderExpress: ApiRootUrl + 'order/express', //物流详情 FootprintList: ApiRootUrl + 'footprint/list', //足迹列表 FootprintDelete: ApiRootUrl + 'footprint/delete', //删除足迹 }; ================================================ FILE: lib/wxParse/html2json.js ================================================ /** * author: Di (微信小程序开发工程师) * organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com) * 垂直微信小程序开发交流社区 * * github地址: https://github.com/icindy/wxParse * * for: 微信小程序富文本解析 * detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184 */ var __placeImgeUrlHttps = "https"; var __emojisReg = ''; var __emojisBaseSrc = ''; var __emojis = {}; var wxDiscode = require('wxDiscode.js'); var HTMLParser = require('htmlparser.js'); // Empty Elements - HTML 5 var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr"); // Block Elements - HTML 5 var block = makeMap("br,a,code,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video"); // Inline Elements - HTML 5 var inline = makeMap("abbr,acronym,applet,b,basefont,bdo,big,button,cite,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var"); // Elements that you can, intentionally, leave open // (and which close themselves) var closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr"); // Attributes that have their values filled in disabled="disabled" var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected"); // Special Elements (can contain anything) var special = makeMap("wxxxcode-style,script,style,view,scroll-view,block"); function makeMap(str) { var obj = {}, items = str.split(","); for (var i = 0; i < items.length; i++) obj[items[i]] = true; return obj; } function q(v) { return '"' + v + '"'; } function removeDOCTYPE(html) { return html .replace(/<\?xml.*\?>\n/, '') .replace(/\n/, '') .replace(/\n/, ''); } function html2json(html, bindName) { //处理字符串 html = removeDOCTYPE(html); html = wxDiscode.strDiscode(html); //生成node节点 var bufArray = []; var results = { node: bindName, nodes: [], images:[], imageUrls:[] }; HTMLParser(html, { start: function (tag, attrs, unary) { //debug(tag, attrs, unary); // node for this element var node = { node: 'element', tag: tag, }; if (block[tag]) { node.tagType = "block"; } else if (inline[tag]) { node.tagType = "inline"; } else if (closeSelf[tag]) { node.tagType = "closeSelf"; } if (attrs.length !== 0) { node.attr = attrs.reduce(function (pre, attr) { var name = attr.name; var value = attr.value; if (name == 'class') { console.dir(value); // value = value.join("") node.classStr = value; } // has multi attibutes // make it array of attribute if (name == 'style') { console.dir(value); // value = value.join("") node.styleStr = value; } if (value.match(/ /)) { value = value.split(' '); } // if attr already exists // merge it if (pre[name]) { if (Array.isArray(pre[name])) { // already array, push to last pre[name].push(value); } else { // single value, make it array pre[name] = [pre[name], value]; } } else { // not exist, put it pre[name] = value; } return pre; }, {}); } //对img添加额外数据 if (node.tag === 'img') { node.imgIndex = results.images.length; var imgUrl = node.attr.src; imgUrl = wxDiscode.urlToHttpUrl(imgUrl, __placeImgeUrlHttps); node.attr.src = imgUrl; node.from = bindName; results.images.push(node); results.imageUrls.push(imgUrl); } if (unary) { // if this tag dosen't have end tag // like // add to parents var parent = bufArray[0] || results; if (parent.nodes === undefined) { parent.nodes = []; } parent.nodes.push(node); } else { bufArray.unshift(node); } }, end: function (tag) { //debug(tag); // merge into parent tag var node = bufArray.shift(); if (node.tag !== tag) console.error('invalid state: mismatch end tag'); if (bufArray.length === 0) { results.nodes.push(node); } else { var parent = bufArray[0]; if (parent.nodes === undefined) { parent.nodes = []; } parent.nodes.push(node); } }, chars: function (text) { //debug(text); var node = { node: 'text', text: text, textArray:transEmojiStr(text) }; if (bufArray.length === 0) { results.nodes.push(node); } else { var parent = bufArray[0]; if (parent.nodes === undefined) { parent.nodes = []; } parent.nodes.push(node); } }, comment: function (text) { //debug(text); var node = { node: 'comment', text: text, }; var parent = bufArray[0]; if (parent.nodes === undefined) { parent.nodes = []; } parent.nodes.push(node); }, }); return results; }; function transEmojiStr(str){ // var eReg = new RegExp("["+__reg+' '+"]"); // str = str.replace(/\[([^\[\]]+)\]/g,':$1:') var emojiObjs = []; //如果正则表达式为空 if(__emojisReg.length == 0 || !__emojis){ var emojiObj = {} emojiObj.node = "text"; emojiObj.text = str; array = [emojiObj]; return array; } //这个地方需要调整 str = str.replace(/\[([^\[\]]+)\]/g,':$1:') var eReg = new RegExp("[:]"); var array = str.split(eReg); for(var i = 0; i < array.length; i++){ var ele = array[i]; var emojiObj = {}; if(__emojis[ele]){ emojiObj.node = "element"; emojiObj.tag = "emoji"; emojiObj.text = __emojis[ele]; emojiObj.baseSrc= __emojisBaseSrc; }else{ emojiObj.node = "text"; emojiObj.text = ele; } emojiObjs.push(emojiObj); } return emojiObjs; } function emojisInit(reg='',baseSrc="/wxParse/emojis/",emojis){ __emojisReg = reg; __emojisBaseSrc=baseSrc; __emojis=emojis; } module.exports = { html2json: html2json, emojisInit:emojisInit }; ================================================ FILE: lib/wxParse/htmlparser.js ================================================ /** * author: Di (微信小程序开发工程师) * organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com) * 垂直微信小程序开发交流社区 * * github地址: https://github.com/icindy/wxParse * * for: 微信小程序富文本解析 * detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184 */ // Regular Expressions for parsing tags and attributes var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/, endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/, attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; // Empty Elements - HTML 5 var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr"); // Block Elements - HTML 5 var block = makeMap("a,address,code,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video"); // Inline Elements - HTML 5 var inline = makeMap("abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var"); // Elements that you can, intentionally, leave open // (and which close themselves) var closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr"); // Attributes that have their values filled in disabled="disabled" var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected"); // Special Elements (can contain anything) var special = makeMap("wxxxcode-style,script,style,view,scroll-view,block"); function HTMLParser(html, handler) { var index, chars, match, stack = [], last = html; stack.last = function () { return this[this.length - 1]; }; while (html) { chars = true; // Make sure we're not in a script or style element if (!stack.last() || !special[stack.last()]) { // Comment if (html.indexOf(""); if (index >= 0) { if (handler.comment) handler.comment(html.substring(4, index)); html = html.substring(index + 3); chars = false; } // end tag } else if (html.indexOf("]*>"), function (all, text) { text = text.replace(/|/g, "$1$2"); if (handler.chars) handler.chars(text); return ""; }); parseEndTag("", stack.last()); } if (html == last) throw "Parse Error: " + html; last = html; } // Clean up any remaining tags parseEndTag(); function parseStartTag(tag, tagName, rest, unary) { tagName = tagName.toLowerCase(); if (block[tagName]) { while (stack.last() && inline[stack.last()]) { parseEndTag("", stack.last()); } } if (closeSelf[tagName] && stack.last() == tagName) { parseEndTag("", tagName); } unary = empty[tagName] || !!unary; if (!unary) stack.push(tagName); if (handler.start) { var attrs = []; rest.replace(attr, function (match, name) { var value = arguments[2] ? arguments[2] : arguments[3] ? arguments[3] : arguments[4] ? arguments[4] : fillAttrs[name] ? name : ""; attrs.push({ name: name, value: value, escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') //" }); }); if (handler.start) { handler.start(tagName, attrs, unary); } } } function parseEndTag(tag, tagName) { // If no tag name is provided, clean shop if (!tagName) var pos = 0; // Find the closest opened tag of the same type else for (var pos = stack.length - 1; pos >= 0; pos--) if (stack[pos] == tagName) break; if (pos >= 0) { // Close all the open elements, up the stack for (var i = stack.length - 1; i >= pos; i--) if (handler.end) handler.end(stack[i]); // Remove the open elements from the stack stack.length = pos; } } }; function makeMap(str) { var obj = {}, items = str.split(","); for (var i = 0; i < items.length; i++) obj[items[i]] = true; return obj; } module.exports = HTMLParser; ================================================ FILE: lib/wxParse/showdown.js ================================================ /** * author: Di (微信小程序开发工程师) * organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com) * 垂直微信小程序开发交流社区 * * github地址: https://github.com/icindy/wxParse * * for: 微信小程序富文本解析 * detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184 */ function getDefaultOpts(simple) { 'use strict'; var defaultOptions = { omitExtraWLInCodeBlocks: { defaultValue: false, describe: 'Omit the default extra whiteline added to code blocks', type: 'boolean' }, noHeaderId: { defaultValue: false, describe: 'Turn on/off generated header id', type: 'boolean' }, prefixHeaderId: { defaultValue: false, describe: 'Specify a prefix to generated header ids', type: 'string' }, headerLevelStart: { defaultValue: false, describe: 'The header blocks level start', type: 'integer' }, parseImgDimensions: { defaultValue: false, describe: 'Turn on/off image dimension parsing', type: 'boolean' }, simplifiedAutoLink: { defaultValue: false, describe: 'Turn on/off GFM autolink style', type: 'boolean' }, literalMidWordUnderscores: { defaultValue: false, describe: 'Parse midword underscores as literal underscores', type: 'boolean' }, strikethrough: { defaultValue: false, describe: 'Turn on/off strikethrough support', type: 'boolean' }, tables: { defaultValue: false, describe: 'Turn on/off tables support', type: 'boolean' }, tablesHeaderId: { defaultValue: false, describe: 'Add an id to table headers', type: 'boolean' }, ghCodeBlocks: { defaultValue: true, describe: 'Turn on/off GFM fenced code blocks support', type: 'boolean' }, tasklists: { defaultValue: false, describe: 'Turn on/off GFM tasklist support', type: 'boolean' }, smoothLivePreview: { defaultValue: false, describe: 'Prevents weird effects in live previews due to incomplete input', type: 'boolean' }, smartIndentationFix: { defaultValue: false, description: 'Tries to smartly fix identation in es6 strings', type: 'boolean' } }; if (simple === false) { return JSON.parse(JSON.stringify(defaultOptions)); } var ret = {}; for (var opt in defaultOptions) { if (defaultOptions.hasOwnProperty(opt)) { ret[opt] = defaultOptions[opt].defaultValue; } } return ret; } /** * Created by Tivie on 06-01-2015. */ // Private properties var showdown = {}, parsers = {}, extensions = {}, globalOptions = getDefaultOpts(true), flavor = { github: { omitExtraWLInCodeBlocks: true, prefixHeaderId: 'user-content-', simplifiedAutoLink: true, literalMidWordUnderscores: true, strikethrough: true, tables: true, tablesHeaderId: true, ghCodeBlocks: true, tasklists: true }, vanilla: getDefaultOpts(true) }; /** * helper namespace * @type {{}} */ showdown.helper = {}; /** * TODO LEGACY SUPPORT CODE * @type {{}} */ showdown.extensions = {}; /** * Set a global option * @static * @param {string} key * @param {*} value * @returns {showdown} */ showdown.setOption = function (key, value) { 'use strict'; globalOptions[key] = value; return this; }; /** * Get a global option * @static * @param {string} key * @returns {*} */ showdown.getOption = function (key) { 'use strict'; return globalOptions[key]; }; /** * Get the global options * @static * @returns {{}} */ showdown.getOptions = function () { 'use strict'; return globalOptions; }; /** * Reset global options to the default values * @static */ showdown.resetOptions = function () { 'use strict'; globalOptions = getDefaultOpts(true); }; /** * Set the flavor showdown should use as default * @param {string} name */ showdown.setFlavor = function (name) { 'use strict'; if (flavor.hasOwnProperty(name)) { var preset = flavor[name]; for (var option in preset) { if (preset.hasOwnProperty(option)) { globalOptions[option] = preset[option]; } } } }; /** * Get the default options * @static * @param {boolean} [simple=true] * @returns {{}} */ showdown.getDefaultOptions = function (simple) { 'use strict'; return getDefaultOpts(simple); }; /** * Get or set a subParser * * subParser(name) - Get a registered subParser * subParser(name, func) - Register a subParser * @static * @param {string} name * @param {function} [func] * @returns {*} */ showdown.subParser = function (name, func) { 'use strict'; if (showdown.helper.isString(name)) { if (typeof func !== 'undefined') { parsers[name] = func; } else { if (parsers.hasOwnProperty(name)) { return parsers[name]; } else { throw Error('SubParser named ' + name + ' not registered!'); } } } }; /** * Gets or registers an extension * @static * @param {string} name * @param {object|function=} ext * @returns {*} */ showdown.extension = function (name, ext) { 'use strict'; if (!showdown.helper.isString(name)) { throw Error('Extension \'name\' must be a string'); } name = showdown.helper.stdExtName(name); // Getter if (showdown.helper.isUndefined(ext)) { if (!extensions.hasOwnProperty(name)) { throw Error('Extension named ' + name + ' is not registered!'); } return extensions[name]; // Setter } else { // Expand extension if it's wrapped in a function if (typeof ext === 'function') { ext = ext(); } // Ensure extension is an array if (!showdown.helper.isArray(ext)) { ext = [ext]; } var validExtension = validate(ext, name); if (validExtension.valid) { extensions[name] = ext; } else { throw Error(validExtension.error); } } }; /** * Gets all extensions registered * @returns {{}} */ showdown.getAllExtensions = function () { 'use strict'; return extensions; }; /** * Remove an extension * @param {string} name */ showdown.removeExtension = function (name) { 'use strict'; delete extensions[name]; }; /** * Removes all extensions */ showdown.resetExtensions = function () { 'use strict'; extensions = {}; }; /** * Validate extension * @param {array} extension * @param {string} name * @returns {{valid: boolean, error: string}} */ function validate(extension, name) { 'use strict'; var errMsg = (name) ? 'Error in ' + name + ' extension->' : 'Error in unnamed extension', ret = { valid: true, error: '' }; if (!showdown.helper.isArray(extension)) { extension = [extension]; } for (var i = 0; i < extension.length; ++i) { var baseMsg = errMsg + ' sub-extension ' + i + ': ', ext = extension[i]; if (typeof ext !== 'object') { ret.valid = false; ret.error = baseMsg + 'must be an object, but ' + typeof ext + ' given'; return ret; } if (!showdown.helper.isString(ext.type)) { ret.valid = false; ret.error = baseMsg + 'property "type" must be a string, but ' + typeof ext.type + ' given'; return ret; } var type = ext.type = ext.type.toLowerCase(); // normalize extension type if (type === 'language') { type = ext.type = 'lang'; } if (type === 'html') { type = ext.type = 'output'; } if (type !== 'lang' && type !== 'output' && type !== 'listener') { ret.valid = false; ret.error = baseMsg + 'type ' + type + ' is not recognized. Valid values: "lang/language", "output/html" or "listener"'; return ret; } if (type === 'listener') { if (showdown.helper.isUndefined(ext.listeners)) { ret.valid = false; ret.error = baseMsg + '. Extensions of type "listener" must have a property called "listeners"'; return ret; } } else { if (showdown.helper.isUndefined(ext.filter) && showdown.helper.isUndefined(ext.regex)) { ret.valid = false; ret.error = baseMsg + type + ' extensions must define either a "regex" property or a "filter" method'; return ret; } } if (ext.listeners) { if (typeof ext.listeners !== 'object') { ret.valid = false; ret.error = baseMsg + '"listeners" property must be an object but ' + typeof ext.listeners + ' given'; return ret; } for (var ln in ext.listeners) { if (ext.listeners.hasOwnProperty(ln)) { if (typeof ext.listeners[ln] !== 'function') { ret.valid = false; ret.error = baseMsg + '"listeners" property must be an hash of [event name]: [callback]. listeners.' + ln + ' must be a function but ' + typeof ext.listeners[ln] + ' given'; return ret; } } } } if (ext.filter) { if (typeof ext.filter !== 'function') { ret.valid = false; ret.error = baseMsg + '"filter" must be a function, but ' + typeof ext.filter + ' given'; return ret; } } else if (ext.regex) { if (showdown.helper.isString(ext.regex)) { ext.regex = new RegExp(ext.regex, 'g'); } if (!ext.regex instanceof RegExp) { ret.valid = false; ret.error = baseMsg + '"regex" property must either be a string or a RegExp object, but ' + typeof ext.regex + ' given'; return ret; } if (showdown.helper.isUndefined(ext.replace)) { ret.valid = false; ret.error = baseMsg + '"regex" extensions must implement a replace string or function'; return ret; } } } return ret; } /** * Validate extension * @param {object} ext * @returns {boolean} */ showdown.validateExtension = function (ext) { 'use strict'; var validateExtension = validate(ext, null); if (!validateExtension.valid) { console.warn(validateExtension.error); return false; } return true; }; /** * showdownjs helper functions */ if (!showdown.hasOwnProperty('helper')) { showdown.helper = {}; } /** * Check if var is string * @static * @param {string} a * @returns {boolean} */ showdown.helper.isString = function isString(a) { 'use strict'; return (typeof a === 'string' || a instanceof String); }; /** * Check if var is a function * @static * @param {string} a * @returns {boolean} */ showdown.helper.isFunction = function isFunction(a) { 'use strict'; var getType = {}; return a && getType.toString.call(a) === '[object Function]'; }; /** * ForEach helper function * @static * @param {*} obj * @param {function} callback */ showdown.helper.forEach = function forEach(obj, callback) { 'use strict'; if (typeof obj.forEach === 'function') { obj.forEach(callback); } else { for (var i = 0; i < obj.length; i++) { callback(obj[i], i, obj); } } }; /** * isArray helper function * @static * @param {*} a * @returns {boolean} */ showdown.helper.isArray = function isArray(a) { 'use strict'; return a.constructor === Array; }; /** * Check if value is undefined * @static * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`. */ showdown.helper.isUndefined = function isUndefined(value) { 'use strict'; return typeof value === 'undefined'; }; /** * Standardidize extension name * @static * @param {string} s extension name * @returns {string} */ showdown.helper.stdExtName = function (s) { 'use strict'; return s.replace(/[_-]||\s/g, '').toLowerCase(); }; function escapeCharactersCallback(wholeMatch, m1) { 'use strict'; var charCodeToEscape = m1.charCodeAt(0); return '~E' + charCodeToEscape + 'E'; } /** * Callback used to escape characters when passing through String.replace * @static * @param {string} wholeMatch * @param {string} m1 * @returns {string} */ showdown.helper.escapeCharactersCallback = escapeCharactersCallback; /** * Escape characters in a string * @static * @param {string} text * @param {string} charsToEscape * @param {boolean} afterBackslash * @returns {XML|string|void|*} */ showdown.helper.escapeCharacters = function escapeCharacters(text, charsToEscape, afterBackslash) { 'use strict'; // First we have to escape the escape characters so that // we can build a character class out of them var regexString = '([' + charsToEscape.replace(/([\[\]\\])/g, '\\$1') + '])'; if (afterBackslash) { regexString = '\\\\' + regexString; } var regex = new RegExp(regexString, 'g'); text = text.replace(regex, escapeCharactersCallback); return text; }; var rgxFindMatchPos = function (str, left, right, flags) { 'use strict'; var f = flags || '', g = f.indexOf('g') > -1, x = new RegExp(left + '|' + right, 'g' + f.replace(/g/g, '')), l = new RegExp(left, f.replace(/g/g, '')), pos = [], t, s, m, start, end; do { t = 0; while ((m = x.exec(str))) { if (l.test(m[0])) { if (!(t++)) { s = x.lastIndex; start = s - m[0].length; } } else if (t) { if (!--t) { end = m.index + m[0].length; var obj = { left: {start: start, end: s}, match: {start: s, end: m.index}, right: {start: m.index, end: end}, wholeMatch: {start: start, end: end} }; pos.push(obj); if (!g) { return pos; } } } } } while (t && (x.lastIndex = s)); return pos; }; /** * matchRecursiveRegExp * * (c) 2007 Steven Levithan * MIT License * * Accepts a string to search, a left and right format delimiter * as regex patterns, and optional regex flags. Returns an array * of matches, allowing nested instances of left/right delimiters. * Use the "g" flag to return all matches, otherwise only the * first is returned. Be careful to ensure that the left and * right format delimiters produce mutually exclusive matches. * Backreferences are not supported within the right delimiter * due to how it is internally combined with the left delimiter. * When matching strings whose format delimiters are unbalanced * to the left or right, the output is intentionally as a * conventional regex library with recursion support would * produce, e.g. "<" and ">" both produce ["x"] when using * "<" and ">" as the delimiters (both strings contain a single, * balanced instance of ""). * * examples: * matchRecursiveRegExp("test", "\\(", "\\)") * returns: [] * matchRecursiveRegExp(">>t<>", "<", ">", "g") * returns: ["t<>", ""] * matchRecursiveRegExp("
test
", "]*>", "", "gi") * returns: ["test"] */ showdown.helper.matchRecursiveRegExp = function (str, left, right, flags) { 'use strict'; var matchPos = rgxFindMatchPos (str, left, right, flags), results = []; for (var i = 0; i < matchPos.length; ++i) { results.push([ str.slice(matchPos[i].wholeMatch.start, matchPos[i].wholeMatch.end), str.slice(matchPos[i].match.start, matchPos[i].match.end), str.slice(matchPos[i].left.start, matchPos[i].left.end), str.slice(matchPos[i].right.start, matchPos[i].right.end) ]); } return results; }; /** * * @param {string} str * @param {string|function} replacement * @param {string} left * @param {string} right * @param {string} flags * @returns {string} */ showdown.helper.replaceRecursiveRegExp = function (str, replacement, left, right, flags) { 'use strict'; if (!showdown.helper.isFunction(replacement)) { var repStr = replacement; replacement = function () { return repStr; }; } var matchPos = rgxFindMatchPos(str, left, right, flags), finalStr = str, lng = matchPos.length; if (lng > 0) { var bits = []; if (matchPos[0].wholeMatch.start !== 0) { bits.push(str.slice(0, matchPos[0].wholeMatch.start)); } for (var i = 0; i < lng; ++i) { bits.push( replacement( str.slice(matchPos[i].wholeMatch.start, matchPos[i].wholeMatch.end), str.slice(matchPos[i].match.start, matchPos[i].match.end), str.slice(matchPos[i].left.start, matchPos[i].left.end), str.slice(matchPos[i].right.start, matchPos[i].right.end) ) ); if (i < lng - 1) { bits.push(str.slice(matchPos[i].wholeMatch.end, matchPos[i + 1].wholeMatch.start)); } } if (matchPos[lng - 1].wholeMatch.end < str.length) { bits.push(str.slice(matchPos[lng - 1].wholeMatch.end)); } finalStr = bits.join(''); } return finalStr; }; /** * POLYFILLS */ if (showdown.helper.isUndefined(console)) { console = { warn: function (msg) { 'use strict'; alert(msg); }, log: function (msg) { 'use strict'; alert(msg); }, error: function (msg) { 'use strict'; throw msg; } }; } /** * Created by Estevao on 31-05-2015. */ /** * Showdown Converter class * @class * @param {object} [converterOptions] * @returns {Converter} */ showdown.Converter = function (converterOptions) { 'use strict'; var /** * Options used by this converter * @private * @type {{}} */ options = {}, /** * Language extensions used by this converter * @private * @type {Array} */ langExtensions = [], /** * Output modifiers extensions used by this converter * @private * @type {Array} */ outputModifiers = [], /** * Event listeners * @private * @type {{}} */ listeners = {}; _constructor(); /** * Converter constructor * @private */ function _constructor() { converterOptions = converterOptions || {}; for (var gOpt in globalOptions) { if (globalOptions.hasOwnProperty(gOpt)) { options[gOpt] = globalOptions[gOpt]; } } // Merge options if (typeof converterOptions === 'object') { for (var opt in converterOptions) { if (converterOptions.hasOwnProperty(opt)) { options[opt] = converterOptions[opt]; } } } else { throw Error('Converter expects the passed parameter to be an object, but ' + typeof converterOptions + ' was passed instead.'); } if (options.extensions) { showdown.helper.forEach(options.extensions, _parseExtension); } } /** * Parse extension * @param {*} ext * @param {string} [name=''] * @private */ function _parseExtension(ext, name) { name = name || null; // If it's a string, the extension was previously loaded if (showdown.helper.isString(ext)) { ext = showdown.helper.stdExtName(ext); name = ext; // LEGACY_SUPPORT CODE if (showdown.extensions[ext]) { console.warn('DEPRECATION WARNING: ' + ext + ' is an old extension that uses a deprecated loading method.' + 'Please inform the developer that the extension should be updated!'); legacyExtensionLoading(showdown.extensions[ext], ext); return; // END LEGACY SUPPORT CODE } else if (!showdown.helper.isUndefined(extensions[ext])) { ext = extensions[ext]; } else { throw Error('Extension "' + ext + '" could not be loaded. It was either not found or is not a valid extension.'); } } if (typeof ext === 'function') { ext = ext(); } if (!showdown.helper.isArray(ext)) { ext = [ext]; } var validExt = validate(ext, name); if (!validExt.valid) { throw Error(validExt.error); } for (var i = 0; i < ext.length; ++i) { switch (ext[i].type) { case 'lang': langExtensions.push(ext[i]); break; case 'output': outputModifiers.push(ext[i]); break; } if (ext[i].hasOwnProperty(listeners)) { for (var ln in ext[i].listeners) { if (ext[i].listeners.hasOwnProperty(ln)) { listen(ln, ext[i].listeners[ln]); } } } } } /** * LEGACY_SUPPORT * @param {*} ext * @param {string} name */ function legacyExtensionLoading(ext, name) { if (typeof ext === 'function') { ext = ext(new showdown.Converter()); } if (!showdown.helper.isArray(ext)) { ext = [ext]; } var valid = validate(ext, name); if (!valid.valid) { throw Error(valid.error); } for (var i = 0; i < ext.length; ++i) { switch (ext[i].type) { case 'lang': langExtensions.push(ext[i]); break; case 'output': outputModifiers.push(ext[i]); break; default:// should never reach here throw Error('Extension loader error: Type unrecognized!!!'); } } } /** * Listen to an event * @param {string} name * @param {function} callback */ function listen(name, callback) { if (!showdown.helper.isString(name)) { throw Error('Invalid argument in converter.listen() method: name must be a string, but ' + typeof name + ' given'); } if (typeof callback !== 'function') { throw Error('Invalid argument in converter.listen() method: callback must be a function, but ' + typeof callback + ' given'); } if (!listeners.hasOwnProperty(name)) { listeners[name] = []; } listeners[name].push(callback); } function rTrimInputText(text) { var rsp = text.match(/^\s*/)[0].length, rgx = new RegExp('^\\s{0,' + rsp + '}', 'gm'); return text.replace(rgx, ''); } /** * Dispatch an event * @private * @param {string} evtName Event name * @param {string} text Text * @param {{}} options Converter Options * @param {{}} globals * @returns {string} */ this._dispatch = function dispatch (evtName, text, options, globals) { if (listeners.hasOwnProperty(evtName)) { for (var ei = 0; ei < listeners[evtName].length; ++ei) { var nText = listeners[evtName][ei](evtName, text, this, options, globals); if (nText && typeof nText !== 'undefined') { text = nText; } } } return text; }; /** * Listen to an event * @param {string} name * @param {function} callback * @returns {showdown.Converter} */ this.listen = function (name, callback) { listen(name, callback); return this; }; /** * Converts a markdown string into HTML * @param {string} text * @returns {*} */ this.makeHtml = function (text) { //check if text is not falsy if (!text) { return text; } var globals = { gHtmlBlocks: [], gHtmlMdBlocks: [], gHtmlSpans: [], gUrls: {}, gTitles: {}, gDimensions: {}, gListLevel: 0, hashLinkCounts: {}, langExtensions: langExtensions, outputModifiers: outputModifiers, converter: this, ghCodeBlocks: [] }; // attacklab: Replace ~ with ~T // This lets us use tilde as an escape char to avoid md5 hashes // The choice of character is arbitrary; anything that isn't // magic in Markdown will work. text = text.replace(/~/g, '~T'); // attacklab: Replace $ with ~D // RegExp interprets $ as a special character // when it's in a replacement string text = text.replace(/\$/g, '~D'); // Standardize line endings text = text.replace(/\r\n/g, '\n'); // DOS to Unix text = text.replace(/\r/g, '\n'); // Mac to Unix if (options.smartIndentationFix) { text = rTrimInputText(text); } // Make sure text begins and ends with a couple of newlines: //text = '\n\n' + text + '\n\n'; text = text; // detab text = showdown.subParser('detab')(text, options, globals); // stripBlankLines text = showdown.subParser('stripBlankLines')(text, options, globals); //run languageExtensions showdown.helper.forEach(langExtensions, function (ext) { text = showdown.subParser('runExtension')(ext, text, options, globals); }); // run the sub parsers text = showdown.subParser('hashPreCodeTags')(text, options, globals); text = showdown.subParser('githubCodeBlocks')(text, options, globals); text = showdown.subParser('hashHTMLBlocks')(text, options, globals); text = showdown.subParser('hashHTMLSpans')(text, options, globals); text = showdown.subParser('stripLinkDefinitions')(text, options, globals); text = showdown.subParser('blockGamut')(text, options, globals); text = showdown.subParser('unhashHTMLSpans')(text, options, globals); text = showdown.subParser('unescapeSpecialChars')(text, options, globals); // attacklab: Restore dollar signs text = text.replace(/~D/g, '$$'); // attacklab: Restore tildes text = text.replace(/~T/g, '~'); // Run output modifiers showdown.helper.forEach(outputModifiers, function (ext) { text = showdown.subParser('runExtension')(ext, text, options, globals); }); return text; }; /** * Set an option of this Converter instance * @param {string} key * @param {*} value */ this.setOption = function (key, value) { options[key] = value; }; /** * Get the option of this Converter instance * @param {string} key * @returns {*} */ this.getOption = function (key) { return options[key]; }; /** * Get the options of this Converter instance * @returns {{}} */ this.getOptions = function () { return options; }; /** * Add extension to THIS converter * @param {{}} extension * @param {string} [name=null] */ this.addExtension = function (extension, name) { name = name || null; _parseExtension(extension, name); }; /** * Use a global registered extension with THIS converter * @param {string} extensionName Name of the previously registered extension */ this.useExtension = function (extensionName) { _parseExtension(extensionName); }; /** * Set the flavor THIS converter should use * @param {string} name */ this.setFlavor = function (name) { if (flavor.hasOwnProperty(name)) { var preset = flavor[name]; for (var option in preset) { if (preset.hasOwnProperty(option)) { options[option] = preset[option]; } } } }; /** * Remove an extension from THIS converter. * Note: This is a costly operation. It's better to initialize a new converter * and specify the extensions you wish to use * @param {Array} extension */ this.removeExtension = function (extension) { if (!showdown.helper.isArray(extension)) { extension = [extension]; } for (var a = 0; a < extension.length; ++a) { var ext = extension[a]; for (var i = 0; i < langExtensions.length; ++i) { if (langExtensions[i] === ext) { langExtensions[i].splice(i, 1); } } for (var ii = 0; ii < outputModifiers.length; ++i) { if (outputModifiers[ii] === ext) { outputModifiers[ii].splice(i, 1); } } } }; /** * Get all extension of THIS converter * @returns {{language: Array, output: Array}} */ this.getAllExtensions = function () { return { language: langExtensions, output: outputModifiers }; }; }; /** * Turn Markdown link shortcuts into XHTML tags. */ showdown.subParser('anchors', function (text, options, globals) { 'use strict'; text = globals.converter._dispatch('anchors.before', text, options, globals); var writeAnchorTag = function (wholeMatch, m1, m2, m3, m4, m5, m6, m7) { if (showdown.helper.isUndefined(m7)) { m7 = ''; } wholeMatch = m1; var linkText = m2, linkId = m3.toLowerCase(), url = m4, title = m7; if (!url) { if (!linkId) { // lower-case and turn embedded newlines into spaces linkId = linkText.toLowerCase().replace(/ ?\n/g, ' '); } url = '#' + linkId; if (!showdown.helper.isUndefined(globals.gUrls[linkId])) { url = globals.gUrls[linkId]; if (!showdown.helper.isUndefined(globals.gTitles[linkId])) { title = globals.gTitles[linkId]; } } else { if (wholeMatch.search(/\(\s*\)$/m) > -1) { // Special case for explicit empty url url = ''; } else { return wholeMatch; } } } url = showdown.helper.escapeCharacters(url, '*_', false); var result = ''; return result; }; // First, handle reference-style links: [link text] [id] /* text = text.replace(/ ( // wrap whole match in $1 \[ ( (?: \[[^\]]*\] // allow brackets nested one level | [^\[] // or anything else )* ) \] [ ]? // one optional space (?:\n[ ]*)? // one optional newline followed by spaces \[ (.*?) // id = $3 \] )()()()() // pad remaining backreferences /g,_DoAnchors_callback); */ text = text.replace(/(\[((?:\[[^\]]*]|[^\[\]])*)][ ]?(?:\n[ ]*)?\[(.*?)])()()()()/g, writeAnchorTag); // // Next, inline-style links: [link text](url "optional title") // /* text = text.replace(/ ( // wrap whole match in $1 \[ ( (?: \[[^\]]*\] // allow brackets nested one level | [^\[\]] // or anything else ) ) \] \( // literal paren [ \t]* () // no id, so leave $3 empty ? // href = $4 [ \t]* ( // $5 (['"]) // quote char = $6 (.*?) // Title = $7 \6 // matching quote [ \t]* // ignore any spaces/tabs between closing quote and ) )? // title is optional \) ) /g,writeAnchorTag); */ text = text.replace(/(\[((?:\[[^\]]*]|[^\[\]])*)]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeAnchorTag); // // Last, handle reference-style shortcuts: [link text] // These must come last in case you've also got [link test][1] // or [link test](/foo) // /* text = text.replace(/ ( // wrap whole match in $1 \[ ([^\[\]]+) // link text = $2; can't contain '[' or ']' \] )()()()()() // pad rest of backreferences /g, writeAnchorTag); */ text = text.replace(/(\[([^\[\]]+)])()()()()()/g, writeAnchorTag); text = globals.converter._dispatch('anchors.after', text, options, globals); return text; }); showdown.subParser('autoLinks', function (text, options, globals) { 'use strict'; text = globals.converter._dispatch('autoLinks.before', text, options, globals); var simpleURLRegex = /\b(((https?|ftp|dict):\/\/|www\.)[^'">\s]+\.[^'">\s]+)(?=\s|$)(?!["<>])/gi, delimUrlRegex = /<(((https?|ftp|dict):\/\/|www\.)[^'">\s]+)>/gi, simpleMailRegex = /(?:^|[ \n\t])([A-Za-z0-9!#$%&'*+-/=?^_`\{|}~\.]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)(?:$|[ \n\t])/gi, delimMailRegex = /<(?:mailto:)?([-.\w]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi; text = text.replace(delimUrlRegex, replaceLink); text = text.replace(delimMailRegex, replaceMail); // simpleURLRegex = /\b(((https?|ftp|dict):\/\/|www\.)[-.+~:?#@!$&'()*,;=[\]\w]+)\b/gi, // Email addresses: if (options.simplifiedAutoLink) { text = text.replace(simpleURLRegex, replaceLink); text = text.replace(simpleMailRegex, replaceMail); } function replaceLink(wm, link) { var lnkTxt = link; if (/^www\./i.test(link)) { link = link.replace(/^www\./i, 'http://www.'); } return '' + lnkTxt + ''; } function replaceMail(wholeMatch, m1) { var unescapedStr = showdown.subParser('unescapeSpecialChars')(m1); return showdown.subParser('encodeEmailAddress')(unescapedStr); } text = globals.converter._dispatch('autoLinks.after', text, options, globals); return text; }); /** * These are all the transformations that form block-level * tags like paragraphs, headers, and list items. */ showdown.subParser('blockGamut', function (text, options, globals) { 'use strict'; text = globals.converter._dispatch('blockGamut.before', text, options, globals); // we parse blockquotes first so that we can have headings and hrs // inside blockquotes text = showdown.subParser('blockQuotes')(text, options, globals); text = showdown.subParser('headers')(text, options, globals); // Do Horizontal Rules: var key = showdown.subParser('hashBlock')('
', options, globals); text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, key); text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm, key); text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm, key); text = showdown.subParser('lists')(text, options, globals); text = showdown.subParser('codeBlocks')(text, options, globals); text = showdown.subParser('tables')(text, options, globals); // We already ran _HashHTMLBlocks() before, in Markdown(), but that // was to escape raw HTML in the original Markdown source. This time, // we're escaping the markup we've just created, so that we don't wrap //

tags around block-level tags. text = showdown.subParser('hashHTMLBlocks')(text, options, globals); text = showdown.subParser('paragraphs')(text, options, globals); text = globals.converter._dispatch('blockGamut.after', text, options, globals); return text; }); showdown.subParser('blockQuotes', function (text, options, globals) { 'use strict'; text = globals.converter._dispatch('blockQuotes.before', text, options, globals); /* text = text.replace(/ ( // Wrap whole match in $1 ( ^[ \t]*>[ \t]? // '>' at the start of a line .+\n // rest of the first line (.+\n)* // subsequent consecutive lines \n* // blanks )+ ) /gm, function(){...}); */ text = text.replace(/((^[ \t]{0,3}>[ \t]?.+\n(.+\n)*\n*)+)/gm, function (wholeMatch, m1) { var bq = m1; // attacklab: hack around Konqueror 3.5.4 bug: // "----------bug".replace(/^-/g,"") == "bug" bq = bq.replace(/^[ \t]*>[ \t]?/gm, '~0'); // trim one level of quoting // attacklab: clean up hack bq = bq.replace(/~0/g, ''); bq = bq.replace(/^[ \t]+$/gm, ''); // trim whitespace-only lines bq = showdown.subParser('githubCodeBlocks')(bq, options, globals); bq = showdown.subParser('blockGamut')(bq, options, globals); // recurse bq = bq.replace(/(^|\n)/g, '$1 '); // These leading spaces screw with

 content, so we need to fix that:
    bq = bq.replace(/(\s*
[^\r]+?<\/pre>)/gm, function (wholeMatch, m1) {
      var pre = m1;
      // attacklab: hack around Konqueror 3.5.4 bug:
      pre = pre.replace(/^  /mg, '~0');
      pre = pre.replace(/~0/g, '');
      return pre;
    });

    return showdown.subParser('hashBlock')('
\n' + bq + '\n
', options, globals); }); text = globals.converter._dispatch('blockQuotes.after', text, options, globals); return text; }); /** * Process Markdown `
` blocks.
 */
showdown.subParser('codeBlocks', function (text, options, globals) {
  'use strict';

  text = globals.converter._dispatch('codeBlocks.before', text, options, globals);
  /*
   text = text.replace(text,
   /(?:\n\n|^)
   (								// $1 = the code block -- one or more lines, starting with a space/tab
   (?:
   (?:[ ]{4}|\t)			// Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
   .*\n+
   )+
   )
   (\n*[ ]{0,3}[^ \t\n]|(?=~0))	// attacklab: g_tab_width
   /g,function(){...});
   */

  // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
  text += '~0';

  var pattern = /(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g;
  text = text.replace(pattern, function (wholeMatch, m1, m2) {
    var codeblock = m1,
        nextChar = m2,
        end = '\n';

    codeblock = showdown.subParser('outdent')(codeblock);
    codeblock = showdown.subParser('encodeCode')(codeblock);
    codeblock = showdown.subParser('detab')(codeblock);
    codeblock = codeblock.replace(/^\n+/g, ''); // trim leading newlines
    codeblock = codeblock.replace(/\n+$/g, ''); // trim trailing newlines

    if (options.omitExtraWLInCodeBlocks) {
      end = '';
    }

    codeblock = '
' + codeblock + end + '
'; return showdown.subParser('hashBlock')(codeblock, options, globals) + nextChar; }); // attacklab: strip sentinel text = text.replace(/~0/, ''); text = globals.converter._dispatch('codeBlocks.after', text, options, globals); return text; }); /** * * * Backtick quotes are used for spans. * * * You can use multiple backticks as the delimiters if you want to * include literal backticks in the code span. So, this input: * * Just type ``foo `bar` baz`` at the prompt. * * Will translate to: * *

Just type foo `bar` baz at the prompt.

* * There's no arbitrary limit to the number of backticks you * can use as delimters. If you need three consecutive backticks * in your code, use four for delimiters, etc. * * * You can use spaces to get literal backticks at the edges: * * ... type `` `bar` `` ... * * Turns to: * * ... type `bar` ... */ showdown.subParser('codeSpans', function (text, options, globals) { 'use strict'; text = globals.converter._dispatch('codeSpans.before', text, options, globals); /* text = text.replace(/ (^|[^\\]) // Character before opening ` can't be a backslash (`+) // $2 = Opening run of ` ( // $3 = The code block [^\r]*? [^`] // attacklab: work around lack of lookbehind ) \2 // Matching closer (?!`) /gm, function(){...}); */ if (typeof(text) === 'undefined') { text = ''; } text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm, function (wholeMatch, m1, m2, m3) { var c = m3; c = c.replace(/^([ \t]*)/g, ''); // leading whitespace c = c.replace(/[ \t]*$/g, ''); // trailing whitespace c = showdown.subParser('encodeCode')(c); return m1 + '' + c + ''; } ); text = globals.converter._dispatch('codeSpans.after', text, options, globals); return text; }); /** * Convert all tabs to spaces */ showdown.subParser('detab', function (text) { 'use strict'; // expand first n-1 tabs text = text.replace(/\t(?=\t)/g, ' '); // g_tab_width // replace the nth with two sentinels text = text.replace(/\t/g, '~A~B'); // use the sentinel to anchor our regex so it doesn't explode text = text.replace(/~B(.+?)~A/g, function (wholeMatch, m1) { var leadingText = m1, numSpaces = 4 - leadingText.length % 4; // g_tab_width // there *must* be a better way to do this: for (var i = 0; i < numSpaces; i++) { leadingText += ' '; } return leadingText; }); // clean up sentinels text = text.replace(/~A/g, ' '); // g_tab_width text = text.replace(/~B/g, ''); return text; }); /** * Smart processing for ampersands and angle brackets that need to be encoded. */ showdown.subParser('encodeAmpsAndAngles', function (text) { 'use strict'; // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: // http://bumppo.net/projects/amputator/ text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, '&'); // Encode naked <'s text = text.replace(/<(?![a-z\/?\$!])/gi, '<'); return text; }); /** * Returns the string, with after processing the following backslash escape sequences. * * attacklab: The polite way to do this is with the new escapeCharacters() function: * * text = escapeCharacters(text,"\\",true); * text = escapeCharacters(text,"`*_{}[]()>#+-.!",true); * * ...but we're sidestepping its use of the (slow) RegExp constructor * as an optimization for Firefox. This function gets called a LOT. */ showdown.subParser('encodeBackslashEscapes', function (text) { 'use strict'; text = text.replace(/\\(\\)/g, showdown.helper.escapeCharactersCallback); text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, showdown.helper.escapeCharactersCallback); return text; }); /** * Encode/escape certain characters inside Markdown code runs. * The point is that in code, these characters are literals, * and lose their special Markdown meanings. */ showdown.subParser('encodeCode', function (text) { 'use strict'; // Encode all ampersands; HTML entities are not // entities within a Markdown code span. text = text.replace(/&/g, '&'); // Do the angle bracket song and dance: text = text.replace(//g, '>'); // Now, escape characters that are magic in Markdown: text = showdown.helper.escapeCharacters(text, '*_{}[]\\', false); // jj the line above breaks this: //--- //* Item // 1. Subitem // special char: * // --- return text; }); /** * Input: an email address, e.g. "foo@example.com" * * Output: the email address as a mailto link, with each character * of the address encoded as either a decimal or hex entity, in * the hopes of foiling most address harvesting spam bots. E.g.: * * foo * @example.com * * Based on a filter by Matthew Wickline, posted to the BBEdit-Talk * mailing list: * */ showdown.subParser('encodeEmailAddress', function (addr) { 'use strict'; var encode = [ function (ch) { return '&#' + ch.charCodeAt(0) + ';'; }, function (ch) { return '&#x' + ch.charCodeAt(0).toString(16) + ';'; }, function (ch) { return ch; } ]; addr = 'mailto:' + addr; addr = addr.replace(/./g, function (ch) { if (ch === '@') { // this *must* be encoded. I insist. ch = encode[Math.floor(Math.random() * 2)](ch); } else if (ch !== ':') { // leave ':' alone (to spot mailto: later) var r = Math.random(); // roughly 10% raw, 45% hex, 45% dec ch = ( r > 0.9 ? encode[2](ch) : r > 0.45 ? encode[1](ch) : encode[0](ch) ); } return ch; }); addr = '' + addr + ''; addr = addr.replace(/">.+:/g, '">'); // strip the mailto: from the visible part return addr; }); /** * Within tags -- meaning between < and > -- encode [\ ` * _] so they * don't conflict with their use in Markdown for code, italics and strong. */ showdown.subParser('escapeSpecialCharsWithinTagAttributes', function (text) { 'use strict'; // Build a regex to find HTML tags and comments. See Friedl's // "Mastering Regular Expressions", 2nd Ed., pp. 200-201. var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi; text = text.replace(regex, function (wholeMatch) { var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, '$1`'); tag = showdown.helper.escapeCharacters(tag, '\\`*_', false); return tag; }); return text; }); /** * Handle github codeblocks prior to running HashHTML so that * HTML contained within the codeblock gets escaped properly * Example: * ```ruby * def hello_world(x) * puts "Hello, #{x}" * end * ``` */ showdown.subParser('githubCodeBlocks', function (text, options, globals) { 'use strict'; // early exit if option is not enabled if (!options.ghCodeBlocks) { return text; } text = globals.converter._dispatch('githubCodeBlocks.before', text, options, globals); text += '~0'; text = text.replace(/(?:^|\n)```(.*)\n([\s\S]*?)\n```/g, function (wholeMatch, language, codeblock) { var end = (options.omitExtraWLInCodeBlocks) ? '' : '\n'; // First parse the github code block codeblock = showdown.subParser('encodeCode')(codeblock); codeblock = showdown.subParser('detab')(codeblock); codeblock = codeblock.replace(/^\n+/g, ''); // trim leading newlines codeblock = codeblock.replace(/\n+$/g, ''); // trim trailing whitespace codeblock = '
' + codeblock + end + '
'; codeblock = showdown.subParser('hashBlock')(codeblock, options, globals); // Since GHCodeblocks can be false positives, we need to // store the primitive text and the parsed text in a global var, // and then return a token return '\n\n~G' + (globals.ghCodeBlocks.push({text: wholeMatch, codeblock: codeblock}) - 1) + 'G\n\n'; }); // attacklab: strip sentinel text = text.replace(/~0/, ''); return globals.converter._dispatch('githubCodeBlocks.after', text, options, globals); }); showdown.subParser('hashBlock', function (text, options, globals) { 'use strict'; text = text.replace(/(^\n+|\n+$)/g, ''); return '\n\n~K' + (globals.gHtmlBlocks.push(text) - 1) + 'K\n\n'; }); showdown.subParser('hashElement', function (text, options, globals) { 'use strict'; return function (wholeMatch, m1) { var blockText = m1; // Undo double lines blockText = blockText.replace(/\n\n/g, '\n'); blockText = blockText.replace(/^\n/, ''); // strip trailing blank lines blockText = blockText.replace(/\n+$/g, ''); // Replace the element text with a marker ("~KxK" where x is its key) blockText = '\n\n~K' + (globals.gHtmlBlocks.push(blockText) - 1) + 'K\n\n'; return blockText; }; }); showdown.subParser('hashHTMLBlocks', function (text, options, globals) { 'use strict'; var blockTags = [ 'pre', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'table', 'dl', 'ol', 'ul', 'script', 'noscript', 'form', 'fieldset', 'iframe', 'math', 'style', 'section', 'header', 'footer', 'nav', 'article', 'aside', 'address', 'audio', 'canvas', 'figure', 'hgroup', 'output', 'video', 'p' ], repFunc = function (wholeMatch, match, left, right) { var txt = wholeMatch; // check if this html element is marked as markdown // if so, it's contents should be parsed as markdown if (left.search(/\bmarkdown\b/) !== -1) { txt = left + globals.converter.makeHtml(match) + right; } return '\n\n~K' + (globals.gHtmlBlocks.push(txt) - 1) + 'K\n\n'; }; for (var i = 0; i < blockTags.length; ++i) { text = showdown.helper.replaceRecursiveRegExp(text, repFunc, '^(?: |\\t){0,3}<' + blockTags[i] + '\\b[^>]*>', '', 'gim'); } // HR SPECIAL CASE text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g, showdown.subParser('hashElement')(text, options, globals)); // Special case for standalone HTML comments: text = text.replace(/()/g, showdown.subParser('hashElement')(text, options, globals)); // PHP and ASP-style processor instructions ( and <%...%>) text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g, showdown.subParser('hashElement')(text, options, globals)); return text; }); /** * Hash span elements that should not be parsed as markdown */ showdown.subParser('hashHTMLSpans', function (text, config, globals) { 'use strict'; var matches = showdown.helper.matchRecursiveRegExp(text, ']*>', '', 'gi'); for (var i = 0; i < matches.length; ++i) { text = text.replace(matches[i][0], '~L' + (globals.gHtmlSpans.push(matches[i][0]) - 1) + 'L'); } return text; }); /** * Unhash HTML spans */ showdown.subParser('unhashHTMLSpans', function (text, config, globals) { 'use strict'; for (var i = 0; i < globals.gHtmlSpans.length; ++i) { text = text.replace('~L' + i + 'L', globals.gHtmlSpans[i]); } return text; }); /** * Hash span elements that should not be parsed as markdown */ showdown.subParser('hashPreCodeTags', function (text, config, globals) { 'use strict'; var repFunc = function (wholeMatch, match, left, right) { // encode html entities var codeblock = left + showdown.subParser('encodeCode')(match) + right; return '\n\n~G' + (globals.ghCodeBlocks.push({text: wholeMatch, codeblock: codeblock}) - 1) + 'G\n\n'; }; text = showdown.helper.replaceRecursiveRegExp(text, repFunc, '^(?: |\\t){0,3}]*>\\s*]*>', '^(?: |\\t){0,3}\\s*
', 'gim'); return text; }); showdown.subParser('headers', function (text, options, globals) { 'use strict'; text = globals.converter._dispatch('headers.before', text, options, globals); var prefixHeader = options.prefixHeaderId, headerLevelStart = (isNaN(parseInt(options.headerLevelStart))) ? 1 : parseInt(options.headerLevelStart), // Set text-style headers: // Header 1 // ======== // // Header 2 // -------- // setextRegexH1 = (options.smoothLivePreview) ? /^(.+)[ \t]*\n={2,}[ \t]*\n+/gm : /^(.+)[ \t]*\n=+[ \t]*\n+/gm, setextRegexH2 = (options.smoothLivePreview) ? /^(.+)[ \t]*\n-{2,}[ \t]*\n+/gm : /^(.+)[ \t]*\n-+[ \t]*\n+/gm; text = text.replace(setextRegexH1, function (wholeMatch, m1) { var spanGamut = showdown.subParser('spanGamut')(m1, options, globals), hID = (options.noHeaderId) ? '' : ' id="' + headerId(m1) + '"', hLevel = headerLevelStart, hashBlock = '' + spanGamut + ''; return showdown.subParser('hashBlock')(hashBlock, options, globals); }); text = text.replace(setextRegexH2, function (matchFound, m1) { var spanGamut = showdown.subParser('spanGamut')(m1, options, globals), hID = (options.noHeaderId) ? '' : ' id="' + headerId(m1) + '"', hLevel = headerLevelStart + 1, hashBlock = '' + spanGamut + ''; return showdown.subParser('hashBlock')(hashBlock, options, globals); }); // atx-style headers: // # Header 1 // ## Header 2 // ## Header 2 with closing hashes ## // ... // ###### Header 6 // text = text.replace(/^(#{1,6})[ \t]*(.+?)[ \t]*#*\n+/gm, function (wholeMatch, m1, m2) { var span = showdown.subParser('spanGamut')(m2, options, globals), hID = (options.noHeaderId) ? '' : ' id="' + headerId(m2) + '"', hLevel = headerLevelStart - 1 + m1.length, header = '' + span + ''; return showdown.subParser('hashBlock')(header, options, globals); }); function headerId(m) { var title, escapedId = m.replace(/[^\w]/g, '').toLowerCase(); if (globals.hashLinkCounts[escapedId]) { title = escapedId + '-' + (globals.hashLinkCounts[escapedId]++); } else { title = escapedId; globals.hashLinkCounts[escapedId] = 1; } // Prefix id to prevent causing inadvertent pre-existing style matches. if (prefixHeader === true) { prefixHeader = 'section'; } if (showdown.helper.isString(prefixHeader)) { return prefixHeader + title; } return title; } text = globals.converter._dispatch('headers.after', text, options, globals); return text; }); /** * Turn Markdown image shortcuts into tags. */ showdown.subParser('images', function (text, options, globals) { 'use strict'; text = globals.converter._dispatch('images.before', text, options, globals); var inlineRegExp = /!\[(.*?)]\s?\([ \t]*()?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(['"])(.*?)\6[ \t]*)?\)/g, referenceRegExp = /!\[([^\]]*?)] ?(?:\n *)?\[(.*?)]()()()()()/g; function writeImageTag (wholeMatch, altText, linkId, url, width, height, m5, title) { var gUrls = globals.gUrls, gTitles = globals.gTitles, gDims = globals.gDimensions; linkId = linkId.toLowerCase(); if (!title) { title = ''; } if (url === '' || url === null) { if (linkId === '' || linkId === null) { // lower-case and turn embedded newlines into spaces linkId = altText.toLowerCase().replace(/ ?\n/g, ' '); } url = '#' + linkId; if (!showdown.helper.isUndefined(gUrls[linkId])) { url = gUrls[linkId]; if (!showdown.helper.isUndefined(gTitles[linkId])) { title = gTitles[linkId]; } if (!showdown.helper.isUndefined(gDims[linkId])) { width = gDims[linkId].width; height = gDims[linkId].height; } } else { return wholeMatch; } } altText = altText.replace(/"/g, '"'); altText = showdown.helper.escapeCharacters(altText, '*_', false); url = showdown.helper.escapeCharacters(url, '*_', false); var result = '' + altText + 'x "optional title") text = text.replace(inlineRegExp, writeImageTag); text = globals.converter._dispatch('images.after', text, options, globals); return text; }); showdown.subParser('italicsAndBold', function (text, options, globals) { 'use strict'; text = globals.converter._dispatch('italicsAndBold.before', text, options, globals); if (options.literalMidWordUnderscores) { //underscores // Since we are consuming a \s character, we need to add it text = text.replace(/(^|\s|>|\b)__(?=\S)([\s\S]+?)__(?=\b|<|\s|$)/gm, '$1$2'); text = text.replace(/(^|\s|>|\b)_(?=\S)([\s\S]+?)_(?=\b|<|\s|$)/gm, '$1$2'); //asterisks text = text.replace(/(\*\*)(?=\S)([^\r]*?\S[*]*)\1/g, '$2'); text = text.replace(/(\*)(?=\S)([^\r]*?\S)\1/g, '$2'); } else { // must go first: text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g, '$2'); text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g, '$2'); } text = globals.converter._dispatch('italicsAndBold.after', text, options, globals); return text; }); /** * Form HTML ordered (numbered) and unordered (bulleted) lists. */ showdown.subParser('lists', function (text, options, globals) { 'use strict'; text = globals.converter._dispatch('lists.before', text, options, globals); /** * Process the contents of a single ordered or unordered list, splitting it * into individual list items. * @param {string} listStr * @param {boolean} trimTrailing * @returns {string} */ function processListItems (listStr, trimTrailing) { // The $g_list_level global keeps track of when we're inside a list. // Each time we enter a list, we increment it; when we leave a list, // we decrement. If it's zero, we're not in a list anymore. // // We do this because when we're not inside a list, we want to treat // something like this: // // I recommend upgrading to version // 8. Oops, now this line is treated // as a sub-list. // // As a single paragraph, despite the fact that the second line starts // with a digit-period-space sequence. // // Whereas when we're inside a list (or sub-list), that line will be // treated as the start of a sub-list. What a kludge, huh? This is // an aspect of Markdown's syntax that's hard to parse perfectly // without resorting to mind-reading. Perhaps the solution is to // change the syntax rules such that sub-lists must start with a // starting cardinal number; e.g. "1." or "a.". globals.gListLevel++; // trim trailing blank lines: listStr = listStr.replace(/\n{2,}$/, '\n'); // attacklab: add sentinel to emulate \z listStr += '~0'; var rgx = /(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm, isParagraphed = (/\n[ \t]*\n(?!~0)/.test(listStr)); listStr = listStr.replace(rgx, function (wholeMatch, m1, m2, m3, m4, taskbtn, checked) { checked = (checked && checked.trim() !== ''); var item = showdown.subParser('outdent')(m4, options, globals), bulletStyle = ''; // Support for github tasklists if (taskbtn && options.tasklists) { bulletStyle = ' class="task-list-item" style="list-style-type: none;"'; item = item.replace(/^[ \t]*\[(x|X| )?]/m, function () { var otp = ' -1)) { item = showdown.subParser('githubCodeBlocks')(item, options, globals); item = showdown.subParser('blockGamut')(item, options, globals); } else { // Recursion for sub-lists: item = showdown.subParser('lists')(item, options, globals); item = item.replace(/\n$/, ''); // chomp(item) if (isParagraphed) { item = showdown.subParser('paragraphs')(item, options, globals); } else { item = showdown.subParser('spanGamut')(item, options, globals); } } item = '\n' + item + '\n'; return item; }); // attacklab: strip sentinel listStr = listStr.replace(/~0/g, ''); globals.gListLevel--; if (trimTrailing) { listStr = listStr.replace(/\s+$/, ''); } return listStr; } /** * Check and parse consecutive lists (better fix for issue #142) * @param {string} list * @param {string} listType * @param {boolean} trimTrailing * @returns {string} */ function parseConsecutiveLists(list, listType, trimTrailing) { // check if we caught 2 or more consecutive lists by mistake // we use the counterRgx, meaning if listType is UL we look for UL and vice versa var counterRxg = (listType === 'ul') ? /^ {0,2}\d+\.[ \t]/gm : /^ {0,2}[*+-][ \t]/gm, subLists = [], result = ''; if (list.search(counterRxg) !== -1) { (function parseCL(txt) { var pos = txt.search(counterRxg); if (pos !== -1) { // slice result += '\n\n<' + listType + '>' + processListItems(txt.slice(0, pos), !!trimTrailing) + '\n\n'; // invert counterType and listType listType = (listType === 'ul') ? 'ol' : 'ul'; counterRxg = (listType === 'ul') ? /^ {0,2}\d+\.[ \t]/gm : /^ {0,2}[*+-][ \t]/gm; //recurse parseCL(txt.slice(pos)); } else { result += '\n\n<' + listType + '>' + processListItems(txt, !!trimTrailing) + '\n\n'; } })(list); for (var i = 0; i < subLists.length; ++i) { } } else { result = '\n\n<' + listType + '>' + processListItems(list, !!trimTrailing) + '\n\n'; } return result; } // attacklab: add sentinel to hack around khtml/safari bug: // http://bugs.webkit.org/show_bug.cgi?id=11231 text += '~0'; // Re-usable pattern to match any entire ul or ol list: var wholeList = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm; if (globals.gListLevel) { text = text.replace(wholeList, function (wholeMatch, list, m2) { var listType = (m2.search(/[*+-]/g) > -1) ? 'ul' : 'ol'; return parseConsecutiveLists(list, listType, true); }); } else { wholeList = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm; //wholeList = /(\n\n|^\n?)( {0,3}([*+-]|\d+\.)[ \t]+[\s\S]+?)(?=(~0)|(\n\n(?!\t| {2,}| {0,3}([*+-]|\d+\.)[ \t])))/g; text = text.replace(wholeList, function (wholeMatch, m1, list, m3) { var listType = (m3.search(/[*+-]/g) > -1) ? 'ul' : 'ol'; return parseConsecutiveLists(list, listType); }); } // attacklab: strip sentinel text = text.replace(/~0/, ''); text = globals.converter._dispatch('lists.after', text, options, globals); return text; }); /** * Remove one level of line-leading tabs or spaces */ showdown.subParser('outdent', function (text) { 'use strict'; // attacklab: hack around Konqueror 3.5.4 bug: // "----------bug".replace(/^-/g,"") == "bug" text = text.replace(/^(\t|[ ]{1,4})/gm, '~0'); // attacklab: g_tab_width // attacklab: clean up hack text = text.replace(/~0/g, ''); return text; }); /** * */ showdown.subParser('paragraphs', function (text, options, globals) { 'use strict'; text = globals.converter._dispatch('paragraphs.before', text, options, globals); // Strip leading and trailing lines: text = text.replace(/^\n+/g, ''); text = text.replace(/\n+$/g, ''); var grafs = text.split(/\n{2,}/g), grafsOut = [], end = grafs.length; // Wrap

tags for (var i = 0; i < end; i++) { var str = grafs[i]; // if this is an HTML marker, copy it if (str.search(/~(K|G)(\d+)\1/g) >= 0) { grafsOut.push(str); } else { str = showdown.subParser('spanGamut')(str, options, globals); str = str.replace(/^([ \t]*)/g, '

'); str += '

'; grafsOut.push(str); } } /** Unhashify HTML blocks */ end = grafsOut.length; for (i = 0; i < end; i++) { var blockText = '', grafsOutIt = grafsOut[i], codeFlag = false; // if this is a marker for an html block... while (grafsOutIt.search(/~(K|G)(\d+)\1/) >= 0) { var delim = RegExp.$1, num = RegExp.$2; if (delim === 'K') { blockText = globals.gHtmlBlocks[num]; } else { // we need to check if ghBlock is a false positive if (codeFlag) { // use encoded version of all text blockText = showdown.subParser('encodeCode')(globals.ghCodeBlocks[num].text); } else { blockText = globals.ghCodeBlocks[num].codeblock; } } blockText = blockText.replace(/\$/g, '$$$$'); // Escape any dollar signs grafsOutIt = grafsOutIt.replace(/(\n\n)?~(K|G)\d+\2(\n\n)?/, blockText); // Check if grafsOutIt is a pre->code if (/^]*>\s*]*>/.test(grafsOutIt)) { codeFlag = true; } } grafsOut[i] = grafsOutIt; } text = grafsOut.join('\n\n'); // Strip leading and trailing lines: text = text.replace(/^\n+/g, ''); text = text.replace(/\n+$/g, ''); return globals.converter._dispatch('paragraphs.after', text, options, globals); }); /** * Run extension */ showdown.subParser('runExtension', function (ext, text, options, globals) { 'use strict'; if (ext.filter) { text = ext.filter(text, globals.converter, options); } else if (ext.regex) { // TODO remove this when old extension loading mechanism is deprecated var re = ext.regex; if (!re instanceof RegExp) { re = new RegExp(re, 'g'); } text = text.replace(re, ext.replace); } return text; }); /** * These are all the transformations that occur *within* block-level * tags like paragraphs, headers, and list items. */ showdown.subParser('spanGamut', function (text, options, globals) { 'use strict'; text = globals.converter._dispatch('spanGamut.before', text, options, globals); text = showdown.subParser('codeSpans')(text, options, globals); text = showdown.subParser('escapeSpecialCharsWithinTagAttributes')(text, options, globals); text = showdown.subParser('encodeBackslashEscapes')(text, options, globals); // Process anchor and image tags. Images must come first, // because ![foo][f] looks like an anchor. text = showdown.subParser('images')(text, options, globals); text = showdown.subParser('anchors')(text, options, globals); // Make links out of things like `` // Must come after _DoAnchors(), because you can use < and > // delimiters in inline links like [this](). text = showdown.subParser('autoLinks')(text, options, globals); text = showdown.subParser('encodeAmpsAndAngles')(text, options, globals); text = showdown.subParser('italicsAndBold')(text, options, globals); text = showdown.subParser('strikethrough')(text, options, globals); // Do hard breaks: text = text.replace(/ +\n/g, '
\n'); text = globals.converter._dispatch('spanGamut.after', text, options, globals); return text; }); showdown.subParser('strikethrough', function (text, options, globals) { 'use strict'; if (options.strikethrough) { text = globals.converter._dispatch('strikethrough.before', text, options, globals); text = text.replace(/(?:~T){2}([\s\S]+?)(?:~T){2}/g, '$1'); text = globals.converter._dispatch('strikethrough.after', text, options, globals); } return text; }); /** * Strip any lines consisting only of spaces and tabs. * This makes subsequent regexs easier to write, because we can * match consecutive blank lines with /\n+/ instead of something * contorted like /[ \t]*\n+/ */ showdown.subParser('stripBlankLines', function (text) { 'use strict'; return text.replace(/^[ \t]+$/mg, ''); }); /** * Strips link definitions from text, stores the URLs and titles in * hash references. * Link defs are in the form: ^[id]: url "optional title" * * ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1 * [ \t]* * \n? // maybe *one* newline * [ \t]* * ? // url = $2 * [ \t]* * \n? // maybe one newline * [ \t]* * (?: * (\n*) // any lines skipped = $3 attacklab: lookbehind removed * ["(] * (.+?) // title = $4 * [")] * [ \t]* * )? // title is optional * (?:\n+|$) * /gm, * function(){...}); * */ showdown.subParser('stripLinkDefinitions', function (text, options, globals) { 'use strict'; var regex = /^ {0,3}\[(.+)]:[ \t]*\n?[ \t]*?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*\n?[ \t]*(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n+|(?=~0))/gm; // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug text += '~0'; text = text.replace(regex, function (wholeMatch, linkId, url, width, height, blankLines, title) { linkId = linkId.toLowerCase(); globals.gUrls[linkId] = showdown.subParser('encodeAmpsAndAngles')(url); // Link IDs are case-insensitive if (blankLines) { // Oops, found blank lines, so it's not a title. // Put back the parenthetical statement we stole. return blankLines + title; } else { if (title) { globals.gTitles[linkId] = title.replace(/"|'/g, '"'); } if (options.parseImgDimensions && width && height) { globals.gDimensions[linkId] = { width: width, height: height }; } } // Completely remove the definition from the text return ''; }); // attacklab: strip sentinel text = text.replace(/~0/, ''); return text; }); showdown.subParser('tables', function (text, options, globals) { 'use strict'; if (!options.tables) { return text; } var tableRgx = /^[ \t]{0,3}\|?.+\|.+\n[ \t]{0,3}\|?[ \t]*:?[ \t]*(?:-|=){2,}[ \t]*:?[ \t]*\|[ \t]*:?[ \t]*(?:-|=){2,}[\s\S]+?(?:\n\n|~0)/gm; function parseStyles(sLine) { if (/^:[ \t]*--*$/.test(sLine)) { return ' style="text-align:left;"'; } else if (/^--*[ \t]*:[ \t]*$/.test(sLine)) { return ' style="text-align:right;"'; } else if (/^:[ \t]*--*[ \t]*:$/.test(sLine)) { return ' style="text-align:center;"'; } else { return ''; } } function parseHeaders(header, style) { var id = ''; header = header.trim(); if (options.tableHeaderId) { id = ' id="' + header.replace(/ /g, '_').toLowerCase() + '"'; } header = showdown.subParser('spanGamut')(header, options, globals); return '' + header + '\n'; } function parseCells(cell, style) { var subText = showdown.subParser('spanGamut')(cell, options, globals); return '' + subText + '\n'; } function buildTable(headers, cells) { var tb = '\n\n\n', tblLgn = headers.length; for (var i = 0; i < tblLgn; ++i) { tb += headers[i]; } tb += '\n\n\n'; for (i = 0; i < cells.length; ++i) { tb += '\n'; for (var ii = 0; ii < tblLgn; ++ii) { tb += cells[i][ii]; } tb += '\n'; } tb += '\n
\n'; return tb; } text = globals.converter._dispatch('tables.before', text, options, globals); text = text.replace(tableRgx, function (rawTable) { var i, tableLines = rawTable.split('\n'); // strip wrong first and last column if wrapped tables are used for (i = 0; i < tableLines.length; ++i) { if (/^[ \t]{0,3}\|/.test(tableLines[i])) { tableLines[i] = tableLines[i].replace(/^[ \t]{0,3}\|/, ''); } if (/\|[ \t]*$/.test(tableLines[i])) { tableLines[i] = tableLines[i].replace(/\|[ \t]*$/, ''); } } var rawHeaders = tableLines[0].split('|').map(function (s) { return s.trim();}), rawStyles = tableLines[1].split('|').map(function (s) { return s.trim();}), rawCells = [], headers = [], styles = [], cells = []; tableLines.shift(); tableLines.shift(); for (i = 0; i < tableLines.length; ++i) { if (tableLines[i].trim() === '') { continue; } rawCells.push( tableLines[i] .split('|') .map(function (s) { return s.trim(); }) ); } if (rawHeaders.length < rawStyles.length) { return rawTable; } for (i = 0; i < rawStyles.length; ++i) { styles.push(parseStyles(rawStyles[i])); } for (i = 0; i < rawHeaders.length; ++i) { if (showdown.helper.isUndefined(styles[i])) { styles[i] = ''; } headers.push(parseHeaders(rawHeaders[i], styles[i])); } for (i = 0; i < rawCells.length; ++i) { var row = []; for (var ii = 0; ii < headers.length; ++ii) { if (showdown.helper.isUndefined(rawCells[i][ii])) { } row.push(parseCells(rawCells[i][ii], styles[ii])); } cells.push(row); } return buildTable(headers, cells); }); text = globals.converter._dispatch('tables.after', text, options, globals); return text; }); /** * Swap back in all the special characters we've hidden. */ showdown.subParser('unescapeSpecialChars', function (text) { 'use strict'; text = text.replace(/~E(\d+)E/g, function (wholeMatch, m1) { var charCodeToReplace = parseInt(m1); return String.fromCharCode(charCodeToReplace); }); return text; }); module.exports = showdown; ================================================ FILE: lib/wxParse/wxDiscode.js ================================================ // HTML 支持的数学符号 function strNumDiscode(str){ str = str.replace(/∀/g, '∀'); str = str.replace(/∂/g, '∂'); str = str.replace(/&exists;/g, '∃'); str = str.replace(/∅/g, '∅'); str = str.replace(/∇/g, '∇'); str = str.replace(/∈/g, '∈'); str = str.replace(/∉/g, '∉'); str = str.replace(/∋/g, '∋'); str = str.replace(/∏/g, '∏'); str = str.replace(/∑/g, '∑'); str = str.replace(/−/g, '−'); str = str.replace(/∗/g, '∗'); str = str.replace(/√/g, '√'); str = str.replace(/∝/g, '∝'); str = str.replace(/∞/g, '∞'); str = str.replace(/∠/g, '∠'); str = str.replace(/∧/g, '∧'); str = str.replace(/∨/g, '∨'); str = str.replace(/∩/g, '∩'); str = str.replace(/∩/g, '∪'); str = str.replace(/∫/g, '∫'); str = str.replace(/∴/g, '∴'); str = str.replace(/∼/g, '∼'); str = str.replace(/≅/g, '≅'); str = str.replace(/≈/g, '≈'); str = str.replace(/≠/g, '≠'); str = str.replace(/≤/g, '≤'); str = str.replace(/≥/g, '≥'); str = str.replace(/⊂/g, '⊂'); str = str.replace(/⊃/g, '⊃'); str = str.replace(/⊄/g, '⊄'); str = str.replace(/⊆/g, '⊆'); str = str.replace(/⊇/g, '⊇'); str = str.replace(/⊕/g, '⊕'); str = str.replace(/⊗/g, '⊗'); str = str.replace(/⊥/g, '⊥'); str = str.replace(/⋅/g, '⋅'); return str; } //HTML 支持的希腊字母 function strGreeceDiscode(str){ str = str.replace(/Α/g, 'Α'); str = str.replace(/Β/g, 'Β'); str = str.replace(/Γ/g, 'Γ'); str = str.replace(/Δ/g, 'Δ'); str = str.replace(/Ε/g, 'Ε'); str = str.replace(/Ζ/g, 'Ζ'); str = str.replace(/Η/g, 'Η'); str = str.replace(/Θ/g, 'Θ'); str = str.replace(/Ι/g, 'Ι'); str = str.replace(/Κ/g, 'Κ'); str = str.replace(/Λ/g, 'Λ'); str = str.replace(/Μ/g, 'Μ'); str = str.replace(/Ν/g, 'Ν'); str = str.replace(/Ξ/g, 'Ν'); str = str.replace(/Ο/g, 'Ο'); str = str.replace(/Π/g, 'Π'); str = str.replace(/Ρ/g, 'Ρ'); str = str.replace(/Σ/g, 'Σ'); str = str.replace(/Τ/g, 'Τ'); str = str.replace(/Υ/g, 'Υ'); str = str.replace(/Φ/g, 'Φ'); str = str.replace(/Χ/g, 'Χ'); str = str.replace(/Ψ/g, 'Ψ'); str = str.replace(/Ω/g, 'Ω'); str = str.replace(/α/g, 'α'); str = str.replace(/β/g, 'β'); str = str.replace(/γ/g, 'γ'); str = str.replace(/δ/g, 'δ'); str = str.replace(/ε/g, 'ε'); str = str.replace(/ζ/g, 'ζ'); str = str.replace(/η/g, 'η'); str = str.replace(/θ/g, 'θ'); str = str.replace(/ι/g, 'ι'); str = str.replace(/κ/g, 'κ'); str = str.replace(/λ/g, 'λ'); str = str.replace(/μ/g, 'μ'); str = str.replace(/ν/g, 'ν'); str = str.replace(/ξ/g, 'ξ'); str = str.replace(/ο/g, 'ο'); str = str.replace(/π/g, 'π'); str = str.replace(/ρ/g, 'ρ'); str = str.replace(/ς/g, 'ς'); str = str.replace(/σ/g, 'σ'); str = str.replace(/τ/g, 'τ'); str = str.replace(/υ/g, 'υ'); str = str.replace(/φ/g, 'φ'); str = str.replace(/χ/g, 'χ'); str = str.replace(/ψ/g, 'ψ'); str = str.replace(/ω/g, 'ω'); str = str.replace(/ϑ/g, 'ϑ'); str = str.replace(/ϒ/g, 'ϒ'); str = str.replace(/ϖ/g, 'ϖ'); str = str.replace(/·/g, '·'); return str; } // function strcharacterDiscode(str){ // 加入常用解析 str = str.replace(/ /g, ' '); str = str.replace(/"/g, '"'); str = str.replace(/&/g, '&'); // str = str.replace(/</g, '‹'); // str = str.replace(/>/g, '›'); str = str.replace(/</g, '<'); str = str.replace(/>/g, '>'); return str; } // HTML 支持的其他实体 function strOtherDiscode(str){ str = str.replace(/Œ/g, 'Œ'); str = str.replace(/œ/g, 'œ'); str = str.replace(/Š/g, 'Š'); str = str.replace(/š/g, 'š'); str = str.replace(/Ÿ/g, 'Ÿ'); str = str.replace(/ƒ/g, 'ƒ'); str = str.replace(/ˆ/g, 'ˆ'); str = str.replace(/˜/g, '˜'); str = str.replace(/ /g, ''); str = str.replace(/ /g, ''); str = str.replace(/ /g, ''); str = str.replace(/‌/g, ''); str = str.replace(/‍/g, ''); str = str.replace(/‎/g, ''); str = str.replace(/‏/g, ''); str = str.replace(/–/g, '–'); str = str.replace(/—/g, '—'); str = str.replace(/‘/g, '‘'); str = str.replace(/’/g, '’'); str = str.replace(/‚/g, '‚'); str = str.replace(/“/g, '“'); str = str.replace(/”/g, '”'); str = str.replace(/„/g, '„'); str = str.replace(/†/g, '†'); str = str.replace(/‡/g, '‡'); str = str.replace(/•/g, '•'); str = str.replace(/…/g, '…'); str = str.replace(/‰/g, '‰'); str = str.replace(/′/g, '′'); str = str.replace(/″/g, '″'); str = str.replace(/‹/g, '‹'); str = str.replace(/›/g, '›'); str = str.replace(/‾/g, '‾'); str = str.replace(/€/g, '€'); str = str.replace(/™/g, '™'); str = str.replace(/←/g, '←'); str = str.replace(/↑/g, '↑'); str = str.replace(/→/g, '→'); str = str.replace(/↓/g, '↓'); str = str.replace(/↔/g, '↔'); str = str.replace(/↵/g, '↵'); str = str.replace(/⌈/g, '⌈'); str = str.replace(/⌉/g, '⌉'); str = str.replace(/⌊/g, '⌊'); str = str.replace(/⌋/g, '⌋'); str = str.replace(/◊/g, '◊'); str = str.replace(/♠/g, '♠'); str = str.replace(/♣/g, '♣'); str = str.replace(/♥/g, '♥'); str = str.replace(/♦/g, '♦'); return str; } function strMoreDiscode(str){ str = str.replace(/\r\n/g,""); str = str.replace(/\n/g,""); str = str.replace(/code/g,"wxxxcode-style"); return str; } function strDiscode(str){ str = strNumDiscode(str); str = strGreeceDiscode(str); str = strcharacterDiscode(str); str = strOtherDiscode(str); str = strMoreDiscode(str); return str; } function urlToHttpUrl(url,rep){ var patt1 = new RegExp("^//"); var result = patt1.test(url); if(result){ url = rep+":"+url; } return url; } module.exports = { strDiscode:strDiscode, urlToHttpUrl:urlToHttpUrl } ================================================ FILE: lib/wxParse/wxParse.js ================================================ /** * author: Di (微信小程序开发工程师) * organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com) * 垂直微信小程序开发交流社区 * * github地址: https://github.com/icindy/wxParse * * for: 微信小程序富文本解析 * detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184 */ /** * utils函数引入 **/ import showdown from 'showdown.js'; import HtmlToJson from 'html2json.js'; /** * 配置及公有属性 **/ /** * 主函数入口区 **/ function wxParse(bindName = 'wxParseData', type='html', data='
数据不能为空
', target,imagePadding) { var that = target; var transData = {};//存放转化后的数据 if (type == 'html') { transData = HtmlToJson.html2json(data, bindName); console.log(JSON.stringify(transData, ' ', ' ')); } else if (type == 'md' || type == 'markdown') { var converter = new showdown.Converter(); var html = converter.makeHtml(data); transData = HtmlToJson.html2json(html, bindName); console.log(JSON.stringify(transData, ' ', ' ')); } transData.view = {}; transData.view.imagePadding = 0; if(typeof(imagePadding) != 'undefined'){ transData.view.imagePadding = imagePadding } var bindData = {}; bindData[bindName] = transData; that.setData(bindData) that.wxParseImgLoad = wxParseImgLoad; that.wxParseImgTap = wxParseImgTap; } // 图片点击事件 function wxParseImgTap(e) { var that = this; var nowImgUrl = e.target.dataset.src; var tagFrom = e.target.dataset.from; if (typeof (tagFrom) != 'undefined' && tagFrom.length > 0) { wx.previewImage({ current: nowImgUrl, // 当前显示图片的http链接 urls: that.data[tagFrom].imageUrls // 需要预览的图片http链接列表 }) } } /** * 图片视觉宽高计算函数区 **/ function wxParseImgLoad(e) { var that = this; var tagFrom = e.target.dataset.from; var idx = e.target.dataset.idx; if (typeof (tagFrom) != 'undefined' && tagFrom.length > 0) { calMoreImageInfo(e, idx, that, tagFrom) } } // 假循环获取计算图片视觉最佳宽高 function calMoreImageInfo(e, idx, that, bindName) { var temData = that.data[bindName]; if (temData.images.length == 0) { return; } var temImages = temData.images; //因为无法获取view宽度 需要自定义padding进行计算,稍后处理 var recal = wxAutoImageCal(e.detail.width, e.detail.height,that,bindName); temImages[idx].width = recal.imageWidth; temImages[idx].height = recal.imageheight; temData.images = temImages; var bindData = {}; bindData[bindName] = temData; that.setData(bindData); } // 计算视觉优先的图片宽高 function wxAutoImageCal(originalWidth, originalHeight,that,bindName) { //获取图片的原始长宽 var windowWidth = 0, windowHeight = 0; var autoWidth = 0, autoHeight = 0; var results = {}; wx.getSystemInfo({ success: function (res) { var padding = that.data[bindName].view.imagePadding; windowWidth = res.windowWidth-2*padding; windowHeight = res.windowHeight; //判断按照那种方式进行缩放 console.log("windowWidth" + windowWidth); if (originalWidth > windowWidth) {//在图片width大于手机屏幕width时候 autoWidth = windowWidth; console.log("autoWidth" + autoWidth); autoHeight = (autoWidth * originalHeight) / originalWidth; console.log("autoHeight" + autoHeight); results.imageWidth = autoWidth; results.imageheight = autoHeight; } else {//否则展示原来的数据 results.imageWidth = originalWidth; results.imageheight = originalHeight; } } }) return results; } function wxParseTemArray(temArrayName,bindNameReg,total,that){ var array = []; var temData = that.data; var obj = null; for(var i = 0; i < total; i++){ var simArr = temData[bindNameReg+i].nodes; array.push(simArr); } temArrayName = temArrayName || 'wxParseTemArray'; obj = JSON.parse('{"'+ temArrayName +'":""}'); obj[temArrayName] = array; that.setData(obj); } /** * 配置emojis * */ function emojisInit(reg='',baseSrc="/wxParse/emojis/",emojis){ HtmlToJson.emojisInit(reg,baseSrc,emojis); } module.exports = { wxParse: wxParse, wxParseTemArray:wxParseTemArray, emojisInit:emojisInit } ================================================ FILE: lib/wxParse/wxParse.wxml ================================================