-code nl-11 ol-11"> 11
+    ]}],
12
+    "selector-type-no-unknown": [true, {"ignoreTypes": [
13
+      "swiper", "swiper-item", "page"
14
+    ]}]
15
+  }
16
+}

+ 0 - 0
README.md


+ 56 - 0
gulpfile.js

@@ -0,0 +1,56 @@
1
+// const gulp = require('gulp');
2
+const { watch, src, dest, series, parallel } = require('gulp');
3
+const rename = require('gulp-rename');
4
+const less = require('gulp-less');
5
+const replace = require('gulp-replace');
6
+const through2 = require('through2');
7
+const jeditor = require('gulp-json-editor');
8
+const gulpInstall = require('gulp-install');
9
+
10
+const srcDir = './src';
11
+// const appJson = require('./miniprogram/app.json');
12
+
13
+function wxss() {
14
+  return (
15
+    src([`${srcDir}/**/*.less`, `!${srcDir}/**/_*.less`, `!${srcDir}/style/less/*.less`,`!${srcDir}/**/*.wxss`])
16
+      .pipe(replace(/\@(import\s[^@;]*)+(;import|\bimport|;|b)?/g, ($1) => {
17
+        let isMixin = $1.indexOf(".less")
18
+        if (isMixin == -1) {
19
+          return `\/*T${$1}T*\/`
20
+        } else {
21
+          return $1
22
+        }
23
+      }))
24
+      .pipe(
25
+        less().on('error', function(err) {
26
+          console.error(err);
27
+          this.emit('end'); // 防止中断
28
+        })
29
+      )
30
+      .pipe(
31
+        rename(path => {
32
+          path.extname = '.wxss';
33
+        })
34
+      )
35
+      .pipe(replace(/\/\*T(@import\s[^@;]*;)?(T\*\/)?/g, '$1'))
36
+      .pipe(
37
+        dest(file => {
38
+          return file.base;
39
+        })
40
+      )
41
+  );
42
+}
43
+
44
+
45
+function watchCSS() {
46
+  watch(`${srcDir}/**/*.less`, series(wxss));
47
+}
48
+
49
+function clean() {
50
+  del(['./dist/**'])
51
+}
52
+
53
+exports.watchCSS = watchCSS;
54
+exports.wxss = wxss;
55
+exports.dev =  series(wxss, watchCSS);
56
+exports.default = wxss;

+ 60 - 0
package.json

@@ -0,0 +1,60 @@
1
+{
2
+  "name": "kodo",
3
+  "version": "1.0.0",
4
+  "description": "统览系统",
5
+  "main": "index.js",
6
+  "miniprogram": "dist",
7
+  "scripts": {
8
+    "dev": "gulp dev",
9
+    "init": "tnpm install && cd src && tnpm install",
10
+    "eslint": "./node_modules/.bin/eslint src",
11
+    "eslint:fix": "./node_modules/.bin/eslint --fix src",
12
+    "stylelint": "./node_modules/.bin/stylelint src/**/*.less src/**/**/*.less src/**/**/**/*.less src/**/**/**/**/*.less src/**/**/**/**/**/*.less src/**/**/**/**/**/**/*.less",
13
+    "stylelint:fix": "./node_modules/.bin/stylelint src/**/*.less src/**/**/*.less src/**/**/**/*.less src/**/**/**/**/*.less src/**/**/**/**/**/*.less src/**/**/**/**/**/**/*.less --fix",
14
+    "format": "prettier --write",
15
+    "test": "echo \"Error: no test specified\" && exit 1"
16
+  },
17
+  "config": {
18
+    "commitizen": {
19
+      "path": "node_modules/cz-customizable"
20
+    },
21
+    "cz-customizable": {
22
+      "config": "node_modules/commitlint-config-imt/cz-config.js"
23
+    }
24
+  },
25
+  "repository": {
26
+    "type": "git",
27
+    "url": "http://git.xfoto.com.cn/SCRM/miniProgram.git"
28
+  },
29
+  "author": "FFIB",
30
+  "devDependencies": {
31
+    "babel-eslint": "^10.0.3",
32
+    "eslint": "^6.7.2",
33
+    "eslint-config-airbnb": "^18.0.1",
34
+    "eslint-config-airbnb-base": "^14.0.0",
35
+    "eslint-config-prettier": "^6.7.0",
36
+    "eslint-plugin-import": "^2.19.1",
37
+    "eslint-plugin-jsx-a11y": "^6.2.3",
38
+    "eslint-plugin-prettier": "^3.1.2",
39
+    "eslint-plugin-react": "^7.14.3",
40
+    "eslint-plugin-react-hooks": "^1.7.0",
41
+    "gulp": "^4.0.0",
42
+    "gulp-install": "^1.1.0",
43
+    "gulp-json-editor": "^2.5.0",
44
+    "gulp-less": "^4.0.1",
45
+    "gulp-rename": "^1.4.0",
46
+    "gulp-replace": "^1.0.0",
47
+    "htmllint": "^0.8.0",
48
+    "husky": "^1.2.0",
49
+    "lint-staged": "^8.1.0",
50
+    "prettier": "^1.19.1",
51
+    "stylelint": "^12.0.0",
52
+    "stylelint-config-recess-order": "^2.0.3",
53
+    "stylelint-config-standard": "^19.0.0",
54
+    "stylelint-prettier": "^1.1.2",
55
+    "through2": "^3.0.0"
56
+  },
57
+  "dependencies": {
58
+    "wxml-to-canvas": "^1.0.3"
59
+  }
60
+}

BIN
src/.DS_Store


+ 83 - 0
src/app.js

@@ -0,0 +1,83 @@
1
+const urls = require('./utils/urls.js')
2
+const network = require('./utils/network.js')
3
+const brandConfig = require('./brand/config.js')
4
+const router = require('./utils/router.js')
5
+const routerInterceptor = require('./utils/routerInterceptor')
6
+
7
+App({
8
+  copywriting: brandConfig.copywriting,
9
+  brandConfig,
10
+  urls,
11
+  network,
12
+  router,
13
+  routerInterceptor,
14
+  globalData: {
15
+    userInfo: {},
16
+    isLogin: false,
17
+    isWarranty: false,
18
+    hasSale: false,
19
+    isWxwork: false,
20
+    levels: [
21
+      '影友',
22
+      'LRC会员',
23
+      '银卡会员',
24
+      '金卡会员',
25
+      '白金卡会员',
26
+      '黑金卡会员'
27
+    ]
28
+  },
29
+
30
+  sceneHandler: null,
31
+
32
+  onLaunch() {},
33
+
34
+  onShow(options) {
35
+    this._warrantyCardCallBack(options.referrerInfo)
36
+  },
37
+
38
+  // 保修卡回调操作
39
+  _warrantyCardCallBack(referrerInfo) {
40
+    if (
41
+      referrerInfo === undefined ||
42
+      referrerInfo.appId !== 'wxeb490c6f9b154ef9'
43
+    ) {
44
+      return
45
+    }
46
+
47
+    this.globalData.isWarranty = true
48
+    wx.switchTab({
49
+      url: '/pages/member/mine/mine'
50
+    })
51
+  },
52
+
53
+  // 静默登录
54
+  login(cb) {
55
+    const that = this
56
+    network.login(res => {
57
+      that.globalData.userInfo = res
58
+      that.globalData.isLogin = true
59
+      cb(res)
60
+    })
61
+  },
62
+
63
+  // 获取用户unionid
64
+  getUserInfo(param, cb) {
65
+    const that = this
66
+    network.post({
67
+      url: urls.MINI_USERINFO,
68
+      data: {
69
+        encryptedData: param.encryptedData,
70
+        iv: param.iv
71
+      },
72
+      success(res) {
73
+        that.globalData.userInfo = res.data
74
+        that.globalData.isLogin = true
75
+        wx.setStorageSync('userInfo', res.data)
76
+        cb()
77
+      },
78
+      complete() {
79
+        wx.hideLoading()
80
+      }
81
+    })
82
+  }
83
+})

+ 23 - 0
src/app.json

@@ -0,0 +1,23 @@
1
+{
2
+  "pages": [
3
+    "pages/index/index",
4
+    "pages/order/order",
5
+    "template/webView/webView",
6
+    "template/resultView/resultView"
7
+  ],
8
+  "window": {
9
+    "backgroundTextStyle": "dark",
10
+    "navigationBarBackgroundColor": "#000000",
11
+    "navigationBarTitleText": "尖货接龙",
12
+    "navigationBarTextStyle": "white"
13
+  },
14
+  "permission": {
15
+    "scope.userLocation": {
16
+      "desc": "你的位置信息将用于申请保修卡"
17
+    }
18
+  },
19
+  "navigateToMiniProgramAppIdList": [
20
+    "wxeb490c6f9b154ef9"
21
+  ],
22
+  "sitemapLocation": "sitemap.json"
23
+}

+ 97 - 0
src/app.wxss

@@ -0,0 +1,97 @@
1
+/**app.wxss**/
2
+@import 'brand/brand.wxss';
3
+@import 'style/button.wxss';
4
+page {
5
+  height: 100%;
6
+}
7
+
8
+.button-hover {
9
+  opacity: 0.8;
10
+  background-color: black;
11
+}
12
+
13
+/*去除所有button边框*/
14
+button::after {
15
+  border: none;
16
+}
17
+
18
+button {
19
+  box-sizing: content-box;
20
+  border-radius: 0;
21
+}
22
+
23
+/* 背景图片 */
24
+.background {
25
+  position: absolute;
26
+  z-index: -1;
27
+
28
+  width: 100%;
29
+  height: 100%;
30
+}
31
+
32
+/** button **/
33
+.bottom-button {
34
+  display: flex;
35
+  flex-direction: column;
36
+  align-items: center;
37
+  justify-content: flex-end;
38
+  flex-grow: 2;
39
+}
40
+
41
+.bottom-view {
42
+  display: flex;
43
+  flex-direction: column;
44
+  justify-content: flex-end;
45
+  flex-grow: 2;
46
+  width: 100%;
47
+}
48
+
49
+.bottom-view .sale-button {
50
+  display: flex;
51
+  justify-content: center;
52
+  align-items: center;
53
+  margin-bottom: 0;
54
+  width: 100%;
55
+  height: calc(50px + env(safe-area-inset-bottom));
56
+  border-radius: 0;
57
+  padding: 0 0 0 0;
58
+  background-color: #1869ad;
59
+  color: #fff;
60
+  font-size: 40rpx;
61
+}
62
+
63
+.bottom-button .sale-button {
64
+  display: flex;
65
+  align-items: center;
66
+  justify-content: center;
67
+  margin-bottom: 50rpx;
68
+  min-width: 200rpx;
69
+  border-radius: 50rpx;
70
+  padding: 5rpx 70rpx 5rpx 70rpx;
71
+  background-color: #fff;
72
+  color: #1869ad;
73
+  font-size: 36rpx;
74
+}
75
+
76
+.bottom-button .consumer-button {
77
+  display: flex;
78
+  align-items: center;
79
+  justify-content: center;
80
+  margin-bottom: 50rpx;
81
+  min-width: 300rpx;
82
+  border-radius: 50rpx;
83
+  padding: 5rpx 70rpx 5rpx 70rpx;
84
+  background-color: #fff;
85
+  color: #1869ad;
86
+  font-size: 36rpx;
87
+}
88
+
89
+.bottom-view .sale-button[pressed] {
90
+  background-color: rgba(24, 105, 173, 0.8);
91
+  color: rgba(255, 255, 255, 0.8);
92
+}
93
+
94
+.bottom-view .sale-button[disabled] {
95
+  background-color: rgba(24, 105, 173, 0.6);
96
+  color: rgba(255, 255, 255, 0.6);
97
+}

+ 79 - 0
src/brand/brand.wxss

@@ -0,0 +1,79 @@
1
+text {
2
+  color: #fff;
3
+}
4
+
5
+.user-name {
6
+  color: #fff;
7
+}
8
+
9
+.button {
10
+  display: flex;
11
+  justify-content: center;
12
+  align-items: center;
13
+  width: 35%;
14
+  color: #1869ad;
15
+  border-radius: 40%/120%;
16
+
17
+  font-size: 80%;
18
+  letter-spacing: 2px;
19
+  font-weight: 500;
20
+  background-color: #fff;
21
+}
22
+
23
+/* index */
24
+.sale-view .integral-view {
25
+  border: 2px solid #fff;
26
+}
27
+
28
+.sale-view .integral-view .content {
29
+  color: #fff;
30
+}
31
+
32
+/* commodity */
33
+.commodity-desc text {
34
+  font-size: 90%;
35
+}
36
+
37
+.commodity-desc .title {
38
+  font-size: 100%;
39
+}
40
+
41
+/* info */
42
+form .cell {
43
+  background-color: #fff;
44
+}
45
+
46
+/* form .cell input {
47
+  color: #fff;
48
+} */
49
+
50
+form text,
51
+form button {
52
+  color: #1869ad;
53
+}
54
+
55
+.empty-hint {
56
+  color: #fff;
57
+}
58
+
59
+.hint-main text {
60
+  color: #1869ad;
61
+}
62
+
63
+.hint-main .confirm {
64
+  background-color: #1869ad;
65
+  color: #fff;
66
+}
67
+/* sale_integral */
68
+.integral-title {
69
+  background-color: #95b9d9;
70
+  color: #fff;
71
+}
72
+
73
+.integral-production {
74
+  background-color: #f8f8f8;
75
+}
76
+
77
+.integral-production text {
78
+  color: #1869ad;
79
+}

+ 37 - 0
src/brand/config.js

@@ -0,0 +1,37 @@
1
+module.exports = {
2
+  baseURL: 'https://kodo.xfoto.com.cn',
3
+  brandID: 'ywkRTh5vtswRvhh8uLvTTQ',
4
+  demo: true,
5
+  copywriting: {
6
+    info: {
7
+      headerTitle:
8
+        'THANK YOU!\n感谢您购买腾龙镜头\n请完善您的个人信息领取保修卡',
9
+      coupon: '并领取意外维修劵',
10
+      hintTitle: '提 交 成 功',
11
+      inputDesc: '请完善您的信息获取5+1年保修服务',
12
+      hintDesc: '恭喜您获得\n腾龙官方5+1年保修服务',
13
+      hintCoupon: '和意外维修劵',
14
+      hintBtn: '领取'
15
+    },
16
+    saleInfo: {
17
+      headerTitle: 'THANK YOU!\n感谢您销售腾龙正品行货镜头\n二维码验证有效',
18
+      hintTitle: '提 交 成 功',
19
+      hintDesc:
20
+        '本次销售您消耗{{integral}}卡路里\n共计消耗{{totalIntegral}}卡路里',
21
+      hintBtn: '确定'
22
+    },
23
+    verification: {
24
+      commodityTitle: '感谢您购买腾龙镜头',
25
+      userInfoBtn: '申请电子保修卡'
26
+    },
27
+    sale_verification: {
28
+      commodityTitle: '请确认您所销售商品型号和编码',
29
+      userInfoBtn: '确认出库'
30
+    },
31
+    equipment: {
32
+      emptyHint: '您还没有绑定腾龙镜头',
33
+      couponTitle: '腾龙镜头意外维修券',
34
+      couponDesc: '此券用于镜头保修范围外的意外或人为损伤的维修费'
35
+    }
36
+  }
37
+}

BIN
src/component/.DS_Store


+ 62 - 0
src/component/btnFooter-protocol/btnFooter-protocol.js

@@ -0,0 +1,62 @@
1
+// component/btnFooter-protocol/btnFooter-protocol.js
2
+Component({
3
+  /**
4
+   * Component properties
5
+   */
6
+  properties: {
7
+    btnClass: {
8
+      type: String,
9
+      value: 'button1'
10
+    },
11
+
12
+    btnTxt: {
13
+      type: String,
14
+      value: '确认'
15
+    },
16
+
17
+    btnDisalbed: {
18
+      type: Boolean,
19
+      value: false,
20
+      observer(newVal, _) {
21
+        this.setData({
22
+          disabled: newVal && this.data.protocolImg === 2
23
+        })
24
+      }
25
+    },
26
+
27
+    openType: {
28
+      type: String,
29
+      value: ''
30
+    }
31
+  },
32
+
33
+  /**
34
+   * Component initial data
35
+   */
36
+  data: {
37
+    protocolImg: 2,
38
+    disabled: false
39
+  },
40
+
41
+  /**
42
+   * Component methods
43
+   */
44
+  methods: {
45
+    switchAuthorization() {
46
+      this.setData({
47
+        disabled: this.data.protocolImg !== 1 || this.data.btnDisalbed,
48
+        protocolImg: 3 - this.data.protocolImg
49
+      })
50
+    },
51
+
52
+    openUrl(e) {
53
+      wx.navigateTo({
54
+        url: `/template/webView/webView?url=${e.currentTarget.dataset.url}`
55
+      })
56
+    },
57
+
58
+    confirm(e) {
59
+      this.triggerEvent('action', e)
60
+    }
61
+  }
62
+})

+ 4 - 0
src/component/btnFooter-protocol/btnFooter-protocol.json

@@ -0,0 +1,4 @@
1
+{
2
+  "component": true,
3
+  "usingComponents": {}
4
+}

+ 42 - 0
src/component/btnFooter-protocol/btnFooter-protocol.less

@@ -0,0 +1,42 @@
1
+@import './src/style/less/variable.less';
2
+@import './src/style/less/layout.less';
3
+@import '/style/button.wxss';
4
+@import '/template/btn/btn.wxss';
5
+
6
+.btn-footer {
7
+  .flex-center(column);
8
+
9
+  position: fixed;
10
+  bottom: 0;
11
+  z-index: 999;
12
+  justify-content: center;
13
+  width: 100%;
14
+  height: @btnFooterWithProtocol-height;
15
+  background-color: @color-white;
16
+  box-shadow: 0 -5px 9px 2px fade(@color-black, 10%);
17
+
18
+  .protocol {
19
+    .flex-center(row);
20
+
21
+    image {
22
+      .square(@icon-small-size);
23
+
24
+      margin-right: @spacing-inline;
25
+    }
26
+
27
+    text {
28
+      font-size: @font-label;
29
+      color: @color-light-gray;
30
+    }
31
+
32
+    .clickable {
33
+      color: @color-brand;
34
+    }
35
+  }
36
+
37
+  button {
38
+    width: @visual-width;
39
+    height: 100rpx !important;
40
+    margin-top: @spacing-inline;
41
+  }
42
+}

+ 11 - 0
src/component/btnFooter-protocol/btnFooter-protocol.wxml

@@ -0,0 +1,11 @@
1
+<import src="/template/btn/btn.wxml"></import>
2
+<view class="btn-footer">
3
+  <view class="protocol">
4
+    <image src="/resources/protocol{{protocolImg}}.png" bindtap="switchAuthorization"></image>
5
+    <text>点击确认即表示已阅读并同意</text>
6
+    <text class="clickable" data-url="https://kodo.tamron.cn/mp/privacy.html" bindtap="openUrl">《隐私条款》</text>
7
+    <text>和</text>
8
+    <text class="clickable" data-url="https://kodo.tamron.cn/mp/regulations.html" bindtap="openUrl">《会员章程》</text>
9
+  </view>
10
+  <template is="iconBtn" data="{{btnClass: btnClass, btnTxt: btnTxt, disabled: disabled, action: 'confirm', openType: openType}}"></template>
11
+</view>

+ 36 - 0
src/component/btnFooter-protocol/btnFooter-protocol.wxss

@@ -0,0 +1,36 @@
1
+@import '/style/button.wxss';
2
+@import '/template/btn/btn.wxss';
3
+.btn-footer {
4
+  display: flex;
5
+  flex-direction: column;
6
+  align-items: center;
7
+  position: fixed;
8
+  bottom: 0;
9
+  z-index: 999;
10
+  justify-content: center;
11
+  width: 100%;
12
+  height: 200rpx;
13
+  background-color: #fff;
14
+  box-shadow: 0 -5px 9px 2px rgba(0, 0, 0, 0.1);
15
+}
16
+.btn-footer .protocol {
17
+  display: flex;
18
+  align-items: center;
19
+}
20
+.btn-footer .protocol image {
21
+  width: 32rpx;
22
+  height: 32rpx;
23
+  margin-right: 12rpx;
24
+}
25
+.btn-footer .protocol text {
26
+  font-size: 8pt;
27
+  color: #959595;
28
+}
29
+.btn-footer .protocol .clickable {
30
+  color: #0967b2;
31
+}
32
+.btn-footer button {
33
+  width: 702rpx;
34
+  height: 100rpx !important;
35
+  margin-top: 12rpx;
36
+}

BIN
src/component/dialog-input/.DS_Store


+ 127 - 0
src/component/dialog-input/dialog-input.js

@@ -0,0 +1,127 @@
1
+// component/dialog-input/dialog-input.js
2
+Component({
3
+  /**
4
+   * Component properties
5
+   */
6
+  properties: {
7
+    title: {
8
+      type: String,
9
+      value: ''
10
+    },
11
+
12
+    btn: {
13
+      type: Object,
14
+      value: {}
15
+    },
16
+
17
+    msg: {
18
+      type: Array,
19
+      value: [],
20
+      observer(newVal, _) {
21
+        let btnDisabled = false
22
+        newVal.forEach(v => {
23
+          if (!v.hasValue && v.txt === '') {
24
+            btnDisabled = true
25
+          }
26
+        })
27
+
28
+        this.setData({
29
+          btnDisabled
30
+        })
31
+      }
32
+    },
33
+
34
+    show: {
35
+      type: Boolean,
36
+      value: false,
37
+      observer(newVal, oldVal) {
38
+        if (newVal === oldVal) {
39
+          return
40
+        }
41
+        if (newVal) {
42
+          this.setData({
43
+            isShow: true
44
+          })
45
+          const animation1 = wx.createAnimation({
46
+            duration: 400,
47
+            timingFunction: 'linear'
48
+          })
49
+          animation1.opacity(1).step()
50
+
51
+          const animation2 = wx.createAnimation({
52
+            duration: 500,
53
+            timingFunction: 'linear'
54
+          })
55
+          animation2.opacity(1).step()
56
+          this.setData({
57
+            bgAnimationData: animation1.export(),
58
+            animationData: animation2.export()
59
+          })
60
+        } else {
61
+          const animation1 = wx.createAnimation({
62
+            duration: 400,
63
+            timingFunction: 'linear'
64
+          })
65
+          animation1.opacity(0).step()
66
+
67
+          const animation2 = wx.createAnimation({
68
+            duration: 500,
69
+            timingFunction: 'linear'
70
+          })
71
+
72
+          animation2.opacity(0).step()
73
+          this.setData({
74
+            bgAnimationData: animation1.export(),
75
+            animationData: animation2.export()
76
+          })
77
+
78
+          const that = this
79
+          setTimeout(() => {
80
+            that.setData({
81
+              isShow: false
82
+            })
83
+          }, 500)
84
+        }
85
+      }
86
+    }
87
+  },
88
+
89
+  /**
90
+   * Component initial data
91
+   */
92
+  data: {
93
+    btnDisabled: false,
94
+    name: '',
95
+    animationData: {},
96
+    bgAnimationData: {},
97
+    isShow: false
98
+  },
99
+
100
+  /**
101
+   * Component methods
102
+   */
103
+  methods: {
104
+    cancel() {
105
+      this.triggerEvent('cancel', !this.data.show)
106
+    },
107
+
108
+    nameInput(e) {
109
+      this.setData({
110
+        name: e.detail.value
111
+      })
112
+    },
113
+
114
+    getPhoneNumber(e) {
115
+      this.triggerEvent('getPhoneNumber', e.detail)
116
+    },
117
+
118
+    submit() {
119
+      this.triggerEvent('submit', { name: this.data.name })
120
+    }
121
+  },
122
+
123
+  pageLifetimes: {
124
+    // 组件所在页面的生命周期函数
125
+    show() {}
126
+  }
127
+})

+ 4 - 0
src/component/dialog-input/dialog-input.json

@@ -0,0 +1,4 @@
1
+{
2
+  "component": true,
3
+  "usingComponents": {}
4
+}

+ 115 - 0
src/component/dialog-input/dialog-input.less

@@ -0,0 +1,115 @@
1
+@import './src/style/less/variable.less';
2
+@import './src/style/less/layout.less';
3
+@import './src/style/less/shape.less';
4
+
5
+.dialog-input {
6
+  .flex-center(row);
7
+
8
+  position: absolute;
9
+  top: 0;
10
+  justify-content: center;
11
+  width: 100%;
12
+  height: 100%;
13
+  background-color: rgba(0, 0, 0, 0.6);
14
+  opacity: 0;
15
+}
16
+
17
+.main {
18
+  position: relative;
19
+  .flex-center(column);
20
+
21
+  justify-content: center;
22
+  width: 600rpx;
23
+  background-color: @color-white;
24
+  border-radius: @radius-big;
25
+  opacity: 0;
26
+}
27
+
28
+.cancel {
29
+  position: absolute;
30
+  top: 30rpx;
31
+  right: 20rpx;
32
+  .cross(30rpx, @color-light-gray);
33
+}
34
+
35
+.main .title {
36
+  .flex-center(row);
37
+
38
+  justify-content: center;
39
+  width: 100%;
40
+  height: 60rpx;
41
+  margin-top: 60rpx;
42
+  font-size: @font-primary;
43
+
44
+  text {
45
+    color: @color-brand;
46
+  }
47
+}
48
+
49
+form {
50
+  width: 500rpx;
51
+  margin: @spacing-view 0;
52
+
53
+  .item {
54
+    .flex-center(row);
55
+
56
+    box-sizing: border-box;
57
+    justify-content: flex-start;
58
+    width: 100%;
59
+    height: 80rpx;
60
+    margin-top: @spacing-item;
61
+    border: 1px @color-bg solid;
62
+    border-radius: @radius;
63
+
64
+    input {
65
+      box-sizing: border-box;
66
+      width: calc(100% - 2px);
67
+      height: calc(100% - 2px);
68
+      padding: @spacing-inline;
69
+      font-size: @font-secondary;
70
+    }
71
+
72
+    .placeholder {
73
+      font-size: @font-tertiary;
74
+      color: @color-light-gray;
75
+    }
76
+
77
+    button {
78
+      .flex-center(row);
79
+
80
+      box-sizing: border-box;
81
+      justify-content: space-between;
82
+      width: calc(100% - 2px);
83
+      height: calc(100% - 2px);
84
+      padding: @spacing-inline;
85
+      background-color: @color-white;
86
+
87
+      .placeholder {
88
+        font-size: @font-tertiary;
89
+        color: #767676;
90
+      }
91
+
92
+      .txt {
93
+        font-size: @font-secondary;
94
+        color: @color-black;
95
+      }
96
+
97
+      .arrow {
98
+        .arrow(right; @color-light-gray; 14rpx);
99
+      }
100
+    }
101
+  }
102
+
103
+  .confirm {
104
+    .flex-center(row);
105
+
106
+    box-sizing: border-box;
107
+    justify-content: center;
108
+    width: 500rpx;
109
+    height: 80rpx;
110
+    margin-top: @spacing-view;
111
+    color: @color-white;
112
+    background-color: @color-brand;
113
+    border-radius: @radius;
114
+  }
115
+}

+ 24 - 0
src/component/dialog-input/dialog-input.wxml

@@ -0,0 +1,24 @@
1
+<view class="dialog-input" wx:if="{{isShow}}" animation="{{bgAnimationData}}">
2
+  <view class="main" animation="{{animationData}}">
3
+    <view class="cancel" bindtap="cancel"></view>
4
+    <view class="title">
5
+      <text>{{title}}</text>
6
+    </view>
7
+    <form>
8
+      <view class="container">
9
+        <block wx:for="{{msg}}">
10
+        <view class="item" wx:if="{{item.type == 'input'}}">
11
+          <input placeholder="{{item.txt || item.placeholder}}" placeholder-class="placeholder" bindinput="nameInput"></input>
12
+        </view>
13
+        <view class="item" wx:if="{{item.type == 'phone'}}">
14
+          <button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">
15
+            <text class="{{item.txt == '' ? 'placeholder': 'txt'}}">{{item.txt || item.placeholder}}</text>
16
+            <view class="arrow"></view>
17
+          </button>
18
+        </view>
19
+        </block>
20
+      </view>
21
+      <button class="confirm" disabled="{{btnDisabled}}" bindtap="submit">{{btn.txt}}</button>
22
+    </form>
23
+  </view>
24
+</view>

+ 122 - 0
src/component/dialog-input/dialog-input.wxss

@@ -0,0 +1,122 @@
1
+.dialog-input {
2
+  display: flex;
3
+  align-items: center;
4
+  position: absolute;
5
+  top: 0;
6
+  justify-content: center;
7
+  width: 100%;
8
+  height: 100%;
9
+  background-color: rgba(0, 0, 0, 0.6);
10
+  opacity: 0;
11
+}
12
+.main {
13
+  position: relative;
14
+  display: flex;
15
+  flex-direction: column;
16
+  align-items: center;
17
+  justify-content: center;
18
+  width: 600rpx;
19
+  background-color: #fff;
20
+  border-radius: 10px;
21
+  opacity: 0;
22
+}
23
+.cancel {
24
+  position: absolute;
25
+  top: 30rpx;
26
+  right: 20rpx;
27
+  display: inline-block;
28
+  width: 30rpx;
29
+  height: 7.5rpx;
30
+  overflow: visible;
31
+  font-size: 0;
32
+  line-height: 0;
33
+  vertical-align: middle;
34
+  background: #959595;
35
+  border-radius: 7.5rpx;
36
+  transform: rotate(45deg);
37
+}
38
+.cancel::after {
39
+  display: block;
40
+  width: 30rpx;
41
+  height: 7.5rpx;
42
+  content: '/';
43
+  background: #959595;
44
+  border-radius: 7.5rpx;
45
+  transform: rotate(-90deg);
46
+}
47
+.main .title {
48
+  display: flex;
49
+  align-items: center;
50
+  justify-content: center;
51
+  width: 100%;
52
+  height: 60rpx;
53
+  margin-top: 60rpx;
54
+  font-size: 14pt;
55
+}
56
+.main .title text {
57
+  color: #0967b2;
58
+}
59
+form {
60
+  width: 500rpx;
61
+  margin: 36rpx 0;
62
+}
63
+form .item {
64
+  display: flex;
65
+  align-items: center;
66
+  box-sizing: border-box;
67
+  justify-content: flex-start;
68
+  width: 100%;
69
+  height: 80rpx;
70
+  margin-top: 24rpx;
71
+  border: 1px #efefef solid;
72
+  border-radius: 5px;
73
+}
74
+form .item input {
75
+  box-sizing: border-box;
76
+  width: calc(100% - 2px);
77
+  height: calc(100% - 2px);
78
+  padding: 12rpx;
79
+  font-size: 13pt;
80
+}
81
+form .item .placeholder {
82
+  font-size: 11pt;
83
+  color: #959595;
84
+}
85
+form .item button {
86
+  display: flex;
87
+  align-items: center;
88
+  box-sizing: border-box;
89
+  justify-content: space-between;
90
+  width: calc(100% - 2px);
91
+  height: calc(100% - 2px);
92
+  padding: 12rpx;
93
+  background-color: #fff;
94
+}
95
+form .item button .placeholder {
96
+  font-size: 11pt;
97
+  color: #767676;
98
+}
99
+form .item button .txt {
100
+  font-size: 13pt;
101
+  color: #000;
102
+}
103
+form .item button .arrow {
104
+  width: 14rpx;
105
+  height: 14rpx;
106
+  box-sizing: border-box;
107
+  border-top: 2px solid #959595;
108
+  border-right: 2px solid #959595;
109
+  transform: rotate(45deg);
110
+}
111
+form .confirm {
112
+  display: flex;
113
+  align-items: center;
114
+  box-sizing: border-box;
115
+  justify-content: center;
116
+  width: 500rpx;
117
+  height: 80rpx;
118
+  margin-top: 36rpx;
119
+  color: #fff;
120
+  background-color: #0967b2;
121
+  border-radius: 5px;
122
+}

+ 1 - 0
src/component/stepper/add.svg

@@ -0,0 +1 @@
1
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1587110727868" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1164" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M512 85.333333c235.637333 0 426.666667 191.029333 426.666667 426.666667S747.637333 938.666667 512 938.666667 85.333333 747.637333 85.333333 512 276.362667 85.333333 512 85.333333z m0 234.666667a32 32 0 0 0-32 32v128H352a32 32 0 0 0 0 64h128v128a32 32 0 0 0 64 0V544h128a32 32 0 0 0 0-64H544V352a32 32 0 0 0-32-32z" p-id="1165" fill="#09bb07"></path></svg>

+ 1 - 0
src/component/stepper/minus.svg

@@ -0,0 +1 @@
1
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1587110843176" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1962" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M510.293333 126.862222c51.863704 0 102.115556 10.145185 149.428148 30.151111 45.700741 19.342222 86.755556 47.028148 122.026667 82.29926 35.271111 35.271111 62.957037 76.325926 82.299259 122.026666 20.005926 47.312593 30.151111 97.564444 30.151112 149.428148s-10.145185 102.115556-30.151112 149.428149c-19.342222 45.700741-47.028148 86.755556-82.299259 122.026666-35.271111 35.271111-76.325926 62.957037-122.026667 82.299259-47.217778 20.100741-97.564444 30.340741-149.428148 30.340741S408.177778 884.622222 360.865185 864.616296c-45.700741-19.342222-86.755556-47.028148-122.026666-82.299259-35.271111-35.271111-62.957037-76.325926-82.29926-122.026667-20.005926-47.312593-30.151111-97.564444-30.151111-149.428148S136.533333 408.651852 156.539259 361.339259c19.342222-45.700741 47.028148-86.755556 82.29926-122.026666 35.271111-35.271111 76.325926-62.957037 122.026666-82.29926 47.312593-20.005926 97.659259-30.151111 149.428148-30.151111m0-42.666666c-235.614815 0-426.666667 191.051852-426.666666 426.666666s191.051852 426.666667 426.666666 426.666667 426.666667-191.051852 426.666667-426.666667-190.957037-426.666667-426.666667-426.666666z" p-id="1963" fill="#09bb07"></path><path d="M709.499259 532.195556h-398.222222c-11.757037 0-21.333333-9.576296-21.333333-21.333334s9.576296-21.333333 21.333333-21.333333h398.222222c11.757037 0 21.333333 9.576296 21.333334 21.333333s-9.576296 21.333333-21.333334 21.333334z" p-id="1964" fill="#09bb07"></path></svg>

+ 27 - 0
src/component/stepper/stepper.js

@@ -0,0 +1,27 @@
1
+// component/stepper/stepper.js
2
+Component({
3
+  data: {
4
+    num: 0
5
+  },
6
+
7
+  /**
8
+   * 组件的方法列表
9
+   */
10
+  methods: {
11
+    bindadd() {
12
+      this.setData({
13
+        num: this.data.num + 1
14
+      })
15
+
16
+      this.triggerEvent('stepperChanged', this.data.num)
17
+    },
18
+
19
+    bindminus() {
20
+      this.setData({
21
+        num: this.data.num - 1
22
+      })
23
+
24
+      this.triggerEvent('stepperChanged', this.data.num)
25
+    }
26
+  }
27
+})

+ 4 - 0
src/component/stepper/stepper.json

@@ -0,0 +1,4 @@
1
+{
2
+  "component": true,
3
+  "usingComponents": {}
4
+}

+ 17 - 0
src/component/stepper/stepper.less

@@ -0,0 +1,17 @@
1
+@import './src/style/less/variable.less';
2
+
3
+.stepper {
4
+  display: flex;
5
+  align-items: center;
6
+
7
+  .icon {
8
+    width: 72rpx;
9
+    height: 72rpx;
10
+  }
11
+
12
+  .num {
13
+    padding: 0 @spacing-short-text;
14
+    color: #000;
15
+    text-align: center;
16
+  }
17
+}

+ 5 - 0
src/component/stepper/stepper.wxml

@@ -0,0 +1,5 @@
1
+<view class="stepper">
2
+  <image class="icon" src="minus.svg" bindtap="bindminus" wx:if="{{ num !== 0 }}"></image>
3
+  <text class="num" wx:if="{{ num !== 0 }}">{{ num }}</text>
4
+  <image class="icon" src="add.svg" bindtap="bindadd"></image>
5
+</view>

+ 13 - 0
src/component/stepper/stepper.wxss

