diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/system/BaseConfig.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/system/BaseConfig.java index 29b48509..41710c77 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/system/BaseConfig.java +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/system/BaseConfig.java @@ -9,6 +9,7 @@ import java.util.Map; * 配置基类,该类实际持有所有的配置,子类只是提供代理访问方法 */ abstract class BaseConfig { + //所有的配置均保存在该 HashMap 中 protected static Map configs = new HashMap<>(); diff --git a/litemall-db/src/main/java/org/linlinjava/litemall/db/dao/StatMapper.java b/litemall-db/src/main/java/org/linlinjava/litemall/db/dao/StatMapper.java index 4d57a60e..b1f65e92 100644 --- a/litemall-db/src/main/java/org/linlinjava/litemall/db/dao/StatMapper.java +++ b/litemall-db/src/main/java/org/linlinjava/litemall/db/dao/StatMapper.java @@ -1,8 +1,5 @@ package org.linlinjava.litemall.db.dao; -import org.apache.ibatis.annotations.Param; - -import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/litemall-db/src/main/java/org/linlinjava/litemall/db/service/StatService.java b/litemall-db/src/main/java/org/linlinjava/litemall/db/service/StatService.java index 0e39bde1..ee2eb098 100644 --- a/litemall-db/src/main/java/org/linlinjava/litemall/db/service/StatService.java +++ b/litemall-db/src/main/java/org/linlinjava/litemall/db/service/StatService.java @@ -1,12 +1,7 @@ package org.linlinjava.litemall.db.service; -import com.github.pagehelper.PageHelper; -import org.linlinjava.litemall.db.dao.LitemallUserMapper; import org.linlinjava.litemall.db.dao.StatMapper; -import org.linlinjava.litemall.db.domain.LitemallUser; -import org.linlinjava.litemall.db.domain.LitemallUserExample; import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; import javax.annotation.Resource; import java.util.List; diff --git a/renard-wx/app.js b/renard-wx/app.js new file mode 100644 index 00000000..e5cb4a08 --- /dev/null +++ b/renard-wx/app.js @@ -0,0 +1,31 @@ +var util = require('./utils/util.js'); +var api = require('./config/api.js'); +var user = require('./utils/user.js'); + +App({ + onLaunch: function() { + const updateManager = wx.getUpdateManager(); + wx.getUpdateManager().onUpdateReady(function() { + wx.showModal({ + title: '更新提示', + content: '新版本已经准备好,是否重启应用?', + success: function(res) { + if (res.confirm) { + // 新的版本已经下载好,调用 applyUpdate 应用新版本并重启 + updateManager.applyUpdate() + } + } + }) + }) + }, + onShow: function(options) { + user.checkLogin().then(res => { + this.globalData.hasLogin = true; + }).catch(() => { + this.globalData.hasLogin = false; + }); + }, + globalData: { + hasLogin: false + } +}) \ No newline at end of file diff --git a/renard-wx/app.json b/renard-wx/app.json new file mode 100644 index 00000000..90ed81c1 --- /dev/null +++ b/renard-wx/app.json @@ -0,0 +1,78 @@ +{ + "pages": [ + "pages/index/index", + "pages/catalog/catalog", + "pages/newGoods/newGoods", + "pages/hotGoods/hotGoods", + "pages/ucenter/index/index", + "pages/ucenter/address/address", + "pages/ucenter/addressAdd/addressAdd", + "pages/ucenter/footprint/footprint", + "pages/ucenter/order/order", + "pages/ucenter/orderDetail/orderDetail", + "pages/ucenter/coupon/coupon", + "pages/ucenter/collect/collect", + "pages/auth/login/login", + "pages/auth/register/register", + "pages/auth/reset/reset", + "pages/payResult/payResult", + "pages/comment/comment", + "pages/commentPost/commentPost", + "pages/topic/topic", + "pages/topicComment/topicComment", + "pages/topicDetail/topicDetail", + "pages/topicCommentPost/topicCommentPost", + "pages/brand/brand", + "pages/brandDetail/brandDetail", + "pages/search/search", + "pages/category/category", + "pages/cart/cart", + "pages/shopping/checkout/checkout", + "pages/goods/goods", + "pages/groupon/myGroupon/myGroupon", + "pages/groupon/grouponDetail/grouponDetail" + ], + "window": { + "navigationBarTitleText": "Renard", + "enablePullDownRefresh": true, + "backgroundTextStyle": "light", + "navigationBarBackgroundColor": "#fff", + "navigationBarTextStyle": "black" + }, + "tabBar": { + "color": "#6e6d6b", + "selectedColor": "#e64340", + "borderStyle": "white", + "backgroundColor": "#fff", + "box-shadow": "0 0 6px 0", + "list": [ + { + "pagePath": "pages/index/index", + "iconPath": "images/tabbar/like-f.png", + "selectedIconPath": "images/tabbar/like-o.png" + }, + { + "pagePath": "pages/catalog/catalog", + "iconPath": "images/tabbar/menu-f.png", + "selectedIconPath": "images/tabbar/menu-o.png" + }, + { + "pagePath": "pages/cart/cart", + "iconPath": "images/tabbar/cart-f.png", + "selectedIconPath": "images/tabbar/cart-o.png" + }, + { + "pagePath": "pages/ucenter/index/index", + "iconPath": "images/tabbar/user-f.png", + "selectedIconPath": "images/tabbar/user-o.png" + } + ] + }, + "networkTimeout": { + "request": 10000, + "connectSocket": 10000, + "uploadFile": 10000, + "downloadFile": 10000 + }, + "debug": true +} \ No newline at end of file diff --git a/renard-wx/app.wxss b/renard-wx/app.wxss new file mode 100644 index 00000000..fe9d0542 --- /dev/null +++ b/renard-wx/app.wxss @@ -0,0 +1,24 @@ +/**app.wxss**/ +.container { + box-sizing: border-box; + font-family: PingFangSC-Light,helvetica,'Heiti SC'; + width: 100%; + height: 100%; +} + +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: #a78845; +} + +.wxParse-img { + display: block !important; +} \ No newline at end of file diff --git a/renard-wx/components/capsule/index.js b/renard-wx/components/capsule/index.js new file mode 100644 index 00000000..7b8452cc --- /dev/null +++ b/renard-wx/components/capsule/index.js @@ -0,0 +1,31 @@ +'use strict'; + +Component({ + externalClasses: ['custom-class'], + /** + * 组件的属性列表 + * 用于组件自定义设置 + */ + properties: { + // 颜色状态 + type: { + type: String, + value: '' + }, + // 自定义颜色 + color: { + type: String, + value: '' + }, + // 左侧内容 + leftText: { + type: String, + value: '' + }, + // 右侧内容 + rightText: { + type: String, + value: '' + } + } +}); \ No newline at end of file diff --git a/renard-wx/components/capsule/index.json b/renard-wx/components/capsule/index.json new file mode 100644 index 00000000..467ce294 --- /dev/null +++ b/renard-wx/components/capsule/index.json @@ -0,0 +1,3 @@ +{ + "component": true +} diff --git a/renard-wx/components/capsule/index.wxml b/renard-wx/components/capsule/index.wxml new file mode 100644 index 00000000..2c81021a --- /dev/null +++ b/renard-wx/components/capsule/index.wxml @@ -0,0 +1,10 @@ + + + {{ leftText }} + {{ rightText }} + + + {{ leftText }} + {{ rightText }} + + \ No newline at end of file diff --git a/renard-wx/components/capsule/index.wxss b/renard-wx/components/capsule/index.wxss new file mode 100644 index 00000000..4ba0eec5 --- /dev/null +++ b/renard-wx/components/capsule/index.wxss @@ -0,0 +1,42 @@ +.zan-capsule { + display: inline-block; + font-size: 12px; + vertical-align: middle; + line-height: 19px; + -webkit-transform: scale(0.83); + transform: scale(0.83); +} + +.zan-capsule__left, .zan-capsule__right { + display: inline-block; + line-height: 17px; + height: 19px; + vertical-align: middle; + box-sizing: border-box; +} + +.zan-capsule__left { + padding: 0 2px; + color: #fff; + background: #999; + border-radius: 2px 0 0 2px; + border: 1rpx solid #999; +} + +.zan-capsule__right { + padding: 0 5px; + color: #999; + border-radius: 0 2px 2px 0; + border: 1rpx solid #999; +} + +.zan-capsule--danger .zan-capsule__left { + color: #fff; + background: #f24544; + border-color: #f24544; +} + +.zan-capsule--danger .zan-capsule__right { + color: #f24544; + border-color: #f24544; +} diff --git a/renard-wx/components/good/good.js b/renard-wx/components/good/good.js new file mode 100644 index 00000000..84e67247 --- /dev/null +++ b/renard-wx/components/good/good.js @@ -0,0 +1,38 @@ +// components/good/good.js +Component({ + /** + * 组件的属性列表 + */ + properties: { + title: { // 属性名 + type: String, // 类型(必填),目前接受的类型包括:String, Number, Boolean, Object, Array, null(表示任意类型) + value: '标题' // 属性初始值(可选),如果未指定则会根据类型选择一个 + }, + imageUrl: { + type: String, + value: '' + }, + counterPrice: { + type: Number, + value: 0 + }, + retailPrice: { + type: Number, + value: 0 + }, + }, + + /** + * 组件的初始数据 + */ + data: { + + }, + + /** + * 组件的方法列表 + */ + methods: { + + } +}) \ No newline at end of file diff --git a/renard-wx/components/good/good.json b/renard-wx/components/good/good.json new file mode 100644 index 00000000..e8cfaaf8 --- /dev/null +++ b/renard-wx/components/good/good.json @@ -0,0 +1,4 @@ +{ + "component": true, + "usingComponents": {} +} \ No newline at end of file diff --git a/renard-wx/components/good/good.wxml b/renard-wx/components/good/good.wxml new file mode 100644 index 00000000..0e0ce410 --- /dev/null +++ b/renard-wx/components/good/good.wxml @@ -0,0 +1,8 @@ + + + {{title}} + + 原价:¥{{counterPrice}} + 现价:¥{{retailPrice}} + + \ No newline at end of file diff --git a/renard-wx/components/good/good.wxss b/renard-wx/components/good/good.wxss new file mode 100644 index 00000000..0a6eb331 --- /dev/null +++ b/renard-wx/components/good/good.wxss @@ -0,0 +1,41 @@ +.good { + height: 480rpx; + width: 372rpx; + align-content: center; +} + +.good .img { + width: 302rpx; + height: 302rpx; + z-index: 1; +} + +.good .name { + text-align: center; + /* display: block; */ + width: 372rpx; + height: 35rpx; + margin-bottom: 14rpx; + overflow: hidden; + font-size: 28rpx; + color: #333; +} + +.good .price { + /* display: block; */ + text-align: center; + line-height: 30rpx; + font-size: 28rpx; + color: #a78845; +} + +.good .counterPrice { + text-decoration: line-through; + font-size: 24rpx; + color: #999; +} + +.good .retailPrice { + font-size: 24rpx; + color: #a78845; +} \ No newline at end of file diff --git a/renard-wx/components/goodList/goodList.js b/renard-wx/components/goodList/goodList.js new file mode 100644 index 00000000..a26c24fd --- /dev/null +++ b/renard-wx/components/goodList/goodList.js @@ -0,0 +1,26 @@ +// components/goodList/goodList.js +Component({ + /** + * 组件的属性列表 + */ + properties: { + goods: { // 属性名 + type: Array, // 类型(必填),目前接受的类型包括:String, Number, Boolean, Object, Array, null(表示任意类型) + value: [] // 属性初始值(可选),如果未指定则会根据类型选择一个 + }, + }, + + /** + * 组件的初始数据 + */ + data: { + + }, + + /** + * 组件的方法列表 + */ + methods: { + + } +}) \ No newline at end of file diff --git a/renard-wx/components/goodList/goodList.json b/renard-wx/components/goodList/goodList.json new file mode 100644 index 00000000..40bd86eb --- /dev/null +++ b/renard-wx/components/goodList/goodList.json @@ -0,0 +1,6 @@ +{ + "component": true, + "usingComponents": { + "good": "../good/good" + } +} \ No newline at end of file diff --git a/renard-wx/components/goodList/goodList.wxml b/renard-wx/components/goodList/goodList.wxml new file mode 100644 index 00000000..43b12614 --- /dev/null +++ b/renard-wx/components/goodList/goodList.wxml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/renard-wx/components/goodList/goodList.wxss b/renard-wx/components/goodList/goodList.wxss new file mode 100644 index 00000000..fc7faf0c --- /dev/null +++ b/renard-wx/components/goodList/goodList.wxss @@ -0,0 +1,33 @@ +.b { + width: 750rpx; + padding: 0 6.25rpx; + height: auto; + overflow: hidden; + z-index: 1; +} + +.b .good { + z-index: 1; +} + +.b .item { + float: left; + background: #fff; + width: 365rpx; + margin-bottom: 6.25rpx; + height: 482rpx; + overflow: hidden; + text-align: center; + z-index: 1; +} + +.b .item .a { + height: 452rpx; + width: 100%; + z-index: 1; +} + +.b .item-b { + margin-left: 6.25rpx; + z-index: 1; +} diff --git a/renard-wx/components/icon/index.js b/renard-wx/components/icon/index.js new file mode 100644 index 00000000..8f2e645c --- /dev/null +++ b/renard-wx/components/icon/index.js @@ -0,0 +1,16 @@ +Component({ + externalClasses: ['custom-class'], + + properties: { + info: null, + name: String, + size: String, + color: String + }, + + methods: { + onClick() { + this.triggerEvent('click'); + } + } +}); diff --git a/renard-wx/components/icon/index.json b/renard-wx/components/icon/index.json new file mode 100644 index 00000000..467ce294 --- /dev/null +++ b/renard-wx/components/icon/index.json @@ -0,0 +1,3 @@ +{ + "component": true +} diff --git a/renard-wx/components/icon/index.wxml b/renard-wx/components/icon/index.wxml new file mode 100644 index 00000000..af2215ae --- /dev/null +++ b/renard-wx/components/icon/index.wxml @@ -0,0 +1,7 @@ + + {{ info }} + diff --git a/renard-wx/components/icon/index.wxss b/renard-wx/components/icon/index.wxss new file mode 100644 index 00000000..74caabfb --- /dev/null +++ b/renard-wx/components/icon/index.wxss @@ -0,0 +1 @@ +@font-face{font-style:normal;font-weight:400;font-family:vant-icon;src:url(https://img.yzcdn.cn/vant/vant-icon-eb8c95.ttf) format('truetype')}.van-icon{position:relative;display:inline-block;font:normal normal normal 14px/1 vant-icon;font-size:inherit;text-rendering:auto}.van-icon__info{color:#fff;left:100%;top:-.5em;font-size:.5em;padding:0 .3em;text-align:center;min-width:1.2em;line-height:1.2;position:absolute;border-radius:.6em;box-sizing:border-box;background-color:#f44;-webkit-transform:translateX(-50%);transform:translateX(-50%);font-family:PingFang SC,Helvetica Neue,Arial,sans-serif}.van-icon::before{display:inline-block}.van-icon-add-o::before{content:"\F000"}.van-icon-add::before{content:"\F001"}.van-icon-add2::before{content:"\F002"}.van-icon-after-sale::before{content:"\F003"}.van-icon-alipay::before{content:"\F004"}.van-icon-arrow-left::before{content:"\F005"}.van-icon-arrow::before{content:"\F006"}.van-icon-balance-pay::before{content:"\F007"}.van-icon-browsing-history::before{content:"\F008"}.van-icon-card::before{content:"\F009"}.van-icon-cart::before{content:"\F00A"}.van-icon-cash-back-record::before{content:"\F00B"}.van-icon-cash-on-deliver::before{content:"\F00C"}.van-icon-certificate::before{content:"\F00D"}.van-icon-chat::before{content:"\F00E"}.van-icon-check::before{content:"\F00F"}.van-icon-checked::before{content:"\F010"}.van-icon-clear::before{content:"\F011"}.van-icon-clock::before{content:"\F012"}.van-icon-close::before{content:"\F013"}.van-icon-completed::before{content:"\F014"}.van-icon-contact::before{content:"\F015"}.van-icon-coupon::before{content:"\F016"}.van-icon-credit-pay::before{content:"\F017"}.van-icon-debit-pay::before{content:"\F018"}.van-icon-delete::before{content:"\F019"}.van-icon-description::before{content:"\F01A"}.van-icon-discount::before{content:"\F01B"}.van-icon-ecard-pay::before{content:"\F01C"}.van-icon-edit-data::before{content:"\F01D"}.van-icon-edit::before{content:"\F01E"}.van-icon-exchange-record::before{content:"\F01F"}.van-icon-exchange::before{content:"\F020"}.van-icon-fail::before{content:"\F021"}.van-icon-free-postage::before{content:"\F022"}.van-icon-gift-card-pay::before{content:"\F023"}.van-icon-gift-card::before{content:"\F024"}.van-icon-gift::before{content:"\F025"}.van-icon-gold-coin::before{content:"\F026"}.van-icon-goods-collect::before{content:"\F027"}.van-icon-home::before{content:"\F028"}.van-icon-hot-sale::before{content:"\F029"}.van-icon-hot::before{content:"\F02A"}.van-icon-idcard::before{content:"\F02B"}.van-icon-info-o::before{content:"\F02C"}.van-icon-like-o::before{content:"\F02D"}.van-icon-like::before{content:"\F02E"}.van-icon-location::before{content:"\F02F"}.van-icon-logistics::before{content:"\F030"}.van-icon-more-o::before{content:"\F031"}.van-icon-more::before{content:"\F032"}.van-icon-new-arrival::before{content:"\F033"}.van-icon-new::before{content:"\F034"}.van-icon-other-pay::before{content:"\F035"}.van-icon-passed::before{content:"\F036"}.van-icon-password-not-view::before{content:"\F037"}.van-icon-password-view::before{content:"\F038"}.van-icon-pause::before{content:"\F039"}.van-icon-peer-pay::before{content:"\F03A"}.van-icon-pending-deliver::before{content:"\F03B"}.van-icon-pending-evaluate::before{content:"\F03C"}.van-icon-pending-orders::before{content:"\F03D"}.van-icon-pending-payment::before{content:"\F03E"}.van-icon-phone::before{content:"\F03F"}.van-icon-photo::before{content:"\F040"}.van-icon-photograph::before{content:"\F041"}.van-icon-play::before{content:"\F042"}.van-icon-point-gift::before{content:"\F043"}.van-icon-points-mall::before{content:"\F044"}.van-icon-points::before{content:"\F045"}.van-icon-qr-invalid::before{content:"\F046"}.van-icon-qr::before{content:"\F047"}.van-icon-question::before{content:"\F048"}.van-icon-receive-gift::before{content:"\F049"}.van-icon-records::before{content:"\F04A"}.van-icon-search::before{content:"\F04B"}.van-icon-send-gift::before{content:"\F04C"}.van-icon-setting::before{content:"\F04D"}.van-icon-share::before{content:"\F04E"}.van-icon-shop-collect::before{content:"\F04F"}.van-icon-shop::before{content:"\F050"}.van-icon-shopping-cart::before{content:"\F051"}.van-icon-sign::before{content:"\F052"}.van-icon-stop::before{content:"\F053"}.van-icon-success::before{content:"\F054"}.van-icon-tosend::before{content:"\F055"}.van-icon-underway::before{content:"\F056"}.van-icon-upgrade::before{content:"\F057"}.van-icon-value-card::before{content:"\F058"}.van-icon-wap-home::before{content:"\F059"}.van-icon-wap-nav::before{content:"\F05A"}.van-icon-warn::before{content:"\F05B"}.van-icon-wechat::before{content:"\F05C"} \ No newline at end of file diff --git a/renard-wx/components/notice-bar/index.js b/renard-wx/components/notice-bar/index.js new file mode 100644 index 00000000..c30faf36 --- /dev/null +++ b/renard-wx/components/notice-bar/index.js @@ -0,0 +1,177 @@ +const VALID_MODE = ['closeable', 'link']; +const FONT_COLOR = '#f60'; +const BG_COLOR = '#fff7cc'; + +Component({ + externalClasses: ['custom-class'], + + properties: { + text: { + type: String, + value: '', + observer() { + this.setData({}, this._init); + } + }, + mode: { + type: String, + value: '' + }, + url: { + type: String, + value: '' + }, + openType: { + type: String, + value: 'navigate' + }, + delay: { + type: Number, + value: 0 + }, + speed: { + type: Number, + value: 50 + }, + scrollable: { + type: Boolean, + value: true + }, + leftIcon: { + type: String, + value: '' + }, + color: { + type: String, + value: FONT_COLOR + }, + backgroundColor: { + type: String, + value: BG_COLOR + } + }, + + data: { + show: true, + hasRightIcon: false, + width: undefined, + wrapWidth: undefined, + elapse: undefined, + animation: null, + resetAnimation: null, + timer: null + }, + + attached() { + const { mode } = this.data; + if (mode && this._checkMode(mode)) { + this.setData({ + hasRightIcon: true + }); + } + }, + + detached() { + const { timer } = this.data; + timer && clearTimeout(timer); + }, + + methods: { + _checkMode(val) { + const isValidMode = ~VALID_MODE.indexOf(val); + if (!isValidMode) { + console.warn(`mode only accept value of ${VALID_MODE}, now get ${val}.`); + } + return isValidMode; + }, + + _init() { + wx.createSelectorQuery() + .in(this) + .select('.van-notice-bar__content') + .boundingClientRect((rect) => { + if (!rect || !rect.width) { + return; + } + this.setData({ + width: rect.width + }); + + wx.createSelectorQuery() + .in(this) + .select('.van-notice-bar__content-wrap') + .boundingClientRect((rect) => { + if (!rect || !rect.width) { + return; + } + + const wrapWidth = rect.width; + const { + width, speed, scrollable, delay + } = this.data; + + if (scrollable && wrapWidth < width) { + const elapse = width / speed * 1000; + const animation = wx.createAnimation({ + duration: elapse, + timeingFunction: 'linear', + delay + }); + const resetAnimation = wx.createAnimation({ + duration: 0, + timeingFunction: 'linear' + }); + + this.setData({ + elapse, + wrapWidth, + animation, + resetAnimation + }, () => { + this._scroll(); + }); + } + }) + .exec(); + }) + .exec(); + }, + + _scroll() { + const { + animation, resetAnimation, wrapWidth, elapse, speed + } = this.data; + resetAnimation.translateX(wrapWidth).step(); + const animationData = animation.translateX(-(elapse * speed) / 1000).step(); + this.setData({ + animationData: resetAnimation.export() + }); + setTimeout(() => { + this.setData({ + animationData: animationData.export() + }); + }, 100); + + const timer = setTimeout(() => { + this._scroll(); + }, elapse); + + this.setData({ + timer + }); + }, + + _handleButtonClick() { + const { timer } = this.data; + timer && clearTimeout(timer); + this.setData({ + show: false, + timer: null + }); + }, + + onClick(event) { + this.triggerEvent('click', event); + } + } +}); diff --git a/renard-wx/components/notice-bar/index.json b/renard-wx/components/notice-bar/index.json new file mode 100644 index 00000000..a9ab393a --- /dev/null +++ b/renard-wx/components/notice-bar/index.json @@ -0,0 +1,6 @@ +{ + "component": true, + "usingComponents": { + "van-icon": "../icon/index" + } +} \ No newline at end of file diff --git a/renard-wx/components/notice-bar/index.wxml b/renard-wx/components/notice-bar/index.wxml new file mode 100644 index 00000000..511b6d66 --- /dev/null +++ b/renard-wx/components/notice-bar/index.wxml @@ -0,0 +1,31 @@ + + + + + + + {{ text }} + + + + + + + + + + diff --git a/renard-wx/components/notice-bar/index.wxss b/renard-wx/components/notice-bar/index.wxss new file mode 100644 index 00000000..549d026d --- /dev/null +++ b/renard-wx/components/notice-bar/index.wxss @@ -0,0 +1 @@ +.van-notice-bar{display:-webkit-box;display:-webkit-flex;display:flex;padding:9px 10px;font-size:12px;line-height:1.5}.van-notice-bar--within-icon{position:relative;padding-right:30px}.van-notice-bar__left-icon{height:18px;min-width:20px;padding-top:1px;box-sizing:border-box}.van-notice-bar__left-icon>image{width:16px;height:16px}.van-notice-bar__right-icon{position:absolute;top:10px;right:10px;font-size:15px;line-height:1}.van-notice-bar__content-wrap{position:relative;-webkit-box-flex:1;-webkit-flex:1;flex:1;height:18px;overflow:hidden}.van-notice-bar__content{position:absolute;white-space:nowrap} \ No newline at end of file diff --git a/renard-wx/components/notify/index.js b/renard-wx/components/notify/index.js new file mode 100644 index 00000000..de20e1a2 --- /dev/null +++ b/renard-wx/components/notify/index.js @@ -0,0 +1,69 @@ +Component({ + properties: { + text: String, + color: { + type: String, + value: '#fff' + }, + backgroundColor: { + type: String, + value: '#e64340' + }, + duration: { + type: Number, + value: 3000 + } + }, + + methods: { + show() { + const { duration } = this.data; + + clearTimeout(this.timer); + this.setData({ + show: true + }); + + if (duration > 0 && duration !== Infinity) { + this.timer = setTimeout(() => { + this.hide(); + }, duration); + } + }, + + hide() { + clearTimeout(this.timer); + this.setData({ + show: false + }); + } + } +}); + +const defaultOptions = { + selector: '#van-notify', + duration: 3000 +}; + +function Notify(options = {}) { + const pages = getCurrentPages(); + const ctx = pages[pages.length - 1]; + + options = Object.assign({}, defaultOptions, parseParam(options)); + + const el = ctx.selectComponent(options.selector); + delete options.selector; + + if (el) { + el.setData({ + ...options + }); + el.show(); + } +} + +function parseParam(params = '') { + return typeof params === 'object' ? params : { text: params }; +} + +module.exports = Notify; diff --git a/renard-wx/components/notify/index.json b/renard-wx/components/notify/index.json new file mode 100644 index 00000000..32640e0d --- /dev/null +++ b/renard-wx/components/notify/index.json @@ -0,0 +1,3 @@ +{ + "component": true +} \ No newline at end of file diff --git a/renard-wx/components/notify/index.wxml b/renard-wx/components/notify/index.wxml new file mode 100644 index 00000000..5fc9f597 --- /dev/null +++ b/renard-wx/components/notify/index.wxml @@ -0,0 +1,6 @@ + + {{ text }} + diff --git a/renard-wx/components/notify/index.wxss b/renard-wx/components/notify/index.wxss new file mode 100644 index 00000000..81be6eb5 --- /dev/null +++ b/renard-wx/components/notify/index.wxss @@ -0,0 +1 @@ +.van-notify{top:0;opacity:0;width:100%;z-index:110;color:#fff;position:fixed;min-height:32px;line-height:2.3;font-size:14px;text-align:center;background-color:#e64340;-webkit-transition:all .4s ease;transition:all .4s ease;-webkit-transform:translateZ(0) translateY(-100%);transform:translateZ(0) translateY(-100%)}.van-notify--show{opacity:1;-webkit-transform:translateZ(0) translateY(0);transform:translateZ(0) translateY(0)} \ No newline at end of file diff --git a/renard-wx/components/sharePop/sharePop.js b/renard-wx/components/sharePop/sharePop.js new file mode 100644 index 00000000..64098a77 --- /dev/null +++ b/renard-wx/components/sharePop/sharePop.js @@ -0,0 +1,86 @@ +// components/sharePop/sharePop.js +Component({ + /** + * 组件的属性列表 + */ + properties: { + url: { + type: String, + value: '', // 属性值 (可选) + }, + bottom:{ + type: Number, + value: 0, // 属性值 (可选) + } + }, + + /** + * 组件的初始数据 + */ + data: { + // 弹窗显示控制 + showPop: false, + }, + + /** + * 组件的方法列表 + */ + methods: { + closeShare: function() { + this.setData({ + showPop: false, + }); + }, + + togglePopup: function() { + let that = this; + this.setData({ + showPop: !this.data.showPop, + }); + }, + + // 保存分享图 + _saveShare: function() { + wx.showLoading({ + title: '加载中', + }); + + setTimeout(function() { + wx.hideLoading() + }, 2000); + + let that = this; + wx.downloadFile({ + url: that.data.url, + success: function(res) { + console.log(res) + wx.saveImageToPhotosAlbum({ + filePath: res.tempFilePath, + success: function(res) { + wx.showModal({ + title: '存图成功', + content: '图片成功保存到相册了,可以分享到朋友圈了', + showCancel: false, + confirmText: '好的', + confirmColor: '#a78845', + success: function(res) { + if (res.confirm) { + console.log('用户点击确定'); + } + } + }); + wx.hideLoading(); + }, + fail: function(res) { + console.log('fail') + } + }) + }, + fail: function() { + console.log('fail') + } + }) + }, + + } +}) \ No newline at end of file diff --git a/renard-wx/components/sharePop/sharePop.json b/renard-wx/components/sharePop/sharePop.json new file mode 100644 index 00000000..e8cfaaf8 --- /dev/null +++ b/renard-wx/components/sharePop/sharePop.json @@ -0,0 +1,4 @@ +{ + "component": true, + "usingComponents": {} +} \ No newline at end of file diff --git a/renard-wx/components/sharePop/sharePop.wxml b/renard-wx/components/sharePop/sharePop.wxml new file mode 100644 index 00000000..49deba84 --- /dev/null +++ b/renard-wx/components/sharePop/sharePop.wxml @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/renard-wx/components/sharePop/sharePop.wxss b/renard-wx/components/sharePop/sharePop.wxss new file mode 100644 index 00000000..d4b3ba7c --- /dev/null +++ b/renard-wx/components/sharePop/sharePop.wxss @@ -0,0 +1,62 @@ +.share-pop-box { + width: 100%; + height: 100%; + position: fixed; + background: rgba(0, 0, 0, 0.5); + z-index: 8; + bottom: 0; + /* display: none; */ +} + +.share-pop { + width: 100%; + height: 400rpx; + max-height: 780rpx; + padding: 31.25rpx; + background: #fff; + position: fixed; + z-index: 9; + bottom: 0rpx; +} + +.share-pop .close { + position: absolute; + width: 48rpx; + height: 48rpx; + right: 31.25rpx; + overflow: hidden; + top: 31.25rpx; +} + +.share-pop .close .icon { + width: 48rpx; + height: 48rpx; +} + +.share-pop .share-items { + margin-top: 80rpx; + flex-flow: row; + display: flex; + justify-content: space-around; +} + +.share-pop .share-touser { + height: 160rpx; + width: 160rpx; + background: url("https://litemall-1256968571.file.myqcloud.com/icon/%E5%88%86%E4%BA%AB_%E5%BE%AE%E4%BF%A1.png") no-repeat center 1rpx; + background-size: 160rpx auto; + border-radius: 80rpx; +} + +.share-pop .share-friend { + height: 160rpx; + width: 160rpx; + background: url("https://litemall-1256968571.file.myqcloud.com/icon/%E5%88%86%E4%BA%AB_%E5%BE%AE%E4%BF%A1%E6%9C%8B%E5%8F%8B%E5%9C%88.png") no-repeat center 1rpx; + background-size: 160rpx auto; + border-radius: 10rpx; +} + +.share-pop image { + height: 120rpx; + width: 120rpx; +} \ No newline at end of file diff --git a/renard-wx/config/api.js b/renard-wx/config/api.js new file mode 100644 index 00000000..20579496 --- /dev/null +++ b/renard-wx/config/api.js @@ -0,0 +1,85 @@ +// 以下是业务服务器API地址 +// 本机开发时使用 +var WxApiRoot = 'http://192.168.1.105:8082/wx/'; +// 云平台上线时使用 +// var WxApiRoot = 'https://litemall.menethil.com.cn/wx/'; +// var WxApiRoot = 'https://www.menethil.com.cn/wx/'; + +module.exports = { + IndexUrl: WxApiRoot + 'home/index', //首页数据接口 + CatalogList: WxApiRoot + 'catalog/index', //分类目录全部分类数据接口 + CatalogCurrent: WxApiRoot + 'catalog/current', //分类目录当前分类数据接口 + + CatalogAll: WxApiRoot + 'catalog/all', //分类目录当前分类数据接口 + + AuthLoginByWeixin: WxApiRoot + 'auth/login_by_weixin', //微信登录 + AuthLoginByAccount: WxApiRoot + 'auth/login', //账号登录 + AuthRegister: WxApiRoot + 'auth/register', //账号注册 + AuthReset: WxApiRoot + 'auth/reset', //账号密码重置 + AuthRegisterCaptcha: WxApiRoot + 'auth/regCaptcha', //验证码 + + GoodsCount: WxApiRoot + 'goods/count', //统计商品总数 + GoodsList: WxApiRoot + 'goods/list', //获得商品列表 + GoodsCategory: WxApiRoot + 'goods/category', //获得分类数据 + GoodsDetail: WxApiRoot + 'goods/detail', //获得商品的详情 + GoodsNew: WxApiRoot + 'goods/new', //新品 + GoodsHot: WxApiRoot + 'goods/hot', //热门 + GoodsRelated: WxApiRoot + 'goods/related', //商品详情页的关联商品(大家都在看) + + BrandList: WxApiRoot + 'brand/list', //品牌列表 + BrandDetail: WxApiRoot + 'brand/detail', //品牌详情 + + CartList: WxApiRoot + 'cart/index', //获取购物车的数据 + CartAdd: WxApiRoot + 'cart/add', // 添加商品到购物车 + CartFastAdd: WxApiRoot + 'cart/fastadd', // 立即购买商品 + CartUpdate: WxApiRoot + 'cart/update', // 更新购物车的商品 + CartDelete: WxApiRoot + 'cart/delete', // 删除购物车的商品 + CartChecked: WxApiRoot + 'cart/checked', // 选择或取消选择商品 + CartGoodsCount: WxApiRoot + 'cart/goodscount', // 获取购物车商品件数 + CartCheckout: WxApiRoot + 'cart/checkout', // 下单前信息确认 + + CollectList: WxApiRoot + 'collect/list', //收藏列表 + CollectAddOrDelete: WxApiRoot + 'collect/addordelete', //添加或取消收藏 + + CommentList: WxApiRoot + 'comment/list', //评论列表 + CommentCount: WxApiRoot + 'comment/count', //评论总数 + CommentPost: WxApiRoot + 'comment/post', //发表评论 + + TopicList: WxApiRoot + 'topic/list', //专题列表 + TopicDetail: WxApiRoot + 'topic/detail', //专题详情 + TopicRelated: WxApiRoot + 'topic/related', //相关专题 + + SearchIndex: WxApiRoot + 'search/index', //搜索关键字 + SearchResult: WxApiRoot + 'search/result', //搜索结果 + SearchHelper: WxApiRoot + 'search/helper', //搜索帮助 + SearchClearHistory: WxApiRoot + 'search/clearhistory', //搜索历史清楚 + + AddressList: WxApiRoot + 'address/list', //收货地址列表 + AddressDetail: WxApiRoot + 'address/detail', //收货地址详情 + AddressSave: WxApiRoot + 'address/save', //保存收货地址 + AddressDelete: WxApiRoot + 'address/delete', //保存收货地址 + + RegionList: WxApiRoot + 'region/list', //获取区域列表 + + OrderSubmit: WxApiRoot + 'order/submit', // 提交订单 + OrderPrepay: WxApiRoot + 'order/prepay', // 订单的预支付会话 + OrderList: WxApiRoot + 'order/list', //订单列表 + OrderDetail: WxApiRoot + 'order/detail', //订单详情 + OrderCancel: WxApiRoot + 'order/cancel', //取消订单 + OrderRefund: WxApiRoot + 'order/refund', //退款取消订单 + OrderDelete: WxApiRoot + 'order/delete', //删除订单 + OrderConfirm: WxApiRoot + 'order/confirm', //确认收货 + OrderComment: WxApiRoot + 'order/comment', // 代评价商品信息 + + FootprintList: WxApiRoot + 'footprint/list', //足迹列表 + FootprintDelete: WxApiRoot + 'footprint/delete', //删除足迹 + + UserFormIdCreate: WxApiRoot + 'formid/create', //用户FromId,用于发送模版消息 + + GroupOn: WxApiRoot + 'groupon/query', //团购API-查询 + GroupOnMy: WxApiRoot + 'groupon/my', //团购API-我的团购 + GroupOnDetail: WxApiRoot + 'groupon/detail', //团购API-详情 + GroupOnJoin: WxApiRoot + 'groupon/join', //团购API-详情 + + StorageUpload: WxApiRoot + 'storage/upload' //图片上传 +}; \ No newline at end of file diff --git a/renard-wx/images/Cool-love-o.png b/renard-wx/images/Cool-love-o.png new file mode 100644 index 00000000..086aee6e Binary files /dev/null and b/renard-wx/images/Cool-love-o.png differ diff --git a/renard-wx/images/Cool-love.png b/renard-wx/images/Cool-love.png new file mode 100644 index 00000000..04d46a69 Binary files /dev/null and b/renard-wx/images/Cool-love.png differ diff --git a/renard-wx/images/Coupons-icon-f.png b/renard-wx/images/Coupons-icon-f.png new file mode 100644 index 00000000..01894154 Binary files /dev/null and b/renard-wx/images/Coupons-icon-f.png differ diff --git a/renard-wx/images/Coupons-icon-o.png b/renard-wx/images/Coupons-icon-o.png new file mode 100644 index 00000000..ee251482 Binary files /dev/null and b/renard-wx/images/Coupons-icon-o.png differ diff --git a/renard-wx/images/Edit-icon.png b/renard-wx/images/Edit-icon.png new file mode 100644 index 00000000..bec9566d Binary files /dev/null and b/renard-wx/images/Edit-icon.png differ diff --git a/renard-wx/images/Hot-cart.png b/renard-wx/images/Hot-cart.png new file mode 100644 index 00000000..b73f3f0b Binary files /dev/null and b/renard-wx/images/Hot-cart.png differ diff --git a/renard-wx/images/Hot-love-o.png b/renard-wx/images/Hot-love-o.png new file mode 100644 index 00000000..f8518a2a Binary files /dev/null and b/renard-wx/images/Hot-love-o.png differ diff --git a/renard-wx/images/Hot-love.png b/renard-wx/images/Hot-love.png new file mode 100644 index 00000000..6b4b9492 Binary files /dev/null and b/renard-wx/images/Hot-love.png differ diff --git a/renard-wx/images/Plaus-icon.png b/renard-wx/images/Plaus-icon.png new file mode 100644 index 00000000..83bc6254 Binary files /dev/null and b/renard-wx/images/Plaus-icon.png differ diff --git a/renard-wx/images/about-icon.png b/renard-wx/images/about-icon.png new file mode 100644 index 00000000..ab7408f3 Binary files /dev/null and b/renard-wx/images/about-icon.png differ diff --git a/renard-wx/images/confirm.png b/renard-wx/images/confirm.png new file mode 100644 index 00000000..e28fbd38 Binary files /dev/null and b/renard-wx/images/confirm.png differ diff --git a/renard-wx/images/goods-chat.png b/renard-wx/images/goods-chat.png new file mode 100644 index 00000000..b500215c Binary files /dev/null and b/renard-wx/images/goods-chat.png differ diff --git a/renard-wx/images/goods-share.png b/renard-wx/images/goods-share.png new file mode 100644 index 00000000..d0b018bb Binary files /dev/null and b/renard-wx/images/goods-share.png differ diff --git a/renard-wx/images/my-icon-1.png b/renard-wx/images/my-icon-1.png new file mode 100644 index 00000000..8bd90352 Binary files /dev/null and b/renard-wx/images/my-icon-1.png differ diff --git a/renard-wx/images/my-icon-2.png b/renard-wx/images/my-icon-2.png new file mode 100644 index 00000000..6a6fff82 Binary files /dev/null and b/renard-wx/images/my-icon-2.png differ diff --git a/renard-wx/images/my-icon-3.png b/renard-wx/images/my-icon-3.png new file mode 100644 index 00000000..3f678b50 Binary files /dev/null and b/renard-wx/images/my-icon-3.png differ diff --git a/renard-wx/images/my-icon-4.png b/renard-wx/images/my-icon-4.png new file mode 100644 index 00000000..5b8a5fd1 Binary files /dev/null and b/renard-wx/images/my-icon-4.png differ diff --git a/renard-wx/images/right-icon.png b/renard-wx/images/right-icon.png new file mode 100644 index 00000000..47b8ddb4 Binary files /dev/null and b/renard-wx/images/right-icon.png differ diff --git a/renard-wx/images/tabbar/cart-f.png b/renard-wx/images/tabbar/cart-f.png new file mode 100644 index 00000000..3f08beba Binary files /dev/null and b/renard-wx/images/tabbar/cart-f.png differ diff --git a/renard-wx/images/tabbar/cart-o.png b/renard-wx/images/tabbar/cart-o.png new file mode 100644 index 00000000..02df8f13 Binary files /dev/null and b/renard-wx/images/tabbar/cart-o.png differ diff --git a/renard-wx/images/tabbar/like-f.png b/renard-wx/images/tabbar/like-f.png new file mode 100644 index 00000000..6b28364c Binary files /dev/null and b/renard-wx/images/tabbar/like-f.png differ diff --git a/renard-wx/images/tabbar/like-o.png b/renard-wx/images/tabbar/like-o.png new file mode 100644 index 00000000..bb0cec58 Binary files /dev/null and b/renard-wx/images/tabbar/like-o.png differ diff --git a/renard-wx/images/tabbar/menu-f.png b/renard-wx/images/tabbar/menu-f.png new file mode 100644 index 00000000..cb6a3cea Binary files /dev/null and b/renard-wx/images/tabbar/menu-f.png differ diff --git a/renard-wx/images/tabbar/menu-o.png b/renard-wx/images/tabbar/menu-o.png new file mode 100644 index 00000000..fc42f7cf Binary files /dev/null and b/renard-wx/images/tabbar/menu-o.png differ diff --git a/renard-wx/images/tabbar/user-f.png b/renard-wx/images/tabbar/user-f.png new file mode 100644 index 00000000..c734a2af Binary files /dev/null and b/renard-wx/images/tabbar/user-f.png differ diff --git a/renard-wx/images/tabbar/user-o.png b/renard-wx/images/tabbar/user-o.png new file mode 100644 index 00000000..754b445b Binary files /dev/null and b/renard-wx/images/tabbar/user-o.png differ diff --git a/renard-wx/lib/wxParse/html2json.js b/renard-wx/lib/wxParse/html2json.js new file mode 100644 index 00000000..e0df8892 --- /dev/null +++ b/renard-wx/lib/wxParse/html2json.js @@ -0,0 +1,242 @@ +/** + * 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 +}; + diff --git a/renard-wx/lib/wxParse/htmlparser.js b/renard-wx/lib/wxParse/htmlparser.js new file mode 100644 index 00000000..7406b45e --- /dev/null +++ b/renard-wx/lib/wxParse/htmlparser.js @@ -0,0 +1,182 @@ +/** + * 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; diff --git a/renard-wx/lib/wxParse/showdown.js b/renard-wx/lib/wxParse/showdown.js new file mode 100644 index 00000000..a1f830fb --- /dev/null +++ b/renard-wx/lib/wxParse/showdown.js @@ -0,0 +1,2529 @@ +/** + * 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; diff --git a/renard-wx/lib/wxParse/wxDiscode.js b/renard-wx/lib/wxParse/wxDiscode.js new file mode 100644 index 00000000..e339f7a2 --- /dev/null +++ b/renard-wx/lib/wxParse/wxDiscode.js @@ -0,0 +1,206 @@ +// 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 +} \ No newline at end of file diff --git a/renard-wx/lib/wxParse/wxParse.js b/renard-wx/lib/wxParse/wxParse.js new file mode 100644 index 00000000..8adab502 --- /dev/null +++ b/renard-wx/lib/wxParse/wxParse.js @@ -0,0 +1,146 @@ +/** + * 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 +} + + diff --git a/renard-wx/lib/wxParse/wxParse.wxml b/renard-wx/lib/wxParse/wxParse.wxml new file mode 100644 index 00000000..963a1b19 --- /dev/null +++ b/renard-wx/lib/wxParse/wxParse.wxml @@ -0,0 +1,928 @@ + + + + + + + + + + + + +