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/kodo - Gogs: Go Git Service

41 次代码提交 (7ecd5dbbe4d7b78e36678d33ff05427370fc1e7b)

作者 SHA1 备注 提交日期
  huangqimin001 b580e80550 :art: iSort & iCheck 1 年之前
  FFIB c3ce0715c0 新增 shot_request_delete 接口 1 年之前
  FFIB d91b0bc92e shot_request_send 支持更新request_number 1 年之前
  FFIB aa2fd2f167 shot_create 防止重复录入 1 年之前
  FFIB b13669bb0d shot_request_update 增加request_number 2 年之前
  FFIB 4e6b51b9c3 借用申请,新增借单编号 2 年之前
  FFIB 1e5744f846 去掉模板消息的颜色 2 年之前
  FFIB 48d0804b24 增加筛选项 2 年之前
  FFIB 0a0083056d 增加筛选项 2 年之前
  huangqimin001 30b9607058 :art: iSort & Pycodestyle 2 年之前
  FFIB 2a3f51dc01 更新模板消息推送 data 3 年之前
  FFIB 4a9542ddac 更新模板消息 3 年之前
  FFIB 85f4f3ca39 TenancyShotRequestInfo 增加备注 3 年之前
  FFIB a7dd7aa658 租用申请,推送模板消息 3 年之前
  FFIB dbae411bb9 update 3 年之前
  FFIB b814caf684 增加删除功能 3 年之前
  FFIB 22a29e7a85 增加权限管理 3 年之前
  FFIB 9529d51065 更新租用镜头状态 3 年之前
  FFIB bdb24bcbd8 shot_list增加筛选model_id 3 年之前
  FFIB 1175fbf001 调整租用逻辑 3 年之前
  FFIB 255791aa68 fix shot_request_send bug 3 年之前
  FFIB ac53e25275 更新镜头租用状态 3 年之前
  huangqimin001 51d86f0a06 :art: Pass phone for KuaiDi100().track 3 年之前
  huangqimin001 8a7de8d3fb :art: Update tenancy_tracking_info_subscribe(req, type_) 3 年之前
  huangqimin001 36c403e36d :art: Default is_upload_qiniu 3 年之前
  FFIB 7f1dc76e27 update 3 年之前
  FFIB 1070e892c3 update 3 年之前
  FFIB 457843a3cc update 3 年之前
  FFIB 4a905870d7 update 3 年之前
  FFIB 3c74dfbc85 Merge branch 'tamron' of http://git.xfoto.com.cn/Kodo/kodo into tamron 3 年之前
  FFIB a52ea2dc87 update 3 年之前
  huangqimin001 d5a918888f :art: Format Codes 3 年之前
  FFIB 193f14303a update 3 年之前
  FFIB aad85a01a5 update 3 年之前
  FFIB 9e1aca7f4f update 3 年之前
  FFIB adfe0b774b 新增 admin/tenancy/tracking/info接口,request_status_at录入 3 年之前
  huangqimin001 43abfa9d2e :art: Update TenancyShotInfo 3 年之前
  huangqimin001 0cce0898b7 :art: TENANCY_TRACKING_*** 3 年之前
  huangqimin001 fd8d621642 :art: KuaiDi100Subscribe for tenancy 3 年之前
  huangqimin001 9161390df8 :art: Add tenancy shot request relative apis 3 年之前
  huangqimin001 7ca8134999 :art: Add tenancy shot relative apis 3 年之前