@@ -0,0 +1,13 @@
1
+.stepper {
2
+  display: flex;
3
+  align-items: center;
4
+}
5
+.stepper .icon {
6
+  width: 72rpx;
7
+  height: 72rpx;
8
+}
9
+.stepper .num {
10
+  padding: 0 4rpx;
11
+  color: #000;
12
+  text-align: center;
13
+}

BIN
src/pages/.DS_Store


+ 205 - 0
src/pages/index/index.js

@@ -0,0 +1,205 @@
1
+const WxParse = require('../../utils/wxParse/wxParse.js')
2
+
3
+const app = getApp()
4
+
5
+app.routerInterceptor.checkLoginStatus({
6
+  /**
7
+   * 页面的初始数据
8
+   */
9
+  data: {
10
+    kol: {},
11
+    commodities: [],
12
+
13
+    consumers: [],
14
+
15
+    pack_id: 'FMHBanG9d9uCm9CAEAdMVa',
16
+    pack: {},
17
+
18
+    nums: [],
19
+    total: 0,
20
+    isLodding: false
21
+  },
22
+
23
+  /**
24
+   * 生命周期函数--监听页面加载
25
+   */
26
+  onLoad() {
27
+    this.getData()
28
+    wx.hideHomeButton()
29
+  },
30
+
31
+  getData() {
32
+    const that = this
33
+    app.network.post({
34
+      url: app.urls.PACK,
35
+      data: {
36
+        pack_id: 'FMHBanG9d9uCm9CAEAdMVa'
37
+      },
38
+      success(res) {
39
+        const { sales, pack, goods } = res.data
40
+        const conusmers = sales.map(sale => {
41
+          const s = sale
42
+          s.content = sale.saleinfo.reduce(
43
+            (accumulator, cur) => `${accumulator + cur.title} x ${cur.num} \n`,
44
+            ''
45
+          )
46
+          s.created_at = that.timeago(s.created_at)
47
+          return s
48
+        })
49
+        WxParse.wxParse('article', 'html', pack.pack_detail, that, 5)
50
+        pack.pack_detail = ''
51
+
52
+        const commodities = goods.map(commodity => {
53
+          const c = commodity
54
+          c.price /= 100
55
+          return c
56
+        })
57
+        that.setData({
58
+          kol: res.data.kol,
59
+          commodities,
60
+          nums: Array(res.data.goods).fill(0),
61
+          consumers: conusmers,
62
+          pack,
63
+          isLodding: true
64
+        })
65
+      }
66
+    })
67
+  },
68
+
69
+  bindstepperChanged(e) {
70
+    const { nums, commodities } = this.data
71
+    nums[e.currentTarget.dataset.index] = e.detail
72
+
73
+    const total = commodities.reduce(
74
+      (accumulator, cur, index) =>
75
+        accumulator + (cur.price * 100 * nums[index]) / 100,
76
+      0
77
+    )
78
+
79
+    this.setData({
80
+      nums,
81
+      total
82
+    })
83
+  },
84
+
85
+  navigateToOrder() {
86
+    const { nums } = this.data
87
+    let { commodities } = this.data
88
+    commodities = commodities
89
+      .map((curr, index) => {
90
+        return Object.assign(curr, { num: nums[index] })
91
+      })
92
+      .filter(curr => {
93
+        return curr.num > 0
94
+      })
95
+
96
+    const that = this
97
+    wx.navigateTo({
98
+      url: '../order/order',
99
+      success(res) {
100
+        res.eventChannel.emit('acceptDataFromOpenerPage', {
101
+          commodities,
102
+          total: that.data.total,
103
+          kol: that.data.kol,
104
+          pack_id: that.data.pack_id
105
+        })
106
+      }
107
+    })
108
+  },
109
+
110
+  getUserInfo(e) {
111
+    if (e.detail.userInfo === undefined) {
112
+      wx.showModal({
113
+        title: '提示',
114
+        content: '需要您的授权,才能继续报名',
115
+        showCancel: false
116
+      })
117
+    } else {
118
+      wx.showLoading({
119
+        title: '登陆中'
120
+      })
121
+
122
+      const that = this
123
+      app.getUserInfo(
124
+        {
125
+          iv: e.detail.iv,
126
+          encryptedData: e.detail.encryptedData
127
+        },
128
+        () => {
129
+          that.navigateToOrder()
130
+        }
131
+      )
132
+    }
133
+  },
134
+
135
+  onShareAppMessage() {
136
+    return {
137
+      title: this.data.pack.title,
138
+      path: '/pages/index/index'
139
+    }
140
+  },
141
+
142
+  timeago(date) {
143
+    // dateTimeStamp是一个时间毫秒,注意时间戳是秒的形式,在这个毫秒的基础上除以1000,就是十位数的时间戳。13位数的都是时间毫秒。
144
+    const dateTimeStamp = new Date(
145
+      Date.parse(date.replace(/-/g, '/'))
146
+    ).getTime()
147
+
148
+    const minute = 1000 * 60 // 把分,时,天,周,半个月,一个月用毫秒表示
149
+    const hour = minute * 60
150
+    const day = hour * 24
151
+    const week = day * 7
152
+    const halfamonth = day * 15
153
+    const month = day * 30
154
+    const now = new Date().getTime() // 获取当前时间毫秒
155
+    const diffValue = now - dateTimeStamp // 时间差
156
+
157
+    if (diffValue < 0) {
158
+      return ''
159
+    }
160
+    const minC = diffValue / minute // 计算时间差的分,时,天,周,月
161
+    const hourC = diffValue / hour
162
+    const dayC = diffValue / day
163
+    const weekC = diffValue / week
164
+    const monthC = diffValue / month
165
+    let result = ''
166
+
167
+    if (monthC >= 1 && monthC <= 3) {
168
+      result = ` ${parseInt(monthC)}月前`
169
+    } else if (weekC >= 1 && weekC <= 3) {
170
+      result = ` ${parseInt(weekC)}周前`
171
+    } else if (dayC >= 1 && dayC <= 6) {
172
+      result = ` ${parseInt(dayC)}天前`
173
+    } else if (hourC >= 1 && hourC <= 23) {
174
+      result = ` ${parseInt(hourC)}小时前`
175
+    } else if (minC >= 1 && minC <= 59) {
176
+      result = ` ${parseInt(minC)}分钟前`
177
+    } else if (diffValue >= 0 && diffValue <= minute) {
178
+      result = '刚刚'
179
+    } else {
180
+      const datetime = new Date()
181
+      datetime.setTime(dateTimeStamp)
182
+      const Nyear = datetime.getFullYear()
183
+      const Nmonth =
184
+        datetime.getMonth() + 1 < 10
185
+          ? `0${datetime.getMonth() + 1}`
186
+          : datetime.getMonth() + 1
187
+      const Ndate =
188
+        datetime.getDate() < 10 ? `0${datetime.getDate()}` : datetime.getDate()
189
+      const Nhour =
190
+        datetime.getHours() < 10
191
+          ? `0${datetime.getHours()}`
192
+          : datetime.getHours()
193
+      const Nminute =
194
+        datetime.getMinutes() < 10
195
+          ? `0${datetime.getMinutes()}`
196
+          : datetime.getMinutes()
197
+      const Nsecond =
198
+        datetime.getSeconds() < 10
199
+          ? `0${datetime.getSeconds()}`
200
+          : datetime.getSeconds()
201
+      result = `${Nyear}-${Nmonth}-${Ndate}`
202
+    }
203
+    return result
204
+  }
205
+})

+ 6 - 0
src/pages/index/index.json

@@ -0,0 +1,6 @@
1
+{
2
+  "usingComponents": {
3
+    "stepper": "/component/stepper/stepper"
4
+  },
5
+  "disableScroll": true
6
+}

+ 310 - 0
src/pages/index/index.less

@@ -0,0 +1,310 @@
1
+@import './src/style/less/variable.less';
2
+@import './src/style/less/layout.less';
3
+@import './src/style/less/shape.less';
4
+@import '/template/footer/footer.wxss';
5
+@import '/template/btn/btn.wxss';
6
+@import '/utils/wxParse/wxParse.wxss';
7
+
8
+page {
9
+  background-color: @color-bg;
10
+}
11
+
12
+.wrap {
13
+  box-sizing: border-box;
14
+  display: flex;
15
+  flex-direction: column;
16
+  align-items: center;
17
+  height: 100%;
18
+  background-color: @color-bg;
19
+}
20
+
21
+.header {
22
+  position: relative;
23
+  width: 100%;
24
+
25
+  .banner-img {
26
+    position: absolute;
27
+    width: 750rpx;
28
+    height: 400rpx;
29
+  }
30
+
31
+  // .banner-container {
32
+  //   position: absolute;
33
+  //   top: 0;
34
+  //   z-index: 1;
35
+  //   width: 100%;
36
+  //   height: 400rpx;
37
+  // }
38
+
39
+  .user-info {
40
+    position: relative;
41
+    box-sizing: border-box;
42
+    display: flex;
43
+    align-items: flex-end;
44
+    width: 750rpx;
45
+    height: 200rpx;
46
+    padding: 0 @spacing-item @spacing-item;
47
+    margin-top: 200rpx;
48
+    background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.8));
49
+
50
+    .avatar {
51
+      width: 100rpx;
52
+      height: 100rpx;
53
+      border-radius: 6px;
54
+    }
55
+
56
+    .nickname {
57
+      margin: 0 0 @spacing-inline @spacing-inline;
58
+      font-size: @font-primary;
59
+      color: #fff;
60
+    }
61
+
62
+    .subscribe {
63
+      display: flex;
64
+      flex-grow: 2;
65
+      align-items: center;
66
+      justify-content: flex-end;
67
+      margin: 0 0 @spacing-inline @spacing-inline;
68
+
69
+      .num {
70
+        font-size: @font-secondary;
71
+        font-weight: 600;
72
+        color: @color-white;
73
+      }
74
+
75
+      .label {
76
+        margin-left: @spacing-short-text;
77
+        font-size: @font-quaternary;
78
+        color: #cbcbcb;
79
+      }
80
+    }
81
+  }
82
+
83
+  .desc {
84
+    margin-top: @spacing-view;
85
+    margin-bottom: 0rpx;
86
+    margin-left: @spacing-item;
87
+
88
+    text {
89
+      color: @color-light-gray;
90
+    }
91
+  }
92
+}
93
+
94
+.main {
95
+  box-sizing: border-box;
96
+  width: @visual-width;
97
+  margin-top: @spacing-view;
98
+}
99
+
100
+.commodity-container {
101
+  .flex-center(column);
102
+
103
+  box-sizing: border-box;
104
+  width: 100%;
105
+  padding: @spacing-view @spacing-item;
106
+  background-color: @color-white;
107
+
108
+  .commodity-title-container {
109
+    display: flex;
110
+    flex-direction: column;
111
+    width: 100%;
112
+
113
+    .title {
114
+      font-size: @font-primary;
115
+      font-weight: 600;
116
+      color: @color-black;
117
+    }
118
+
119
+    .expired {
120
+      margin-top: @spacing-short-text;
121
+      font-size: @font-quaternary;
122
+      color: @color-light-gray;
123
+    }
124
+  }
125
+
126
+  .commodity-details {
127
+    width: 100%;
128
+    margin-top: @spacing-item;
129
+  }
130
+
131
+  .commodity-list {
132
+    .flex-center(column);
133
+
134
+    width: 100%;
135
+    margin-top: @spacing-view;
136
+
137
+    .commodity-item {
138
+      display: flex;
139
+      align-items: center;
140
+      justify-content: space-between;
141
+      width: 100%;
142
+      height: 200rpx;
143
+      padding: @spacing-item 0;
144
+      border-bottom: 1px @color-bg solid;
145
+
146
+      .commodity-left {
147
+        .flex-center(row);
148
+
149
+        height: 200rpx;
150
+
151
+        .commodity-img {
152
+          width: 180rpx;
153
+          height: 180rpx;
154
+        }
155
+
156
+        .commodity-content {
157
+          display: flex;
158
+          flex-direction: column;
159
+          height: 100%;
160
+          margin-left: @spacing-item;
161
+
162
+          .commodity-title {
163
+            font-size: @font-secondary;
164
+            color: @color-gray;
165
+          }
166
+
167
+          .commodity-desc {
168
+            margin-top: @spacing-short-text;
169
+            font-size: @font-quaternary;
170
+            color: @color-light-gray;
171
+          }
172
+        }
173
+      }
174
+
175
+      .commodity-price {
176
+        display: flex;
177
+        flex-grow: 2;
178
+        align-items: flex-end;
179
+
180
+        text {
181
+          font-size: @font-primary;
182
+          color: #e64340;
183
+        }
184
+      }
185
+    }
186
+
187
+    .commodity-right {
188
+      display: flex;
189
+      flex-direction: column;
190
+      align-items: flex-end;
191
+      height: 100%;
192
+
193
+      .bought {
194
+        font-size: @font-quaternary;
195
+        color: #e64340;
196
+        text-align: right;
197
+      }
198
+
199
+      .stepper-container {
200
+        margin-top: @spacing-item;
201
+      }
202
+    }
203
+  }
204
+}
205
+
206
+.consumer-container {
207
+  padding: @spacing-item;
208
+  background-color: @color-white;
209
+
210
+  .consumer-title {
211
+    height: 80rpx;
212
+
213
+    text {
214
+      font-size: @font-primary;
215
+      font-weight: 600;
216
+      color: @color-gray;
217
+    }
218
+  }
219
+
220
+  .swiper-container {
221
+    width: 100%;
222
+    height: 200rpx;
223
+  }
224
+
225
+  .consumer-item {
226
+    width: @visual-width - @spacing-item * 2;
227
+
228
+    .consumer-info {
229
+      display: flex;
230
+      align-items: center;
231
+
232
+      .avatar {
233
+        width: 56rpx;
234
+        height: 56rpx;
235
+        border-radius: 28rpx;
236
+      }
237
+
238
+      .name {
239
+        margin-left: @spacing-inline;
240
+        font-size: @font-tertiary;
241
+        font-weight: 600;
242
+        color: @color-black;
243
+      }
244
+
245
+      .time {
246
+        margin-top: 4rpx;
247
+        margin-left: @spacing-item;
248
+        font-size: @font-label;
249
+        color: lighten(@color-light-gray, 20%);
250
+      }
251
+    }
252
+
253
+    .consumer-commodity {
254
+      display: flex;
255
+      justify-content: flex-end;
256
+      width: 100%;
257
+      margin-top: @spacing-inline;
258
+
259
+      text {
260
+        width: 100%;
261
+        font-size: @font-tertiary;
262
+        color: @color-gray;
263
+        text-align: right;
264
+      }
265
+    }
266
+  }
267
+}
268
+
269
+.btn-footer {
270
+  .flex-center(row);
271
+
272
+  position: fixed;
273
+  bottom: 0;
274
+  box-sizing: border-box;
275
+  justify-content: center;
276
+  width: 100%;
277
+  height: @btnFooter-height;
278
+  padding: @spacing-item;
279
+  background-color: @color-white;
280
+  box-shadow: 0 -5px 9px 2px fade(@color-black, 10%);
281
+
282
+  .btn-footer-container {
283
+    display: flex;
284
+    width: 100%;
285
+    height: 100rpx;
286
+    overflow: hidden;
287
+    border: 1px #09bb07 solid;
288
+    border-radius: @radius;
289
+
290
+    .left {
291
+      .flex-center(row);
292
+
293
+      justify-content: center;
294
+      width: 40%;
295
+      height: 100rpx;
296
+      background-color: @color-white;
297
+
298
+      text {
299
+        font-size: @font-primary;
300
+        font-weight: 700;
301
+        color: #e64340;
302
+      }
303
+    }
304
+
305
+    .right {
306
+      width: 100%;
307
+      height: 100%;
308
+    }
309
+  }
310
+}

+ 93 - 0
src/pages/index/index.wxml

@@ -0,0 +1,93 @@
1
+<scroll-view
2
+  class="wrap"
3
+  scroll-y="{{ true }}"
4
+  enable-flex="{{ true }}"
5
+  style="padding-bottom:{{ total === 0 ? '36rpx' : '196rpx' }}">
6
+  <view style="z-index:999;background-color:#fff;width:100%;height:100%;" wx:if="{{isLoading}}"></view>
7
+  <view class="header">
8
+    <image class="banner-img" src="{{ kol.banner_url }}"></image>
9
+
10
+    <view class="user-info">
11
+      <image class="avatar" src="{{ kol.avatar_url }}"></image>
12
+      <text class="nickname">{{ kol.nickname }}</text>
13
+      <view class="subscribe">
14
+        <text class="num">{{ kol.fans }}</text>
15
+        <text class="label">粉丝</text>
16
+      </view>
17
+    </view>
18
+    <view class="desc">
19
+      <text>{{ kol.intro }}</text>
20
+    </view>
21
+  </view>
22
+  <view class="main">
23
+    <view class="commodity-container">
24
+      <view class="commodity-title-container">
25
+        <text class="title">{{ pack.title }}</text>
26
+        <text class="expired">将于 {{ pack.expired_at }} 结束</text>
27
+      </view>
28
+      <view class="commodity-details">
29
+        <import src="/utils/wxParse/wxParse.wxml"></import>
30
+        <template is="wxParse" data="{{wxParseData:article.nodes}}"></template>
31
+      </view>
32
+      <view class="commodity-list">
33
+        <block wx:for="{{ commodities }}" wx:key="good_id">
34
+          <view class="commodity-item">
35
+            <view class="commodity-left">
36
+              <image class="commodity-img" src="{{ item.image_url }}"></image>
37
+              <view class="commodity-content">
38
+                <text class="commodity-title">{{ item.title }}</text>
39
+                <text class="commodity-desc">{{ item.desc }}</text>
40
+                <view class="commodity-price">
41
+                  <text>¥{{ item.price }}</text>
42
+                </view>
43
+              </view>
44
+            </view>
45
+            <view class="commodity-right">
46
+              <text class="bought"wx:if="{{item.has_sale_num}}">已购 {{ item.has_sale_num }}</text>
47
+              <view class="stepper-container">
48
+                <stepper data-index="{{ index }}" bindstepperChanged="bindstepperChanged"></stepper>
49
+              </view>
50
+            </view>
51
+          </view>
52
+        </block>
53
+      </view>
54
+    </view>
55
+    <view class="consumer-container" wx:if="{{ consumers.length > 0 }}">
56
+      <swiper
57
+        class="swiper-container"
58
+        autoplay="{{ true }}"
59
+        interval="2000"
60
+        vertical="{{ true }}"
61
+        circular="{{ true }}"
62
+      >
63
+        <block wx:for="{{ consumers }}">
64
+          <swiper-item class="consumer-item">
65
+            <view class="consumer-info">
66
+              <image class="avatar" src="{{ item.userinfo.avatar }}"></image>
67
+              <text class="name">{{ item.userinfo.nickname }}</text>
68
+              <text class="time">{{ item.created_at }}</text>
69
+            </view>
70
+            <view class="consumer-commodity">
71
+              <text>{{ item.content }}</text>
72
+            </view>
73
+          </swiper-item>
74
+        </block>
75
+      </swiper>
76
+    </view>
77
+  </view>
78
+</scroll-view>
79
+
80
+<import src="/template/btn/btn.wxml"></import>
81
+<view class="btn-footer" wx:if="{{ total !== 0 }}">
82
+  <view class="btn-footer-container">
83
+    <view class="left">
84
+      <text class="money">¥{{ total }}</text>
85
+    </view>
86
+    <view class="right">
87
+      <template
88
+        is="iconBtn"
89
+        data="{{btnClass: 'button4', btnTxt: '购买', openType: 'getUserInfo', action: 'getUserInfo'}}"
90
+      ></template>
91
+    </view>
92
+  </view>
93
+</view>

+ 255 - 0
src/pages/index/index.wxss

@@ -0,0 +1,255 @@
1
+@import '/template/footer/footer.wxss';
2
+@import '/template/btn/btn.wxss';
3
+@import '/utils/wxParse/wxParse.wxss';
4
+page {
5
+  background-color: #efefef;
6
+}
7
+.wrap {
8
+  box-sizing: border-box;
9
+  display: flex;
10
+  flex-direction: column;
11
+  align-items: center;
12
+  height: 100%;
13
+  background-color: #efefef;
14
+}
15
+.header {
16
+  position: relative;
17
+  width: 100%;
18
+}
19
+.header .banner-img {
20
+  position: absolute;
21
+  width: 750rpx;
22
+  height: 400rpx;
23
+}
24
+.header .user-info {
25
+  position: relative;
26
+  box-sizing: border-box;
27
+  display: flex;
28
+  align-items: flex-end;
29
+  width: 750rpx;
30
+  height: 200rpx;
31
+  padding: 0 24rpx 24rpx;
32
+  margin-top: 200rpx;
33
+  background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.8));
34
+}
35
+.header .user-info .avatar {
36
+  width: 100rpx;
37
+  height: 100rpx;
38
+  border-radius: 6px;
39
+}
40
+.header .user-info .nickname {
41
+  margin: 0 0 12rpx 12rpx;
42
+  font-size: 14pt;
43
+  color: #fff;
44
+}
45
+.header .user-info .subscribe {
46
+  display: flex;
47
+  flex-grow: 2;
48
+  align-items: center;
49
+  justify-content: flex-end;
50
+  margin: 0 0 12rpx 12rpx;
51
+}
52
+.header .user-info .subscribe .num {
53
+  font-size: 13pt;
54
+  font-weight: 600;
55
+  color: #fff;
56
+}
57
+.header .user-info .subscribe .label {
58
+  margin-left: 4rpx;
59
+  font-size: 10pt;
60
+  color: #cbcbcb;
61
+}
62
+.header .desc {
63
+  margin-top: 36rpx;
64
+  margin-bottom: 0rpx;
65
+  margin-left: 24rpx;
66
+}
67
+.header .desc text {
68
+  color: #959595;
69
+}
70
+.main {
71
+  box-sizing: border-box;
72
+  width: 702rpx;
73
+  margin-top: 36rpx;
74
+}
75
+.commodity-container {
76
+  display: flex;
77
+  flex-direction: column;
78
+  align-items: center;
79
+  box-sizing: border-box;
80
+  width: 100%;
81
+  padding: 36rpx 24rpx;
82
+  background-color: #fff;
83
+}
84
+.commodity-container .commodity-title-container {
85
+  display: flex;
86
+  flex-direction: column;
87
+  width: 100%;
88
+}
89
+.commodity-container .commodity-title-container .title {
90
+  font-size: 14pt;
91
+  font-weight: 600;
92
+  color: #000;
93
+}
94
+.commodity-container .commodity-title-container .expired {
95
+  margin-top: 4rpx;
96
+  font-size: 10pt;
97
+  color: #959595;
98
+}
99
+.commodity-container .commodity-details {
100
+  width: 100%;
101
+  margin-top: 24rpx;
102
+}
103
+.commodity-container .commodity-list {
104
+  display: flex;
105
+  flex-direction: column;
106
+  align-items: center;
107
+  width: 100%;
108
+  margin-top: 36rpx;
109
+}
110
+.commodity-container .commodity-list .commodity-item {
111
+  display: flex;
112
+  align-items: center;
113
+  justify-content: space-between;
114
+  width: 100%;
115
+  height: 200rpx;
116
+  padding: 24rpx 0;
117
+  border-bottom: 1px #efefef solid;
118
+}
119
+.commodity-container .commodity-list .commodity-item .commodity-left {
120
+  display: flex;
121
+  align-items: center;
122
+  height: 200rpx;
123
+}
124
+.commodity-container .commodity-list .commodity-item .commodity-left .commodity-img {
125
+  width: 180rpx;
126
+  height: 180rpx;
127
+}
128
+.commodity-container .commodity-list .commodity-item .commodity-left .commodity-content {
129
+  display: flex;
130
+  flex-direction: column;
131
+  height: 100%;
132
+  margin-left: 24rpx;
133
+}
134
+.commodity-container .commodity-list .commodity-item .commodity-left .commodity-content .commodity-title {
135
+  font-size: 13pt;
136
+  color: #3e3e3e;
137
+}
138
+.commodity-container .commodity-list .commodity-item .commodity-left .commodity-content .commodity-desc {
139
+  margin-top: 4rpx;
140
+  font-size: 10pt;
141
+  color: #959595;
142
+}
143
+.commodity-container .commodity-list .commodity-item .commodity-price {
144
+  display: flex;
145
+  flex-grow: 2;
146
+  align-items: flex-end;
147
+}
148
+.commodity-container .commodity-list .commodity-item .commodity-price text {
149
+  font-size: 14pt;
150
+  color: #e64340;
151
+}
152
+.commodity-container .commodity-list .commodity-right {
153
+  display: flex;
154
+  flex-direction: column;
155
+  align-items: flex-end;
156
+  height: 100%;
157
+}
158
+.commodity-container .commodity-list .commodity-right .bought {
159
+  font-size: 10pt;
160
+  color: #e64340;
161
+  text-align: right;
162
+}
163
+.commodity-container .commodity-list .commodity-right .stepper-container {
164
+  margin-top: 24rpx;
165
+}
166
+.consumer-container {
167
+  padding: 24rpx;
168
+  background-color: #fff;
169
+}
170
+.consumer-container .consumer-title {
171
+  height: 80rpx;
172
+}
173
+.consumer-container .consumer-title text {
174
+  font-size: 14pt;
175
+  font-weight: 600;
176
+  color: #3e3e3e;
177
+}
178
+.consumer-container .swiper-container {
179
+  width: 100%;
180
+  height: 200rpx;
181
+}
182
+.consumer-container .consumer-item {
183
+  width: 654rpx;
184
+}
185
+.consumer-container .consumer-item .consumer-info {
186
+  display: flex;
187
+  align-items: center;
188
+}
189
+.consumer-container .consumer-item .consumer-info .avatar {
190
+  width: 56rpx;
191
+  height: 56rpx;
192
+  border-radius: 28rpx;
193
+}
194
+.consumer-container .consumer-item .consumer-info .name {
195
+  margin-left: 12rpx;
196
+  font-size: 11pt;
197
+  font-weight: 600;
198
+  color: #000;
199
+}
200
+.consumer-container .consumer-item .consumer-info .time {
201
+  margin-top: 4rpx;
202
+  margin-left: 24rpx;
203
+  font-size: 8pt;
204
+  color: #c8c8c8;
205
+}
206
+.consumer-container .consumer-item .consumer-commodity {
207
+  display: flex;
208
+  justify-content: flex-end;
209
+  width: 100%;
210
+  margin-top: 12rpx;
211
+}
212
+.consumer-container .consumer-item .consumer-commodity text {
213
+  width: 100%;
214
+  font-size: 11pt;
215
+  color: #3e3e3e;
216
+  text-align: right;
217
+}
218
+.btn-footer {
219
+  display: flex;
220
+  align-items: center;
221
+  position: fixed;
222
+  bottom: 0;
223
+  box-sizing: border-box;
224
+  justify-content: center;
225
+  width: 100%;
226
+  height: 160rpx;
227
+  padding: 24rpx;
228
+  background-color: #fff;
229
+  box-shadow: 0 -5px 9px 2px rgba(0, 0, 0, 0.1);
230
+}
231
+.btn-footer .btn-footer-container {
232
+  display: flex;
233
+  width: 100%;
234
+  height: 100rpx;
235
+  overflow: hidden;
236
+  border: 1px #09bb07 solid;
237
+  border-radius: 5px;
238
+}
239
+.btn-footer .btn-footer-container .left {
240
+  display: flex;
241
+  align-items: center;
242
+  justify-content: center;
243
+  width: 40%;
244
+  height: 100rpx;
245
+  background-color: #fff;
246
+}
247
+.btn-footer .btn-footer-container .left text {
248
+  font-size: 14pt;
249
+  font-weight: 700;
250
+  color: #e64340;
251
+}
252
+.btn-footer .btn-footer-container .right {
253
+  width: 100%;
254
+  height: 100%;
255
+}

+ 85 - 0
src/pages/order/order.js

@@ -0,0 +1,85 @@
1
+const app = getApp()
2
+
3
+Page({
4
+  /**
5
+   * Page initial data
6
+   */
7
+  data: {
8
+    hasAddress: false,
9
+    commodities: [],
10
+    address: {},
11
+    kol: {},
12
+    pack_id: '',
13
+    total: 0
14
+  },
15
+
16
+  onLoad() {
17
+    const eventChannel = this.getOpenerEventChannel()
18
+    const that = this
19
+    eventChannel.on('acceptDataFromOpenerPage', data => {
20
+      that.setData({
21
+        commodities: data.commodities,
22
+        total: data.total,
23
+        kol: data.kol,
24
+        pack_id: data.pack_id
25
+      })
26
+    })
27
+  },
28
+
29
+  navigateToAddress() {
30
+    const that = this
31
+    wx.chooseAddress({
32
+      success: res => {
33
+        res.detailInfo =
34
+          res.provinceName + res.cityName + res.countyName + res.detailInfo
35
+        that.setData({
36
+          address: res,
37
+          hasAddress: true
38
+        })
39
+      }
40
+    })
41
+  },
42
+
43
+  confirm() {
44
+    const { commodities } = this.data
45
+    // const body = commodities.reduce(
46
+    //   (accumulator, c) => `${accumulator}${c.title}x${c.num};`,
47
+    //   ''
48
+    // )
49
+    console.log(body)
50
+    app.network.post({
51
+      url: app.urls.ORDER_CREATE,
52
+      data: {
53
+        pack_id: this.data.pack_id,
54
+        kol_id: this.data.kol.kol_id,
55
+        total_fee: this.data.total * 100,
56
+        goods_info: JSON.stringify(this.data.commodities),
57
+        name: this.data.address.userName || '',
58
+        phone: this.data.address.telNumber || '',
59
+        address: this.data.address.detailInfo || '',
60
+        body: '尖货接龙'
61
+      },
62
+      success(res1) {
63
+        const { wxpay_params } = res1.data
64
+        wx.requestPayment({
65
+          timeStamp: wxpay_params.timeStamp,
66
+          nonceStr: wxpay_params.nonceStr,
67
+          package: wxpay_params.package,
68
+          signType: wxpay_params.signType,
69
+          paySign: wxpay_params.paySign,
70
+          success: res2 => {
71
+            wx.reLaunch({
72
+              url: '/pages/index/index',
73
+              success() {
74
+                wx.showToast({
75
+                  title: '购买成功',
76
+                  icon: 'success'
77
+                })
78
+              }
79
+            })
80
+          }
81
+        })
82
+      }
83
+    })
84
+  }
85
+})

+ 5 - 0
src/pages/order/order.json

@@ -0,0 +1,5 @@
1
+{
2
+  "usingComponents": {},
3
+  "disableScroll": true,
4
+  "navigationBarTitleText": "订单确认"
5
+}

+ 204 - 0
src/pages/order/order.less

@@ -0,0 +1,204 @@
1
+@import './src/style/less/variable.less';
2
+@import './src/style/less/layout.less';
3
+@import './src/style/less/shape.less';
4
+@import '/template/footer/footer.wxss';
5
+@import '/template/btn/btn.wxss';
6
+
7
+.order-view {
8
+  .flex-center(column);
9
+  .with-btnFooter();
10
+
11
+  width: 100%;
12
+  background-color: @color-bg;
13
+
14
+  text {
15
+    color: @color-black;
16
+  }
17
+
18
+  view {
19
+    color: @color-black;
20
+  }
21
+}
22
+
23
+.address {
24
+  .flex-center(row);
25
+
26
+  box-sizing: border-box;
27
+  width: @visual-width;
28
+  min-height: 80rpx;
29
+  padding: 0 @spacing-item;
30
+  margin: @spacing-item;
31
+  background-color: @color-white;
32
+  border-radius: @radius;
33
+
34
+  .item() {
35
+    .square(40rpx);
36
+
37
+    .icon {
38
+      .full-image();
39
+    }
40
+  }
41
+
42
+  .left {
43
+    .item();
44
+  }
45
+
46
+  .content {
47
+    .flex-center(column);
48
+
49
+    box-sizing: border-box;
50
+    width: @visual-width - 80rpx - @spacing-item * 2;
51
+    min-height: 80rpx;
52
+    padding: @spacing-inline @spacing-item;
53
+
54
+    view {
55
+      .flex-center(row);
56
+
57
+      width: 100%;
58
+      margin-top: @spacing-inline;
59
+    }
60
+
61
+    .top {
62
+      justify-content: flex-start;
63
+
64
+      .name {
65
+        font-size: @font-primary;
66
+      }
67
+
68
+      .phone {
69
+        margin-left: @spacing-item;
70
+        font-size: @font-quaternary;
71
+        color: @color-gray;
72
+      }
73
+    }
74
+
75
+    .bottom {
76
+      font-size: @font-tertiary;
77
+    }
78
+  }
79
+
80
+  .right {
81
+    .item();
82
+
83
+    .arrow {
84
+      .arrow(right; @color-dark-bg; @icon-small-size);
85
+    }
86
+  }
87
+
88
+  &-hover {
89
+    background-color: darken(@color-white, 15%);
90
+  }
91
+}
92
+
93
+.authorization {
94
+  justify-content: center;
95
+
96
+  text {
97
+    margin-left: @spacing-short-text;
98
+    font-size: @font-secondary;
99
+    color: @color-brand;
100
+  }
101
+
102
+  .plus {
103
+    .plus(4rpx; @color-brand);
104
+    .square(@icon-little-small-size);
105
+  }
106
+}
107
+
108
+.goods {
109
+  .flex-center(row);
110
+
111
+  width: @visual-width;
112
+  height: 200rpx;
113
+  margin-top: @spacing-inline;
114
+  background-color: @color-white;
115
+  border-radius: @radius;
116
+
117
+  .left {
118
+    .flex-center(row);
119
+
120
+    justify-content: center;
121
+    width: 200rpx;
122
+    height: 100%;
123
+
124
+    image {
125
+      .square(180rpx);
126
+    }
127
+  }
128
+
129
+  .right {
130
+    box-sizing: border-box;
131
+    display: flex;
132
+    flex-direction: column;
133
+    justify-content: space-between;
134
+    width: @visual-width - 200rpx;
135
+    height: 100%;
136
+    padding: @spacing-item;
137
+
138
+    .top {
139
+      width: 100%;
140
+
141
+      .title {
142
+        font-size: @font-primary;
143
+      }
144
+    }
145
+
146
+    .bottom {
147
+      .flex-center(row);
148
+
149
+      justify-content: space-between;
150
+      width: 100%;
151
+
152
+      .num {
153
+        font-size: @font-primary;
154
+      }
155
+    }
156
+  }
157
+}
158
+
159
+.btn-footer {
160
+  .flex-center(row);
161
+
162
+  position: fixed;
163
+  bottom: 0;
164
+  box-sizing: border-box;
165
+  justify-content: center;
166
+  width: 100%;
167
+  height: @btnFooter-height;
168
+  padding: @spacing-item;
169
+  background-color: @color-white;
170
+  box-shadow: 0 -5px 9px 2px fade(@color-black, 10%);
171
+
172
+  .btn-footer-container {
173
+    display: flex;
174
+    width: 100%;
175
+    height: 100rpx;
176
+    overflow: hidden;
177
+    border: 1px #09bb07 solid;
178
+    border-radius: @radius;
179
+
180
+    &-disabled {
181
+      border: 1px @color-light-gray solid !important;
182
+    }
183
+
184
+    .left {
185
+      .flex-center(row);
186
+
187
+      justify-content: center;
188
+      width: 40%;
189
+      height: 100rpx;
190
+      background-color: @color-white;
191
+
192
+      text {
193
+        font-size: @font-primary;
194
+        font-weight: 700;
195
+        color: #e64340;
196
+      }
197
+    }
198
+
199
+    .right {
200
+      width: 100%;
201
+      height: 100%;
202
+    }
203
+  }
204
+}

+ 64 - 0
src/pages/order/order.wxml

@@ -0,0 +1,64 @@
1
+<view class="order-view">
2
+  <view
3
+    class="address"
4
+    hover-class="address-hover"
5
+    bindtap="navigateToAddress"
6
+    wx:if="{{ hasAddress }}"
7
+  >
8
+    <view class="left">
9
+      <image class="icon" src="/resources/common/location.png"></image>
10
+    </view>
11
+    <view class="content">
12
+      <view class="top">
13
+        <text class="name">{{ address.userName }}</text>
14
+        <text class="phone">{{ address.telNumber }}</text>
15
+      </view>
16
+      <view class="bottom">{{ address.detailInfo }}</view>
17
+    </view>
18
+    <view class="right">
19
+      <view class="icon arrow"></view>
20
+    </view>
21
+  </view>
22
+  <view
23
+    class="address authorization"
24
+    hover-class="address-hover"
25
+    bindtap="navigateToAddress"
26
+    wx:elif="{{ !hasAddress }}"
27
+  >
28
+    <view class="plus"></view>
29
+    <text>新建地址</text>
30
+  </view>
31
+  <view class="goods-container">
32
+    <block wx:for="{{ commodities }}">
33
+      <view class="goods">
34
+        <view class="left">
35
+          <image class="" src="{{ item.image_url }}" mode="aspectFit"></image>
36
+        </view>
37
+        <view class="right">
38
+          <view class="top">
39
+            <text class="title">{{ item.title }}</text>
40
+          </view>
41
+          <view class="bottom">
42
+            <text class="integral" wx:if="{{ item.price > 0 }}">¥{{ item.price }}</text>
43
+            <text class="num">x{{ item.num }}</text>
44
+          </view>
45
+        </view>
46
+      </view>
47
+    </block>
48
+  </view>
49
+</view>
50
+
51
+<import src="/template/btn/btn.wxml"></import>
52
+<view class="btn-footer">
53
+  <view class="btn-footer-container {{hasAddress ? '' : 'btn-footer-container-disabled'}}">
54
+    <view class="left" wx:if="{{ total > 0 }}">
55
+      <text class="money">¥{{ total }}</text>
56
+    </view>
57
+    <view class="right">
58
+      <template
59
+        is="iconBtn"
60
+        data="{{btnClass: 'button4', btnTxt: '立即支付', disabled: !hasAddress, action: 'confirm'}}"
61
+      ></template>
62
+    </view>
63
+  </view>
64
+</view>

+ 201 - 0
src/pages/order/order.wxss

@@ -0,0 +1,201 @@
1
+@import '/template/footer/footer.wxss';
2
+@import '/template/btn/btn.wxss';
3
+.order-view {
4
+  display: flex;
5
+  flex-direction: column;
6
+  align-items: center;
7
+  height: calc(100% - 160rpx);
8
+  width: 100%;
9
+  background-color: #efefef;
10
+}
11
+.order-view text {
12
+  color: #000;
13
+}
14
+.order-view view {
15
+  color: #000;
16
+}
17
+.address {
18
+  display: flex;
19
+  align-items: center;
20
+  box-sizing: border-box;
21
+  width: 702rpx;
22
+  min-height: 80rpx;
23
+  padding: 0 24rpx;
24
+  margin: 24rpx;
25
+  background-color: #fff;
26
+  border-radius: 5px;
27
+}
28
+.address .left {
29
+  width: 40rpx;
30
+  height: 40rpx;
31
+}
32
+.address .left .icon {
33
+  width: 100%;
34
+  height: 100%;
35
+}
36
+.address .content {
37
+  display: flex;
38
+  flex-direction: column;
39
+  align-items: center;
40
+  box-sizing: border-box;
41
+  width: 574rpx;
42
+  min-height: 80rpx;
43
+  padding: 12rpx 24rpx;
44
+}
45
+.address .content view {
46
+  display: flex;
47
+  align-items: center;
48
+  width: 100%;
49
+  margin-top: 12rpx;
50
+}
51
+.address .content .top {
52
+  justify-content: flex-start;
53
+}
54
+.address .content .top .name {
55
+  font-size: 14pt;
56
+}
57
+.address .content .top .phone {
58
+  margin-left: 24rpx;
59
+  font-size: 10pt;
60
+  color: #3e3e3e;
61
+}
62
+.address .content .bottom {
63
+  font-size: 11pt;
64
+}
65
+.address .right {
66
+  width: 40rpx;
67
+  height: 40rpx;
68
+}
69
+.address .right .icon {
70
+  width: 100%;
71
+  height: 100%;
72
+}
73
+.address .right .arrow {
74
+  width: 32rpx;
75
+  height: 32rpx;
76
+  box-sizing: border-box;
77
+  border-top: 2px solid #3e3e3e;
78
+  border-right: 2px solid #3e3e3e;
79
+  transform: rotate(45deg);
80
+}
81
+.address-hover {
82
+  background-color: #d9d9d9;
83
+}
84
+.authorization {
85
+  justify-content: center;
86
+}
87
+.authorization text {
88
+  margin-left: 4rpx;
89
+  font-size: 13pt;
90
+  color: #0967b2;
91
+}
92
+.authorization .plus {
93
+  position: relative;
94
+  width: 24rpx;
95
+  height: 24rpx;
96
+}
97
+.authorization .plus::before {
98
+  position: absolute;
99
+  top: calc(50% - 4rpx / 2);
100
+  left: 0;
101
+  width: 100%;
102
+  height: 4rpx;
103
+  content: '';
104
+  background-color: #0967b2;
105
+  border-radius: 4rpx;
106
+}
107
+.authorization .plus::after {
108
+  position: absolute;
109
+  top: 0;
110
+  left: calc(50% - 4rpx / 2);
111
+  width: 4rpx;
112
+  height: 100%;
113
+  content: '';
114
+  background-color: #0967b2;
115
+  border-radius: 4rpx;
116
+}
117
+.goods {
118
+  display: flex;
119
+  align-items: center;
120
+  width: 702rpx;
121
+  height: 200rpx;
122
+  margin-top: 12rpx;
123
+  background-color: #fff;
124
+  border-radius: 5px;
125
+}
126
+.goods .left {
127
+  display: flex;
128
+  align-items: center;
129
+  justify-content: center;
130
+  width: 200rpx;
131
+  height: 100%;
132
+}
133
+.goods .left image {
134
+  width: 180rpx;
135
+  height: 180rpx;
136
+}
137
+.goods .right {
138
+  box-sizing: border-box;
139
+  display: flex;
140
+  flex-direction: column;
141
+  justify-content: space-between;
142
+  width: 502rpx;
143
+  height: 100%;
144
+  padding: 24rpx;
145
+}
146
+.goods .right .top {
147
+  width: 100%;
148
+}
149
+.goods .right .top .title {
150
+  font-size: 14pt;
151
+}
152
+.goods .right .bottom {
153
+  display: flex;
154
+  align-items: center;
155
+  justify-content: space-between;
156
+  width: 100%;
157
+}
158
+.goods .right .bottom .num {
159
+  font-size: 14pt;
160
+}
161
+.btn-footer {
162
+  display: flex;
163
+  align-items: center;
164
+  position: fixed;
165
+  bottom: 0;
166
+  box-sizing: border-box;
167
+  justify-content: center;
168
+  width: 100%;
169
+  height: 160rpx;
170
+  padding: 24rpx;
171
+  background-color: #fff;
172
+  box-shadow: 0 -5px 9px 2px rgba(0, 0, 0, 0.1);
173
+}
174
+.btn-footer .btn-footer-container {
175
+  display: flex;
176
+  width: 100%;
177
+  height: 100rpx;
178
+  overflow: hidden;
179
+  border: 1px #09bb07 solid;
180
+  border-radius: 5px;
181
+}
182
+.btn-footer .btn-footer-container-disabled {
183
+  border: 1px #959595 solid !important;
184
+}
185
+.btn-footer .btn-footer-container .left {
186
+  display: flex;
187
+  align-items: center;
188
+  justify-content: center;
189
+  width: 40%;
190
+  height: 100rpx;
191
+  background-color: #fff;
192
+}
193
+.btn-footer .btn-footer-container .left text {
194
+  font-size: 14pt;
195
+  font-weight: 700;
196
+  color: #e64340;
197
+}
198
+.btn-footer .btn-footer-container .right {
199
+  width: 100%;
200
+  height: 100%;
201
+}

+ 126 - 0
src/project.config.json

@@ -0,0 +1,126 @@
1
+{
2
+	"description": "项目配置文件。",
3
+	"setting": {
4
+		"urlCheck": true,
5
+		"es6": true,
6
+		"enhance": true,
7
+		"postcss": true,
8
+		"minified": false,
9
+		"newFeature": true,
10
+		"coverView": true,
11
+		"nodeModules": true,
12
+		"autoAudits": false,
13
+		"showShadowRootInWxmlPanel": true,
14
+		"scopeDataCheck": false,
15
+		"checkInvalidKey": true,
16
+		"checkSiteMap": false,
17
+		"uploadWithSourceMap": true,
18
+		"babelSetting": {
19
+			"ignore": [],
20
+			"disablePlugins": [],
21
+			"outputPath": ""
22
+		}
23
+	},
24
+	"compileType": "miniprogram",
25
+	"libVersion": "2.10.1",
26
+	"appid": "wx82344118083f47c8",
27
+	"projectname": "%E7%BB%9F%E8%A7%88",
28
+	"scripts": {
29
+		"beforeCompile": "",
30
+		"beforePreview": "",
31
+		"beforeUpload": ""
32
+	},
33
+	"simulatorType": "wechat",
34
+	"simulatorPluginLibVersion": {},
35
+	"condition": {
36
+		"search": {
37
+			"current": -1,
38
+			"list": []
39
+		},
40
+		"conversation": {
41
+			"current": -1,
42
+			"list": []
43
+		},
44
+		"plugin": {
45
+			"current": -1,
46
+			"list": []
47
+		},
48
+		"game": {
49
+			"current": -1,
50
+			"list": []
51
+		},
52
+		"gamePlugin": {
53
+			"current": -1,
54
+			"list": []
55
+		},
56
+		"miniprogram": {
57
+			"current": -1,
58
+			"list": [
59
+				{
60
+					"id": 0,
61
+					"name": "pages/cameraCompatible/cameraCompatible",
62
+					"pathName": "pages/cameraCompatible/cameraCompatible",
63
+					"query": "activity_id=nBJ56hN9ik94js4duSh34V&isSignIn=1",
64
+					"scene": 1047
65
+				},
66
+				{
67
+					"id": 1,
68
+					"name": "pages/index/index",
69
+					"pathName": "pages/index/index",
70
+					"query": "q=http://kodo.tamron.cn/v/0+21+KUjsUKURRRRRRUK6KxxK",
71
+					"scene": 1011
72
+				},
73
+				{
74
+					"id": 5,
75
+					"name": "公众号菜单栏",
76
+					"pathName": "pages/index/index",
77
+					"query": "page=memberCard",
78
+					"scene": 1035,
79
+					"referrerInfo": {}
80
+				},
81
+				{
82
+					"id": 3,
83
+					"name": "一物一码",
84
+					"pathName": "pages/index/index",
85
+					"query": "code_ticket=P.URL.CN/0MX5D3AIAJZ2:PW3RPK",
86
+					"scene": 1124
87
+				},
88
+				{
89
+					"id": 4,
90
+					"name": "开卡",
91
+					"pathName": "pages/index/index",
92
+					"query": "openCard=1",
93
+					"scene": 1074
94
+				},
95
+				{
96
+					"id": 5,
97
+					"name": "会员活动详情页",
98
+					"pathName": "pages/member/activity/activity",
99
+					"query": "activity_id=KNFYGrDrBGe4MzVw9wNpxL",
100
+					"scene": null
101
+				},
102
+				{
103
+					"id": 6,
104
+					"name": "测试",
105
+					"pathName": "pages/index/index",
106
+					"query": "q= http://kodo.tamron.cn/v/0+20+KUscUKUwwKzzxUxRRKRs33RCV5ODKVQZ7CHBK4F75Q7PHSJWLFEO1RJ9DQHZYEIQX1Q2DH30",
107
+					"scene": 1011
108
+				},
109
+				{
110
+					"id": -1,
111
+					"name": "template/resultView/resultView",
112
+					"pathName": "template/resultView/resultView",
113
+					"query": "q= http%3A%2F%2Fkodo.tamron.cn%2Fv%2F0%2B21%2BKUKy6UKURw6jjyUK6KxKyTKTA9LLUFW13BDY2HD3B1ODC74EIPI14UTE0LHFDDV3G7QY3QTX",
114
+					"scene": null
115
+				},
116
+				{
117
+					"id": -1,
118
+					"name": "pages/commodity/commodity",
119
+					"pathName": "pages/commodity/commodity",
120
+					"query": "",
121
+					"scene": null
122
+				}
123
+			]
124
+		}
125
+	}
126
+}

BIN
src/resources/.DS_Store


BIN
src/resources/common/.DS_Store


BIN
src/resources/common/location.png


+ 9 - 0
src/sitemap.json

@@ -0,0 +1,9 @@
1
+{
2
+  "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
3
+  "rules": [
4
+    {
5
+      "action": "allow",
6
+      "page": "*"
7
+    }
8
+  ]
9
+}

+ 96 - 0
src/style/button.less

@@ -0,0 +1,96 @@
1
+@import './less/variable.less';
2
+
3
+.button1 {
4
+  padding: 0;
5
+  background-color: @color-brand;
6
+  border-radius: @radius;
7
+
8
+  &-hover {
9
+    color: shade(@color-white, 10%) !important;
10
+    background-color: darken(@color-brand, 5%) !important;
11
+
12
+    text {
13
+      color: shade(@color-white, 10%) !important;
14
+    }
15
+  }
16
+
17
+  &[disabled] {
18
+    color: fade(@color-white, 60%) !important;
19
+    background-color: fade(@color-brand, 60%) !important;
20
+
21
+    text {
22
+      color: fade(@color-white, 60%) !important;
23
+    }
24
+  }
25
+}
26
+
27
+.button3 {
28
+  padding: 0;
29
+  background-color: @color-other1;
30
+
31
+  &-hover {
32
+    color: shade(@color-white, 10%) !important;
33
+    background-color: darken(@color-other1, 8%) !important;
34
+
35
+    text {
36
+      color: shade(@color-white, 10%) !important;
37
+    }
38
+  }
39
+
40
+  &[disabled] {
41
+    color: fade(@color-white, 60%) !important;
42
+    background-color: fade(@color-other1, 60%) !important;
43
+
44
+    text {
45
+      color: fade(@color-white, 60%) !important;
46
+    }
47
+  }
48
+}
49
+
50
+.button2 {
51
+  padding: 0;
52
+  background-color: @color-brand;
53
+  border-radius: @radius;
54
+
55
+  text {
56
+    color: @color-white;
57
+  }
58
+
59
+  &-hover {
60
+    color: shade(@color-white, 10%) !important;
61
+    background-color: darken(@color-brand, 5%) !important;
62
+
63
+    text {
64
+      color: shade(@color-white, 10%) !important;
65
+    }
66
+  }
67
+
68
+  &[disabled] {
69
+    color: darken(@color-white, 10%) !important;
70
+    background-color: @color-light-gray !important;
71
+  }
72
+}
73
+
74
+.button4 {
75
+  height: 100%;
76
+  padding: 0;
77
+  background-color: #09bb07;
78
+
79
+  text {
80
+    color: @color-white;
81
+  }
82
+
83
+  &-hover {
84
+    color: shade(@color-white, 10%) !important;
85
+    background-color: darken(#09bb07, 5%) !important;
86
+
87
+    text {
88
+      color: shade(@color-white, 10%) !important;
89
+    }
90
+  }
91
+
92
+  &[disabled] {
93
+    color: darken(@color-white, 10%) !important;
94
+    background-color: @color-light-gray !important;
95
+  }
96
+}

+ 75 - 0
src/style/button.wxss

@@ -0,0 +1,75 @@
1
+.button1 {
2
+  padding: 0;
3
+  background-color: #0967b2;
4
+  border-radius: 5px;
5
+}
6
+.button1-hover {
7
+  color: #e6e6e6 !important;
8
+  background-color: #08599a !important;
9
+}
10
+.button1-hover text {
11
+  color: #e6e6e6 !important;
12
+}
13
+.button1[disabled] {
14
+  color: rgba(255, 255, 255, 0.6) !important;
15
+  background-color: rgba(9, 103, 178, 0.6) !important;
16
+}
17
+.button1[disabled] text {
18
+  color: rgba(255, 255, 255, 0.6) !important;
19
+}
20
+.button3 {
21
+  padding: 0;
22
+  background-color: #d2b167;
23
+}
24
+.button3-hover {
25
+  color: #e6e6e6 !important;
26
+  background-color: #c9a148 !important;
27
+}
28
+.button3-hover text {
29
+  color: #e6e6e6 !important;
30
+}
31
+.button3[disabled] {
32
+  color: rgba(255, 255, 255, 0.6) !important;
33
+  background-color: rgba(210, 177, 103, 0.6) !important;
34
+}
35
+.button3[disabled] text {
36
+  color: rgba(255, 255, 255, 0.6) !important;
37
+}
38
+.button2 {
39
+  padding: 0;
40
+  background-color: #0967b2;
41
+  border-radius: 5px;
42
+}
43
+.button2 text {
44
+  color: #fff;
45
+}
46
+.button2-hover {
47
+  color: #e6e6e6 !important;
48
+  background-color: #08599a !important;
49
+}
50
+.button2-hover text {
51
+  color: #e6e6e6 !important;
52
+}
53
+.button2[disabled] {
54
+  color: #e6e6e6 !important;
55
+  background-color: #959595 !important;
56
+}
57
+.button4 {
58
+  height: 100%;
59
+  padding: 0;
60
+  background-color: #09bb07;
61
+}
62
+.button4 text {
63
+  color: #fff;
64
+}
65
+.button4-hover {
66
+  color: #e6e6e6 !important;
67
+  background-color: #08a206 !important;
68
+}
69
+.button4-hover text {
70
+  color: #e6e6e6 !important;
71
+}
72
+.button4[disabled] {
73
+  color: #e6e6e6 !important;
74
+  background-color: #959595 !important;
75
+}

+ 85 - 0
src/style/less/layout.less

@@ -0,0 +1,85 @@
1
+@import './variable.less';
2
+
3
+// flex
4
+.flex-center(column) {
5
+  display: flex;
6
+  flex-direction: column;
7
+  align-items: center;
8
+}
9
+
10
+.flex-center(row) {
11
+  display: flex;
12
+  align-items: center;
13
+}
14
+
15
+.banner(@height: 400rpx) {
16
+  width: 100%;
17
+  height: @height;
18
+
19
+  image {
20
+    .full-image();
21
+  }
22
+}
23
+
24
+// list
25
+.table-view(@width: 100%; @spacing: 12rpx) {
26
+  .flex-center(column);
27
+
28
+  width: 100%;
29
+  padding-bottom: @spacing * 2;
30
+
31
+  &-item {
32
+    width: @width;
33
+    margin-top: @spacing;
34
+  }
35
+}
36
+
37
+.collection-view(@width: 650rpx; @row: 4; @spacing-top: 24rpx; @spacing-left: 24rpx) {
38
+  .flex-center(row);
39
+
40
+  flex-wrap: wrap;
41
+  width: @width;
42
+
43
+  &-item {
44
+    width: (@width - @spacing-left * (@row - 1)) / @row;
45
+    margin-top: @spacing-top;
46
+    margin-right: @spacing-left;
47
+
48
+    &:nth-child(@{row}n) {
49
+      margin-right: 0;
50
+    }
51
+  }
52
+}
53
+
54
+.footer() {
55
+  position: fixed;
56
+  bottom: 0;
57
+  display: flex;
58
+  width: 100%;
59
+  height: calc(80rpx + env(safe-area-inset-bottom));
60
+}
61
+
62
+.full-image() {
63
+  width: 100%;
64
+  height: 100%;
65
+}
66
+
67
+.square(@len) {
68
+  width: @len;
69
+  height: @len;
70
+}
71
+
72
+.circle(@radius) {
73
+  width: @radius;
74
+  height: @radius;
75
+  overflow: hidden;
76
+  border-radius: @radius / 2;
77
+}
78
+
79
+.with-btnFooter() {
80
+  height: calc(100% - @btnFooter-height);
81
+}
82
+
83
+.with-footer() {
84
+  height: calc(100% - @descFooter-height);
85
+}

+ 70 - 0
src/style/less/shape.less

@@ -0,0 +1,70 @@
1
+@import './variable.less';
2
+
3
+.arrow(right; @color; @size) {
4
+  .square(@size);
5
+
6
+  box-sizing: border-box;
7
+  border-top: 2px solid @color;
8
+  border-right: 2px solid @color;
9
+  transform: rotate(45deg);
10
+}
11
+
12
+.triangle-center(bottom; @color; @len: 25rpx) {
13
+  border-right: @len solid transparent;
14
+  border-bottom: @len solid @color;
15
+  border-left: @len solid transparent;
16
+}
17
+
18
+.triangle-corner(bottomRight; @color; @len) {
19
+  border-bottom: @len solid @color;
20
+  border-left: @len solid transparent;
21
+}
22
+
23
+.cross(@len; @color) {
24
+  display: inline-block;
25
+  width: @len;
26
+  height: @len / 4;
27
+  overflow: visible;
28
+  font-size: 0;
29
+  line-height: 0;
30
+  vertical-align: middle;
31
+  background: @color;
32
+  border-radius: @len / 4;
33
+  transform: rotate(45deg);
34
+
35
+  &::after {
36
+    display: block;
37
+    width: @len;
38
+    height: @len / 4;
39
+    content: '/';
40
+    background: @color;
41
+    border-radius: @len / 4;
42
+    transform: rotate(-90deg);
43
+  }
44
+}
45
+
46
+.plus(@borderWidth; @color) {
47
+  position: relative;
48
+
49
+  &::before {
50
+    position: absolute;
51
+    top: calc(50% - @borderWidth / 2);
52
+    left: 0;
53
+    width: 100%;
54
+    height: @borderWidth;
55
+    content: '';
56
+    background-color: @color-brand;
57
+    border-radius: @borderWidth;
58
+  }
59
+
60
+  &::after {
61
+    position: absolute;
62
+    top: 0;
63
+    left: calc(50% - @borderWidth / 2);
64
+    width: @borderWidth;
65
+    height: 100%;
66
+    content: '';
67
+    background-color: @color-brand;
68
+    border-radius: @borderWidth;
69
+  }
70
+}

+ 48 - 0
src/style/less/variable.less

@@ -0,0 +1,48 @@
1
+//color
2
+@color-bg: #efefef;
3
+@color-dark-bg: #3e3e3e;
4
+@color-black: #000;
5
+@color-white: #fff;
6
+@color-brand: #0967b2;
7
+@color-other1: #d2b167;
8
+@color-gray: #3e3e3e;
9
+@color-light-gray: #959595;
10
+
11
+//font
12
+@font-h1: 40pt;
13
+@font-h2: 20pt;
14
+@font-h3: 18pt;
15
+@font-title: 17pt;
16
+@font-primary: 14pt;
17
+@font-secondary: 13pt;
18
+@font-tertiary: 11pt;
19
+@font-quaternary: 10pt;
20
+@font-label: 8pt;
21
+
22
+// spacing
23
+@spacing-view: 36rpx;
24
+@spacing-item: 24rpx;
25
+@spacing-inline: 12rpx;
26
+
27
+@spacing-text: 12rpx;
28
+@spacing-short-text: 4rpx;
29
+
30
+@padding-quaternary: 12rpx;
31
+
32
+//icon size
33
+@icon-min-size: 16rpx;
34
+@icon-little-small-size: 24rpx;
35
+@icon-small-size: 32rpx;
36
+@icon-middle-size: 48rpx;
37
+@icon-big-size: 60rpx;
38
+@icon-large-size: 72rpx;
39
+
40
+//height
41
+@btnFooter-height: 160rpx;
42
+@descFooter-height: 100rpx;
43
+@btnFooterWithProtocol-height: 200rpx;
44
+
45
+@radius: 5px;
46
+@radius-big: 10px;
47
+
48
+@visual-width: 750rpx - @spacing-item * 2;

+ 19 - 0
src/template/btn/btn.less

@@ -0,0 +1,19 @@
1
+@import './src/style/less/variable.less';
2
+@import './src/style/less/layout.less';
3
+
4
+.icon-btn {
5
+  .flex-center(row);
6
+
7
+  justify-content: center;
8
+  padding: 0;
9
+
10
+  text {
11
+    font-size: @font-primary;
12
+  }
13
+
14
+  image {
15
+    width: @icon-middle-size;
16
+    height: @icon-middle-size;
17
+    margin-left: @spacing-inline;
18
+  }
19
+}

+ 17 - 0
src/template/btn/btn.wxml

@@ -0,0 +1,17 @@
1
+<template name="iconBtn">
2
+  <button
3
+    class="icon-btn {{ btnClass }}"
4
+    hover-class="{{ btnClass }}-hover"
5
+    disabled="{{ disabled || false }}"
6
+    open-type="{{ openType }}"
7
+    bindtap="{{ (openType == '' || openType == undefined) ? action : '' }}"
8
+    bindgetuserinfo="{{ action }}"
9
+    lang="zh_CN"
10
+  >
11
+    <text>{{ btnTxt }}</text>
12
+    <image
13
+      src="{{ btnIcon || '/resources/common/left_arrow1.png' }}"
14
+      hidden="{{ disabled }}"
15
+    ></image>
16
+  </button>
17
+</template>

+ 14 - 0
src/template/btn/btn.wxss

@@ -0,0 +1,14 @@
1
+.icon-btn {
2
+  display: flex;
3
+  align-items: center;
4
+  justify-content: center;
5
+  padding: 0;
6
+}
7
+.icon-btn text {
8
+  font-size: 14pt;
9
+}
10
+.icon-btn image {
11
+  width: 48rpx;
12
+  height: 48rpx;
13
+  margin-left: 12rpx;
14
+}

+ 81 - 0
src/template/footer/footer.less

@@ -0,0 +1,81 @@
1
+@import './src/style/less/variable.less';
2
+@import './src/style/less/layout.less';
3
+@import '/template/btn/btn.wxss';
4
+
5
+.member-footer {
6
+  position: fixed;
7
+  bottom: 0;
8
+  z-index: 999;
9
+  display: flex;
10
+  width: 100%;
11
+  height: @descFooter-height;
12
+  background-color: #c8c8c8;
13
+
14
+  .left {
15
+    .flex-center(row);
16
+
17
+    justify-content: center;
18
+    width: 40%;
19
+    height: 100%;
20
+
21
+    image {
22
+      width: 80%;
23
+    }
24
+  }
25
+
26
+  .right {
27
+    .flex-center(row);
28
+
29
+    width: 70%;
30
+    height: 100%;
31
+
32
+    text {
33
+      margin-left: @spacing-item;
34
+      font-size: @font-label;
35
+      color: @color-gray;
36
+    }
37
+  }
38
+}
39
+
40
+.btn-footer() {
41
+  .flex-center(row);
42
+
43
+  position: fixed;
44
+  bottom: 0;
45
+  justify-content: center;
46
+  width: 100%;
47
+  height: @btnFooter-height;
48
+  background-color: @color-white;
49
+  box-shadow: 0 -5px 9px 2px fade(@color-black, 10%);
50
+
51
+  button {
52
+    height: 100rpx;
53
+  }
54
+}
55
+
56
+.btn-footer1 {
57
+  .btn-footer();
58
+
59
+  button {
60
+    width: @visual-width;
61
+  }
62
+}
63
+
64
+.btn-footer2 {
65
+  .btn-footer();
66
+
67
+  button {
68
+    width: 100%;
69
+    border-radius: 0 !important;
70
+  }
71
+
72
+  .left {
73
+    width: 250rpx;
74
+    border-radius: @radius 0 0 @radius;
75
+  }
76
+
77
+  .right {
78
+    width: 452rpx;
79
+    border-radius: 0 @radius @radius 0;
80
+  }
81
+}

+ 37 - 0
src/template/footer/footer.wxml

@@ -0,0 +1,37 @@
1
+<template name="memberFooter">
2
+  <view class="member-footer">
3
+    <view class="left">
4
+      <image mode="widthFix" src="/resources/member/lrclogo1.png"></image>
5
+    </view>
6
+
7
+    <view class="right">
8
+      <wxs src="./../../wxs/stringFilter.wxs" module="tools"></wxs>
9
+      <text>{{ tools.filter(text) }}</text>
10
+    </view>
11
+  </view>
12
+</template>
13
+
14
+<import src="/template/btn/btn.wxml"></import>
15
+<template name="btnFooter">
16
+  <view class="btn-footer1">
17
+    <template is="iconBtn" data="{{btnClass, btnTxt, disabled, action, openType}}"></template>
18
+  </view>
19
+</template>
20
+
21
+<template name="multiBtnFooter">
22
+  <view class="btn-footer2">
23
+    <view class="left">
24
+      <template
25
+        is="iconBtn"
26
+        data="{{btnClass: btnClass1, btnTxt: btnTxt1, disabled: disabled1, action: action1, btnIcon: btnIcon1, openType: openType1}}"
27
+      ></template>
28
+    </view>
29
+    <view class="right">
30
+      <template
31
+        is="iconBtn"
32
+        data="{{btnClass: btnClass2, btnTxt: btnTxt2, disabled: disabled2, action: action2, btnIcon: btnIcon2, openType: openType2}}"
33
+      ></template>
34
+    </view>
35
+  </view>
36
+</template>
37
+

+ 74 - 0
src/template/footer/footer.wxss

@@ -0,0 +1,74 @@
1
+@import '/template/btn/btn.wxss';
2
+.member-footer {
3
+  position: fixed;
4
+  bottom: 0;
5
+  z-index: 999;
6
+  display: flex;
7
+  width: 100%;
8
+  height: 100rpx;
9
+  background-color: #c8c8c8;
10
+}
11
+.member-footer .left {
12
+  display: flex;
13
+  align-items: center;
14
+  justify-content: center;
15
+  width: 40%;
16
+  height: 100%;
17
+}
18
+.member-footer .left image {
19
+  width: 80%;
20
+}
21
+.member-footer .right {
22
+  display: flex;
23
+  align-items: center;
24
+  width: 70%;
25
+  height: 100%;
26
+}
27
+.member-footer .right text {
28
+  margin-left: 24rpx;
29
+  font-size: 8pt;
30
+  color: #3e3e3e;
31
+}
32
+.btn-footer1 {
33
+  display: flex;
34
+  align-items: center;
35
+  position: fixed;
36
+  bottom: 0;
37
+  justify-content: center;
38
+  width: 100%;
39
+  height: 160rpx;
40
+  background-color: #fff;
41
+  box-shadow: 0 -5px 9px 2px rgba(0, 0, 0, 0.1);
42
+}
43
+.btn-footer1 button {
44
+  height: 100rpx;
45
+}
46
+.btn-footer1 button {
47
+  width: 702rpx;
48
+}
49
+.btn-footer2 {
50
+  display: flex;
51
+  align-items: center;
52
+  position: fixed;
53
+  bottom: 0;
54
+  justify-content: center;
55
+  width: 100%;
56
+  height: 160rpx;
57
+  background-color: #fff;
58
+  box-shadow: 0 -5px 9px 2px rgba(0, 0, 0, 0.1);
59
+}
60
+.btn-footer2 button {
61
+  height: 100rpx;
62
+}
63
+.btn-footer2 button {
64
+  width: 100%;
65
+  border-radius: 0 !important;
66
+}
67
+.btn-footer2 .left {
68
+  width: 250rpx;
69
+  border-radius: 5px 0 0 5px;
70
+}
71
+.btn-footer2 .right {
72
+  width: 452rpx;
73
+  border-radius: 0 5px 5px 0;
74
+}

+ 65 - 0
src/template/resultView/resultView.js

@@ -0,0 +1,65 @@
1
+Page({
2
+  data: {
3
+    title: '兑换成功',
4
+    desc: '',
5
+    isVirtual: false,
6
+    desc1: '在个人中心->专享劵,查看已兑换的劵。',
7
+    desc2:
8
+      '请留意微信服务通知内 小程序TAMRON的订阅消息,查收实物商品的邮寄情况。',
9
+    desc3:
10
+      '专属礼品已兑换,请留意微信服务通知内 小程序 TARMON 的订阅消息,查收专属礼品的邮寄情况。',
11
+    action: 'navigateBackToMall',
12
+    btnTitle: '返回',
13
+    isHightlight: false
14
+  },
15
+
16
+  onLoad(options) {
17
+    if (options.goods) {
18
+      const goods = JSON.parse(options.goods)
19
+      let desc = this.data.desc1
20
+      if (goods.only_for_member) {
21
+        desc = this.data.desc3
22
+      } else if (goods.good_type === 0) {
23
+        desc = this.data.desc2
24
+      }
25
+      this.setData({
26
+        isVirtual: goods.good_type === 1,
27
+        desc
28
+      })
29
+    } else {
30
+      const eventChannel = this.getOpenerEventChannel()
31
+      const that = this
32
+      eventChannel.on('acceptDataFromOpenerPage', data => {
33
+        that.setData({
34
+          title: data.title,
35
+          desc: data.desc,
36
+          action: 'navigateToMine',
37
+          btnTitle: data.btnTitle,
38
+          isHightlight: data.isHightlight
39
+        })
40
+      })
41
+    }
42
+  },
43
+
44
+  navigateToMine() {
45
+    wx.switchTab({
46
+      url: '/pages/member/mine/mine'
47
+    })
48
+  },
49
+
50
+  navigateBackToMall() {
51
+    wx.navigateBack({
52
+      delta: 2
53
+    })
54
+  },
55
+
56
+  navigateToVoucher() {
57
+    wx.redirectTo({
58
+      url: '/pages/member/mine/voucher/voucher'
59
+    })
60
+  },
61
+
62
+  officialAccountError(e) {
63
+    console.log(e)
64
+  }
65
+})

+ 5 - 0
src/template/resultView/resultView.json

@@ -0,0 +1,5 @@
1
+{
2
+  "component": true,
3
+  "usingComponents": {},
4
+  "disableScroll": true
5
+}

+ 99 - 0
src/template/resultView/resultView.less

@@ -0,0 +1,99 @@
1
+@import './src/style/less/variable.less';
2
+@import './src/style/less/layout.less';
3
+
4
+.result-view {
5
+  .flex-center(column);
6
+
7
+  box-sizing: border-box;
8
+  width: 100%;
9
+  height: 100%;
10
+  padding: @spacing-item;
11
+  background-color: @color-bg;
12
+}
13
+
14
+.logo {
15
+  width: 200rpx;
16
+  height: 200rpx;
17
+  margin-top: 50rpx;
18
+
19
+  image {
20
+    .full-image();
21
+  }
22
+}
23
+
24
+.content {
25
+  .flex-center(column);
26
+
27
+  width: 100%;
28
+  margin-top: @spacing-item;
29
+
30
+  .title {
31
+    margin-top: @spacing-item;
32
+    font-size: @font-title;
33
+    font-weight: 600;
34
+    color: @color-black;
35
+  }
36
+
37
+  .desc {
38
+    margin-top: @spacing-item;
39
+    font-size: @font-tertiary;
40
+    color: @color-light-gray;
41
+    text-align: center;
42
+  }
43
+
44
+  .hint {
45
+    margin-top: @spacing-item;
46
+    .flex-center(row);
47
+
48
+    image {
49
+      .square(@icon-large-size);
50
+    }
51
+
52
+    text {
53
+      margin-left: @spacing-inline;
54
+      font-size: @font-primary;
55
+      color: @color-brand;
56
+    }
57
+  }
58
+}
59
+
60
+.action {
61
+  width: 100%;
62
+  margin-top: @spacing-item;
63
+
64
+  view {
65
+    .flex-center(column);
66
+
67
+    justify-content: center;
68
+    width: 100%;
69
+    height: 80rpx;
70
+    margin-top: @spacing-inline;
71
+    color: @color-white;
72
+    background-color: @color-brand;
73
+    border-radius: @radius;
74
+  }
75
+}
76
+
77
+.action-bottom {
78
+  .flex-center(column);
79
+
80
+  position: fixed;
81
+  bottom: @spacing-view;
82
+  width: 100%;
83
+
84
+  view {
85
+    .flex-center(column);
86
+
87
+    justify-content: center;
88
+    width: auto;
89
+    padding: @spacing-inline @spacing-view;
90
+    color: @color-brand;
91
+    border: 1px @color-light-gray solid;
92
+    border-radius: @radius;
93
+  }
94
+}
95
+
96
+.official-account {
97
+  width: 702rpx;
98
+  margin-top: @spacing-view;
99
+}

+ 33 - 0
src/template/resultView/resultView.wxml

@@ -0,0 +1,33 @@
1
+
2
+<view class="result-view" wx:if="{{isHightlight}}">
3
+  <view class="logo">
4
+    <image src="success.svg"></image>
5
+  </view>
6
+  <view class="content">
7
+    <text class="title">{{ title }}</text>
8
+    <view class="hint">
9
+      <image src="/resources/trumpet.svg"></image>
10
+      <text>{{ desc }}</text>
11
+    </view>
12
+    <view class="official-account">
13
+      <official-account binderror="officialAccountError"></official-account>
14
+    </view>
15
+  </view>
16
+  <view class="action-bottom">
17
+    <view class="goOn" bindtap="{{ action }}">{{ btnTitle }}</view>
18
+  </view>
19
+</view>
20
+
21
+<view class="result-view" wx:else>
22
+  <view class="logo">
23
+    <image src="success.svg"></image>
24
+  </view>
25
+  <view class="content">
26
+    <text class="title">{{ title }}</text>
27
+    <text class="desc">{{ desc }}</text>
28
+  </view>
29
+  <view class="action">
30
+    <view class="goOn" bindtap="{{ action }}">{{ btnTitle }}</view>
31
+    <view class="detail" bindtap="navigateToVoucher" wx:if="{{ isVirtual }}">去查看</view>
32
+  </view>
33
+</view>

+ 91 - 0
src/template/resultView/resultView.wxss

@@ -0,0 +1,91 @@
1
+.result-view {
2
+  display: flex;
3
+  flex-direction: column;
4
+  align-items: center;
5
+  box-sizing: border-box;
6
+  width: 100%;
7
+  height: 100%;
8
+  padding: 24rpx;
9
+  background-color: #efefef;
10
+}
11
+.logo {
12
+  width: 200rpx;
13
+  height: 200rpx;
14
+  margin-top: 50rpx;
15
+}
16
+.logo image {
17
+  width: 100%;
18
+  height: 100%;
19
+}
20
+.content {
21
+  display: flex;
22
+  flex-direction: column;
23
+  align-items: center;
24
+  width: 100%;
25
+  margin-top: 24rpx;
26
+}
27
+.content .title {
28
+  margin-top: 24rpx;
29
+  font-size: 17pt;
30
+  font-weight: 600;
31
+  color: #000;
32
+}
33
+.content .desc {
34
+  margin-top: 24rpx;
35
+  font-size: 11pt;
36
+  color: #959595;
37
+  text-align: center;
38
+}
39
+.content .hint {
40
+  margin-top: 24rpx;
41
+  display: flex;
42
+  align-items: center;
43
+}
44
+.content .hint image {
45
+  width: 72rpx;
46
+  height: 72rpx;
47
+}
48
+.content .hint text {
49
+  margin-left: 12rpx;
50
+  font-size: 14pt;
51
+  color: #0967b2;
52
+}
53
+.action {
54
+  width: 100%;
55
+  margin-top: 24rpx;
56
+}
57
+.action view {
58
+  display: flex;
59
+  flex-direction: column;
60
+  align-items: center;
61
+  justify-content: center;
62
+  width: 100%;
63
+  height: 80rpx;
64
+  margin-top: 12rpx;
65
+  color: #fff;
66
+  background-color: #0967b2;
67
+  border-radius: 5px;
68
+}
69
+.action-bottom {
70
+  display: flex;
71
+  flex-direction: column;
72
+  align-items: center;
73
+  position: fixed;
74
+  bottom: 36rpx;
75
+  width: 100%;
76
+}
77
+.action-bottom view {
78
+  display: flex;
79
+  flex-direction: column;
80
+  align-items: center;
81
+  justify-content: center;
82
+  width: auto;
83
+  padding: 12rpx 36rpx;
84
+  color: #0967b2;
85
+  border: 1px #959595 solid;
86
+  border-radius: 5px;
87
+}
88
+.official-account {
89
+  width: 702rpx;
90
+  margin-top: 36rpx;
91
+}

+ 1 - 0
src/template/resultView/success.svg

@@ -0,0 +1 @@
1
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575547268867" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3403" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><defs><style type="text/css"></style></defs><path d="M512 981.333333C252.8 981.333333 42.666667 771.2 42.666667 512S252.8 42.666667 512 42.666667s469.333333 210.133333 469.333333 469.333333-210.133333 469.333333-469.333333 469.333333z m-50.432-326.101333L310.613333 504.32a32 32 0 0 0-45.226666 45.226667l174.72 174.762666a32.341333 32.341333 0 0 0 0.341333 0.341334l0.256 0.213333a32 32 0 0 0 50.048-6.144l337.450667-379.605333a32 32 0 1 0-47.872-42.496l-318.762667 358.613333z" fill="#0967b2" p-id="3404"></path></svg>

+ 18 - 0
src/template/webView/webView.js

@@ -0,0 +1,18 @@
1
+// template/webView/webView.js
2
+Page({
3
+  /**
4
+   * Page initial data
5
+   */
6
+  data: {
7
+    url: ''
8
+  },
9
+
10
+  /**
11
+   * Lifecycle function--Called when page load
12
+   */
13
+  onLoad(options) {
14
+    this.setData({
15
+      url: options.url
16
+    })
17
+  }
18
+})

+ 3 - 0
src/template/webView/webView.json

@@ -0,0 +1,3 @@
1
+{
2
+  "usingComponents": {}
3
+}

+ 1 - 0
src/template/webView/webView.wxml

@@ -0,0 +1 @@
1
+<web-view src="{{url}}"></web-view>

+ 1 - 0
src/template/webView/webView.wxss

@@ -0,0 +1 @@
1
+/* template/webView/webView.wxss */

BIN
src/utils/.DS_Store


+ 155 - 0
src/utils/network.js

@@ -0,0 +1,155 @@
1
+const urls = require('./urls.js')
2
+const brandConfig = require('../brand/config.js')
3
+
4
+// 服务器登录
5
+function _login(code, cb) {
6
+  wx.request({
7
+    url: urls.MINI_LOGIN_API_URL,
8
+    data: {
9
+      code
10
+    },
11
+    method: 'POST',
12
+    header: {
13
+      'content-type': 'application/x-www-form-urlencoded'
14
+    },
15
+    success(res) {
16
+      wx.setStorage({
17
+        key: 'userInfo',
18
+        data: res.data.data,
19
+        success() {
20
+          cb(res.data.data)
21
+        }
22
+      })
23
+    },
24
+    complete: () => {
25
+      wx.hideLoading()
26
+    }
27
+  })
28
+}
29
+
30
+function _slientLogin(cb) {
31
+  wx.showLoading({
32
+    title: '加载中...',
33
+    mask: true
34
+  })
35
+  const { environment } = wx.getSystemInfoSync()
36
+
37
+  if (environment === 'wxwork') {
38
+    wx.login({
39
+      success(res) {
40
+        console.log(res)
41
+        if (res.code) {
42
+          _login(res.code, cb)
43
+        } else {
44
+          wx.showToast({
45
+            title: '微信登录异常',
46
+            icon: 'none'
47
+          })
48
+          wx.hideLoading()
49
+        }
50
+      },
51
+      fail() {
52
+        wx.showToast({
53
+          title: '微信登录异常',
54
+          icon: 'none'
55
+        })
56
+        wx.hideLoading()
57
+      }
58
+    })
59
+
60
+    return
61
+  }
62
+  wx.login({
63
+    success(res) {
64
+      if (res.code) {
65
+        _login(res.code, cb)
66
+      } else {
67
+        wx.showToast({
68
+          title: '微信登录异常',
69
+          icon: 'none'
70
+        })
71
+        wx.hideLoading()
72
+      }
73
+    },
74
+    fail() {
75
+      wx.showToast({
76
+        title: '微信登录异常',
77
+        icon: 'none'
78
+      })
79
+      wx.hideLoading()
80
+    }
81
+  })
82
+}
83
+
84
+// 静默登录
85
+function login(cb) {
86
+  _slientLogin(cb)
87
+}
88
+
89
+function checkLoginStatus(cb) {
90
+  wx.getStorage({
91
+    key: 'userInfo',
92
+    success: res => {
93
+      cb(res.data)
94
+    },
95
+    fail: () => {
96
+      login(cb)
97
+    }
98
+  })
99
+}
100
+
101
+function post(params, isTip = true) {
102
+  const finalUrl = `${params.url}?ts=${Date.now()}`
103
+  checkLoginStatus(userInfo => {
104
+    const { user_id } = userInfo
105
+    const data = params.data || {}
106
+    data.user_id = user_id
107
+    wx.request({
108
+      url: finalUrl,
109
+      data,
110
+      method: 'POST',
111
+      header: {
112
+        'content-type': 'application/x-www-form-urlencoded'
113
+      },
114
+      success: res => {
115
+        // success
116
+        wx.hideLoading()
117
+        if (res.data.status !== 200 && isTip) {
118
+          wx.showToast({
119
+            title: res.data.description,
120
+            image: '/resources/alert_fail.svg'
121
+          })
122
+
123
+          if (params.fail) {
124
+            params.fail(res)
125
+          }
126
+        } else if (params.success) {
127
+          params.success(res.data)
128
+        }
129
+      },
130
+      fail: res => {
131
+        // fail
132
+        if (params.fail) {
133
+          params.fail(res)
134
+        }
135
+        wx.hideLoading()
136
+        wx.showToast({
137
+          title: '当前网络开小差, 请稍后再试',
138
+          image: '/resources/alert_fail.svg'
139
+        })
140
+      },
141
+      complete: res => {
142
+        // complete
143
+
144
+        if (params.complete) {
145
+          params.complete(res)
146
+        }
147
+      }
148
+    })
149
+  })
150
+}
151
+
152
+module.exports = {
153
+  post,
154
+  login
155
+}

+ 34 - 0
src/utils/router.js

@@ -0,0 +1,34 @@
1
+module.exports = {
2
+  index: '/pages/index/index',
3
+  webView: '/template/resultView/resultView',
4
+  member: {
5
+    activity: {
6
+      index: '/pages/member/activity/activity',
7
+      details: '/pages/member/activity/details/details'
8
+    },
9
+    mine: {
10
+      index: '/pages/member/mine/mine',
11
+      voucher: '/pages/member/mine/voucher/voucher',
12
+      voucherDetails: 'pages/member/mine/voucher/details/details',
13
+      benefits: {
14
+        index: '/pages/member/mine/benefits/benefits',
15
+        details: '/pages/member/mine/benefits/details/details'
16
+      },
17
+      integral: {
18
+        mall: '/pages/member/mine/integral/mall/mall',
19
+        mallDetails: '/pages/member/mine/integral/mall/details/details',
20
+        rule: '/pages/member/mine/integral/rule/rule'
21
+      }
22
+    },
23
+
24
+    saleManager: {
25
+      index: '/pages/sale_manager_home/sale_manager_home'
26
+    },
27
+
28
+    register: {
29
+      saleManager:
30
+        '/pages/register/sale_manager_register/sale_manager_register',
31
+      consumer: '/pages/register/consumer/consumer'
32
+    }
33
+  }
34
+}

+ 27 - 0
src/utils/routerInterceptor.js

@@ -0,0 +1,27 @@
1
+/**
2
+ * routerFillter --全局路由拦截器
3
+ * @function
4
+ * @param{Object} pageObj 当前页面的page对象
5
+ * @param{Boolean} flag 是否开启权限判断
6
+ */
7
+
8
+function checkLoginStatus(pageObj) {
9
+  const _onLoad = pageObj.onLoad
10
+  pageObj.onLoad = function onLoad(options) {
11
+    const that = this
12
+    // 这一步是自己定义获取登录状态的,只是个判断权限的
13
+    const app = getApp()
14
+    if (!app.globalData.isLogin) {
15
+      app.network.login(res => {
16
+        app.globalData.userInfo = res
17
+        app.globalData.isLogin = true
18
+        _onLoad.call(that, options)
19
+      })
20
+    } else {
21
+      _onLoad.call(that, options)
22
+    }
23
+  }
24
+  return Page(pageObj)
25
+}
26
+
27
+exports.checkLoginStatus = checkLoginStatus

+ 10 - 0
src/utils/urls.js

@@ -0,0 +1,10 @@
1
+const config = require('../brand/config.js')
2
+
3
+module.exports = {
4
+  // 小程序登录接口
5
+  MINI_LOGIN_API_URL: `${config.baseURL}/api/mini/login`,
6
+  MINI_USERINFO: `${config.baseURL}/api/mini/userinfo`,
7
+
8
+  PACK: `${config.baseURL}/api/pack`,
9
+  ORDER_CREATE: `${config.baseURL}/api/pay/wx/order_create`
10
+}

BIN
src/utils/wxParse/.DS_Store


+ 302 - 0
src/utils/wxParse/html2json.js

@@ -0,0 +1,302 @@
1
+/**
2
+ * html2Json 改造来自: https://github.com/Jxck/html2json
3
+ *
4
+ *
5
+ * author: Di (微信小程序开发工程师)
6
+ * organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
7
+ *               垂直微信小程序开发交流社区
8
+ *
9
+ * github地址: https://github.com/icindy/wxParse
10
+ *
11
+ * for: 微信小程序富文本解析
12
+ * detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
13
+ */
14
+
15
+const __placeImgeUrlHttps = 'https';
16
+let __emojisReg = '';
17
+let __emojisBaseSrc = '';
18
+let __emojis = {};
19
+const wxDiscode = require('./wxDiscode.js');
20
+const HTMLParser = require('./htmlparser.js');
21
+// Empty Elements - HTML 5
22
+const empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr');
23
+// Block Elements - HTML 5
24
+const 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');
25
+
26
+// Inline Elements - HTML 5
27
+const 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');
28
+
29
+// Elements that you can, intentionally, leave open
30
+// (and which close themselves)
31
+const closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
32
+
33
+// Attributes that have their values filled in disabled="disabled"
34
+const fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected');
35
+
36
+// Special Elements (can contain anything)
37
+const special = makeMap('wxxxcode-style,script,style,view,scroll-view,block');
38
+function makeMap(str) {
39
+  const obj = {}; const
40
+    items = str.split(',');
41
+  for (let i = 0; i < items.length; i++) obj[items[i]] = true;
42
+  return obj;
43
+}
44
+
45
+function q(v) {
46
+  return `"${v}"`;
47
+}
48
+
49
+function removeDOCTYPE(html) {
50
+  return html
51
+    .replace(/<\?xml.*\?>\n/, '')
52
+    .replace(/<.*!doctype.*\>\n/, '')
53
+    .replace(/<.*!DOCTYPE.*\>\n/, '');
54
+}
55
+
56
+function trimHtml(html) {
57
+  return html
58
+    .replace(/\r?\n+/g, '')
59
+    .replace(/<!--.*?-->/ig, '')
60
+    .replace(/\/\*.*?\*\//ig, '')
61
+    .replace(/[ ]+</ig, '<');
62
+}
63
+
64
+
65
+function html2json(html, bindName) {
66
+  // 处理字符串
67
+  html = removeDOCTYPE(html);
68
+  html = trimHtml(html);
69
+  html = wxDiscode.strDiscode(html);
70
+  // 生成node节点
71
+  const bufArray = [];
72
+  const results = {
73
+    node: bindName,
74
+    nodes: [],
75
+    images: [],
76
+    imageUrls: [],
77
+  };
78
+  let index = 0;
79
+  HTMLParser(html, {
80
+    start(tag, attrs, unary) {
81
+      // debug(tag, attrs, unary);
82
+      // node for this element
83
+      const node = {
84
+        node: 'element',
85
+        tag,
86
+      };
87
+
88
+      if (bufArray.length === 0) {
89
+        node.index = index.toString();
90
+        index += 1;
91
+      } else {
92
+        var parent = bufArray[0];
93
+        if (parent.nodes === undefined) {
94
+          parent.nodes = [];
95
+        }
96
+        node.index = `${parent.index}.${parent.nodes.length}`;
97
+      }
98
+
99
+      if (block[tag]) {
100
+        node.tagType = 'block';
101
+      } else if (inline[tag]) {
102
+        node.tagType = 'inline';
103
+      } else if (closeSelf[tag]) {
104
+        node.tagType = 'closeSelf';
105
+      }
106
+
107
+      if (attrs.length !== 0) {
108
+        node.attr = attrs.reduce((pre, attr) => {
109
+          const { name } = attr;
110
+          let { value } = attr;
111
+          if (name == 'class') {
112
+            console.dir(value);
113
+            //  value = value.join("")
114
+            node.classStr = value;
115
+          }
116
+          // has multi attibutes
117
+          // make it array of attribute
118
+          if (name == 'style') {
119
+            console.dir(value);
120
+            //  value = value.join("")
121
+            node.styleStr = value;
122
+          }
123
+          if (value.match(/ /)) {
124
+            value = value.split(' ');
125
+          }
126
+
127
+
128
+          // if attr already exists
129
+          // merge it
130
+          if (pre[name]) {
131
+            if (Array.isArray(pre[name])) {
132
+              // already array, push to last
133
+              pre[name].push(value);
134
+            } else {
135
+              // single value, make it array
136
+              pre[name] = [pre[name], value];
137
+            }
138
+          } else {
139
+            // not exist, put it
140
+            pre[name] = value;
141
+          }
142
+
143
+          return pre;
144
+        }, {});
145
+      }
146
+
147
+      // 对img添加额外数据
148
+      if (node.tag === 'img') {
149
+        node.imgIndex = results.images.length;
150
+        let imgUrl = node.attr.src;
151
+        if (imgUrl[0] == '') {
152
+          imgUrl.splice(0, 1);
153
+        }
154
+        imgUrl = wxDiscode.urlToHttpUrl(imgUrl, __placeImgeUrlHttps);
155
+        node.attr.src = imgUrl;
156
+        node.from = bindName;
157
+        results.images.push(node);
158
+        results.imageUrls.push(imgUrl);
159
+      }
160
+
161
+      // 处理font标签样式属性
162
+      if (node.tag === 'font') {
163
+        const fontSize = ['x-small', 'small', 'medium', 'large', 'x-large', 'xx-large', '-webkit-xxx-large'];
164
+        const styleAttrs = {
165
+          color: 'color',
166
+          face: 'font-family',
167
+          size: 'font-size',
168
+        };
169
+        if (!node.attr.style) node.attr.style = [];
170
+        if (!node.styleStr) node.styleStr = '';
171
+        for (const key in styleAttrs) {
172
+          if (node.attr[key]) {
173
+            const value = key === 'size' ? fontSize[node.attr[key] - 1] : node.attr[key];
174
+            node.attr.style.push(styleAttrs[key]);
175
+            node.attr.style.push(value);
176
+            node.styleStr += `${styleAttrs[key]}: ${value};`;
177
+          }
178
+        }
179
+      }
180
+
181
+      // 临时记录source资源
182
+      if (node.tag === 'source') {
183
+        results.source = node.attr.src;
184
+      }
185
+
186
+      if (unary) {
187
+        // if this tag doesn't have end tag
188
+        // like <img src="hoge.png"/>
189
+        // add to parents
190
+        var parent = bufArray[0] || results;
191
+        if (parent.nodes === undefined) {
192
+          parent.nodes = [];
193
+        }
194
+        parent.nodes.push(node);
195
+      } else {
196
+        bufArray.unshift(node);
197
+      }
198
+    },
199
+    end(tag) {
200
+      // debug(tag);
201
+      // merge into parent tag
202
+      const node = bufArray.shift();
203
+      if (node.tag !== tag) console.error('invalid state: mismatch end tag');
204
+
205
+      // 当有缓存source资源时于于video补上src资源
206
+      if (node.tag === 'video' && results.source) {
207
+        node.attr.src = results.source;
208
+        delete results.source;
209
+      }
210
+
211
+      if (bufArray.length === 0) {
212
+        results.nodes.push(node);
213
+      } else {
214
+        const parent = bufArray[0];
215
+        if (parent.nodes === undefined) {
216
+          parent.nodes = [];
217
+        }
218
+        parent.nodes.push(node);
219
+      }
220
+    },
221
+    chars(text) {
222
+      // debug(text);
223
+      const node = {
224
+        node: 'text',
225
+        text,
226
+        textArray: transEmojiStr(text),
227
+      };
228
+
229
+      if (bufArray.length === 0) {
230
+        node.index = index.toString();
231
+        index += 1;
232
+        results.nodes.push(node);
233
+      } else {
234
+        const parent = bufArray[0];
235
+        if (parent.nodes === undefined) {
236
+          parent.nodes = [];
237
+        }
238
+        node.index = `${parent.index}.${parent.nodes.length}`;
239
+        parent.nodes.push(node);
240
+      }
241
+    },
242
+    comment(text) {
243
+      // debug(text);
244
+      // var node = {
245
+      //     node: 'comment',
246
+      //     text: text,
247
+      // };
248
+      // var parent = bufArray[0];
249
+      // if (parent.nodes === undefined) {
250
+      //     parent.nodes = [];
251
+      // }
252
+      // parent.nodes.push(node);
253
+    },
254
+  });
255
+  return results;
256
+}
257
+
258
+function transEmojiStr(str) {
259
+  // var eReg = new RegExp("["+__reg+' '+"]");
260
+//   str = str.replace(/\[([^\[\]]+)\]/g,':$1:')
261
+
262
+  const emojiObjs = [];
263
+  // 如果正则表达式为空
264
+  if (__emojisReg.length == 0 || !__emojis) {
265
+    var emojiObj = {};
266
+    emojiObj.node = 'text';
267
+    emojiObj.text = str;
268
+    array = [emojiObj];
269
+    return array;
270
+  }
271
+  // 这个地方需要调整
272
+  str = str.replace(/\[([^\[\]]+)\]/g, ':$1:');
273
+  const eReg = new RegExp('[:]');
274
+  var array = str.split(eReg);
275
+  for (let i = 0; i < array.length; i++) {
276
+    const ele = array[i];
277
+    var emojiObj = {};
278
+    if (__emojis[ele]) {
279
+      emojiObj.node = 'element';
280
+      emojiObj.tag = 'emoji';
281
+      emojiObj.text = __emojis[ele];
282
+      emojiObj.baseSrc = __emojisBaseSrc;
283
+    } else {
284
+      emojiObj.node = 'text';
285
+      emojiObj.text = ele;
286
+    }
287
+    emojiObjs.push(emojiObj);
288
+  }
289
+
290
+  return emojiObjs;
291
+}
292
+
293
+function emojisInit(reg = '', baseSrc = '/wxParse/emojis/', emojis) {
294
+  __emojisReg = reg;
295
+  __emojisBaseSrc = baseSrc;
296
+  __emojis = emojis;
297
+}
298
+
299
+module.exports = {
300
+  html2json,
301
+  emojisInit,
302
+};

+ 183 - 0
src/utils/wxParse/htmlparser.js

@@ -0,0 +1,183 @@
1
+/**
2
+ *
3
+ * htmlParser改造自: https://github.com/blowsie/Pure-JavaScript-HTML5-Parser
4
+ *
5
+ * author: Di (微信小程序开发工程师)
6
+ * organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
7
+ *               垂直微信小程序开发交流社区
8
+ *
9
+ * github地址: https://github.com/icindy/wxParse
10
+ *
11
+ * for: 微信小程序富文本解析
12
+ * detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
13
+ */
14
+// Regular Expressions for parsing tags and attributes
15
+const startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
16
+const endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
17
+const attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
18
+
19
+// Empty Elements - HTML 5
20
+const empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr');
21
+
22
+// Block Elements - HTML 5
23
+const 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');
24
+
25
+// Inline Elements - HTML 5
26
+const 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');
27
+
28
+// Elements that you can, intentionally, leave open
29
+// (and which close themselves)
30
+const closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
31
+
32
+// Attributes that have their values filled in disabled="disabled"
33
+const fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected');
34
+
35
+// Special Elements (can contain anything)
36
+const special = makeMap('wxxxcode-style,script,style,view,scroll-view,block');
37
+
38
+function HTMLParser(html, handler) {
39
+  let index; let chars; let match; const stack = []; let
40
+    last = html;
41
+  stack.last = function () {
42
+    return this[this.length - 1];
43
+  };
44
+
45
+  while (html) {
46
+    chars = true;
47
+
48
+    // Make sure we're not in a script or style element
49
+    if (!stack.last() || !special[stack.last()]) {
50
+      // Comment
51
+      if (html.indexOf('<!--') == 0) {
52
+        index = html.indexOf('-->');
53
+
54
+        if (index >= 0) {
55
+          if (handler.comment) { handler.comment(html.substring(4, index)); }
56
+          html = html.substring(index + 3);
57
+          chars = false;
58
+        }
59
+
60
+        // end tag
61
+      } else if (html.indexOf('</') == 0) {
62
+        match = html.match(endTag);
63
+
64
+        if (match) {
65
+          html = html.substring(match[0].length);
66
+          match[0].replace(endTag, parseEndTag);
67
+          chars = false;
68
+        }
69
+
70
+        // start tag
71
+      } else if (html.indexOf('<') == 0) {
72
+        match = html.match(startTag);
73
+
74
+        if (match) {
75
+          html = html.substring(match[0].length);
76
+          match[0].replace(startTag, parseStartTag);
77
+          chars = false;
78
+        }
79
+      }
80
+
81
+      if (chars) {
82
+        index = html.indexOf('<');
83
+        let text = '';
84
+        while (index === 0) {
85
+          text += '<';
86
+          html = html.substring(1);
87
+          index = html.indexOf('<');
88
+        }
89
+        text += index < 0 ? html : html.substring(0, index);
90
+        html = index < 0 ? '' : html.substring(index);
91
+
92
+        if (handler.chars) { handler.chars(text); }
93
+      }
94
+    } else {
95
+      html = html.replace(new RegExp(`([\\s\\S]*?)<\/${stack.last()}[^>]*>`), (all, text) => {
96
+        text = text.replace(/<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)]]>/g, '$1$2');
97
+        if (handler.chars) { handler.chars(text); }
98
+
99
+        return '';
100
+      });
101
+
102
+
103
+      parseEndTag('', stack.last());
104
+    }
105
+
106
+    if (html == last) { throw `Parse Error: ${html}`; }
107
+    last = html;
108
+  }
109
+
110
+  // Clean up any remaining tags
111
+  parseEndTag();
112
+
113
+  function parseStartTag(tag, tagName, rest, unary) {
114
+    tagName = tagName.toLowerCase();
115
+
116
+    if (block[tagName]) {
117
+      while (stack.last() && inline[stack.last()]) {
118
+        parseEndTag('', stack.last());
119
+      }
120
+    }
121
+
122
+    if (closeSelf[tagName] && stack.last() == tagName) {
123
+      parseEndTag('', tagName);
124
+    }
125
+
126
+    unary = empty[tagName] || !!unary;
127
+
128
+    if (!unary) { stack.push(tagName); }
129
+
130
+    if (handler.start) {
131
+      const attrs = [];
132
+
133
+      rest.replace(attr, function (match, name) {
134
+        const value = arguments[2] ? arguments[2]
135
+          : arguments[3] ? arguments[3]
136
+            : arguments[4] ? arguments[4]
137
+              : fillAttrs[name] ? name : '';
138
+
139
+        attrs.push({
140
+          name,
141
+          value,
142
+          escaped: value.replace(/(^|[^\\])"/g, '$1\\\"'), // "
143
+        });
144
+      });
145
+
146
+      if (handler.start) {
147
+        handler.start(tagName, attrs, unary);
148
+      }
149
+    }
150
+  }
151
+
152
+  function parseEndTag(tag, tagName) {
153
+    // If no tag name is provided, clean shop
154
+    if (!tagName) { var pos = 0; }
155
+
156
+    // Find the closest opened tag of the same type
157
+    else {
158
+      tagName = tagName.toLowerCase();
159
+      for (var pos = stack.length - 1; pos >= 0; pos--) {
160
+        if (stack[pos] == tagName) { break; }
161
+      }
162
+    }
163
+    if (pos >= 0) {
164
+      // Close all the open elements, up the stack
165
+      for (let i = stack.length - 1; i >= pos; i--) {
166
+        if (handler.end) { handler.end(stack[i]); }
167
+      }
168
+
169
+      // Remove the open elements from the stack
170
+      stack.length = pos;
171
+    }
172
+  }
173
+}
174
+
175
+
176
+function makeMap(str) {
177
+  const obj = {}; const
178
+    items = str.split(',');
179
+  for (let i = 0; i < items.length; i++) { obj[items[i]] = true; }
180
+  return obj;
181
+}
182
+
183
+module.exports = HTMLParser;

+ 2412 - 0
src/utils/wxParse/showdown.js

@@ -0,0 +1,2412 @@
1
+/**
2
+ *
3
+ * showdown: https://github.com/showdownjs/showdown
4
+ *
5
+ * author: Di (微信小程序开发工程师)
6
+ * organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
7
+ *               垂直微信小程序开发交流社区
8
+ *
9
+ * github地址: https://github.com/icindy/wxParse
10
+ *
11
+ * for: 微信小程序富文本解析
12
+ * detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
13
+ */
14
+
15
+function getDefaultOpts(simple) {
16
+  const defaultOptions = {
17
+    omitExtraWLInCodeBlocks: {
18
+      defaultValue: false,
19
+      describe: 'Omit the default extra whiteline added to code blocks',
20
+      type: 'boolean',
21
+    },
22
+    noHeaderId: {
23
+      defaultValue: false,
24
+      describe: 'Turn on/off generated header id',
25
+      type: 'boolean',
26
+    },
27
+    prefixHeaderId: {
28
+      defaultValue: false,
29
+      describe: 'Specify a prefix to generated header ids',
30
+      type: 'string',
31
+    },
32
+    headerLevelStart: {
33
+      defaultValue: false,
34
+      describe: 'The header blocks level start',
35
+      type: 'integer',
36
+    },
37
+    parseImgDimensions: {
38
+      defaultValue: false,
39
+      describe: 'Turn on/off image dimension parsing',
40
+      type: 'boolean',
41
+    },
42
+    simplifiedAutoLink: {
43
+      defaultValue: false,
44
+      describe: 'Turn on/off GFM autolink style',
45
+      type: 'boolean',
46
+    },
47
+    literalMidWordUnderscores: {
48
+      defaultValue: false,
49
+      describe: 'Parse midword underscores as literal underscores',
50
+      type: 'boolean',
51
+    },
52
+    strikethrough: {
53
+      defaultValue: false,
54
+      describe: 'Turn on/off strikethrough support',
55
+      type: 'boolean',
56
+    },
57
+    tables: {
58
+      defaultValue: false,
59
+      describe: 'Turn on/off tables support',
60
+      type: 'boolean',
61
+    },
62
+    tablesHeaderId: {
63
+      defaultValue: false,
64
+      describe: 'Add an id to table headers',
65
+      type: 'boolean',
66
+    },
67
+    ghCodeBlocks: {
68
+      defaultValue: true,
69
+      describe: 'Turn on/off GFM fenced code blocks support',
70
+      type: 'boolean',
71
+    },
72
+    tasklists: {
73
+      defaultValue: false,
74
+      describe: 'Turn on/off GFM tasklist support',
75
+      type: 'boolean',
76
+    },
77
+    smoothLivePreview: {
78
+      defaultValue: false,
79
+      describe: 'Prevents weird effects in live previews due to incomplete input',
80
+      type: 'boolean',
81
+    },
82
+    smartIndentationFix: {
83
+      defaultValue: false,
84
+      description: 'Tries to smartly fix identation in es6 strings',
85
+      type: 'boolean',
86
+    },
87
+  };
88
+  if (simple === false) {
89
+    return JSON.parse(JSON.stringify(defaultOptions));
90
+  }
91
+  const ret = {};
92
+  for (const opt in defaultOptions) {
93
+    if (defaultOptions.hasOwnProperty(opt)) {
94
+      ret[opt] = defaultOptions[opt].defaultValue;
95
+    }
96
+  }
97
+  return ret;
98
+}
99
+
100
+/**
101
+ * Created by Tivie on 06-01-2015.
102
+ */
103
+
104
+// Private properties
105
+const showdown = {};
106
+const parsers = {};
107
+let extensions = {};
108
+let globalOptions = getDefaultOpts(true);
109
+const flavor = {
110
+  github: {
111
+    omitExtraWLInCodeBlocks: true,
112
+    prefixHeaderId: 'user-content-',
113
+    simplifiedAutoLink: true,
114
+    literalMidWordUnderscores: true,
115
+    strikethrough: true,
116
+    tables: true,
117
+    tablesHeaderId: true,
118
+    ghCodeBlocks: true,
119
+    tasklists: true,
120
+  },
121
+  vanilla: getDefaultOpts(true),
122
+};
123
+
124
+/**
125
+ * helper namespace
126
+ * @type {{}}
127
+ */
128
+showdown.helper = {};
129
+
130
+/**
131
+ * TODO LEGACY SUPPORT CODE
132
+ * @type {{}}
133
+ */
134
+showdown.extensions = {};
135
+
136
+/**
137
+ * Set a global option
138
+ * @static
139
+ * @param {string} key
140
+ * @param {*} value
141
+ * @returns {showdown}
142
+ */
143
+showdown.setOption = function (key, value) {
144
+  globalOptions[key] = value;
145
+  return this;
146
+};
147
+
148
+/**
149
+ * Get a global option
150
+ * @static
151
+ * @param {string} key
152
+ * @returns {*}
153
+ */
154
+showdown.getOption = function (key) {
155
+  return globalOptions[key];
156
+};
157
+
158
+/**
159
+ * Get the global options
160
+ * @static
161
+ * @returns {{}}
162
+ */
163
+showdown.getOptions = function () {
164
+  return globalOptions;
165
+};
166
+
167
+/**
168
+ * Reset global options to the default values
169
+ * @static
170
+ */
171
+showdown.resetOptions = function () {
172
+  globalOptions = getDefaultOpts(true);
173
+};
174
+
175
+/**
176
+ * Set the flavor showdown should use as default
177
+ * @param {string} name
178
+ */
179
+showdown.setFlavor = function (name) {
180
+  if (flavor.hasOwnProperty(name)) {
181
+    const preset = flavor[name];
182
+    for (const option in preset) {
183
+      if (preset.hasOwnProperty(option)) {
184
+        globalOptions[option] = preset[option];
185
+      }
186
+    }
187
+  }
188
+};
189
+
190
+/**
191
+ * Get the default options
192
+ * @static
193
+ * @param {boolean} [simple=true]
194
+ * @returns {{}}
195
+ */
196
+showdown.getDefaultOptions = function (simple) {
197
+  return getDefaultOpts(simple);
198
+};
199
+
200
+/**
201
+ * Get or set a subParser
202
+ *
203
+ * subParser(name)       - Get a registered subParser
204
+ * subParser(name, func) - Register a subParser
205
+ * @static
206
+ * @param {string} name
207
+ * @param {function} [func]
208
+ * @returns {*}
209
+ */
210
+showdown.subParser = function (name, func) {
211
+  if (showdown.helper.isString(name)) {
212
+    if (typeof func !== 'undefined') {
213
+      parsers[name] = func;
214
+    } else {
215
+      if (parsers.hasOwnProperty(name)) {
216
+        return parsers[name];
217
+      }
218
+      throw Error(`SubParser named ${name} not registered!`);
219
+    }
220
+  }
221
+};
222
+
223
+/**
224
+ * Gets or registers an extension
225
+ * @static
226
+ * @param {string} name
227
+ * @param {object|function=} ext
228
+ * @returns {*}
229
+ */
230
+showdown.extension = function (name, ext) {
231
+  if (!showdown.helper.isString(name)) {
232
+    throw Error('Extension \'name\' must be a string');
233
+  }
234
+
235
+  name = showdown.helper.stdExtName(name);
236
+
237
+  // Getter
238
+  if (showdown.helper.isUndefined(ext)) {
239
+    if (!extensions.hasOwnProperty(name)) {
240
+      throw Error(`Extension named ${name} is not registered!`);
241
+    }
242
+    return extensions[name];
243
+
244
+    // Setter
245
+  }
246
+  // Expand extension if it's wrapped in a function
247
+  if (typeof ext === 'function') {
248
+    ext = ext();
249
+  }
250
+
251
+  // Ensure extension is an array
252
+  if (!showdown.helper.isArray(ext)) {
253
+    ext = [ext];
254
+  }
255
+
256
+  const validExtension = validate(ext, name);
257
+
258
+  if (validExtension.valid) {
259
+    extensions[name] = ext;
260
+  } else {
261
+    throw Error(validExtension.error);
262
+  }
263
+};
264
+
265
+/**
266
+ * Gets all extensions registered
267
+ * @returns {{}}
268
+ */
269
+showdown.getAllExtensions = function () {
270
+  return extensions;
271
+};
272
+
273
+/**
274
+ * Remove an extension
275
+ * @param {string} name
276
+ */
277
+showdown.removeExtension = function (name) {
278
+  delete extensions[name];
279
+};
280
+
281
+/**
282
+ * Removes all extensions
283
+ */
284
+showdown.resetExtensions = function () {
285
+  extensions = {};
286
+};
287
+
288
+/**
289
+ * Validate extension
290
+ * @param {array} extension
291
+ * @param {string} name
292
+ * @returns {{valid: boolean, error: string}}
293
+ */
294
+function validate(extension, name) {
295
+  const errMsg = (name) ? `Error in ${name} extension->` : 'Error in unnamed extension';
296
+  const ret = {
297
+    valid: true,
298
+    error: '',
299
+  };
300
+
301
+  if (!showdown.helper.isArray(extension)) {
302
+    extension = [extension];
303
+  }
304
+
305
+  for (let i = 0; i < extension.length; ++i) {
306
+    const baseMsg = `${errMsg} sub-extension ${i}: `;
307
+    const ext = extension[i];
308
+    if (typeof ext !== 'object') {
309
+      ret.valid = false;
310
+      ret.error = `${baseMsg}must be an object, but ${typeof ext} given`;
311
+      return ret;
312
+    }
313
+
314
+    if (!showdown.helper.isString(ext.type)) {
315
+      ret.valid = false;
316
+      ret.error = `${baseMsg}property "type" must be a string, but ${typeof ext.type} given`;
317
+      return ret;
318
+    }
319
+
320
+    let type = ext.type = ext.type.toLowerCase();
321
+
322
+    // normalize extension type
323
+    if (type === 'language') {
324
+      type = ext.type = 'lang';
325
+    }
326
+
327
+    if (type === 'html') {
328
+      type = ext.type = 'output';
329
+    }
330
+
331
+    if (type !== 'lang' && type !== 'output' && type !== 'listener') {
332
+      ret.valid = false;
333
+      ret.error = `${baseMsg}type ${type} is not recognized. Valid values: "lang/language", "output/html" or "listener"`;
334
+      return ret;
335
+    }
336
+
337
+    if (type === 'listener') {
338
+      if (showdown.helper.isUndefined(ext.listeners)) {
339
+        ret.valid = false;
340
+        ret.error = `${baseMsg}. Extensions of type "listener" must have a property called "listeners"`;
341
+        return ret;
342
+      }
343
+    } else if (showdown.helper.isUndefined(ext.filter) && showdown.helper.isUndefined(ext.regex)) {
344
+      ret.valid = false;
345
+      ret.error = `${baseMsg + type} extensions must define either a "regex" property or a "filter" method`;
346
+      return ret;
347
+    }
348
+
349
+    if (ext.listeners) {
350
+      if (typeof ext.listeners !== 'object') {
351
+        ret.valid = false;
352
+        ret.error = `${baseMsg}"listeners" property must be an object but ${typeof ext.listeners} given`;
353
+        return ret;
354
+      }
355
+      for (const ln in ext.listeners) {
356
+        if (ext.listeners.hasOwnProperty(ln)) {
357
+          if (typeof ext.listeners[ln] !== 'function') {
358
+            ret.valid = false;
359
+            ret.error = `${baseMsg}"listeners" property must be an hash of [event name]: [callback]. listeners.${ln
360
+            } must be a function but ${typeof ext.listeners[ln]} given`;
361
+            return ret;
362
+          }
363
+        }
364
+      }
365
+    }
366
+
367
+    if (ext.filter) {
368
+      if (typeof ext.filter !== 'function') {
369
+        ret.valid = false;
370
+        ret.error = `${baseMsg}"filter" must be a function, but ${typeof ext.filter} given`;
371
+        return ret;
372
+      }
373
+    } else if (ext.regex) {
374
+      if (showdown.helper.isString(ext.regex)) {
375
+        ext.regex = new RegExp(ext.regex, 'g');
376
+      }
377
+      if (!ext.regex instanceof RegExp) {
378
+        ret.valid = false;
379
+        ret.error = `${baseMsg}"regex" property must either be a string or a RegExp object, but ${typeof ext.regex} given`;
380
+        return ret;
381
+      }
382
+      if (showdown.helper.isUndefined(ext.replace)) {
383
+        ret.valid = false;
384
+        ret.error = `${baseMsg}"regex" extensions must implement a replace string or function`;
385
+        return ret;
386
+      }
387
+    }
388
+  }
389
+  return ret;
390
+}
391
+
392
+/**
393
+ * Validate extension
394
+ * @param {object} ext
395
+ * @returns {boolean}
396
+ */
397
+showdown.validateExtension = function (ext) {
398
+  const validateExtension = validate(ext, null);
399
+  if (!validateExtension.valid) {
400
+    console.warn(validateExtension.error);
401
+    return false;
402
+  }
403
+  return true;
404
+};
405
+
406
+/**
407
+ * showdownjs helper functions
408
+ */
409
+
410
+if (!showdown.hasOwnProperty('helper')) {
411
+  showdown.helper = {};
412
+}
413
+
414
+/**
415
+ * Check if var is string
416
+ * @static
417
+ * @param {string} a
418
+ * @returns {boolean}
419
+ */
420
+showdown.helper.isString = function isString(a) {
421
+  return (typeof a === 'string' || a instanceof String);
422
+};
423
+
424
+/**
425
+ * Check if var is a function
426
+ * @static
427
+ * @param {string} a
428
+ * @returns {boolean}
429
+ */
430
+showdown.helper.isFunction = function isFunction(a) {
431
+  const getType = {};
432
+  return a && getType.toString.call(a) === '[object Function]';
433
+};
434
+
435
+/**
436
+ * ForEach helper function
437
+ * @static
438
+ * @param {*} obj
439
+ * @param {function} callback
440
+ */
441
+showdown.helper.forEach = function forEach(obj, callback) {
442
+  if (typeof obj.forEach === 'function') {
443
+    obj.forEach(callback);
444
+  } else {
445
+    for (let i = 0; i < obj.length; i++) {
446
+      callback(obj[i], i, obj);
447
+    }
448
+  }
449
+};
450
+
451
+/**
452
+ * isArray helper function
453
+ * @static
454
+ * @param {*} a
455
+ * @returns {boolean}
456
+ */
457
+showdown.helper.isArray = function isArray(a) {
458
+  return a.constructor === Array;
459
+};
460
+
461
+/**
462
+ * Check if value is undefined
463
+ * @static
464
+ * @param {*} value The value to check.
465
+ * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.
466
+ */
467
+showdown.helper.isUndefined = function isUndefined(value) {
468
+  return typeof value === 'undefined';
469
+};
470
+
471
+/**
472
+ * Standardidize extension name
473
+ * @static
474
+ * @param {string} s extension name
475
+ * @returns {string}
476
+ */
477
+showdown.helper.stdExtName = function (s) {
478
+  return s.replace(/[_-]||\s/g, '').toLowerCase();
479
+};
480
+
481
+function escapeCharactersCallback(wholeMatch, m1) {
482
+  const charCodeToEscape = m1.charCodeAt(0);
483
+  return `~E${charCodeToEscape}E`;
484
+}
485
+
486
+/**
487
+ * Callback used to escape characters when passing through String.replace
488
+ * @static
489
+ * @param {string} wholeMatch
490
+ * @param {string} m1
491
+ * @returns {string}
492
+ */
493
+showdown.helper.escapeCharactersCallback = escapeCharactersCallback;
494
+
495
+/**
496
+ * Escape characters in a string
497
+ * @static
498
+ * @param {string} text
499
+ * @param {string} charsToEscape
500
+ * @param {boolean} afterBackslash
501
+ * @returns {XML|string|void|*}
502
+ */
503
+showdown.helper.escapeCharacters = function escapeCharacters(text, charsToEscape, afterBackslash) {
504
+  // First we have to escape the escape characters so that
505
+  // we can build a character class out of them
506
+  let regexString = `([${charsToEscape.replace(/([\[\]\\])/g, '\\$1')}])`;
507
+
508
+  if (afterBackslash) {
509
+    regexString = `\\\\${regexString}`;
510
+  }
511
+
512
+  const regex = new RegExp(regexString, 'g');
513
+  text = text.replace(regex, escapeCharactersCallback);
514
+
515
+  return text;
516
+};
517
+
518
+const rgxFindMatchPos = function (str, left, right, flags) {
519
+  const f = flags || '';
520
+  const g = f.indexOf('g') > -1;
521
+  const x = new RegExp(`${left}|${right}`, `g${f.replace(/g/g, '')}`);
522
+  const l = new RegExp(left, f.replace(/g/g, ''));
523
+  const pos = [];
524
+  let t; let s; let m; let start; let
525
+    end;
526
+
527
+  do {
528
+    t = 0;
529
+    while ((m = x.exec(str))) {
530
+      if (l.test(m[0])) {
531
+        if (!(t++)) {
532
+          s = x.lastIndex;
533
+          start = s - m[0].length;
534
+        }
535
+      } else if (t) {
536
+        if (!--t) {
537
+          end = m.index + m[0].length;
538
+          const obj = {
539
+            left: { start, end: s },
540
+            match: { start: s, end: m.index },
541
+            right: { start: m.index, end },
542
+            wholeMatch: { start, end },
543
+          };
544
+          pos.push(obj);
545
+          if (!g) {
546
+            return pos;
547
+          }
548
+        }
549
+      }
550
+    }
551
+  } while (t && (x.lastIndex = s));
552
+
553
+  return pos;
554
+};
555
+
556
+/**
557
+ * matchRecursiveRegExp
558
+ *
559
+ * (c) 2007 Steven Levithan <stevenlevithan.com>
560
+ * MIT License
561
+ *
562
+ * Accepts a string to search, a left and right format delimiter
563
+ * as regex patterns, and optional regex flags. Returns an array
564
+ * of matches, allowing nested instances of left/right delimiters.
565
+ * Use the "g" flag to return all matches, otherwise only the
566
+ * first is returned. Be careful to ensure that the left and
567
+ * right format delimiters produce mutually exclusive matches.
568
+ * Backreferences are not supported within the right delimiter
569
+ * due to how it is internally combined with the left delimiter.
570
+ * When matching strings whose format delimiters are unbalanced
571
+ * to the left or right, the output is intentionally as a
572
+ * conventional regex library with recursion support would
573
+ * produce, e.g. "<<x>" and "<x>>" both produce ["x"] when using
574
+ * "<" and ">" as the delimiters (both strings contain a single,
575
+ * balanced instance of "<x>").
576
+ *
577
+ * examples:
578
+ * matchRecursiveRegExp("test", "\\(", "\\)")
579
+ * returns: []
580
+ * matchRecursiveRegExp("<t<<e>><s>>t<>", "<", ">", "g")
581
+ * returns: ["t<<e>><s>", ""]
582
+ * matchRecursiveRegExp("<div id=\"x\">test</div>", "<div\\b[^>]*>", "</div>", "gi")
583
+ * returns: ["test"]
584
+ */
585
+showdown.helper.matchRecursiveRegExp = function (str, left, right, flags) {
586
+  const matchPos = rgxFindMatchPos(str, left, right, flags);
587
+  const results = [];
588
+
589
+  for (let i = 0; i < matchPos.length; ++i) {
590
+    results.push([
591
+      str.slice(matchPos[i].wholeMatch.start, matchPos[i].wholeMatch.end),
592
+      str.slice(matchPos[i].match.start, matchPos[i].match.end),
593
+      str.slice(matchPos[i].left.start, matchPos[i].left.end),
594
+      str.slice(matchPos[i].right.start, matchPos[i].right.end),
595
+    ]);
596
+  }
597
+  return results;
598
+};
599
+
600
+/**
601
+ *
602
+ * @param {string} str
603
+ * @param {string|function} replacement
604
+ * @param {string} left
605
+ * @param {string} right
606
+ * @param {string} flags
607
+ * @returns {string}
608
+ */
609
+showdown.helper.replaceRecursiveRegExp = function (str, replacement, left, right, flags) {
610
+  if (!showdown.helper.isFunction(replacement)) {
611
+    const repStr = replacement;
612
+    replacement = function () {
613
+      return repStr;
614
+    };
615
+  }
616
+
617
+  const matchPos = rgxFindMatchPos(str, left, right, flags);
618
+  let finalStr = str;
619
+  const lng = matchPos.length;
620
+
621
+  if (lng > 0) {
622
+    const bits = [];
623
+    if (matchPos[0].wholeMatch.start !== 0) {
624
+      bits.push(str.slice(0, matchPos[0].wholeMatch.start));
625
+    }
626
+    for (let i = 0; i < lng; ++i) {
627
+      bits.push(
628
+        replacement(
629
+          str.slice(matchPos[i].wholeMatch.start, matchPos[i].wholeMatch.end),
630
+          str.slice(matchPos[i].match.start, matchPos[i].match.end),
631
+          str.slice(matchPos[i].left.start, matchPos[i].left.end),
632
+          str.slice(matchPos[i].right.start, matchPos[i].right.end),
633
+        ),
634
+      );
635
+      if (i < lng - 1) {
636
+        bits.push(str.slice(matchPos[i].wholeMatch.end, matchPos[i + 1].wholeMatch.start));
637
+      }
638
+    }
639
+    if (matchPos[lng - 1].wholeMatch.end < str.length) {
640
+      bits.push(str.slice(matchPos[lng - 1].wholeMatch.end));
641
+    }
642
+    finalStr = bits.join('');
643
+  }
644
+  return finalStr;
645
+};
646
+
647
+/**
648
+ * POLYFILLS
649
+ */
650
+if (showdown.helper.isUndefined(console)) {
651
+  console = {
652
+    warn(msg) {
653
+      alert(msg);
654
+    },
655
+    log(msg) {
656
+      alert(msg);
657
+    },
658
+    error(msg) {
659
+      throw msg;
660
+    },
661
+  };
662
+}
663
+
664
+/**
665
+ * Created by Estevao on 31-05-2015.
666
+ */
667
+
668
+/**
669
+ * Showdown Converter class
670
+ * @class
671
+ * @param {object} [converterOptions]
672
+ * @returns {Converter}
673
+ */
674
+showdown.Converter = function (converterOptions) {
675
+  const
676
+    /**
677
+       * Options used by this converter
678
+       * @private
679
+       * @type {{}}
680
+       */
681
+    options = {};
682
+
683
+  /**
684
+       * Language extensions used by this converter
685
+       * @private
686
+       * @type {Array}
687
+       */
688
+  const langExtensions = [];
689
+
690
+  /**
691
+       * Output modifiers extensions used by this converter
692
+       * @private
693
+       * @type {Array}
694
+       */
695
+  const outputModifiers = [];
696
+
697
+  /**
698
+       * Event listeners
699
+       * @private
700
+       * @type {{}}
701
+       */
702
+  const listeners = {};
703
+
704
+  _constructor();
705
+
706
+  /**
707
+   * Converter constructor
708
+   * @private
709
+   */
710
+  function _constructor() {
711
+    converterOptions = converterOptions || {};
712
+
713
+    for (const gOpt in globalOptions) {
714
+      if (globalOptions.hasOwnProperty(gOpt)) {
715
+        options[gOpt] = globalOptions[gOpt];
716
+      }
717
+    }
718
+
719
+    // Merge options
720
+    if (typeof converterOptions === 'object') {
721
+      for (const opt in converterOptions) {
722
+        if (converterOptions.hasOwnProperty(opt)) {
723
+          options[opt] = converterOptions[opt];
724
+        }
725
+      }
726
+    } else {
727
+      throw Error(`Converter expects the passed parameter to be an object, but ${typeof converterOptions
728
+      } was passed instead.`);
729
+    }
730
+
731
+    if (options.extensions) {
732
+      showdown.helper.forEach(options.extensions, _parseExtension);
733
+    }
734
+  }
735
+
736
+  /**
737
+   * Parse extension
738
+   * @param {*} ext
739
+   * @param {string} [name='']
740
+   * @private
741
+   */
742
+  function _parseExtension(ext, name) {
743
+    name = name || null;
744
+    // If it's a string, the extension was previously loaded
745
+    if (showdown.helper.isString(ext)) {
746
+      ext = showdown.helper.stdExtName(ext);
747
+      name = ext;
748
+
749
+      // LEGACY_SUPPORT CODE
750
+      if (showdown.extensions[ext]) {
751
+        console.warn(`DEPRECATION WARNING: ${ext} is an old extension that uses a deprecated loading method.`
752
+          + 'Please inform the developer that the extension should be updated!');
753
+        legacyExtensionLoading(showdown.extensions[ext], ext);
754
+        return;
755
+        // END LEGACY SUPPORT CODE
756
+      } if (!showdown.helper.isUndefined(extensions[ext])) {
757
+        ext = extensions[ext];
758
+      } else {
759
+        throw Error(`Extension "${ext}" could not be loaded. It was either not found or is not a valid extension.`);
760
+      }
761
+    }
762
+
763
+    if (typeof ext === 'function') {
764
+      ext = ext();
765
+    }
766
+
767
+    if (!showdown.helper.isArray(ext)) {
768
+      ext = [ext];
769
+    }
770
+
771
+    const validExt = validate(ext, name);
772
+    if (!validExt.valid) {
773
+      throw Error(validExt.error);
774
+    }
775
+
776
+    for (let i = 0; i < ext.length; ++i) {
777
+      switch (ext[i].type) {
778
+        case 'lang':
779
+          langExtensions.push(ext[i]);
780
+          break;
781
+
782
+        case 'output':
783
+          outputModifiers.push(ext[i]);
784
+          break;
785
+      }
786
+      if (ext[i].hasOwnProperty(listeners)) {
787
+        for (const ln in ext[i].listeners) {
788
+          if (ext[i].listeners.hasOwnProperty(ln)) {
789
+            listen(ln, ext[i].listeners[ln]);
790
+          }
791
+        }
792
+      }
793
+    }
794
+  }
795
+
796
+  /**
797
+   * LEGACY_SUPPORT
798
+   * @param {*} ext
799
+   * @param {string} name
800
+   */
801
+  function legacyExtensionLoading(ext, name) {
802
+    if (typeof ext === 'function') {
803
+      ext = ext(new showdown.Converter());
804
+    }
805
+    if (!showdown.helper.isArray(ext)) {
806
+      ext = [ext];
807
+    }
808
+    const valid = validate(ext, name);
809
+
810
+    if (!valid.valid) {
811
+      throw Error(valid.error);
812
+    }
813
+
814
+    for (let i = 0; i < ext.length; ++i) {
815
+      switch (ext[i].type) {
816
+        case 'lang':
817
+          langExtensions.push(ext[i]);
818
+          break;
819
+        case 'output':
820
+          outputModifiers.push(ext[i]);
821
+          break;
822
+        default:// should never reach here
823
+          throw Error('Extension loader error: Type unrecognized!!!');
824
+      }
825
+    }
826
+  }
827
+
828
+  /**
829
+   * Listen to an event
830
+   * @param {string} name
831
+   * @param {function} callback
832
+   */
833
+  function listen(name, callback) {
834
+    if (!showdown.helper.isString(name)) {
835
+      throw Error(`Invalid argument in converter.listen() method: name must be a string, but ${typeof name} given`);
836
+    }
837
+
838
+    if (typeof callback !== 'function') {
839
+      throw Error(`Invalid argument in converter.listen() method: callback must be a function, but ${typeof callback} given`);
840
+    }
841
+
842
+    if (!listeners.hasOwnProperty(name)) {
843
+      listeners[name] = [];
844
+    }
845
+    listeners[name].push(callback);
846
+  }
847
+
848
+  function rTrimInputText(text) {
849
+    const rsp = text.match(/^\s*/)[0].length;
850
+    const rgx = new RegExp(`^\\s{0,${rsp}}`, 'gm');
851
+    return text.replace(rgx, '');
852
+  }
853
+
854
+  /**
855
+   * Dispatch an event
856
+   * @private
857
+   * @param {string} evtName Event name
858
+   * @param {string} text Text
859
+   * @param {{}} options Converter Options
860
+   * @param {{}} globals
861
+   * @returns {string}
862
+   */
863
+  this._dispatch = function dispatch(evtName, text, options, globals) {
864
+    if (listeners.hasOwnProperty(evtName)) {
865
+      for (let ei = 0; ei < listeners[evtName].length; ++ei) {
866
+        const nText = listeners[evtName][ei](evtName, text, this, options, globals);
867
+        if (nText && typeof nText !== 'undefined') {
868
+          text = nText;
869
+        }
870
+      }
871
+    }
872
+    return text;
873
+  };
874
+
875
+  /**
876
+   * Listen to an event
877
+   * @param {string} name
878
+   * @param {function} callback
879
+   * @returns {showdown.Converter}
880
+   */
881
+  this.listen = function (name, callback) {
882
+    listen(name, callback);
883
+    return this;
884
+  };
885
+
886
+  /**
887
+   * Converts a markdown string into HTML
888
+   * @param {string} text
889
+   * @returns {*}
890
+   */
891
+  this.makeHtml = function (text) {
892
+    // check if text is not falsy
893
+    if (!text) {
894
+      return text;
895
+    }
896
+
897
+    const globals = {
898
+      gHtmlBlocks: [],
899
+      gHtmlMdBlocks: [],
900
+      gHtmlSpans: [],
901
+      gUrls: {},
902
+      gTitles: {},
903
+      gDimensions: {},
904
+      gListLevel: 0,
905
+      hashLinkCounts: {},
906
+      langExtensions,
907
+      outputModifiers,
908
+      converter: this,
909
+      ghCodeBlocks: [],
910
+    };
911
+
912
+    // attacklab: Replace ~ with ~T
913
+    // This lets us use tilde as an escape char to avoid md5 hashes
914
+    // The choice of character is arbitrary; anything that isn't
915
+    // magic in Markdown will work.
916
+    text = text.replace(/~/g, '~T');
917
+
918
+    // attacklab: Replace $ with ~D
919
+    // RegExp interprets $ as a special character
920
+    // when it's in a replacement string
921
+    text = text.replace(/\$/g, '~D');
922
+
923
+    // Standardize line endings
924
+    text = text.replace(/\r\n/g, '\n'); // DOS to Unix
925
+    text = text.replace(/\r/g, '\n'); // Mac to Unix
926
+
927
+    if (options.smartIndentationFix) {
928
+      text = rTrimInputText(text);
929
+    }
930
+
931
+    // Make sure text begins and ends with a couple of newlines:
932
+    // text = '\n\n' + text + '\n\n';
933
+    text = text;
934
+    // detab
935
+    text = showdown.subParser('detab')(text, options, globals);
936
+
937
+    // stripBlankLines
938
+    text = showdown.subParser('stripBlankLines')(text, options, globals);
939
+
940
+    // run languageExtensions
941
+    showdown.helper.forEach(langExtensions, (ext) => {
942
+      text = showdown.subParser('runExtension')(ext, text, options, globals);
943
+    });
944
+
945
+    // run the sub parsers
946
+    text = showdown.subParser('hashPreCodeTags')(text, options, globals);
947
+    text = showdown.subParser('githubCodeBlocks')(text, options, globals);
948
+    text = showdown.subParser('hashHTMLBlocks')(text, options, globals);
949
+    text = showdown.subParser('hashHTMLSpans')(text, options, globals);
950
+    text = showdown.subParser('stripLinkDefinitions')(text, options, globals);
951
+    text = showdown.subParser('blockGamut')(text, options, globals);
952
+    text = showdown.subParser('unhashHTMLSpans')(text, options, globals);
953
+    text = showdown.subParser('unescapeSpecialChars')(text, options, globals);
954
+
955
+    // attacklab: Restore dollar signs
956
+    text = text.replace(/~D/g, '$$');
957
+
958
+    // attacklab: Restore tildes
959
+    text = text.replace(/~T/g, '~');
960
+
961
+    // Run output modifiers
962
+    showdown.helper.forEach(outputModifiers, (ext) => {
963
+      text = showdown.subParser('runExtension')(ext, text, options, globals);
964
+    });
965
+    return text;
966
+  };
967
+
968
+  /**
969
+   * Set an option of this Converter instance
970
+   * @param {string} key
971
+   * @param {*} value
972
+   */
973
+  this.setOption = function (key, value) {
974
+    options[key] = value;
975
+  };
976
+
977
+  /**
978
+   * Get the option of this Converter instance
979
+   * @param {string} key
980
+   * @returns {*}
981
+   */
982
+  this.getOption = function (key) {
983
+    return options[key];
984
+  };
985
+
986
+  /**
987
+   * Get the options of this Converter instance
988
+   * @returns {{}}
989
+   */
990
+  this.getOptions = function () {
991
+    return options;
992
+  };
993
+
994
+  /**
995
+   * Add extension to THIS converter
996
+   * @param {{}} extension
997
+   * @param {string} [name=null]
998
+   */
999
+  this.addExtension = function (extension, name) {
1000
+    name = name || null;
1001
+    _parseExtension(extension, name);
1002
+  };
1003
+
1004
+  /**
1005
+   * Use a global registered extension with THIS converter
1006
+   * @param {string} extensionName Name of the previously registered extension
1007
+   */
1008
+  this.useExtension = function (extensionName) {
1009
+    _parseExtension(extensionName);
1010
+  };
1011
+
1012
+  /**
1013
+   * Set the flavor THIS converter should use
1014
+   * @param {string} name
1015
+   */
1016
+  this.setFlavor = function (name) {
1017
+    if (flavor.hasOwnProperty(name)) {
1018
+      const preset = flavor[name];
1019
+      for (const option in preset) {
1020
+        if (preset.hasOwnProperty(option)) {
1021
+          options[option] = preset[option];
1022
+        }
1023
+      }
1024
+    }
1025
+  };
1026
+
1027
+  /**
1028
+   * Remove an extension from THIS converter.
1029
+   * Note: This is a costly operation. It's better to initialize a new converter
1030
+   * and specify the extensions you wish to use
1031
+   * @param {Array} extension
1032
+   */
1033
+  this.removeExtension = function (extension) {
1034
+    if (!showdown.helper.isArray(extension)) {
1035
+      extension = [extension];
1036
+    }
1037
+    for (let a = 0; a < extension.length; ++a) {
1038
+      const ext = extension[a];
1039
+      for (var i = 0; i < langExtensions.length; ++i) {
1040
+        if (langExtensions[i] === ext) {
1041
+          langExtensions[i].splice(i, 1);
1042
+        }
1043
+      }
1044
+      for (let ii = 0; ii < outputModifiers.length; ++i) {
1045
+        if (outputModifiers[ii] === ext) {
1046
+          outputModifiers[ii].splice(i, 1);
1047
+        }
1048
+      }
1049
+    }
1050
+  };
1051
+
1052
+  /**
1053
+   * Get all extension of THIS converter
1054
+   * @returns {{language: Array, output: Array}}
1055
+   */
1056
+  this.getAllExtensions = function () {
1057
+    return {
1058
+      language: langExtensions,
1059
+      output: outputModifiers,
1060
+    };
1061
+  };
1062
+};
1063
+
1064
+/**
1065
+ * Turn Markdown link shortcuts into XHTML <a> tags.
1066
+ */
1067
+showdown.subParser('anchors', (text, options, globals) => {
1068
+  text = globals.converter._dispatch('anchors.before', text, options, globals);
1069
+
1070
+  const writeAnchorTag = function (wholeMatch, m1, m2, m3, m4, m5, m6, m7) {
1071
+    if (showdown.helper.isUndefined(m7)) {
1072
+      m7 = '';
1073
+    }
1074
+    wholeMatch = m1;
1075
+    const linkText = m2;
1076
+    let linkId = m3.toLowerCase();
1077
+    let url = m4;
1078
+    let title = m7;
1079
+
1080
+    if (!url) {
1081
+      if (!linkId) {
1082
+        // lower-case and turn embedded newlines into spaces
1083
+        linkId = linkText.toLowerCase().replace(/ ?\n/g, ' ');
1084
+      }
1085
+      url = `#${linkId}`;
1086
+
1087
+      if (!showdown.helper.isUndefined(globals.gUrls[linkId])) {
1088
+        url = globals.gUrls[linkId];
1089
+        if (!showdown.helper.isUndefined(globals.gTitles[linkId])) {
1090
+          title = globals.gTitles[linkId];
1091
+        }
1092
+      } else if (wholeMatch.search(/\(\s*\)$/m) > -1) {
1093
+        // Special case for explicit empty url
1094
+        url = '';
1095
+      } else {
1096
+        return wholeMatch;
1097
+      }
1098
+    }
1099
+
1100
+    url = showdown.helper.escapeCharacters(url, '*_', false);
1101
+    let result = `<a href="${url}"`;
1102
+
1103
+    if (title !== '' && title !== null) {
1104
+      title = title.replace(/"/g, '&quot;');
1105
+      title = showdown.helper.escapeCharacters(title, '*_', false);
1106
+      result += ` title="${title}"`;
1107
+    }
1108
+
1109
+    result += `>${linkText}</a>`;
1110
+
1111
+    return result;
1112
+  };
1113
+
1114
+  // First, handle reference-style links: [link text] [id]
1115
+  /*
1116
+   text = text.replace(/
1117
+   (							// wrap whole match in $1
1118
+   \[
1119
+   (
1120
+   (?:
1121
+   \[[^\]]*\]		// allow brackets nested one level
1122
+   |
1123
+   [^\[]			// or anything else
1124
+   )*
1125
+   )
1126
+   \]
1127
+
1128
+   [ ]?					// one optional space
1129
+   (?:\n[ ]*)?				// one optional newline followed by spaces
1130
+
1131
+   \[
1132
+   (.*?)					// id = $3
1133
+   \]
1134
+   )()()()()					// pad remaining backreferences
1135
+   /g,_DoAnchors_callback);
1136
+   */
1137
+  text = text.replace(/(\[((?:\[[^\]]*]|[^\[\]])*)][ ]?(?:\n[ ]*)?\[(.*?)])()()()()/g, writeAnchorTag);
1138
+
1139
+  //
1140
+  // Next, inline-style links: [link text](url "optional title")
1141
+  //
1142
+
1143
+  /*
1144
+   text = text.replace(/
1145
+   (						// wrap whole match in $1
1146
+   \[
1147
+   (
1148
+   (?:
1149
+   \[[^\]]*\]	// allow brackets nested one level
1150
+   |
1151
+   [^\[\]]			// or anything else
1152
+   )
1153
+   )
1154
+   \]
1155
+   \(						// literal paren
1156
+   [ \t]*
1157
+   ()						// no id, so leave $3 empty
1158
+   <?(.*?)>?				// href = $4
1159
+   [ \t]*
1160
+   (						// $5
1161
+   (['"])				// quote char = $6
1162
+   (.*?)				// Title = $7
1163
+   \6					// matching quote
1164
+   [ \t]*				// ignore any spaces/tabs between closing quote and )
1165
+   )?						// title is optional
1166
+   \)
1167
+   )
1168
+   /g,writeAnchorTag);
1169
+   */
1170
+  text = text.replace(/(\[((?:\[[^\]]*]|[^\[\]])*)]\([ \t]*()<?(.*?(?:\(.*?\).*?)?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,
1171
+    writeAnchorTag);
1172
+
1173
+  //
1174
+  // Last, handle reference-style shortcuts: [link text]
1175
+  // These must come last in case you've also got [link test][1]
1176
+  // or [link test](/foo)
1177
+  //
1178
+
1179
+  /*
1180
+   text = text.replace(/
1181
+   (                // wrap whole match in $1
1182
+   \[
1183
+   ([^\[\]]+)       // link text = $2; can't contain '[' or ']'
1184
+   \]
1185
+   )()()()()()      // pad rest of backreferences
1186
+   /g, writeAnchorTag);
1187
+   */
1188
+  text = text.replace(/(\[([^\[\]]+)])()()()()()/g, writeAnchorTag);
1189
+
1190
+  text = globals.converter._dispatch('anchors.after', text, options, globals);
1191
+  return text;
1192
+});
1193
+
1194
+showdown.subParser('autoLinks', (text, options, globals) => {
1195
+  text = globals.converter._dispatch('autoLinks.before', text, options, globals);
1196
+
1197
+  const simpleURLRegex = /\b(((https?|ftp|dict):\/\/|www\.)[^'">\s]+\.[^'">\s]+)(?=\s|$)(?!["<>])/gi;
1198
+  const delimUrlRegex = /<(((https?|ftp|dict):\/\/|www\.)[^'">\s]+)>/gi;
1199
+  const simpleMailRegex = /(?:^|[ \n\t])([A-Za-z0-9!#$%&'*+-/=?^_`\{|}~\.]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)(?:$|[ \n\t])/gi;
1200
+  const delimMailRegex = /<(?:mailto:)?([-.\w]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi;
1201
+
1202
+  text = text.replace(delimUrlRegex, replaceLink);
1203
+  text = text.replace(delimMailRegex, replaceMail);
1204
+  // simpleURLRegex  = /\b(((https?|ftp|dict):\/\/|www\.)[-.+~:?#@!$&'()*,;=[\]\w]+)\b/gi,
1205
+  // Email addresses: <address@domain.foo>
1206
+
1207
+  if (options.simplifiedAutoLink) {
1208
+    text = text.replace(simpleURLRegex, replaceLink);
1209
+    text = text.replace(simpleMailRegex, replaceMail);
1210
+  }
1211
+
1212
+  function replaceLink(wm, link) {
1213
+    const lnkTxt = link;
1214
+    if (/^www\./i.test(link)) {
1215
+      link = link.replace(/^www\./i, 'http://www.');
1216
+    }
1217
+    return `<a href="${link}">${lnkTxt}</a>`;
1218
+  }
1219
+
1220
+  function replaceMail(wholeMatch, m1) {
1221
+    const unescapedStr = showdown.subParser('unescapeSpecialChars')(m1);
1222
+    return showdown.subParser('encodeEmailAddress')(unescapedStr);
1223
+  }
1224
+
1225
+  text = globals.converter._dispatch('autoLinks.after', text, options, globals);
1226
+
1227
+  return text;
1228
+});
1229
+
1230
+/**
1231
+ * These are all the transformations that form block-level
1232
+ * tags like paragraphs, headers, and list items.
1233
+ */
1234
+showdown.subParser('blockGamut', (text, options, globals) => {
1235
+  text = globals.converter._dispatch('blockGamut.before', text, options, globals);
1236
+
1237
+  // we parse blockquotes first so that we can have headings and hrs
1238
+  // inside blockquotes
1239
+  text = showdown.subParser('blockQuotes')(text, options, globals);
1240
+  text = showdown.subParser('headers')(text, options, globals);
1241
+
1242
+  // Do Horizontal Rules:
1243
+  const key = showdown.subParser('hashBlock')('<hr />', options, globals);
1244
+  text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, key);
1245
+  text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm, key);
1246
+  text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm, key);
1247
+
1248
+  text = showdown.subParser('lists')(text, options, globals);
1249
+  text = showdown.subParser('codeBlocks')(text, options, globals);
1250
+  text = showdown.subParser('tables')(text, options, globals);
1251
+
1252
+  // We already ran _HashHTMLBlocks() before, in Markdown(), but that
1253
+  // was to escape raw HTML in the original Markdown source. This time,
1254
+  // we're escaping the markup we've just created, so that we don't wrap
1255
+  // <p> tags around block-level tags.
1256
+  text = showdown.subParser('hashHTMLBlocks')(text, options, globals);
1257
+  text = showdown.subParser('paragraphs')(text, options, globals);
1258
+
1259
+  text = globals.converter._dispatch('blockGamut.after', text, options, globals);
1260
+
1261
+  return text;
1262
+});
1263
+
1264
+showdown.subParser('blockQuotes', (text, options, globals) => {
1265
+  text = globals.converter._dispatch('blockQuotes.before', text, options, globals);
1266
+  /*
1267
+   text = text.replace(/
1268
+   (								// Wrap whole match in $1
1269
+   (
1270
+   ^[ \t]*>[ \t]?			// '>' at the start of a line
1271
+   .+\n					// rest of the first line
1272
+   (.+\n)*					// subsequent consecutive lines
1273
+   \n*						// blanks
1274
+   )+
1275
+   )
1276
+   /gm, function(){...});
1277
+   */
1278
+
1279
+  text = text.replace(/((^[ \t]{0,3}>[ \t]?.+\n(.+\n)*\n*)+)/gm, (wholeMatch, m1) => {
1280
+    let bq = m1;
1281
+
1282
+    // attacklab: hack around Konqueror 3.5.4 bug:
1283
+    // "----------bug".replace(/^-/g,"") == "bug"
1284
+    bq = bq.replace(/^[ \t]*>[ \t]?/gm, '~0'); // trim one level of quoting
1285
+
1286
+    // attacklab: clean up hack
1287
+    bq = bq.replace(/~0/g, '');
1288
+
1289
+    bq = bq.replace(/^[ \t]+$/gm, ''); // trim whitespace-only lines
1290
+    bq = showdown.subParser('githubCodeBlocks')(bq, options, globals);
1291
+    bq = showdown.subParser('blockGamut')(bq, options, globals); // recurse
1292
+
1293
+    bq = bq.replace(/(^|\n)/g, '$1  ');
1294
+    // These leading spaces screw with <pre> content, so we need to fix that:
1295
+    bq = bq.replace(/(\s*<pre>[^\r]+?<\/pre>)/gm, (wholeMatch, m1) => {
1296
+      let pre = m1;
1297
+      // attacklab: hack around Konqueror 3.5.4 bug:
1298
+      pre = pre.replace(/^ {2}/mg, '~0');
1299
+      pre = pre.replace(/~0/g, '');
1300
+      return pre;
1301
+    });
1302
+
1303
+    return showdown.subParser('hashBlock')(`<blockquote>\n${bq}\n</blockquote>`, options, globals);
1304
+  });
1305
+
1306
+  text = globals.converter._dispatch('blockQuotes.after', text, options, globals);
1307
+  return text;
1308
+});
1309
+
1310
+/**
1311
+ * Process Markdown `<pre><code>` blocks.
1312
+ */
1313
+showdown.subParser('codeBlocks', (text, options, globals) => {
1314
+  text = globals.converter._dispatch('codeBlocks.before', text, options, globals);
1315
+  /*
1316
+   text = text.replace(text,
1317
+   /(?:\n\n|^)
1318
+   (								// $1 = the code block -- one or more lines, starting with a space/tab
1319
+   (?:
1320
+   (?:[ ]{4}|\t)			// Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
1321
+   .*\n+
1322
+   )+
1323
+   )
1324
+   (\n*[ ]{0,3}[^ \t\n]|(?=~0))	// attacklab: g_tab_width
1325
+   /g,function(){...});
1326
+   */
1327
+
1328
+  // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
1329
+  text += '~0';
1330
+
1331
+  const pattern = /(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g;
1332
+  text = text.replace(pattern, (wholeMatch, m1, m2) => {
1333
+    let codeblock = m1;
1334
+    const nextChar = m2;
1335
+    let end = '\n';
1336
+
1337
+    codeblock = showdown.subParser('outdent')(codeblock);
1338
+    codeblock = showdown.subParser('encodeCode')(codeblock);
1339
+    codeblock = showdown.subParser('detab')(codeblock);
1340
+    codeblock = codeblock.replace(/^\n+/g, ''); // trim leading newlines
1341
+    codeblock = codeblock.replace(/\n+$/g, ''); // trim trailing newlines
1342
+
1343
+    if (options.omitExtraWLInCodeBlocks) {
1344
+      end = '';
1345
+    }
1346
+
1347
+    codeblock = `<pre><code>${codeblock}${end}</code></pre>`;
1348
+
1349
+    return showdown.subParser('hashBlock')(codeblock, options, globals) + nextChar;
1350
+  });
1351
+
1352
+  // attacklab: strip sentinel
1353
+  text = text.replace(/~0/, '');
1354
+
1355
+  text = globals.converter._dispatch('codeBlocks.after', text, options, globals);
1356
+  return text;
1357
+});
1358
+
1359
+/**
1360
+ *
1361
+ *   *  Backtick quotes are used for <code></code> spans.
1362
+ *
1363
+ *   *  You can use multiple backticks as the delimiters if you want to
1364
+ *     include literal backticks in the code span. So, this input:
1365
+ *
1366
+ *         Just type ``foo `bar` baz`` at the prompt.
1367
+ *
1368
+ *       Will translate to:
1369
+ *
1370
+ *         <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
1371
+ *
1372
+ *    There's no arbitrary limit to the number of backticks you
1373
+ *    can use as delimters. If you need three consecutive backticks
1374
+ *    in your code, use four for delimiters, etc.
1375
+ *
1376
+ *  *  You can use spaces to get literal backticks at the edges:
1377
+ *
1378
+ *         ... type `` `bar` `` ...
1379
+ *
1380
+ *       Turns to:
1381
+ *
1382
+ *         ... type <code>`bar`</code> ...
1383
+ */
1384
+showdown.subParser('codeSpans', (text, options, globals) => {
1385
+  text = globals.converter._dispatch('codeSpans.before', text, options, globals);
1386
+
1387
+  /*
1388
+   text = text.replace(/
1389
+   (^|[^\\])					// Character before opening ` can't be a backslash
1390
+   (`+)						// $2 = Opening run of `
1391
+   (							// $3 = The code block
1392
+   [^\r]*?
1393
+   [^`]					// attacklab: work around lack of lookbehind
1394
+   )
1395
+   \2							// Matching closer
1396
+   (?!`)
1397
+   /gm, function(){...});
1398
+   */
1399
+
1400
+  if (typeof (text) === 'undefined') {
1401
+    text = '';
1402
+  }
1403
+  text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
1404
+    (wholeMatch, m1, m2, m3) => {
1405
+      let c = m3;
1406
+      c = c.replace(/^([ \t]*)/g, '');	// leading whitespace
1407
+      c = c.replace(/[ \t]*$/g, '');	// trailing whitespace
1408
+      c = showdown.subParser('encodeCode')(c);
1409
+      return `${m1}<code>${c}</code>`;
1410
+    });
1411
+
1412
+  text = globals.converter._dispatch('codeSpans.after', text, options, globals);
1413
+  return text;
1414
+});
1415
+
1416
+/**
1417
+ * Convert all tabs to spaces
1418
+ */
1419
+showdown.subParser('detab', (text) => {
1420
+  // expand first n-1 tabs
1421
+  text = text.replace(/\t(?=\t)/g, '    '); // g_tab_width
1422
+
1423
+  // replace the nth with two sentinels
1424
+  text = text.replace(/\t/g, '~A~B');
1425
+
1426
+  // use the sentinel to anchor our regex so it doesn't explode
1427
+  text = text.replace(/~B(.+?)~A/g, (wholeMatch, m1) => {
1428
+    let leadingText = m1;
1429
+    const numSpaces = 4 - leadingText.length % 4; // g_tab_width
1430
+
1431
+    // there *must* be a better way to do this:
1432
+    for (let i = 0; i < numSpaces; i++) {
1433
+      leadingText += ' ';
1434
+    }
1435
+
1436
+    return leadingText;
1437
+  });
1438
+
1439
+  // clean up sentinels
1440
+  text = text.replace(/~A/g, '    '); // g_tab_width
1441
+  text = text.replace(/~B/g, '');
1442
+
1443
+  return text;
1444
+});
1445
+
1446
+/**
1447
+ * Smart processing for ampersands and angle brackets that need to be encoded.
1448
+ */
1449
+showdown.subParser('encodeAmpsAndAngles', (text) => {
1450
+  // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
1451
+  // http://bumppo.net/projects/amputator/
1452
+  text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, '&amp;');
1453
+
1454
+  // Encode naked <'s
1455
+  text = text.replace(/<(?![a-z\/?\$!])/gi, '&lt;');
1456
+
1457
+  return text;
1458
+});
1459
+
1460
+/**
1461
+ * Returns the string, with after processing the following backslash escape sequences.
1462
+ *
1463
+ * attacklab: The polite way to do this is with the new escapeCharacters() function:
1464
+ *
1465
+ *    text = escapeCharacters(text,"\\",true);
1466
+ *    text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
1467
+ *
1468
+ * ...but we're sidestepping its use of the (slow) RegExp constructor
1469
+ * as an optimization for Firefox.  This function gets called a LOT.
1470
+ */
1471
+showdown.subParser('encodeBackslashEscapes', (text) => {
1472
+  text = text.replace(/\\(\\)/g, showdown.helper.escapeCharactersCallback);
1473
+  text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, showdown.helper.escapeCharactersCallback);
1474
+  return text;
1475
+});
1476
+
1477
+/**
1478
+ * Encode/escape certain characters inside Markdown code runs.
1479
+ * The point is that in code, these characters are literals,
1480
+ * and lose their special Markdown meanings.
1481
+ */
1482
+showdown.subParser('encodeCode', (text) => {
1483
+  // Encode all ampersands; HTML entities are not
1484
+  // entities within a Markdown code span.
1485
+  text = text.replace(/&/g, '&amp;');
1486
+
1487
+  // Do the angle bracket song and dance:
1488
+  text = text.replace(/</g, '&lt;');
1489
+  text = text.replace(/>/g, '&gt;');
1490
+
1491
+  // Now, escape characters that are magic in Markdown:
1492
+  text = showdown.helper.escapeCharacters(text, '*_{}[]\\', false);
1493
+
1494
+  // jj the line above breaks this:
1495
+  //---
1496
+  //* Item
1497
+  //   1. Subitem
1498
+  //            special char: *
1499
+  // ---
1500
+
1501
+  return text;
1502
+});
1503
+
1504
+/**
1505
+ *  Input: an email address, e.g. "foo@example.com"
1506
+ *
1507
+ *  Output: the email address as a mailto link, with each character
1508
+ *    of the address encoded as either a decimal or hex entity, in
1509
+ *    the hopes of foiling most address harvesting spam bots. E.g.:
1510
+ *
1511
+ *    <a href="&#x6D;&#97;&#105;&#108;&#x74;&#111;:&#102;&#111;&#111;&#64;&#101;
1512
+ *       x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;">&#102;&#111;&#111;
1513
+ *       &#64;&#101;x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;</a>
1514
+ *
1515
+ *  Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
1516
+ *  mailing list: <http://tinyurl.com/yu7ue>
1517
+ *
1518
+ */
1519
+showdown.subParser('encodeEmailAddress', (addr) => {
1520
+  const encode = [
1521
+    function (ch) {
1522
+      return `&#${ch.charCodeAt(0)};`;
1523
+    },
1524
+    function (ch) {
1525
+      return `&#x${ch.charCodeAt(0).toString(16)};`;
1526
+    },
1527
+    function (ch) {
1528
+      return ch;
1529
+    },
1530
+  ];
1531
+
1532
+  addr = `mailto:${addr}`;
1533
+
1534
+  addr = addr.replace(/./g, (ch) => {
1535
+    if (ch === '@') {
1536
+      // this *must* be encoded. I insist.
1537
+      ch = encode[Math.floor(Math.random() * 2)](ch);
1538
+    } else if (ch !== ':') {
1539
+      // leave ':' alone (to spot mailto: later)
1540
+      const r = Math.random();
1541
+      // roughly 10% raw, 45% hex, 45% dec
1542
+      ch = (
1543
+        r > 0.9 ? encode[2](ch) : r > 0.45 ? encode[1](ch) : encode[0](ch)
1544
+      );
1545
+    }
1546
+    return ch;
1547
+  });
1548
+
1549
+  addr = `<a href="${addr}">${addr}</a>`;
1550
+  addr = addr.replace(/">.+:/g, '">'); // strip the mailto: from the visible part
1551
+
1552
+  return addr;
1553
+});
1554
+
1555
+/**
1556
+ * Within tags -- meaning between < and > -- encode [\ ` * _] so they
1557
+ * don't conflict with their use in Markdown for code, italics and strong.
1558
+ */
1559
+showdown.subParser('escapeSpecialCharsWithinTagAttributes', (text) => {
1560
+  // Build a regex to find HTML tags and comments.  See Friedl's
1561
+  // "Mastering Regular Expressions", 2nd Ed., pp. 200-201.
1562
+  const regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi;
1563
+
1564
+  text = text.replace(regex, (wholeMatch) => {
1565
+    let tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, '$1`');
1566
+    tag = showdown.helper.escapeCharacters(tag, '\\`*_', false);
1567
+    return tag;
1568
+  });
1569
+
1570
+  return text;
1571
+});
1572
+
1573
+/**
1574
+ * Handle github codeblocks prior to running HashHTML so that
1575
+ * HTML contained within the codeblock gets escaped properly
1576
+ * Example:
1577
+ * ```ruby
1578
+ *     def hello_world(x)
1579
+ *       puts "Hello, #{x}"
1580
+ *     end
1581
+ * ```
1582
+ */
1583
+showdown.subParser('githubCodeBlocks', (text, options, globals) => {
1584
+  // early exit if option is not enabled
1585
+  if (!options.ghCodeBlocks) {
1586
+    return text;
1587
+  }
1588
+
1589
+  text = globals.converter._dispatch('githubCodeBlocks.before', text, options, globals);
1590
+
1591
+  text += '~0';
1592
+
1593
+  text = text.replace(/(?:^|\n)```(.*)\n([\s\S]*?)\n```/g, (wholeMatch, language, codeblock) => {
1594
+    const end = (options.omitExtraWLInCodeBlocks) ? '' : '\n';
1595
+
1596
+    // First parse the github code block
1597
+    codeblock = showdown.subParser('encodeCode')(codeblock);
1598
+    codeblock = showdown.subParser('detab')(codeblock);
1599
+    codeblock = codeblock.replace(/^\n+/g, ''); // trim leading newlines
1600
+    codeblock = codeblock.replace(/\n+$/g, ''); // trim trailing whitespace
1601
+
1602
+    codeblock = `<pre><code${language ? ` class="${language} language-${language}"` : ''}>${codeblock}${end}</code></pre>`;
1603
+
1604
+    codeblock = showdown.subParser('hashBlock')(codeblock, options, globals);
1605
+
1606
+    // Since GHCodeblocks can be false positives, we need to
1607
+    // store the primitive text and the parsed text in a global var,
1608
+    // and then return a token
1609
+    return `\n\n~G${globals.ghCodeBlocks.push({ text: wholeMatch, codeblock }) - 1}G\n\n`;
1610
+  });
1611
+
1612
+  // attacklab: strip sentinel
1613
+  text = text.replace(/~0/, '');
1614
+
1615
+  return globals.converter._dispatch('githubCodeBlocks.after', text, options, globals);
1616
+});
1617
+
1618
+showdown.subParser('hashBlock', (text, options, globals) => {
1619
+  text = text.replace(/(^\n+|\n+$)/g, '');
1620
+  return `\n\n~K${globals.gHtmlBlocks.push(text) - 1}K\n\n`;
1621
+});
1622
+
1623
+showdown.subParser('hashElement', (text, options, globals) => function (wholeMatch, m1) {
1624
+  let blockText = m1;
1625
+
1626
+  // Undo double lines
1627
+  blockText = blockText.replace(/\n\n/g, '\n');
1628
+  blockText = blockText.replace(/^\n/, '');
1629
+
1630
+  // strip trailing blank lines
1631
+  blockText = blockText.replace(/\n+$/g, '');
1632
+
1633
+  // Replace the element text with a marker ("~KxK" where x is its key)
1634
+  blockText = `\n\n~K${globals.gHtmlBlocks.push(blockText) - 1}K\n\n`;
1635
+
1636
+  return blockText;
1637
+});
1638
+
1639
+showdown.subParser('hashHTMLBlocks', (text, options, globals) => {
1640
+  const blockTags = [
1641
+    'pre',
1642
+    'div',
1643
+    'h1',
1644
+    'h2',
1645
+    'h3',
1646
+    'h4',
1647
+    'h5',
1648
+    'h6',
1649
+    'blockquote',
1650
+    'table',
1651
+    'dl',
1652
+    'ol',
1653
+    'ul',
1654
+    'script',
1655
+    'noscript',
1656
+    'form',
1657
+    'fieldset',
1658
+    'iframe',
1659
+    'math',
1660
+    'style',
1661
+    'section',
1662
+    'header',
1663
+    'footer',
1664
+    'nav',
1665
+    'article',
1666
+    'aside',
1667
+    'address',
1668
+    'audio',
1669
+    'canvas',
1670
+    'figure',
1671
+    'hgroup',
1672
+    'output',
1673
+    'video',
1674
+    'p',
1675
+  ];
1676
+  const repFunc = function (wholeMatch, match, left, right) {
1677
+    let txt = wholeMatch;
1678
+    // check if this html element is marked as markdown
1679
+    // if so, it's contents should be parsed as markdown
1680
+    if (left.search(/\bmarkdown\b/) !== -1) {
1681
+      txt = left + globals.converter.makeHtml(match) + right;
1682
+    }
1683
+    return `\n\n~K${globals.gHtmlBlocks.push(txt) - 1}K\n\n`;
1684
+  };
1685
+
1686
+  for (let i = 0; i < blockTags.length; ++i) {
1687
+    text = showdown.helper.replaceRecursiveRegExp(text, repFunc, `^(?: |\\t){0,3}<${blockTags[i]}\\b[^>]*>`, `</${blockTags[i]}>`, 'gim');
1688
+  }
1689
+
1690
+  // HR SPECIAL CASE
1691
+  text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,
1692
+    showdown.subParser('hashElement')(text, options, globals));
1693
+
1694
+  // Special case for standalone HTML comments:
1695
+  text = text.replace(/(<!--[\s\S]*?-->)/g,
1696
+    showdown.subParser('hashElement')(text, options, globals));
1697
+
1698
+  // PHP and ASP-style processor instructions (<?...?> and <%...%>)
1699
+  text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,
1700
+    showdown.subParser('hashElement')(text, options, globals));
1701
+  return text;
1702
+});
1703
+
1704
+/**
1705
+ * Hash span elements that should not be parsed as markdown
1706
+ */
1707
+showdown.subParser('hashHTMLSpans', (text, config, globals) => {
1708
+  const matches = showdown.helper.matchRecursiveRegExp(text, '<code\\b[^>]*>', '</code>', 'gi');
1709
+
1710
+  for (let i = 0; i < matches.length; ++i) {
1711
+    text = text.replace(matches[i][0], `~L${globals.gHtmlSpans.push(matches[i][0]) - 1}L`);
1712
+  }
1713
+  return text;
1714
+});
1715
+
1716
+/**
1717
+ * Unhash HTML spans
1718
+ */
1719
+showdown.subParser('unhashHTMLSpans', (text, config, globals) => {
1720
+  for (let i = 0; i < globals.gHtmlSpans.length; ++i) {
1721
+    text = text.replace(`~L${i}L`, globals.gHtmlSpans[i]);
1722
+  }
1723
+
1724
+  return text;
1725
+});
1726
+
1727
+/**
1728
+ * Hash span elements that should not be parsed as markdown
1729
+ */
1730
+showdown.subParser('hashPreCodeTags', (text, config, globals) => {
1731
+  const repFunc = function (wholeMatch, match, left, right) {
1732
+    // encode html entities
1733
+    const codeblock = left + showdown.subParser('encodeCode')(match) + right;
1734
+    return `\n\n~G${globals.ghCodeBlocks.push({ text: wholeMatch, codeblock }) - 1}G\n\n`;
1735
+  };
1736
+
1737
+  text = showdown.helper.replaceRecursiveRegExp(text, repFunc, '^(?: |\\t){0,3}<pre\\b[^>]*>\\s*<code\\b[^>]*>', '^(?: |\\t){0,3}</code>\\s*</pre>', 'gim');
1738
+  return text;
1739
+});
1740
+
1741
+showdown.subParser('headers', (text, options, globals) => {
1742
+  text = globals.converter._dispatch('headers.before', text, options, globals);
1743
+
1744
+  let prefixHeader = options.prefixHeaderId;
1745
+  const headerLevelStart = (isNaN(parseInt(options.headerLevelStart))) ? 1 : parseInt(options.headerLevelStart);
1746
+
1747
+  // Set text-style headers:
1748
+  //	Header 1
1749
+  //	========
1750
+  //
1751
+  //	Header 2
1752
+  //	--------
1753
+  //
1754
+  const setextRegexH1 = (options.smoothLivePreview) ? /^(.+)[ \t]*\n={2,}[ \t]*\n+/gm : /^(.+)[ \t]*\n=+[ \t]*\n+/gm;
1755
+  const setextRegexH2 = (options.smoothLivePreview) ? /^(.+)[ \t]*\n-{2,}[ \t]*\n+/gm : /^(.+)[ \t]*\n-+[ \t]*\n+/gm;
1756
+
1757
+  text = text.replace(setextRegexH1, (wholeMatch, m1) => {
1758
+    const spanGamut = showdown.subParser('spanGamut')(m1, options, globals);
1759
+    const hID = (options.noHeaderId) ? '' : ` id="${headerId(m1)}"`;
1760
+    const hLevel = headerLevelStart;
1761
+    const hashBlock = `<h${hLevel}${hID}>${spanGamut}</h${hLevel}>`;
1762
+    return showdown.subParser('hashBlock')(hashBlock, options, globals);
1763
+  });
1764
+
1765
+  text = text.replace(setextRegexH2, (matchFound, m1) => {
1766
+    const spanGamut = showdown.subParser('spanGamut')(m1, options, globals);
1767
+    const hID = (options.noHeaderId) ? '' : ` id="${headerId(m1)}"`;
1768
+    const hLevel = headerLevelStart + 1;
1769
+    const hashBlock = `<h${hLevel}${hID}>${spanGamut}</h${hLevel}>`;
1770
+    return showdown.subParser('hashBlock')(hashBlock, options, globals);
1771
+  });
1772
+
1773
+  // atx-style headers:
1774
+  //  # Header 1
1775
+  //  ## Header 2
1776
+  //  ## Header 2 with closing hashes ##
1777
+  //  ...
1778
+  //  ###### Header 6
1779
+  //
1780
+  text = text.replace(/^(#{1,6})[ \t]*(.+?)[ \t]*#*\n+/gm, (wholeMatch, m1, m2) => {
1781
+    const span = showdown.subParser('spanGamut')(m2, options, globals);
1782
+    const hID = (options.noHeaderId) ? '' : ` id="${headerId(m2)}"`;
1783
+    const hLevel = headerLevelStart - 1 + m1.length;
1784
+    const header = `<h${hLevel}${hID}>${span}</h${hLevel}>`;
1785
+
1786
+    return showdown.subParser('hashBlock')(header, options, globals);
1787
+  });
1788
+
1789
+  function headerId(m) {
1790
+    let title; const
1791
+      escapedId = m.replace(/[^\w]/g, '').toLowerCase();
1792
+
1793
+    if (globals.hashLinkCounts[escapedId]) {
1794
+      title = `${escapedId}-${globals.hashLinkCounts[escapedId]++}`;
1795
+    } else {
1796
+      title = escapedId;
1797
+      globals.hashLinkCounts[escapedId] = 1;
1798
+    }
1799
+
1800
+    // Prefix id to prevent causing inadvertent pre-existing style matches.
1801
+    if (prefixHeader === true) {
1802
+      prefixHeader = 'section';
1803
+    }
1804
+
1805
+    if (showdown.helper.isString(prefixHeader)) {
1806
+      return prefixHeader + title;
1807
+    }
1808
+    return title;
1809
+  }
1810
+
1811
+  text = globals.converter._dispatch('headers.after', text, options, globals);
1812
+  return text;
1813
+});
1814
+
1815
+/**
1816
+ * Turn Markdown image shortcuts into <img> tags.
1817
+ */
1818
+showdown.subParser('images', (text, options, globals) => {
1819
+  text = globals.converter._dispatch('images.before', text, options, globals);
1820
+
1821
+  const inlineRegExp = /!\[(.*?)]\s?\([ \t]*()<?(\S+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(['"])(.*?)\6[ \t]*)?\)/g;
1822
+  const referenceRegExp = /!\[([^\]]*?)] ?(?:\n *)?\[(.*?)]()()()()()/g;
1823
+
1824
+  function writeImageTag(wholeMatch, altText, linkId, url, width, height, m5, title) {
1825
+    const { gUrls } = globals;
1826
+    const { gTitles } = globals;
1827
+    const gDims = globals.gDimensions;
1828
+
1829
+    linkId = linkId.toLowerCase();
1830
+
1831
+    if (!title) {
1832
+      title = '';
1833
+    }
1834
+
1835
+    if (url === '' || url === null) {
1836
+      if (linkId === '' || linkId === null) {
1837
+        // lower-case and turn embedded newlines into spaces
1838
+        linkId = altText.toLowerCase().replace(/ ?\n/g, ' ');
1839
+      }
1840
+      url = `#${linkId}`;
1841
+
1842
+      if (!showdown.helper.isUndefined(gUrls[linkId])) {
1843
+        url = gUrls[linkId];
1844
+        if (!showdown.helper.isUndefined(gTitles[linkId])) {
1845
+          title = gTitles[linkId];
1846
+        }
1847
+        if (!showdown.helper.isUndefined(gDims[linkId])) {
1848
+          width = gDims[linkId].width;
1849
+          height = gDims[linkId].height;
1850
+        }
1851
+      } else {
1852
+        return wholeMatch;
1853
+      }
1854
+    }
1855
+
1856
+    altText = altText.replace(/"/g, '&quot;');
1857
+    altText = showdown.helper.escapeCharacters(altText, '*_', false);
1858
+    url = showdown.helper.escapeCharacters(url, '*_', false);
1859
+    let result = `<img src="${url}" alt="${altText}"`;
1860
+
1861
+    if (title) {
1862
+      title = title.replace(/"/g, '&quot;');
1863
+      title = showdown.helper.escapeCharacters(title, '*_', false);
1864
+      result += ` title="${title}"`;
1865
+    }
1866
+
1867
+    if (width && height) {
1868
+      width = (width === '*') ? 'auto' : width;
1869
+      height = (height === '*') ? 'auto' : height;
1870
+
1871
+      result += ` width="${width}"`;
1872
+      result += ` height="${height}"`;
1873
+    }
1874
+
1875
+    result += ' />';
1876
+    return result;
1877
+  }
1878
+
1879
+  // First, handle reference-style labeled images: ![alt text][id]
1880
+  text = text.replace(referenceRegExp, writeImageTag);
1881
+
1882
+  // Next, handle inline images:  ![alt text](url =<width>x<height> "optional title")
1883
+  text = text.replace(inlineRegExp, writeImageTag);
1884
+
1885
+  text = globals.converter._dispatch('images.after', text, options, globals);
1886
+  return text;
1887
+});
1888
+
1889
+showdown.subParser('italicsAndBold', (text, options, globals) => {
1890
+  text = globals.converter._dispatch('italicsAndBold.before', text, options, globals);
1891
+
1892
+  if (options.literalMidWordUnderscores) {
1893
+    // underscores
1894
+    // Since we are consuming a \s character, we need to add it
1895
+    text = text.replace(/(^|\s|>|\b)__(?=\S)([\s\S]+?)__(?=\b|<|\s|$)/gm, '$1<strong>$2</strong>');
1896
+    text = text.replace(/(^|\s|>|\b)_(?=\S)([\s\S]+?)_(?=\b|<|\s|$)/gm, '$1<em>$2</em>');
1897
+    // asterisks
1898
+    text = text.replace(/(\*\*)(?=\S)([^\r]*?\S[*]*)\1/g, '<strong>$2</strong>');
1899
+    text = text.replace(/(\*)(?=\S)([^\r]*?\S)\1/g, '<em>$2</em>');
1900
+  } else {
1901
+    // <strong> must go first:
1902
+    text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g, '<strong>$2</strong>');
1903
+    text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g, '<em>$2</em>');
1904
+  }
1905
+
1906
+  text = globals.converter._dispatch('italicsAndBold.after', text, options, globals);
1907
+  return text;
1908
+});
1909
+
1910
+/**
1911
+ * Form HTML ordered (numbered) and unordered (bulleted) lists.
1912
+ */
1913
+showdown.subParser('lists', (text, options, globals) => {
1914
+  text = globals.converter._dispatch('lists.before', text, options, globals);
1915
+  /**
1916
+   * Process the contents of a single ordered or unordered list, splitting it
1917
+   * into individual list items.
1918
+   * @param {string} listStr
1919
+   * @param {boolean} trimTrailing
1920
+   * @returns {string}
1921
+   */
1922
+  function processListItems(listStr, trimTrailing) {
1923
+    // The $g_list_level global keeps track of when we're inside a list.
1924
+    // Each time we enter a list, we increment it; when we leave a list,
1925
+    // we decrement. If it's zero, we're not in a list anymore.
1926
+    //
1927
+    // We do this because when we're not inside a list, we want to treat
1928
+    // something like this:
1929
+    //
1930
+    //    I recommend upgrading to version
1931
+    //    8. Oops, now this line is treated
1932
+    //    as a sub-list.
1933
+    //
1934
+    // As a single paragraph, despite the fact that the second line starts
1935
+    // with a digit-period-space sequence.
1936
+    //
1937
+    // Whereas when we're inside a list (or sub-list), that line will be
1938
+    // treated as the start of a sub-list. What a kludge, huh? This is
1939
+    // an aspect of Markdown's syntax that's hard to parse perfectly
1940
+    // without resorting to mind-reading. Perhaps the solution is to
1941
+    // change the syntax rules such that sub-lists must start with a
1942
+    // starting cardinal number; e.g. "1." or "a.".
1943
+    globals.gListLevel++;
1944
+
1945
+    // trim trailing blank lines:
1946
+    listStr = listStr.replace(/\n{2,}$/, '\n');
1947
+
1948
+    // attacklab: add sentinel to emulate \z
1949
+    listStr += '~0';
1950
+
1951
+    const rgx = /(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm;
1952
+    const isParagraphed = (/\n[ \t]*\n(?!~0)/.test(listStr));
1953
+
1954
+    listStr = listStr.replace(rgx, (wholeMatch, m1, m2, m3, m4, taskbtn, checked) => {
1955
+      checked = (checked && checked.trim() !== '');
1956
+      let item = showdown.subParser('outdent')(m4, options, globals);
1957
+      let bulletStyle = '';
1958
+
1959
+      // Support for github tasklists
1960
+      if (taskbtn && options.tasklists) {
1961
+        bulletStyle = ' class="task-list-item" style="list-style-type: none;"';
1962
+        item = item.replace(/^[ \t]*\[(x|X| )?]/m, () => {
1963
+          let otp = '<input type="checkbox" disabled style="margin: 0px 0.35em 0.25em -1.6em; vertical-align: middle;"';
1964
+          if (checked) {
1965
+            otp += ' checked';
1966
+          }
1967
+          otp += '>';
1968
+          return otp;
1969
+        });
1970
+      }
1971
+      // m1 - Leading line or
1972
+      // Has a double return (multi paragraph) or
1973
+      // Has sublist
1974
+      if (m1 || (item.search(/\n{2,}/) > -1)) {
1975
+        item = showdown.subParser('githubCodeBlocks')(item, options, globals);
1976
+        item = showdown.subParser('blockGamut')(item, options, globals);
1977
+      } else {
1978
+        // Recursion for sub-lists:
1979
+        item = showdown.subParser('lists')(item, options, globals);
1980
+        item = item.replace(/\n$/, ''); // chomp(item)
1981
+        if (isParagraphed) {
1982
+          item = showdown.subParser('paragraphs')(item, options, globals);
1983
+        } else {
1984
+          item = showdown.subParser('spanGamut')(item, options, globals);
1985
+        }
1986
+      }
1987
+      item = `\n<li${bulletStyle}>${item}</li>\n`;
1988
+      return item;
1989
+    });
1990
+
1991
+    // attacklab: strip sentinel
1992
+    listStr = listStr.replace(/~0/g, '');
1993
+
1994
+    globals.gListLevel--;
1995
+
1996
+    if (trimTrailing) {
1997
+      listStr = listStr.replace(/\s+$/, '');
1998
+    }
1999
+
2000
+    return listStr;
2001
+  }
2002
+
2003
+  /**
2004
+   * Check and parse consecutive lists (better fix for issue #142)
2005
+   * @param {string} list
2006
+   * @param {string} listType
2007
+   * @param {boolean} trimTrailing
2008
+   * @returns {string}
2009
+   */
2010
+  function parseConsecutiveLists(list, listType, trimTrailing) {
2011
+    // check if we caught 2 or more consecutive lists by mistake
2012
+    // we use the counterRgx, meaning if listType is UL we look for UL and vice versa
2013
+    let counterRxg = (listType === 'ul') ? /^ {0,2}\d+\.[ \t]/gm : /^ {0,2}[*+-][ \t]/gm;
2014
+    const subLists = [];
2015
+    let result = '';
2016
+
2017
+    if (list.search(counterRxg) !== -1) {
2018
+      (function parseCL(txt) {
2019
+        const pos = txt.search(counterRxg);
2020
+        if (pos !== -1) {
2021
+          // slice
2022
+          result += `\n\n<${listType}>${processListItems(txt.slice(0, pos), !!trimTrailing)}</${listType}>\n\n`;
2023
+
2024
+          // invert counterType and listType
2025
+          listType = (listType === 'ul') ? 'ol' : 'ul';
2026
+          counterRxg = (listType === 'ul') ? /^ {0,2}\d+\.[ \t]/gm : /^ {0,2}[*+-][ \t]/gm;
2027
+
2028
+          // recurse
2029
+          parseCL(txt.slice(pos));
2030
+        } else {
2031
+          result += `\n\n<${listType}>${processListItems(txt, !!trimTrailing)}</${listType}>\n\n`;
2032
+        }
2033
+      }(list));
2034
+      for (let i = 0; i < subLists.length; ++i) {
2035
+
2036
+      }
2037
+    } else {
2038
+      result = `\n\n<${listType}>${processListItems(list, !!trimTrailing)}</${listType}>\n\n`;
2039
+    }
2040
+
2041
+    return result;
2042
+  }
2043
+
2044
+  // attacklab: add sentinel to hack around khtml/safari bug:
2045
+  // http://bugs.webkit.org/show_bug.cgi?id=11231
2046
+  text += '~0';
2047
+
2048
+  // Re-usable pattern to match any entire ul or ol list:
2049
+  let wholeList = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
2050
+
2051
+  if (globals.gListLevel) {
2052
+    text = text.replace(wholeList, (wholeMatch, list, m2) => {
2053
+      const listType = (m2.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
2054
+      return parseConsecutiveLists(list, listType, true);
2055
+    });
2056
+  } else {
2057
+    wholeList = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
2058
+    // wholeList = /(\n\n|^\n?)( {0,3}([*+-]|\d+\.)[ \t]+[\s\S]+?)(?=(~0)|(\n\n(?!\t| {2,}| {0,3}([*+-]|\d+\.)[ \t])))/g;
2059
+    text = text.replace(wholeList, (wholeMatch, m1, list, m3) => {
2060
+      const listType = (m3.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
2061
+      return parseConsecutiveLists(list, listType);
2062
+    });
2063
+  }
2064
+
2065
+  // attacklab: strip sentinel
2066
+  text = text.replace(/~0/, '');
2067
+
2068
+  text = globals.converter._dispatch('lists.after', text, options, globals);
2069
+  return text;
2070
+});
2071
+
2072
+/**
2073
+ * Remove one level of line-leading tabs or spaces
2074
+ */
2075
+showdown.subParser('outdent', (text) => {
2076
+  // attacklab: hack around Konqueror 3.5.4 bug:
2077
+  // "----------bug".replace(/^-/g,"") == "bug"
2078
+  text = text.replace(/^(\t|[ ]{1,4})/gm, '~0'); // attacklab: g_tab_width
2079
+
2080
+  // attacklab: clean up hack
2081
+  text = text.replace(/~0/g, '');
2082
+
2083
+  return text;
2084
+});
2085
+
2086
+/**
2087
+ *
2088
+ */
2089
+showdown.subParser('paragraphs', (text, options, globals) => {
2090
+  text = globals.converter._dispatch('paragraphs.before', text, options, globals);
2091
+  // Strip leading and trailing lines:
2092
+  text = text.replace(/^\n+/g, '');
2093
+  text = text.replace(/\n+$/g, '');
2094
+
2095
+  const grafs = text.split(/\n{2,}/g);
2096
+  const grafsOut = [];
2097
+  let end = grafs.length; // Wrap <p> tags
2098
+
2099
+  for (var i = 0; i < end; i++) {
2100
+    let str = grafs[i];
2101
+    // if this is an HTML marker, copy it
2102
+    if (str.search(/~(K|G)(\d+)\1/g) >= 0) {
2103
+      grafsOut.push(str);
2104
+    } else {
2105
+      str = showdown.subParser('spanGamut')(str, options, globals);
2106
+      str = str.replace(/^([ \t]*)/g, '<p>');
2107
+      str += '</p>';
2108
+      grafsOut.push(str);
2109
+    }
2110
+  }
2111
+
2112
+  /** Unhashify HTML blocks */
2113
+  end = grafsOut.length;
2114
+  for (i = 0; i < end; i++) {
2115
+    let blockText = '';
2116
+    let grafsOutIt = grafsOut[i];
2117
+    let codeFlag = false;
2118
+    // if this is a marker for an html block...
2119
+    while (grafsOutIt.search(/~(K|G)(\d+)\1/) >= 0) {
2120
+      const delim = RegExp.$1;
2121
+      const num = RegExp.$2;
2122
+
2123
+      if (delim === 'K') {
2124
+        blockText = globals.gHtmlBlocks[num];
2125
+      } else {
2126
+        // we need to check if ghBlock is a false positive
2127
+        if (codeFlag) {
2128
+          // use encoded version of all text
2129
+          blockText = showdown.subParser('encodeCode')(globals.ghCodeBlocks[num].text);
2130
+        } else {
2131
+          blockText = globals.ghCodeBlocks[num].codeblock;
2132
+        }
2133
+      }
2134
+      blockText = blockText.replace(/\$/g, '$$$$'); // Escape any dollar signs
2135
+
2136
+      grafsOutIt = grafsOutIt.replace(/(\n\n)?~(K|G)\d+\2(\n\n)?/, blockText);
2137
+      // Check if grafsOutIt is a pre->code
2138
+      if (/^<pre\b[^>]*>\s*<code\b[^>]*>/.test(grafsOutIt)) {
2139
+        codeFlag = true;
2140
+      }
2141
+    }
2142
+    grafsOut[i] = grafsOutIt;
2143
+  }
2144
+  text = grafsOut.join('\n\n');
2145
+  // Strip leading and trailing lines:
2146
+  text = text.replace(/^\n+/g, '');
2147
+  text = text.replace(/\n+$/g, '');
2148
+  return globals.converter._dispatch('paragraphs.after', text, options, globals);
2149
+});
2150
+
2151
+/**
2152
+ * Run extension
2153
+ */
2154
+showdown.subParser('runExtension', (ext, text, options, globals) => {
2155
+  if (ext.filter) {
2156
+    text = ext.filter(text, globals.converter, options);
2157
+  } else if (ext.regex) {
2158
+    // TODO remove this when old extension loading mechanism is deprecated
2159
+    let re = ext.regex;
2160
+    if (!re instanceof RegExp) {
2161
+      re = new RegExp(re, 'g');
2162
+    }
2163
+    text = text.replace(re, ext.replace);
2164
+  }
2165
+
2166
+  return text;
2167
+});
2168
+
2169
+/**
2170
+ * These are all the transformations that occur *within* block-level
2171
+ * tags like paragraphs, headers, and list items.
2172
+ */
2173
+showdown.subParser('spanGamut', (text, options, globals) => {
2174
+  text = globals.converter._dispatch('spanGamut.before', text, options, globals);
2175
+  text = showdown.subParser('codeSpans')(text, options, globals);
2176
+  text = showdown.subParser('escapeSpecialCharsWithinTagAttributes')(text, options, globals);
2177
+  text = showdown.subParser('encodeBackslashEscapes')(text, options, globals);
2178
+
2179
+  // Process anchor and image tags. Images must come first,
2180
+  // because ![foo][f] looks like an anchor.
2181
+  text = showdown.subParser('images')(text, options, globals);
2182
+  text = showdown.subParser('anchors')(text, options, globals);
2183
+
2184
+  // Make links out of things like `<http://example.com/>`
2185
+  // Must come after _DoAnchors(), because you can use < and >
2186
+  // delimiters in inline links like [this](<url>).
2187
+  text = showdown.subParser('autoLinks')(text, options, globals);
2188
+  text = showdown.subParser('encodeAmpsAndAngles')(text, options, globals);
2189
+  text = showdown.subParser('italicsAndBold')(text, options, globals);
2190
+  text = showdown.subParser('strikethrough')(text, options, globals);
2191
+
2192
+  // Do hard breaks:
2193
+  text = text.replace(/  +\n/g, ' <br />\n');
2194
+
2195
+  text = globals.converter._dispatch('spanGamut.after', text, options, globals);
2196
+  return text;
2197
+});
2198
+
2199
+showdown.subParser('strikethrough', (text, options, globals) => {
2200
+  if (options.strikethrough) {
2201
+    text = globals.converter._dispatch('strikethrough.before', text, options, globals);
2202
+    text = text.replace(/(?:~T){2}([\s\S]+?)(?:~T){2}/g, '<del>$1</del>');
2203
+    text = globals.converter._dispatch('strikethrough.after', text, options, globals);
2204
+  }
2205
+
2206
+  return text;
2207
+});
2208
+
2209
+/**
2210
+ * Strip any lines consisting only of spaces and tabs.
2211
+ * This makes subsequent regexs easier to write, because we can
2212
+ * match consecutive blank lines with /\n+/ instead of something
2213
+ * contorted like /[ \t]*\n+/
2214
+ */
2215
+showdown.subParser('stripBlankLines', (text) => text.replace(/^[ \t]+$/mg, ''));
2216
+
2217
+/**
2218
+ * Strips link definitions from text, stores the URLs and titles in
2219
+ * hash references.
2220
+ * Link defs are in the form: ^[id]: url "optional title"
2221
+ *
2222
+ * ^[ ]{0,3}\[(.+)\]: // id = $1  attacklab: g_tab_width - 1
2223
+ * [ \t]*
2224
+ * \n?                  // maybe *one* newline
2225
+ * [ \t]*
2226
+ * <?(\S+?)>?          // url = $2
2227
+ * [ \t]*
2228
+ * \n?                // maybe one newline
2229
+ * [ \t]*
2230
+ * (?:
2231
+ * (\n*)              // any lines skipped = $3 attacklab: lookbehind removed
2232
+ * ["(]
2233
+ * (.+?)              // title = $4
2234
+ * [")]
2235
+ * [ \t]*
2236
+ * )?                 // title is optional
2237
+ * (?:\n+|$)
2238
+ * /gm,
2239
+ * function(){...});
2240
+ *
2241
+ */
2242
+showdown.subParser('stripLinkDefinitions', (text, options, globals) => {
2243
+  const regex = /^ {0,3}\[(.+)]:[ \t]*\n?[ \t]*<?(\S+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*\n?[ \t]*(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n+|(?=~0))/gm;
2244
+
2245
+  // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
2246
+  text += '~0';
2247
+
2248
+  text = text.replace(regex, (wholeMatch, linkId, url, width, height, blankLines, title) => {
2249
+    linkId = linkId.toLowerCase();
2250
+    globals.gUrls[linkId] = showdown.subParser('encodeAmpsAndAngles')(url); // Link IDs are case-insensitive
2251
+
2252
+    if (blankLines) {
2253
+      // Oops, found blank lines, so it's not a title.
2254
+      // Put back the parenthetical statement we stole.
2255
+      return blankLines + title;
2256
+    }
2257
+    if (title) {
2258
+      globals.gTitles[linkId] = title.replace(/"|'/g, '&quot;');
2259
+    }
2260
+    if (options.parseImgDimensions && width && height) {
2261
+      globals.gDimensions[linkId] = {
2262
+        width,
2263
+        height,
2264
+      };
2265
+    }
2266
+
2267
+    // Completely remove the definition from the text
2268
+    return '';
2269
+  });
2270
+
2271
+  // attacklab: strip sentinel
2272
+  text = text.replace(/~0/, '');
2273
+
2274
+  return text;
2275
+});
2276
+
2277
+showdown.subParser('tables', (text, options, globals) => {
2278
+  if (!options.tables) {
2279
+    return text;
2280
+  }
2281
+
2282
+  const tableRgx = /^[ \t]{0,3}\|?.+\|.+\n[ \t]{0,3}\|?[ \t]*:?[ \t]*(?:-|=){2,}[ \t]*:?[ \t]*\|[ \t]*:?[ \t]*(?:-|=){2,}[\s\S]+?(?:\n\n|~0)/gm;
2283
+
2284
+  function parseStyles(sLine) {
2285
+    if (/^:[ \t]*--*$/.test(sLine)) {
2286
+      return ' style="text-align:left;"';
2287
+    } if (/^--*[ \t]*:[ \t]*$/.test(sLine)) {
2288
+      return ' style="text-align:right;"';
2289
+    } if (/^:[ \t]*--*[ \t]*:$/.test(sLine)) {
2290
+      return ' style="text-align:center;"';
2291
+    }
2292
+    return '';
2293
+  }
2294
+
2295
+  function parseHeaders(header, style) {
2296
+    let id = '';
2297
+    header = header.trim();
2298
+    if (options.tableHeaderId) {
2299
+      id = ` id="${header.replace(/ /g, '_').toLowerCase()}"`;
2300
+    }
2301
+    header = showdown.subParser('spanGamut')(header, options, globals);
2302
+
2303
+    return `<th${id}${style}>${header}</th>\n`;
2304
+  }
2305
+
2306
+  function parseCells(cell, style) {
2307
+    const subText = showdown.subParser('spanGamut')(cell, options, globals);
2308
+    return `<td${style}>${subText}</td>\n`;
2309
+  }
2310
+
2311
+  function buildTable(headers, cells) {
2312
+    let tb = '<table>\n<thead>\n<tr>\n';
2313
+    const tblLgn = headers.length;
2314
+
2315
+    for (var i = 0; i < tblLgn; ++i) {
2316
+      tb += headers[i];
2317
+    }
2318
+    tb += '</tr>\n</thead>\n<tbody>\n';
2319
+
2320
+    for (i = 0; i < cells.length; ++i) {
2321
+      tb += '<tr>\n';
2322
+      for (let ii = 0; ii < tblLgn; ++ii) {
2323
+        tb += cells[i][ii];
2324
+      }
2325
+      tb += '</tr>\n';
2326
+    }
2327
+    tb += '</tbody>\n</table>\n';
2328
+    return tb;
2329
+  }
2330
+
2331
+  text = globals.converter._dispatch('tables.before', text, options, globals);
2332
+
2333
+  text = text.replace(tableRgx, (rawTable) => {
2334
+    let i; const
2335
+      tableLines = rawTable.split('\n');
2336
+
2337
+    // strip wrong first and last column if wrapped tables are used
2338
+    for (i = 0; i < tableLines.length; ++i) {
2339
+      if (/^[ \t]{0,3}\|/.test(tableLines[i])) {
2340
+        tableLines[i] = tableLines[i].replace(/^[ \t]{0,3}\|/, '');
2341
+      }
2342
+      if (/\|[ \t]*$/.test(tableLines[i])) {
2343
+        tableLines[i] = tableLines[i].replace(/\|[ \t]*$/, '');
2344
+      }
2345
+    }
2346
+
2347
+    const rawHeaders = tableLines[0].split('|').map((s) => s.trim());
2348
+    const rawStyles = tableLines[1].split('|').map((s) => s.trim());
2349
+    const rawCells = [];
2350
+    const headers = [];
2351
+    const styles = [];
2352
+    const cells = [];
2353
+
2354
+    tableLines.shift();
2355
+    tableLines.shift();
2356
+
2357
+    for (i = 0; i < tableLines.length; ++i) {
2358
+      if (tableLines[i].trim() === '') {
2359
+        continue;
2360
+      }
2361
+      rawCells.push(
2362
+        tableLines[i]
2363
+          .split('|')
2364
+          .map((s) => s.trim()),
2365
+      );
2366
+    }
2367
+
2368
+    if (rawHeaders.length < rawStyles.length) {
2369
+      return rawTable;
2370
+    }
2371
+
2372
+    for (i = 0; i < rawStyles.length; ++i) {
2373
+      styles.push(parseStyles(rawStyles[i]));
2374
+    }
2375
+
2376
+    for (i = 0; i < rawHeaders.length; ++i) {
2377
+      if (showdown.helper.isUndefined(styles[i])) {
2378
+        styles[i] = '';
2379
+      }
2380
+      headers.push(parseHeaders(rawHeaders[i], styles[i]));
2381
+    }
2382
+
2383
+    for (i = 0; i < rawCells.length; ++i) {
2384
+      const row = [];
2385
+      for (let ii = 0; ii < headers.length; ++ii) {
2386
+        if (showdown.helper.isUndefined(rawCells[i][ii])) {
2387
+
2388
+        }
2389
+        row.push(parseCells(rawCells[i][ii], styles[ii]));
2390
+      }
2391
+      cells.push(row);
2392
+    }
2393
+
2394
+    return buildTable(headers, cells);
2395
+  });
2396
+
2397
+  text = globals.converter._dispatch('tables.after', text, options, globals);
2398
+
2399
+  return text;
2400
+});
2401
+
2402
+/**
2403
+ * Swap back in all the special characters we've hidden.
2404
+ */
2405
+showdown.subParser('unescapeSpecialChars', (text) => {
2406
+  text = text.replace(/~E(\d+)E/g, (wholeMatch, m1) => {
2407
+    const charCodeToReplace = parseInt(m1);
2408
+    return String.fromCharCode(charCodeToReplace);
2409
+  });
2410
+  return text;
2411
+});
2412
+module.exports = showdown;

+ 206 - 0
src/utils/wxParse/wxDiscode.js

@@ -0,0 +1,206 @@
1
+// HTML 支持的数学符号
2
+function strNumDiscode(str) {
3
+  str = str.replace(/&forall;/g, '∀');
4
+  str = str.replace(/&part;/g, '∂');
5
+  str = str.replace(/&exists;/g, '∃');
6
+  str = str.replace(/&empty;/g, '∅');
7
+  str = str.replace(/&nabla;/g, '∇');
8
+  str = str.replace(/&isin;/g, '∈');
9
+  str = str.replace(/&notin;/g, '∉');
10
+  str = str.replace(/&ni;/g, '∋');
11
+  str = str.replace(/&prod;/g, '∏');
12
+  str = str.replace(/&sum;/g, '∑');
13
+  str = str.replace(/&minus;/g, '−');
14
+  str = str.replace(/&lowast;/g, '∗');
15
+  str = str.replace(/&radic;/g, '√');
16
+  str = str.replace(/&prop;/g, '∝');
17
+  str = str.replace(/&infin;/g, '∞');
18
+  str = str.replace(/&ang;/g, '∠');
19
+  str = str.replace(/&and;/g, '∧');
20
+  str = str.replace(/&or;/g, '∨');
21
+  str = str.replace(/&cap;/g, '∩');
22
+  str = str.replace(/&cap;/g, '∪');
23
+  str = str.replace(/&int;/g, '∫');
24
+  str = str.replace(/&there4;/g, '∴');
25
+  str = str.replace(/&sim;/g, '∼');
26
+  str = str.replace(/&cong;/g, '≅');
27
+  str = str.replace(/&asymp;/g, '≈');
28
+  str = str.replace(/&ne;/g, '≠');
29
+  str = str.replace(/&le;/g, '≤');
30
+  str = str.replace(/&ge;/g, '≥');
31
+  str = str.replace(/&sub;/g, '⊂');
32
+  str = str.replace(/&sup;/g, '⊃');
33
+  str = str.replace(/&nsub;/g, '⊄');
34
+  str = str.replace(/&sube;/g, '⊆');
35
+  str = str.replace(/&supe;/g, '⊇');
36
+  str = str.replace(/&oplus;/g, '⊕');
37
+  str = str.replace(/&otimes;/g, '⊗');
38
+  str = str.replace(/&perp;/g, '⊥');
39
+  str = str.replace(/&sdot;/g, '⋅');
40
+  return str;
41
+}
42
+
43
+// HTML 支持的希腊字母
44
+function strGreeceDiscode(str) {
45
+  str = str.replace(/&Alpha;/g, 'Α');
46
+  str = str.replace(/&Beta;/g, 'Β');
47
+  str = str.replace(/&Gamma;/g, 'Γ');
48
+  str = str.replace(/&Delta;/g, 'Δ');
49
+  str = str.replace(/&Epsilon;/g, 'Ε');
50
+  str = str.replace(/&Zeta;/g, 'Ζ');
51
+  str = str.replace(/&Eta;/g, 'Η');
52
+  str = str.replace(/&Theta;/g, 'Θ');
53
+  str = str.replace(/&Iota;/g, 'Ι');
54
+  str = str.replace(/&Kappa;/g, 'Κ');
55
+  str = str.replace(/&Lambda;/g, 'Λ');
56
+  str = str.replace(/&Mu;/g, 'Μ');
57
+  str = str.replace(/&Nu;/g, 'Ν');
58
+  str = str.replace(/&Xi;/g, 'Ν');
59
+  str = str.replace(/&Omicron;/g, 'Ο');
60
+  str = str.replace(/&Pi;/g, 'Π');
61
+  str = str.replace(/&Rho;/g, 'Ρ');
62
+  str = str.replace(/&Sigma;/g, 'Σ');
63
+  str = str.replace(/&Tau;/g, 'Τ');
64
+  str = str.replace(/&Upsilon;/g, 'Υ');
65
+  str = str.replace(/&Phi;/g, 'Φ');
66
+  str = str.replace(/&Chi;/g, 'Χ');
67
+  str = str.replace(/&Psi;/g, 'Ψ');
68
+  str = str.replace(/&Omega;/g, 'Ω');
69
+
70
+  str = str.replace(/&alpha;/g, 'α');
71
+  str = str.replace(/&beta;/g, 'β');
72
+  str = str.replace(/&gamma;/g, 'γ');
73
+  str = str.replace(/&delta;/g, 'δ');
74
+  str = str.replace(/&epsilon;/g, 'ε');
75
+  str = str.replace(/&zeta;/g, 'ζ');
76
+  str = str.replace(/&eta;/g, 'η');
77
+  str = str.replace(/&theta;/g, 'θ');
78
+  str = str.replace(/&iota;/g, 'ι');
79
+  str = str.replace(/&kappa;/g, 'κ');
80
+  str = str.replace(/&lambda;/g, 'λ');
81
+  str = str.replace(/&mu;/g, 'μ');
82
+  str = str.replace(/&nu;/g, 'ν');
83
+  str = str.replace(/&xi;/g, 'ξ');
84
+  str = str.replace(/&omicron;/g, 'ο');
85
+  str = str.replace(/&pi;/g, 'π');
86
+  str = str.replace(/&rho;/g, 'ρ');
87
+  str = str.replace(/&sigmaf;/g, 'ς');
88
+  str = str.replace(/&sigma;/g, 'σ');
89
+  str = str.replace(/&tau;/g, 'τ');
90
+  str = str.replace(/&upsilon;/g, 'υ');
91
+  str = str.replace(/&phi;/g, 'φ');
92
+  str = str.replace(/&chi;/g, 'χ');
93
+  str = str.replace(/&psi;/g, 'ψ');
94
+  str = str.replace(/&omega;/g, 'ω');
95
+  str = str.replace(/&thetasym;/g, 'ϑ');
96
+  str = str.replace(/&upsih;/g, 'ϒ');
97
+  str = str.replace(/&piv;/g, 'ϖ');
98
+  str = str.replace(/&middot;/g, '·');
99
+  return str;
100
+}
101
+
102
+//
103
+
104
+function strcharacterDiscode(str) {
105
+  // 加入常用解析
106
+  str = str.replace(/&nbsp;/g, ' ');
107
+  str = str.replace(/&quot;/g, "'");
108
+  str = str.replace(/&amp;/g, '&');
109
+  // str = str.replace(/&lt;/g, '‹');
110
+  // str = str.replace(/&gt;/g, '›');
111
+
112
+  str = str.replace(/&lt;/g, '<');
113
+  str = str.replace(/&gt;/g, '>');
114
+  str = str.replace(/&#8226;/g, '•');
115
+
116
+  return str;
117
+}
118
+
119
+// HTML 支持的其他实体
120
+function strOtherDiscode(str) {
121
+  str = str.replace(/&OElig;/g, 'Œ');
122
+  str = str.replace(/&oelig;/g, 'œ');
123
+  str = str.replace(/&Scaron;/g, 'Š');
124
+  str = str.replace(/&scaron;/g, 'š');
125
+  str = str.replace(/&Yuml;/g, 'Ÿ');
126
+  str = str.replace(/&fnof;/g, 'ƒ');
127
+  str = str.replace(/&circ;/g, 'ˆ');
128
+  str = str.replace(/&tilde;/g, '˜');
129
+  str = str.replace(/&ensp;/g, '');
130
+  str = str.replace(/&emsp;/g, '');
131
+  str = str.replace(/&thinsp;/g, '');
132
+  str = str.replace(/&zwnj;/g, '');
133
+  str = str.replace(/&zwj;/g, '');
134
+  str = str.replace(/&lrm;/g, '');
135
+  str = str.replace(/&rlm;/g, '');
136
+  str = str.replace(/&ndash;/g, '–');
137
+  str = str.replace(/&mdash;/g, '—');
138
+  str = str.replace(/&lsquo;/g, '‘');
139
+  str = str.replace(/&rsquo;/g, '’');
140
+  str = str.replace(/&sbquo;/g, '‚');
141
+  str = str.replace(/&ldquo;/g, '“');
142
+  str = str.replace(/&rdquo;/g, '”');
143
+  str = str.replace(/&bdquo;/g, '„');
144
+  str = str.replace(/&dagger;/g, '†');
145
+  str = str.replace(/&Dagger;/g, '‡');
146
+  str = str.replace(/&bull;/g, '•');
147
+  str = str.replace(/&hellip;/g, '…');
148
+  str = str.replace(/&permil;/g, '‰');
149
+  str = str.replace(/&prime;/g, '′');
150
+  str = str.replace(/&Prime;/g, '″');
151
+  str = str.replace(/&lsaquo;/g, '‹');
152
+  str = str.replace(/&rsaquo;/g, '›');
153
+  str = str.replace(/&oline;/g, '‾');
154
+  str = str.replace(/&euro;/g, '€');
155
+  str = str.replace(/&trade;/g, '™');
156
+
157
+  str = str.replace(/&larr;/g, '←');
158
+  str = str.replace(/&uarr;/g, '↑');
159
+  str = str.replace(/&rarr;/g, '→');
160
+  str = str.replace(/&darr;/g, '↓');
161
+  str = str.replace(/&harr;/g, '↔');
162
+  str = str.replace(/&crarr;/g, '↵');
163
+  str = str.replace(/&lceil;/g, '⌈');
164
+  str = str.replace(/&rceil;/g, '⌉');
165
+
166
+  str = str.replace(/&lfloor;/g, '⌊');
167
+  str = str.replace(/&rfloor;/g, '⌋');
168
+  str = str.replace(/&loz;/g, '◊');
169
+  str = str.replace(/&spades;/g, '♠');
170
+  str = str.replace(/&clubs;/g, '♣');
171
+  str = str.replace(/&hearts;/g, '♥');
172
+
173
+  str = str.replace(/&diams;/g, '♦');
174
+  str = str.replace(/&#39;/g, '\'');
175
+  return str;
176
+}
177
+
178
+function strMoreDiscode(str) {
179
+  str = str.replace(/\r\n/g, '');
180
+  str = str.replace(/\n/g, '');
181
+
182
+  str = str.replace(/code/g, 'wxxxcode-style');
183
+  return str;
184
+}
185
+
186
+function strDiscode(str) {
187
+  str = strNumDiscode(str);
188
+  str = strGreeceDiscode(str);
189
+  str = strcharacterDiscode(str);
190
+  str = strOtherDiscode(str);
191
+  str = strMoreDiscode(str);
192
+  return str;
193
+}
194
+function urlToHttpUrl(url, rep) {
195
+  const patt1 = new RegExp('^//');
196
+  const result = patt1.test(url);
197
+  if (result) {
198
+    url = `${rep}:${url}`;
199
+  }
200
+  return url;
201
+}
202
+
203
+module.exports = {
204
+  strDiscode,
205
+  urlToHttpUrl,
206
+};

+ 159 - 0
src/utils/wxParse/wxParse.js

@@ -0,0 +1,159 @@
1
+/**
2
+ * author: Di (微信小程序开发工程师)
3
+ * organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
4
+ *               垂直微信小程序开发交流社区
5
+ *
6
+ * github地址: https://github.com/icindy/wxParse
7
+ *
8
+ * for: 微信小程序富文本解析
9
+ * detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
10
+ */
11
+
12
+/**
13
+ * utils函数引入
14
+ * */
15
+import showdown from './showdown.js';
16
+import HtmlToJson from './html2json.js';
17
+/**
18
+ * 配置及公有属性
19
+ * */
20
+let realWindowWidth = 0;
21
+let realWindowHeight = 0;
22
+wx.getSystemInfo({
23
+  success(res) {
24
+    realWindowWidth = res.windowWidth;
25
+    realWindowHeight = res.windowHeight;
26
+  },
27
+});
28
+/**
29
+ * 主函数入口区
30
+ * */
31
+function wxParse(bindName = 'wxParseData', type = 'html', data = '<div class="color:red;">数据不能为空</div>', target, imagePadding) {
32
+  const that = target;
33
+  let transData = {};// 存放转化后的数据
34
+  if (type == 'html') {
35
+    transData = HtmlToJson.html2json(data, bindName);
36
+    console.log(JSON.stringify(transData, ' ', ' '));
37
+  } else if (type == 'md' || type == 'markdown') {
38
+    const converter = new showdown.Converter();
39
+    const html = converter.makeHtml(data);
40
+    transData = HtmlToJson.html2json(html, bindName);
41
+    console.log(JSON.stringify(transData, ' ', ' '));
42
+  }
43
+  transData.view = {};
44
+  transData.view.imagePadding = 0;
45
+  if (typeof (imagePadding) !== 'undefined') {
46
+    transData.view.imagePadding = imagePadding;
47
+  }
48
+  const bindData = {};
49
+  bindData[bindName] = transData;
50
+  that.setData(bindData);
51
+  that.wxParseImgLoad = wxParseImgLoad;
52
+  that.wxParseImgTap = wxParseImgTap;
53
+}
54
+// 图片点击事件
55
+function wxParseImgTap(e) {
56
+  const that = this;
57
+  const nowImgUrl = e.target.dataset.src;
58
+  const tagFrom = e.target.dataset.from;
59
+  if (typeof (tagFrom) !== 'undefined' && tagFrom.length > 0) {
60
+    wx.previewImage({
61
+      current: nowImgUrl, // 当前显示图片的http链接
62
+      urls: that.data[tagFrom].imageUrls, // 需要预览的图片http链接列表
63
+    });
64
+  }
65
+}
66
+
67
+/**
68
+ * 图片视觉宽高计算函数区
69
+ * */
70
+function wxParseImgLoad(e) {
71
+  const that = this;
72
+  const tagFrom = e.target.dataset.from;
73
+  const { idx } = e.target.dataset;
74
+  if (typeof (tagFrom) !== 'undefined' && tagFrom.length > 0) {
75
+    calMoreImageInfo(e, idx, that, tagFrom);
76
+  }
77
+}
78
+// 假循环获取计算图片视觉最佳宽高
79
+function calMoreImageInfo(e, idx, that, bindName) {
80
+  const temData = that.data[bindName];
81
+  if (!temData || temData.images.length == 0) {
82
+    return;
83
+  }
84
+  const temImages = temData.images;
85
+  // 因为无法获取view宽度 需要自定义padding进行计算,稍后处理
86
+  const recal = wxAutoImageCal(e.detail.width, e.detail.height, that, bindName);
87
+  // temImages[idx].width = recal.imageWidth;
88
+  // temImages[idx].height = recal.imageheight;
89
+  // temData.images = temImages;
90
+  // var bindData = {};
91
+  // bindData[bindName] = temData;
92
+  // that.setData(bindData);
93
+  const { index } = temImages[idx];
94
+  let key = `${bindName}`;
95
+  for (const i of index.split('.')) key += `.nodes[${i}]`;
96
+  const keyW = `${key}.width`;
97
+  const keyH = `${key}.height`;
98
+  that.setData({
99
+    [keyW]: recal.imageWidth,
100
+    [keyH]: recal.imageheight,
101
+  });
102
+}
103
+
104
+// 计算视觉优先的图片宽高
105
+function wxAutoImageCal(originalWidth, originalHeight, that, bindName) {
106
+  // 获取图片的原始长宽
107
+  let windowWidth = 0; let
108
+    windowHeight = 0;
109
+  let autoWidth = 0; let
110
+    autoHeight = 0;
111
+  const results = {};
112
+  const padding = that.data[bindName].view.imagePadding;
113
+  windowWidth = realWindowWidth - 2 * padding;
114
+  windowHeight = realWindowHeight;
115
+  // 判断按照那种方式进行缩放
116
+  // console.log("windowWidth" + windowWidth);
117
+  if (originalWidth > windowWidth) { // 在图片width大于手机屏幕width时候
118
+    autoWidth = windowWidth;
119
+    // console.log("autoWidth" + autoWidth);
120
+    autoHeight = (autoWidth * originalHeight) / originalWidth;
121
+    // console.log("autoHeight" + autoHeight);
122
+    results.imageWidth = autoWidth;
123
+    results.imageheight = autoHeight;
124
+  } else { // 否则展示原来的数据
125
+    results.imageWidth = originalWidth;
126
+    results.imageheight = originalHeight;
127
+  }
128
+  return results;
129
+}
130
+
131
+function wxParseTemArray(temArrayName, bindNameReg, total, that) {
132
+  const array = [];
133
+  const temData = that.data;
134
+  let obj = null;
135
+  for (let i = 0; i < total; i++) {
136
+    const simArr = temData[bindNameReg + i].nodes;
137
+    array.push(simArr);
138
+  }
139
+
140
+  temArrayName = temArrayName || 'wxParseTemArray';
141
+  obj = JSON.parse(`{"${temArrayName}":""}`);
142
+  obj[temArrayName] = array;
143
+  that.setData(obj);
144
+}
145
+
146
+/**
147
+ * 配置emojis
148
+ *
149
+ */
150
+
151
+function emojisInit(reg = '', baseSrc = '/wxParse/emojis/', emojis) {
152
+  HtmlToJson.emojisInit(reg, baseSrc, emojis);
153
+}
154
+
155
+module.exports = {
156
+  wxParse,
157
+  wxParseTemArray,
158
+  emojisInit,
159
+};

+ 967 - 0
src/utils/wxParse/wxParse.wxml

@@ -0,0 +1,967 @@
1
+<!--**
2
+ * author: Di (微信小程序开发工程师)
3
+ * organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
4
+ *               垂直微信小程序开发交流社区
5
+ * 
6
+ * github地址: https://github.com/icindy/wxParse
7
+ * 
8
+ * for: 微信小程序富文本解析
9
+ * detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
10
+ */-->
11
+
12
+<!--基础元素-->
13
+<template name="wxParseVideo">
14
+  <!--增加video标签支持,并循环添加-->
15
+  <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
16
+    <video class="{{item.classStr}} wxParse-{{item.tag}}-video" src="{{item.attr.src}}"></video>
17
+  </view>
18
+</template>
19
+
20
+<template name="wxParseImg">
21
+  <image class="{{item.classStr}} wxParse-{{item.tag}}" data-from="{{item.from}}" data-src="{{item.attr.src}}" data-idx="{{item.imgIndex}}" src="{{item.attr.src}}" bindload="wxParseImgLoad" bindtap="wxParseImgTap" mode="widthFix" style="width:100%;"
22
+  />
23
+</template>
24
+
25
+<template name="WxEmojiView">
26
+  <view class="WxEmojiView wxParse-inline" style="{{item.styleStr}}">
27
+    <block wx:for="{{item.textArray}}" wx:key="">
28
+      <block class="{{item.text == '\\n' ? 'wxParse-hide':''}}" wx:if="{{item.node == 'text'}}">{{item.text}}</block>
29
+      <block wx:elif="{{item.node == 'element'}}">
30
+        <image class="wxEmoji" src="{{item.baseSrc}}{{item.text}}" />
31
+      </block>
32
+    </block>
33
+  </view>
34
+</template>
35
+
36
+<template name="WxParseBr">
37
+  <text>\n</text>
38
+</template>
39
+<!--入口模版-->
40
+
41
+<template name="wxParse">
42
+  <block wx:for="{{wxParseData}}" wx:key="">
43
+    <template is="wxParse0" data="{{item}}" />
44
+  </block>
45
+</template>
46
+
47
+
48
+<!--循环模版-->
49
+<template name="wxParse0">
50
+  <!--<template is="wxParse1" data="{{item}}" />-->
51
+  <!--判断是否是标签节点-->
52
+  <block wx:if="{{item.node == 'element'}}">
53
+    <block wx:if="{{item.tag == 'button'}}">
54
+      <button type="default" size="mini">
55
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
56
+          <template is="wxParse1" data="{{item}}" />
57
+        </block>
58
+      </button>
59
+    </block>
60
+    <!--li类型-->
61
+    <block wx:elif="{{item.tag == 'li'}}">
62
+      <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
63
+        <view class="{{item.classStr}} wxParse-li-inner">
64
+          <view class="{{item.classStr}} wxParse-li-text">
65
+            <view class="{{item.classStr}} wxParse-li-circle"></view>
66
+          </view>
67
+          <view class="{{item.classStr}} wxParse-li-text">
68
+            <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
69
+              <template is="wxParse1" data="{{item}}" />
70
+            </block>
71
+          </view>
72
+        </view>
73
+      </view>
74
+    </block>
75
+
76
+    <!--video类型-->
77
+    <block wx:elif="{{item.tag == 'video'}}">
78
+      <template is="wxParseVideo" data="{{item}}" />
79
+    </block>
80
+
81
+    <!--img类型-->
82
+    <block wx:elif="{{item.tag == 'img'}}">
83
+      <template is="wxParseImg" data="{{item}}" />
84
+    </block>
85
+
86
+    <!--a类型-->
87
+    <block wx:elif="{{item.tag == 'a'}}">
88
+      <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}" hover-class="wxParse-a-hover">
89
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
90
+          <template is="wxParse1" data="{{item}}" />
91
+        </block>
92
+      </view>
93
+    </block>
94
+    <block wx:elif="{{item.tag == 'table'}}">
95
+      <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
96
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
97
+          <template is="wxParse1" data="{{item}}" />
98
+        </block>
99
+      </view>
100
+    </block>
101
+
102
+    <block wx:elif="{{item.tag == 'br'}}">
103
+      <template is="WxParseBr"></template>
104
+    </block>
105
+    <!--其他块级标签-->
106
+    <block wx:elif="{{item.tagType == 'block'}}">
107
+      <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
108
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
109
+          <template is="wxParse1" data="{{item}}" />
110
+        </block>
111
+      </view>
112
+    </block>
113
+
114
+    <!--内联标签-->
115
+    <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
116
+      <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
117
+        <template is="wxParse1" data="{{item}}" />
118
+      </block>
119
+    </view>
120
+
121
+  </block>
122
+
123
+  <!--判断是否是文本节点-->
124
+  <block wx:elif="{{item.node == 'text'}}">
125
+    <!--如果是,直接进行-->
126
+    <template is="WxEmojiView" data="{{item}}" />
127
+  </block>
128
+
129
+</template>
130
+
131
+
132
+
133
+<!--循环模版-->
134
+<template name="wxParse1">
135
+  <!--<template is="wxParse2" data="{{item}}" />-->
136
+  <!--判断是否是标签节点-->
137
+  <block wx:if="{{item.node == 'element'}}">
138
+    <block wx:if="{{item.tag == 'button'}}">
139
+      <button type="default" size="mini">
140
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
141
+          <template is="wxParse2" data="{{item}}" />
142
+        </block>
143
+      </button>
144
+    </block>
145
+    <!--li类型-->
146
+    <block wx:elif="{{item.tag == 'li'}}">
147
+      <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
148
+        <view class="{{item.classStr}} wxParse-li-inner">
149
+          <view class="{{item.classStr}} wxParse-li-text">
150
+            <view class="{{item.classStr}} wxParse-li-circle"></view>
151
+          </view>
152
+          <view class="{{item.classStr}} wxParse-li-text">
153
+            <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
154
+              <template is="wxParse2" data="{{item}}" />
155
+            </block>
156
+          </view>
157
+        </view>
158
+      </view>
159
+    </block>
160
+
161
+    <!--video类型-->
162
+    <block wx:elif="{{item.tag == 'video'}}">
163
+      <template is="wxParseVideo" data="{{item}}" />
164
+    </block>
165
+
166
+    <!--img类型-->
167
+    <block wx:elif="{{item.tag == 'img'}}">
168
+      <template is="wxParseImg" data="{{item}}" />
169
+    </block>
170
+
171
+    <!--a类型-->
172
+    <block wx:elif="{{item.tag == 'a'}}">
173
+      <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}" hover-class="wxParse-a-hover">
174
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
175
+          <template is="wxParse2" data="{{item}}" />
176
+        </block>
177
+      </view>
178
+    </block>
179
+
180
+    <block wx:elif="{{item.tag == 'br'}}">
181
+      <template is="WxParseBr"></template>
182
+    </block>
183
+    <!--其他块级标签-->
184
+    <block wx:elif="{{item.tagType == 'block'}}">
185
+      <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
186
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
187
+          <template is="wxParse2" data="{{item}}" />
188
+        </block>
189
+      </view>
190
+    </block>
191
+
192
+    <!--内联标签-->
193
+    <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
194
+      <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
195
+        <template is="wxParse2" data="{{item}}" />
196
+      </block>
197
+    </view>
198
+
199
+  </block>
200
+
201
+  <!--判断是否是文本节点-->
202
+  <block wx:elif="{{item.node == 'text'}}">
203
+    <!--如果是,直接进行-->
204
+    <template is="WxEmojiView" data="{{item}}" />
205
+  </block>
206
+
207
+</template>
208
+
209
+
210
+<!--循环模版-->
211
+<template name="wxParse2">
212
+  <!--<template is="wxParse3" data="{{item}}" />-->
213
+  <!--判断是否是标签节点-->
214
+  <block wx:if="{{item.node == 'element'}}">
215
+    <block wx:if="{{item.tag == 'button'}}">
216
+      <button type="default" size="mini">
217
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
218
+          <template is="wxParse3" data="{{item}}" />
219
+        </block>
220
+      </button>
221
+    </block>
222
+    <!--li类型-->
223
+    <block wx:elif="{{item.tag == 'li'}}">
224
+      <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
225
+        <view class="{{item.classStr}} wxParse-li-inner">
226
+          <view class="{{item.classStr}} wxParse-li-text">
227
+            <view class="{{item.classStr}} wxParse-li-circle"></view>
228
+          </view>
229
+          <view class="{{item.classStr}} wxParse-li-text">
230
+            <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
231
+              <template is="wxParse3" data="{{item}}" />
232
+            </block>
233
+          </view>
234
+        </view>
235
+      </view>
236
+    </block>
237
+
238
+    <!--video类型-->
239
+    <block wx:elif="{{item.tag == 'video'}}">
240
+      <template is="wxParseVideo" data="{{item}}" />
241
+    </block>
242
+
243
+    <!--img类型-->
244
+    <block wx:elif="{{item.tag == 'img'}}">
245
+      <template is="wxParseImg" data="{{item}}" />
246
+    </block>
247
+
248
+    <!--a类型-->
249
+    <block wx:elif="{{item.tag == 'a'}}">
250
+      <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}" hover-class="wxParse-a-hover">
251
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
252
+          <template is="wxParse3" data="{{item}}" />
253
+        </block>
254
+      </view>
255
+    </block>
256
+
257
+    <block wx:elif="{{item.tag == 'br'}}">
258
+      <template is="WxParseBr"></template>
259
+    </block>
260
+    <!--其他块级标签-->
261
+    <block wx:elif="{{item.tagType == 'block'}}">
262
+      <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
263
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
264
+          <template is="wxParse3" data="{{item}}" />
265
+        </block>
266
+      </view>
267
+    </block>
268
+
269
+    <!--内联标签-->
270
+    <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
271
+      <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
272
+        <template is="wxParse3" data="{{item}}" />
273
+      </block>
274
+    </view>
275
+
276
+  </block>
277
+
278
+  <!--判断是否是文本节点-->
279
+  <block wx:elif="{{item.node == 'text'}}">
280
+    <!--如果是,直接进行-->
281
+    <template is="WxEmojiView" data="{{item}}" />
282
+  </block>
283
+
284
+</template>
285
+
286
+<!--循环模版-->
287
+<template name="wxParse3">
288
+  <!--<template is="wxParse4" data="{{item}}" />-->
289
+  <!--判断是否是标签节点-->
290
+  <block wx:if="{{item.node == 'element'}}">
291
+    <block wx:if="{{item.tag == 'button'}}">
292
+      <button type="default" size="mini">
293
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
294
+          <template is="wxParse4" data="{{item}}" />
295
+        </block>
296
+      </button>
297
+    </block>
298
+    <!--li类型-->
299
+    <block wx:elif="{{item.tag == 'li'}}">
300
+      <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
301
+        <view class="{{item.classStr}} wxParse-li-inner">
302
+          <view class="{{item.classStr}} wxParse-li-text">
303
+            <view class="{{item.classStr}} wxParse-li-circle"></view>
304
+          </view>
305
+          <view class="{{item.classStr}} wxParse-li-text">
306
+            <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
307
+              <template is="wxParse4" data="{{item}}" />
308
+            </block>
309
+          </view>
310
+        </view>
311
+      </view>
312
+    </block>
313
+
314
+    <!--video类型-->
315
+    <block wx:elif="{{item.tag == 'video'}}">
316
+      <template is="wxParseVideo" data="{{item}}" />
317
+    </block>
318
+
319
+    <!--img类型-->
320
+    <block wx:elif="{{item.tag == 'img'}}">
321
+      <template is="wxParseImg" data="{{item}}" />
322
+    </block>
323
+
324
+    <!--a类型-->
325
+    <block wx:elif="{{item.tag == 'a'}}">
326
+      <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}" hover-class="wxParse-a-hover">
327
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
328
+          <template is="wxParse4" data="{{item}}" />
329
+        </block>
330
+      </view>
331
+    </block>
332
+
333
+    <block wx:elif="{{item.tag == 'br'}}">
334
+      <template is="WxParseBr"></template>
335
+    </block>
336
+    <!--其他块级标签-->
337
+    <block wx:elif="{{item.tagType == 'block'}}">
338
+      <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
339
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
340
+          <template is="wxParse4" data="{{item}}" />
341
+        </block>
342
+      </view>
343
+    </block>
344
+
345
+    <!--内联标签-->
346
+    <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
347
+      <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
348
+        <template is="wxParse4" data="{{item}}" />
349
+      </block>
350
+    </view>
351
+
352
+  </block>
353
+
354
+  <!--判断是否是文本节点-->
355
+  <block wx:elif="{{item.node == 'text'}}">
356
+    <!--如果是,直接进行-->
357
+    <template is="WxEmojiView" data="{{item}}" />
358
+  </block>
359
+
360
+</template>
361
+
362
+<!--循环模版-->
363
+<template name="wxParse4">
364
+  <!--<template is="wxParse5" data="{{item}}" />-->
365
+  <!--判断是否是标签节点-->
366
+  <block wx:if="{{item.node == 'element'}}">
367
+    <block wx:if="{{item.tag == 'button'}}">
368
+      <button type="default" size="mini">
369
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
370
+          <template is="wxParse5" data="{{item}}" />
371
+        </block>
372
+      </button>
373
+    </block>
374
+    <!--li类型-->
375
+    <block wx:elif="{{item.tag == 'li'}}">
376
+      <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
377
+        <view class="{{item.classStr}} wxParse-li-inner">
378
+          <view class="{{item.classStr}} wxParse-li-text">
379
+            <view class="{{item.classStr}} wxParse-li-circle"></view>
380
+          </view>
381
+          <view class="{{item.classStr}} wxParse-li-text">
382
+            <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
383
+              <template is="wxParse5" data="{{item}}" />
384
+            </block>
385
+          </view>
386
+        </view>
387
+      </view>
388
+    </block>
389
+
390
+    <!--video类型-->
391
+    <block wx:elif="{{item.tag == 'video'}}">
392
+      <template is="wxParseVideo" data="{{item}}" />
393
+    </block>
394
+
395
+    <!--img类型-->
396
+    <block wx:elif="{{item.tag == 'img'}}">
397
+      <template is="wxParseImg" data="{{item}}" />
398
+    </block>
399
+
400
+    <!--a类型-->
401
+    <block wx:elif="{{item.tag == 'a'}}">
402
+      <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}" hover-class="wxParse-a-hover">
403
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
404
+          <template is="wxParse5" data="{{item}}" />
405
+        </block>
406
+      </view>
407
+    </block>
408
+
409
+    <block wx:elif="{{item.tag == 'br'}}">
410
+      <template is="WxParseBr"></template>
411
+    </block>
412
+    <!--其他块级标签-->
413
+    <block wx:elif="{{item.tagType == 'block'}}">
414
+      <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
415
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
416
+          <template is="wxParse5" data="{{item}}" />
417
+        </block>
418
+      </view>
419
+    </block>
420
+
421
+    <!--内联标签-->
422
+    <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
423
+      <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
424
+        <template is="wxParse5" data="{{item}}" />
425
+      </block>
426
+    </view>
427
+
428
+  </block>
429
+
430
+  <!--判断是否是文本节点-->
431
+  <block wx:elif="{{item.node == 'text'}}">
432
+    <!--如果是,直接进行-->
433
+    <template is="WxEmojiView" data="{{item}}" />
434
+  </block>
435
+
436
+</template>
437
+
438
+<!--循环模版-->
439
+<template name="wxParse5">
440
+  <!--<template is="wxParse6" data="{{item}}" />-->
441
+  <!--判断是否是标签节点-->
442
+  <block wx:if="{{item.node == 'element'}}">
443
+    <block wx:if="{{item.tag == 'button'}}">
444
+      <button type="default" size="mini">
445
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
446
+          <template is="wxParse6" data="{{item}}" />
447
+        </block>
448
+      </button>
449
+    </block>
450
+    <!--li类型-->
451
+    <block wx:elif="{{item.tag == 'li'}}">
452
+      <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
453
+        <view class="{{item.classStr}} wxParse-li-inner">
454
+          <view class="{{item.classStr}} wxParse-li-text">
455
+            <view class="{{item.classStr}} wxParse-li-circle"></view>
456
+          </view>
457
+          <view class="{{item.classStr}} wxParse-li-text">
458
+            <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
459
+              <template is="wxParse6" data="{{item}}" />
460
+            </block>
461
+          </view>
462
+        </view>
463
+      </view>
464
+    </block>
465
+
466
+    <!--video类型-->
467
+    <block wx:elif="{{item.tag == 'video'}}">
468
+      <template is="wxParseVideo" data="{{item}}" />
469
+    </block>
470
+
471
+    <!--img类型-->
472
+    <block wx:elif="{{item.tag == 'img'}}">
473
+      <template is="wxParseImg" data="{{item}}" />
474
+    </block>
475
+
476
+    <!--a类型-->
477
+    <block wx:elif="{{item.tag == 'a'}}">
478
+      <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}" hover-class="wxParse-a-hover">
479
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
480
+          <template is="wxParse6" data="{{item}}" />
481
+        </block>
482
+      </view>
483
+    </block>
484
+
485
+    <block wx:elif="{{item.tag == 'br'}}">
486
+      <template is="WxParseBr"></template>
487
+    </block>
488
+    <!--其他块级标签-->
489
+    <block wx:elif="{{item.tagType == 'block'}}">
490
+      <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
491
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
492
+          <template is="wxParse6" data="{{item}}" />
493
+        </block>
494
+      </view>
495
+    </block>
496
+
497
+    <!--内联标签-->
498
+    <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
499
+      <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
500
+        <template is="wxParse6" data="{{item}}" />
501
+      </block>
502
+    </view>
503
+
504
+  </block>
505
+
506
+  <!--判断是否是文本节点-->
507
+  <block wx:elif="{{item.node == 'text'}}">
508
+    <!--如果是,直接进行-->
509
+    <template is="WxEmojiView" data="{{item}}" />
510
+  </block>
511
+
512
+</template>
513
+
514
+<!--循环模版-->
515
+<template name="wxParse6">
516
+  <!--<template is="wxParse7" data="{{item}}" />-->
517
+  <!--判断是否是标签节点-->
518
+  <block wx:if="{{item.node == 'element'}}">
519
+    <block wx:if="{{item.tag == 'button'}}">
520
+      <button type="default" size="mini">
521
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
522
+          <template is="wxParse7" data="{{item}}" />
523
+        </block>
524
+      </button>
525
+    </block>
526
+    <!--li类型-->
527
+    <block wx:elif="{{item.tag == 'li'}}">
528
+      <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
529
+        <view class="{{item.classStr}} wxParse-li-inner">
530
+          <view class="{{item.classStr}} wxParse-li-text">
531
+            <view class="{{item.classStr}} wxParse-li-circle"></view>
532
+          </view>
533
+          <view class="{{item.classStr}} wxParse-li-text">
534
+            <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
535
+              <template is="wxParse7" data="{{item}}" />
536
+            </block>
537
+          </view>
538
+        </view>
539
+      </view>
540
+    </block>
541
+
542
+    <!--video类型-->
543
+    <block wx:elif="{{item.tag == 'video'}}">
544
+      <template is="wxParseVideo" data="{{item}}" />
545
+    </block>
546
+
547
+    <!--img类型-->
548
+    <block wx:elif="{{item.tag == 'img'}}">
549
+      <template is="wxParseImg" data="{{item}}" />
550
+    </block>
551
+
552
+    <!--a类型-->
553
+    <block wx:elif="{{item.tag == 'a'}}">
554
+      <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}" hover-class="wxParse-a-hover">
555
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
556
+          <template is="wxParse7" data="{{item}}" />
557
+        </block>
558
+      </view>
559
+    </block>
560
+
561
+    <block wx:elif="{{item.tag == 'br'}}">
562
+      <template is="WxParseBr"></template>
563
+    </block>
564
+    <!--其他块级标签-->
565
+    <block wx:elif="{{item.tagType == 'block'}}">
566
+      <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
567
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
568
+          <template is="wxParse7" data="{{item}}" />
569
+        </block>
570
+      </view>
571
+    </block>
572
+
573
+    <!--内联标签-->
574
+    <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
575
+      <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
576
+        <template is="wxParse7" data="{{item}}" />
577
+      </block>
578
+    </view>
579
+
580
+  </block>
581
+
582
+  <!--判断是否是文本节点-->
583
+  <block wx:elif="{{item.node == 'text'}}">
584
+    <!--如果是,直接进行-->
585
+    <template is="WxEmojiView" data="{{item}}" />
586
+  </block>
587
+
588
+</template>
589
+<!--循环模版-->
590
+<template name="wxParse7">
591
+  <!--<template is="wxParse8" data="{{item}}" />-->
592
+  <!--判断是否是标签节点-->
593
+  <block wx:if="{{item.node == 'element'}}">
594
+    <block wx:if="{{item.tag == 'button'}}">
595
+      <button type="default" size="mini">
596
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
597
+          <template is="wxParse8" data="{{item}}" />
598
+        </block>
599
+      </button>
600
+    </block>
601
+    <!--li类型-->
602
+    <block wx:elif="{{item.tag == 'li'}}">
603
+      <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
604
+        <view class="{{item.classStr}} wxParse-li-inner">
605
+          <view class="{{item.classStr}} wxParse-li-text">
606
+            <view class="{{item.classStr}} wxParse-li-circle"></view>
607
+          </view>
608
+          <view class="{{item.classStr}} wxParse-li-text">
609
+            <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
610
+              <template is="wxParse8" data="{{item}}" />
611
+            </block>
612
+          </view>
613
+        </view>
614
+      </view>
615
+    </block>
616
+
617
+    <!--video类型-->
618
+    <block wx:elif="{{item.tag == 'video'}}">
619
+      <template is="wxParseVideo" data="{{item}}" />
620
+    </block>
621
+
622
+    <!--img类型-->
623
+    <block wx:elif="{{item.tag == 'img'}}">
624
+      <template is="wxParseImg" data="{{item}}" />
625
+    </block>
626
+
627
+    <!--a类型-->
628
+    <block wx:elif="{{item.tag == 'a'}}">
629
+      <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}" hover-class="wxParse-a-hover">
630
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
631
+          <template is="wxParse8" data="{{item}}" />
632
+        </block>
633
+      </view>
634
+    </block>
635
+
636
+    <block wx:elif="{{item.tag == 'br'}}">
637
+      <template is="WxParseBr"></template>
638
+    </block>
639
+    <!--其他块级标签-->
640
+    <block wx:elif="{{item.tagType == 'block'}}">
641
+      <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
642
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
643
+          <template is="wxParse8" data="{{item}}" />
644
+        </block>
645
+      </view>
646
+    </block>
647
+
648
+    <!--内联标签-->
649
+    <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
650
+      <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
651
+        <template is="wxParse8" data="{{item}}" />
652
+      </block>
653
+    </view>
654
+
655
+  </block>
656
+
657
+  <!--判断是否是文本节点-->
658
+  <block wx:elif="{{item.node == 'text'}}">
659
+    <!--如果是,直接进行-->
660
+    <template is="WxEmojiView" data="{{item}}" />
661
+  </block>
662
+
663
+</template>
664
+
665
+<!--循环模版-->
666
+<template name="wxParse8">
667
+  <!--<template is="wxParse9" data="{{item}}" />-->
668
+  <!--判断是否是标签节点-->
669
+  <block wx:if="{{item.node == 'element'}}">
670
+    <block wx:if="{{item.tag == 'button'}}">
671
+      <button type="default" size="mini">
672
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
673
+          <template is="wxParse9" data="{{item}}" />
674
+        </block>
675
+      </button>
676
+    </block>
677
+    <!--li类型-->
678
+    <block wx:elif="{{item.tag == 'li'}}">
679
+      <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
680
+        <view class="{{item.classStr}} wxParse-li-inner">
681
+          <view class="{{item.classStr}} wxParse-li-text">
682
+            <view class="{{item.classStr}} wxParse-li-circle"></view>
683
+          </view>
684
+          <view class="{{item.classStr}} wxParse-li-text">
685
+            <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
686
+              <template is="wxParse9" data="{{item}}" />
687
+            </block>
688
+          </view>
689
+        </view>
690
+      </view>
691
+    </block>
692
+
693
+    <!--video类型-->
694
+    <block wx:elif="{{item.tag == 'video'}}">
695
+      <template is="wxParseVideo" data="{{item}}" />
696
+    </block>
697
+
698
+    <!--img类型-->
699
+    <block wx:elif="{{item.tag == 'img'}}">
700
+      <template is="wxParseImg" data="{{item}}" />
701
+    </block>
702
+
703
+    <!--a类型-->
704
+    <block wx:elif="{{item.tag == 'a'}}">
705
+      <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}" hover-class="wxParse-a-hover">
706
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
707
+          <template is="wxParse9" data="{{item}}" />
708
+        </block>
709
+      </view>
710
+    </block>
711
+
712
+    <block wx:elif="{{item.tag == 'br'}}">
713
+      <template is="WxParseBr"></template>
714
+    </block>
715
+    <!--其他块级标签-->
716
+    <block wx:elif="{{item.tagType == 'block'}}">
717
+      <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
718
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
719
+          <template is="wxParse9" data="{{item}}" />
720
+        </block>
721
+      </view>
722
+    </block>
723
+
724
+    <!--内联标签-->
725
+    <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
726
+      <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
727
+        <template is="wxParse9" data="{{item}}" />
728
+      </block>
729
+    </view>
730
+
731
+  </block>
732
+
733
+  <!--判断是否是文本节点-->
734
+  <block wx:elif="{{item.node == 'text'}}">
735
+    <!--如果是,直接进行-->
736
+    <template is="WxEmojiView" data="{{item}}" />
737
+  </block>
738
+
739
+</template>
740
+
741
+<!--循环模版-->
742
+<template name="wxParse9">
743
+  <!--<template is="wxParse10" data="{{item}}" />-->
744
+  <!--判断是否是标签节点-->
745
+  <block wx:if="{{item.node == 'element'}}">
746
+    <block wx:if="{{item.tag == 'button'}}">
747
+      <button type="default" size="mini">
748
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
749
+          <template is="wxParse10" data="{{item}}" />
750
+        </block>
751
+      </button>
752
+    </block>
753
+    <!--li类型-->
754
+    <block wx:elif="{{item.tag == 'li'}}">
755
+      <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
756
+        <view class="{{item.classStr}} wxParse-li-inner">
757
+          <view class="{{item.classStr}} wxParse-li-text">
758
+            <view class="{{item.classStr}} wxParse-li-circle"></view>
759
+          </view>
760
+          <view class="{{item.classStr}} wxParse-li-text">
761
+            <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
762
+              <template is="wxParse10" data="{{item}}" />
763
+            </block>
764
+          </view>
765
+        </view>
766
+      </view>
767
+    </block>
768
+
769
+    <!--video类型-->
770
+    <block wx:elif="{{item.tag == 'video'}}">
771
+      <template is="wxParseVideo" data="{{item}}" />
772
+    </block>
773
+
774
+    <!--img类型-->
775
+    <block wx:elif="{{item.tag == 'img'}}">
776
+      <template is="wxParseImg" data="{{item}}" />
777
+    </block>
778
+
779
+    <!--a类型-->
780
+    <block wx:elif="{{item.tag == 'a'}}">
781
+      <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}" hover-class="wxParse-a-hover">
782
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
783
+          <template is="wxParse10" data="{{item}}" />
784
+        </block>
785
+      </view>
786
+    </block>
787
+
788
+    <block wx:elif="{{item.tag == 'br'}}">
789
+      <template is="WxParseBr"></template>
790
+    </block>
791
+    <!--其他块级标签-->
792
+    <block wx:elif="{{item.tagType == 'block'}}">
793
+      <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
794
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
795
+          <template is="wxParse10" data="{{item}}" />
796
+        </block>
797
+      </view>
798
+    </block>
799
+
800
+    <!--内联标签-->
801
+    <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
802
+      <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
803
+        <template is="wxParse10" data="{{item}}" />
804
+      </block>
805
+    </view>
806
+
807
+  </block>
808
+
809
+  <!--判断是否是文本节点-->
810
+  <block wx:elif="{{item.node == 'text'}}">
811
+    <!--如果是,直接进行-->
812
+    <template is="WxEmojiView" data="{{item}}" />
813
+  </block>
814
+
815
+</template>
816
+
817
+<!--循环模版-->
818
+<template name="wxParse10">
819
+  <!--<template is="wxParse11" data="{{item}}" />-->
820
+  <!--判断是否是标签节点-->
821
+  <block wx:if="{{item.node == 'element'}}">
822
+    <block wx:if="{{item.tag == 'button'}}">
823
+      <button type="default" size="mini">
824
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
825
+          <template is="wxParse11" data="{{item}}" />
826
+        </block>
827
+      </button>
828
+    </block>
829
+    <!--li类型-->
830
+    <block wx:elif="{{item.tag == 'li'}}">
831
+      <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
832
+        <view class="{{item.classStr}} wxParse-li-inner">
833
+          <view class="{{item.classStr}} wxParse-li-text">
834
+            <view class="{{item.classStr}} wxParse-li-circle"></view>
835
+          </view>
836
+          <view class="{{item.classStr}} wxParse-li-text">
837
+            <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
838
+              <template is="wxParse11" data="{{item}}" />
839
+            </block>
840
+          </view>
841
+        </view>
842
+      </view>
843
+    </block>
844
+
845
+    <!--video类型-->
846
+    <block wx:elif="{{item.tag == 'video'}}">
847
+      <template is="wxParseVideo" data="{{item}}" />
848
+    </block>
849
+
850
+    <!--img类型-->
851
+    <block wx:elif="{{item.tag == 'img'}}">
852
+      <template is="wxParseImg" data="{{item}}" />
853
+    </block>
854
+
855
+    <!--a类型-->
856
+    <block wx:elif="{{item.tag == 'a'}}">
857
+      <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}" hover-class="wxParse-a-hover">
858
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
859
+          <template is="wxParse11" data="{{item}}" />
860
+        </block>
861
+      </view>
862
+    </block>
863
+
864
+    <block wx:elif="{{item.tag == 'br'}}">
865
+      <template is="WxParseBr"></template>
866
+    </block>
867
+    <!--其他块级标签-->
868
+    <block wx:elif="{{item.tagType == 'block'}}">
869
+      <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
870
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
871
+          <template is="wxParse11" data="{{item}}" />
872
+        </block>
873
+      </view>
874
+    </block>
875
+
876
+    <!--内联标签-->
877
+    <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
878
+      <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
879
+        <template is="wxParse11" data="{{item}}" />
880
+      </block>
881
+    </view>
882
+
883
+  </block>
884
+
885
+  <!--判断是否是文本节点-->
886
+  <block wx:elif="{{item.node == 'text'}}">
887
+    <!--如果是,直接进行-->
888
+    <template is="WxEmojiView" data="{{item}}" />
889
+  </block>
890
+
891
+</template>
892
+
893
+<!--循环模版-->
894
+<template name="wxParse11">
895
+  <!--<template is="wxParse12" data="{{item}}" />-->
896
+  <!--判断是否是标签节点-->
897
+  <block wx:if="{{item.node == 'element'}}">
898
+    <block wx:if="{{item.tag == 'button'}}">
899
+      <button type="default" size="mini">
900
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
901
+          <template is="wxParse12" data="{{item}}" />
902
+        </block>
903
+      </button>
904
+    </block>
905
+    <!--li类型-->
906
+    <block wx:elif="{{item.tag == 'li'}}">
907
+      <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
908
+        <view class="{{item.classStr}} wxParse-li-inner">
909
+          <view class="{{item.classStr}} wxParse-li-text">
910
+            <view class="{{item.classStr}} wxParse-li-circle"></view>
911
+          </view>
912
+          <view class="{{item.classStr}} wxParse-li-text">
913
+            <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
914
+              <template is="wxParse12" data="{{item}}" />
915
+            </block>
916
+          </view>
917
+        </view>
918
+      </view>
919
+    </block>
920
+
921
+    <!--video类型-->
922
+    <block wx:elif="{{item.tag == 'video'}}">
923
+      <template is="wxParseVideo" data="{{item}}" />
924
+    </block>
925
+
926
+    <!--img类型-->
927
+    <block wx:elif="{{item.tag == 'img'}}">
928
+      <template is="wxParseImg" data="{{item}}" />
929
+    </block>
930
+
931
+    <!--a类型-->
932
+    <block wx:elif="{{item.tag == 'a'}}">
933
+      <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}" hover-class="wxParse-a-hover">
934
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
935
+          <template is="wxParse12" data="{{item}}" />
936
+        </block>
937
+      </view>
938
+    </block>
939
+
940
+    <block wx:elif="{{item.tag == 'br'}}">
941
+      <template is="WxParseBr"></template>
942
+    </block>
943
+    <!--其他块级标签-->
944
+    <block wx:elif="{{item.tagType == 'block'}}">
945
+      <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
946
+        <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
947
+          <template is="wxParse12" data="{{item}}" />
948
+        </block>
949
+      </view>
950
+    </block>
951
+
952
+    <!--内联标签-->
953
+    <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
954
+      <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
955
+        <template is="wxParse12" data="{{item}}" />
956
+      </block>
957
+    </view>
958
+
959
+  </block>
960
+
961
+  <!--判断是否是文本节点-->
962
+  <block wx:elif="{{item.node == 'text'}}">
963
+    <!--如果是,直接进行-->
964
+    <template is="WxEmojiView" data="{{item}}" />
965
+  </block>
966
+
967
+</template>

+ 270 - 0
src/utils/wxParse/wxParse.wxss

@@ -0,0 +1,270 @@
1
+/**
2
+ * author: Di (微信小程序开发工程师)
3
+ * organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
4
+ *               垂直微信小程序开发交流社区
5
+ * 
6
+ * github地址: https://github.com/icindy/wxParse
7
+ * 
8
+ * for: 微信小程序富文本解析
9
+ * detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
10
+ */
11
+
12
+.wxParse {
13
+  margin: 0 5px;
14
+  font-family: Helvetica, sans-serif;
15
+  font-size: 28rpx;
16
+  color: #666;
17
+  line-height: 1.8;
18
+}
19
+view {
20
+  word-break: break-all;
21
+}
22
+.wxParse-inline {
23
+  display: inline;
24
+  margin: 0;
25
+  padding: 0;
26
+}
27
+/*//标题 */
28
+.wxParse-div {
29
+  margin: 0;
30
+  padding: 0;
31
+}
32
+.wxParse-h1 {
33
+  font-size: 2em;
34
+  margin: 0.67em 0;
35
+}
36
+.wxParse-h2 {
37
+  font-size: 1.5em;
38
+  margin: 0.75em 0;
39
+}
40
+.wxParse-h3 {
41
+  font-size: 1.17em;
42
+  margin: 0.83em 0;
43
+}
44
+.wxParse-h4 {
45
+  margin: 1.12em 0;
46
+}
47
+.wxParse-h5 {
48
+  font-size: 0.83em;
49
+  margin: 1.5em 0;
50
+}
51
+.wxParse-h6 {
52
+  font-size: 0.75em;
53
+  margin: 1.67em 0;
54
+}
55
+
56
+.wxParse-h1 {
57
+  font-size: 18px;
58
+  font-weight: 400;
59
+  margin-bottom: 0.9em;
60
+}
61
+.wxParse-h2 {
62
+  font-size: 16px;
63
+  font-weight: 400;
64
+  margin-bottom: 0.34em;
65
+}
66
+.wxParse-h3 {
67
+  font-weight: 400;
68
+  font-size: 15px;
69
+  margin-bottom: 0.34em;
70
+}
71
+.wxParse-h4 {
72
+  font-weight: 400;
73
+  font-size: 14px;
74
+  margin-bottom: 0.24em;
75
+}
76
+.wxParse-h5 {
77
+  font-weight: 400;
78
+  font-size: 13px;
79
+  margin-bottom: 0.14em;
80
+}
81
+.wxParse-h6 {
82
+  font-weight: 400;
83
+  font-size: 12px;
84
+  margin-bottom: 0.04em;
85
+}
86
+
87
+.wxParse-h1,
88
+.wxParse-h2,
89
+.wxParse-h3,
90
+.wxParse-h4,
91
+.wxParse-h5,
92
+.wxParse-h6,
93
+.wxParse-b,
94
+.wxParse-strong {
95
+  font-weight: bolder;
96
+}
97
+
98
+.wxParse-i,
99
+.wxParse-cite,
100
+.wxParse-em,
101
+.wxParse-var,
102
+.wxParse-address {
103
+  font-style: italic;
104
+}
105
+.wxParse-pre,
106
+.wxParse-tt,
107
+.wxParse-code,
108
+.wxParse-kbd,
109
+.wxParse-samp {
110
+  font-family: monospace;
111
+}
112
+.wxParse-pre {
113
+  white-space: pre;
114
+}
115
+.wxParse-big {
116
+  font-size: 1.17em;
117
+}
118
+.wxParse-small,
119
+.wxParse-sub,
120
+.wxParse-sup {
121
+  font-size: 0.83em;
122
+}
123
+.wxParse-sub {
124
+  vertical-align: sub;
125
+}
126
+.wxParse-sup {
127
+  vertical-align: super;
128
+}
129
+.wxParse-s,
130
+.wxParse-strike,
131
+.wxParse-del {
132
+  text-decoration: line-through;
133
+}
134
+/*wxparse-自定义个性化的css样式*/
135
+/*增加video的css样式*/
136
+.wxParse-strong,
137
+.wxParse-s {
138
+  display: inline;
139
+}
140
+.wxParse-a {
141
+  color: #576b95;
142
+  text-decoration: underline;
143
+  word-break: break-all;
144
+  overflow: auto;
145
+}
146
+
147
+.wxParse-a-hover {
148
+  opacity: 0.8;
149
+}
150
+
151
+.wxParse-video {
152
+  text-align: center;
153
+  margin: 10px 0;
154
+}
155
+
156
+.wxParse-video-video {
157
+  width: 100%;
158
+}
159
+
160
+.wxParse-img {
161
+  /*background-color: #efefef;*/
162
+  overflow: hidden;
163
+}
164
+
165
+.wxParse-blockquote {
166
+  margin: 0;
167
+  padding: 10px 0 10px 5px;
168
+  font-family: Courier, Calibri, '宋体';
169
+  background: #f5f5f5;
170
+  border-left: 3px solid #dbdbdb;
171
+}
172
+
173
+.wxParse-code,
174
+.wxParse-wxxxcode-style {
175
+  display: inline;
176
+  background: #f5f5f5;
177
+}
178
+.wxParse-ul {
179
+  margin: 20rpx 10rpx;
180
+}
181
+
182
+.wxParse-li,
183
+.wxParse-li-inner {
184
+  display: flex;
185
+  align-items: baseline;
186
+}
187
+.wxParse-li-text {
188
+  align-items: center;
189
+}
190
+
191
+.wxParse-li-circle {
192
+  display: inline-flex;
193
+  width: 6px;
194
+  height: 6px;
195
+  border-radius: 3px;
196
+  background-color: #333;
197
+  margin-right: 5px;
198
+}
199
+
200
+.wxParse-li-square {
201
+  display: inline-flex;
202
+  width: 10rpx;
203
+  height: 10rpx;
204
+  background-color: #333;
205
+  margin-right: 5px;
206
+}
207
+.wxParse-li-ring {
208
+  display: inline-flex;
209
+  width: 10rpx;
210
+  height: 10rpx;
211
+  border: 2rpx solid #333;
212
+  border-radius: 50%;
213
+  background-color: #fff;
214
+  margin-right: 5px;
215
+}
216
+
217
+/*.wxParse-table{
218
+    width: 100%;
219
+    height: 400px;
220
+}
221
+.wxParse-thead,.wxParse-tfoot,.wxParse-tr{
222
+    display: flex;
223
+    flex-direction: row;
224
+}
225
+.wxParse-th,.wxParse-td{
226
+    display: flex;
227
+    width: 580px;
228
+    overflow: auto;
229
+}*/
230
+
231
+.wxParse-u {
232
+  text-decoration: underline;
233
+}
234
+.wxParse-hide {
235
+  display: none;
236
+}
237
+.WxEmojiView {
238
+  align-items: center;
239
+}
240
+.wxEmoji {
241
+  width: 16px;
242
+  height: 16px;
243
+}
244
+.wxParse-tr {
245
+  display: flex;
246
+  border-right: 1px solid #e0e0e0;
247
+  border-bottom: 1px solid #e0e0e0;
248
+  border-top: 1px solid #e0e0e0;
249
+}
250
+.wxParse-th,
251
+.wxParse-td {
252
+  flex: 1;
253
+  padding: 5px;
254
+  font-size: 28rpx;
255
+  border-left: 1px solid #e0e0e0;
256
+  word-break: break-all;
257
+}
258
+.wxParse-td:last {
259
+  border-top: 1px solid #e0e0e0;
260
+}
261
+.wxParse-th {
262
+  background: #f0f0f0;
263
+  border-top: 1px solid #e0e0e0;
264
+}
265
+.wxParse-del {
266
+  display: inline;
267
+}
268
+.wxParse-figure {
269
+  overflow: hidden;
270
+}

+ 10 - 0
src/wxs/stringFilter.wxs

@@ -0,0 +1,10 @@
1
+var filter = function (text) {
2
+  if (text) {
3
+    var pattern = '\\\\n';
4
+    var target = '\n';
5
+    var reg = getRegExp(pattern, 'g');
6
+    return text.replace(reg, target);
7
+  }
8
+};
9
+
10
+module.exports.filter = filter;

kodo - Gogs: Go Git Service

Keine Beschreibung

Brightcells: 2fb52216da Fix Bug: wx authorize doesn't store wx_uid vor 10 Jahren
account 2fb52216da Fix Bug: wx authorize doesn't store wx_uid vor 10 Jahren
api 814ed79bfc add api group_unlock_api vor 10 Jahren
docs 814ed79bfc add api group_unlock_api vor 10 Jahren
group 814ed79bfc add api group_unlock_api vor 10 Jahren
pai2 8b5ea74192 add location /fly vor 10 Jahren
photo 4afdb94e20 add avatar for GroupUserInfo vor 10 Jahren
utils ce10db91f9 thumbnail_utils make_thumb vor 10 Jahren
.editorconfig 4defb80fdc gogs first init vor 10 Jahren
.gitignore 4defb80fdc gogs first init vor 10 Jahren
manage.py 4defb80fdc gogs first init vor 10 Jahren
pep8.sh 4defb80fdc gogs first init vor 10 Jahren
requirements.txt d3798a39f1 update session_detail_api vor 10 Jahren