├── pages
├── home
│ ├── home.json
│ ├── home.wxml
│ ├── home.wxss
│ ├── home-model.js
│ └── home.js
├── detail
│ ├── detail.json
│ ├── detail-model.js
│ ├── detail.js
│ ├── detail.wxss
│ └── detail.wxml
├── qr-pay
│ ├── qr-json.json
│ ├── qr-pay-model.js
│ ├── qr-pay.wxml
│ ├── qr-pay.js
│ └── qr-pay.wxss
├── after-pay
│ ├── after-pay.json
│ ├── after-pay.js
│ ├── after-pay.wxss
│ └── after-pay.wxml
├── before-pay
│ ├── before-pay.json
│ ├── before-pay-model.js
│ ├── before-pay.wxml
│ ├── before-pay.wxss
│ └── before-pay.js
└── pay-success
│ ├── pay-success.json
│ ├── pay-success.wxss
│ ├── pay-success.js
│ └── pay-success.wxml
├── jsconfig.json
├── utils
├── config.js
├── base.js
└── token.js
├── app.js
├── app.json
├── common
├── coupon
│ ├── index.wxml
│ └── index.wxss
└── wxParse
│ ├── wxParse.wxss
│ ├── wxParse.js
│ ├── htmlparser.js
│ ├── wxDiscode.js
│ ├── html2json.js
│ ├── wxParse.wxml
│ └── showdown.js
├── app.wxss
├── README.md
└── LICENSE
/pages/home/home.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "我的优惠券"
3 | }
--------------------------------------------------------------------------------
/pages/detail/detail.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "优惠券详情"
3 | }
--------------------------------------------------------------------------------
/pages/qr-pay/qr-json.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "支付"
3 | }
--------------------------------------------------------------------------------
/pages/after-pay/after-pay.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "购买成功"
3 | }
--------------------------------------------------------------------------------
/pages/before-pay/before-pay.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "确认订单"
3 | }
--------------------------------------------------------------------------------
/pages/pay-success/pay-success.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "支付成功"
3 | }
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2015",
4 | "module": "commonjs"
5 | }
6 | }
--------------------------------------------------------------------------------
/utils/config.js:
--------------------------------------------------------------------------------
1 | const Config = {
2 | baseUrl: 'https://gjb.demo.chilunyc.com/api/weapp/',
3 | }
4 |
5 | export default Config
--------------------------------------------------------------------------------
/pages/home/home.wxml:
--------------------------------------------------------------------------------
1 |
14 |
15 | ### 优惠券详情页↓
16 | - URL: `pages/detail/detail`
17 | -
18 |
19 | ### 购买优惠券页↓
20 | - URL: `pages/before-pay/before-pay`
21 | -
22 |
23 | ### 优惠券支付完成页↓
24 | - URL: `pages/after-pay/after-pay`
25 | -
26 |
27 | ### 扫码商家的二维码,优惠券抵扣消费金额页面↓
28 | - URL: `pages/qr-pay/qr-pay`
29 | -
30 |
31 | ### 消费成功页面↓
32 | - URL: `pages/pay-success/pay-success`
33 | -
34 |
35 |
36 | ## 感谢第三方库
37 | [wxParse](微信小程序富文本解析自定义组件)
38 |
39 | ## TODO
40 | - model.js 虽然做到了 views 和 model 的分离,但是当 ajax 发生错误时的判断没有交给 model.js 来处理,而是不恰当的放到 views 里判断了。
41 | model.js 应该再过滤和判断一下数据,让 views 只需要拿到自己想到数据,不需要做其他的事。
42 |
43 | - 使用优惠券抵用消费金额时,当总优惠的金额大于消费金额时,应该提示用户
44 |
--------------------------------------------------------------------------------
/pages/before-pay/before-pay.wxml:
--------------------------------------------------------------------------------
1 |
180 | // add to parents
181 | var parent = bufArray[0] || results;
182 | if (parent.nodes === undefined) {
183 | parent.nodes = [];
184 | }
185 | parent.nodes.push(node);
186 | } else {
187 | bufArray.unshift(node);
188 | }
189 | },
190 | end: function (tag) {
191 | //debug(tag);
192 | // merge into parent tag
193 | var node = bufArray.shift();
194 | if (node.tag !== tag) console.error('invalid state: mismatch end tag');
195 |
196 | //当有缓存source资源时于于video补上src资源
197 | if(node.tag === 'video' && results.source){
198 | node.attr.src = results.source;
199 | delete result.source;
200 | }
201 |
202 | if (bufArray.length === 0) {
203 | results.nodes.push(node);
204 | } else {
205 | var parent = bufArray[0];
206 | if (parent.nodes === undefined) {
207 | parent.nodes = [];
208 | }
209 | parent.nodes.push(node);
210 | }
211 | },
212 | chars: function (text) {
213 | //debug(text);
214 | var node = {
215 | node: 'text',
216 | text: text,
217 | textArray:transEmojiStr(text)
218 | };
219 |
220 | if (bufArray.length === 0) {
221 | results.nodes.push(node);
222 | } else {
223 | var parent = bufArray[0];
224 | if (parent.nodes === undefined) {
225 | parent.nodes = [];
226 | }
227 | node.index = parent.index + '.' + parent.nodes.length
228 | parent.nodes.push(node);
229 | }
230 | },
231 | comment: function (text) {
232 | //debug(text);
233 | // var node = {
234 | // node: 'comment',
235 | // text: text,
236 | // };
237 | // var parent = bufArray[0];
238 | // if (parent.nodes === undefined) {
239 | // parent.nodes = [];
240 | // }
241 | // parent.nodes.push(node);
242 | },
243 | });
244 | return results;
245 | };
246 |
247 | function transEmojiStr(str){
248 | // var eReg = new RegExp("["+__reg+' '+"]");
249 | // str = str.replace(/\[([^\[\]]+)\]/g,':$1:')
250 |
251 | var emojiObjs = [];
252 | //如果正则表达式为空
253 | if(__emojisReg.length == 0 || !__emojis){
254 | var emojiObj = {}
255 | emojiObj.node = "text";
256 | emojiObj.text = str;
257 | array = [emojiObj];
258 | return array;
259 | }
260 | //这个地方需要调整
261 | str = str.replace(/\[([^\[\]]+)\]/g,':$1:')
262 | var eReg = new RegExp("[:]");
263 | var array = str.split(eReg);
264 | for(var i = 0; i < array.length; i++){
265 | var ele = array[i];
266 | var emojiObj = {};
267 | if(__emojis[ele]){
268 | emojiObj.node = "element";
269 | emojiObj.tag = "emoji";
270 | emojiObj.text = __emojis[ele];
271 | emojiObj.baseSrc= __emojisBaseSrc;
272 | }else{
273 | emojiObj.node = "text";
274 | emojiObj.text = ele;
275 | }
276 | emojiObjs.push(emojiObj);
277 | }
278 |
279 | return emojiObjs;
280 | }
281 |
282 | function emojisInit(reg='',baseSrc="/wxParse/emojis/",emojis){
283 | __emojisReg = reg;
284 | __emojisBaseSrc=baseSrc;
285 | __emojis=emojis;
286 | }
287 |
288 | module.exports = {
289 | html2json: html2json,
290 | emojisInit:emojisInit
291 | };
292 |
293 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/common/wxParse/wxParse.wxml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
15 |
16 | tags around block-level tags. 1308 | text = showdown.subParser('hashHTMLBlocks')(text, options, globals); 1309 | text = showdown.subParser('paragraphs')(text, options, globals); 1310 | 1311 | text = globals.converter._dispatch('blockGamut.after', text, options, globals); 1312 | 1313 | return text; 1314 | }); 1315 | 1316 | showdown.subParser('blockQuotes', function (text, options, globals) { 1317 | 'use strict'; 1318 | 1319 | text = globals.converter._dispatch('blockQuotes.before', text, options, globals); 1320 | /* 1321 | text = text.replace(/ 1322 | ( // Wrap whole match in $1 1323 | ( 1324 | ^[ \t]*>[ \t]? // '>' at the start of a line 1325 | .+\n // rest of the first line 1326 | (.+\n)* // subsequent consecutive lines 1327 | \n* // blanks 1328 | )+ 1329 | ) 1330 | /gm, function(){...}); 1331 | */ 1332 | 1333 | text = text.replace(/((^[ \t]{0,3}>[ \t]?.+\n(.+\n)*\n*)+)/gm, function (wholeMatch, m1) { 1334 | var bq = m1; 1335 | 1336 | // attacklab: hack around Konqueror 3.5.4 bug: 1337 | // "----------bug".replace(/^-/g,"") == "bug" 1338 | bq = bq.replace(/^[ \t]*>[ \t]?/gm, '~0'); // trim one level of quoting 1339 | 1340 | // attacklab: clean up hack 1341 | bq = bq.replace(/~0/g, ''); 1342 | 1343 | bq = bq.replace(/^[ \t]+$/gm, ''); // trim whitespace-only lines 1344 | bq = showdown.subParser('githubCodeBlocks')(bq, options, globals); 1345 | bq = showdown.subParser('blockGamut')(bq, options, globals); // recurse 1346 | 1347 | bq = bq.replace(/(^|\n)/g, '$1 '); 1348 | // These leading spaces screw with
content, so we need to fix that:
1349 | bq = bq.replace(/(\s*[^\r]+?<\/pre>)/gm, function (wholeMatch, m1) {
1350 | var pre = m1;
1351 | // attacklab: hack around Konqueror 3.5.4 bug:
1352 | pre = pre.replace(/^ /mg, '~0');
1353 | pre = pre.replace(/~0/g, '');
1354 | return pre;
1355 | });
1356 |
1357 | return showdown.subParser('hashBlock')('\n' + bq + '\n
', options, globals);
1358 | });
1359 |
1360 | text = globals.converter._dispatch('blockQuotes.after', text, options, globals);
1361 | return text;
1362 | });
1363 |
1364 | /**
1365 | * Process Markdown `` blocks.
1366 | */
1367 | showdown.subParser('codeBlocks', function (text, options, globals) {
1368 | 'use strict';
1369 |
1370 | text = globals.converter._dispatch('codeBlocks.before', text, options, globals);
1371 | /*
1372 | text = text.replace(text,
1373 | /(?:\n\n|^)
1374 | ( // $1 = the code block -- one or more lines, starting with a space/tab
1375 | (?:
1376 | (?:[ ]{4}|\t) // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
1377 | .*\n+
1378 | )+
1379 | )
1380 | (\n*[ ]{0,3}[^ \t\n]|(?=~0)) // attacklab: g_tab_width
1381 | /g,function(){...});
1382 | */
1383 |
1384 | // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
1385 | text += '~0';
1386 |
1387 | var pattern = /(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g;
1388 | text = text.replace(pattern, function (wholeMatch, m1, m2) {
1389 | var codeblock = m1,
1390 | nextChar = m2,
1391 | end = '\n';
1392 |
1393 | codeblock = showdown.subParser('outdent')(codeblock);
1394 | codeblock = showdown.subParser('encodeCode')(codeblock);
1395 | codeblock = showdown.subParser('detab')(codeblock);
1396 | codeblock = codeblock.replace(/^\n+/g, ''); // trim leading newlines
1397 | codeblock = codeblock.replace(/\n+$/g, ''); // trim trailing newlines
1398 |
1399 | if (options.omitExtraWLInCodeBlocks) {
1400 | end = '';
1401 | }
1402 |
1403 | codeblock = '' + codeblock + end + '
';
1404 |
1405 | return showdown.subParser('hashBlock')(codeblock, options, globals) + nextChar;
1406 | });
1407 |
1408 | // attacklab: strip sentinel
1409 | text = text.replace(/~0/, '');
1410 |
1411 | text = globals.converter._dispatch('codeBlocks.after', text, options, globals);
1412 | return text;
1413 | });
1414 |
1415 | /**
1416 | *
1417 | * * Backtick quotes are used for spans.
1418 | *
1419 | * * You can use multiple backticks as the delimiters if you want to
1420 | * include literal backticks in the code span. So, this input:
1421 | *
1422 | * Just type ``foo `bar` baz`` at the prompt.
1423 | *
1424 | * Will translate to:
1425 | *
1426 | * Just type foo `bar` baz at the prompt.
1427 | *
1428 | * There's no arbitrary limit to the number of backticks you
1429 | * can use as delimters. If you need three consecutive backticks
1430 | * in your code, use four for delimiters, etc.
1431 | *
1432 | * * You can use spaces to get literal backticks at the edges:
1433 | *
1434 | * ... type `` `bar` `` ...
1435 | *
1436 | * Turns to:
1437 | *
1438 | * ... type `bar` ...
1439 | */
1440 | showdown.subParser('codeSpans', function (text, options, globals) {
1441 | 'use strict';
1442 |
1443 | text = globals.converter._dispatch('codeSpans.before', text, options, globals);
1444 |
1445 | /*
1446 | text = text.replace(/
1447 | (^|[^\\]) // Character before opening ` can't be a backslash
1448 | (`+) // $2 = Opening run of `
1449 | ( // $3 = The code block
1450 | [^\r]*?
1451 | [^`] // attacklab: work around lack of lookbehind
1452 | )
1453 | \2 // Matching closer
1454 | (?!`)
1455 | /gm, function(){...});
1456 | */
1457 |
1458 | if (typeof(text) === 'undefined') {
1459 | text = '';
1460 | }
1461 | text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
1462 | function (wholeMatch, m1, m2, m3) {
1463 | var c = m3;
1464 | c = c.replace(/^([ \t]*)/g, ''); // leading whitespace
1465 | c = c.replace(/[ \t]*$/g, ''); // trailing whitespace
1466 | c = showdown.subParser('encodeCode')(c);
1467 | return m1 + '' + c + '';
1468 | }
1469 | );
1470 |
1471 | text = globals.converter._dispatch('codeSpans.after', text, options, globals);
1472 | return text;
1473 | });
1474 |
1475 | /**
1476 | * Convert all tabs to spaces
1477 | */
1478 | showdown.subParser('detab', function (text) {
1479 | 'use strict';
1480 |
1481 | // expand first n-1 tabs
1482 | text = text.replace(/\t(?=\t)/g, ' '); // g_tab_width
1483 |
1484 | // replace the nth with two sentinels
1485 | text = text.replace(/\t/g, '~A~B');
1486 |
1487 | // use the sentinel to anchor our regex so it doesn't explode
1488 | text = text.replace(/~B(.+?)~A/g, function (wholeMatch, m1) {
1489 | var leadingText = m1,
1490 | numSpaces = 4 - leadingText.length % 4; // g_tab_width
1491 |
1492 | // there *must* be a better way to do this:
1493 | for (var i = 0; i < numSpaces; i++) {
1494 | leadingText += ' ';
1495 | }
1496 |
1497 | return leadingText;
1498 | });
1499 |
1500 | // clean up sentinels
1501 | text = text.replace(/~A/g, ' '); // g_tab_width
1502 | text = text.replace(/~B/g, '');
1503 |
1504 | return text;
1505 |
1506 | });
1507 |
1508 | /**
1509 | * Smart processing for ampersands and angle brackets that need to be encoded.
1510 | */
1511 | showdown.subParser('encodeAmpsAndAngles', function (text) {
1512 | 'use strict';
1513 | // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
1514 | // http://bumppo.net/projects/amputator/
1515 | text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, '&');
1516 |
1517 | // Encode naked <'s
1518 | text = text.replace(/<(?![a-z\/?\$!])/gi, '<');
1519 |
1520 | return text;
1521 | });
1522 |
1523 | /**
1524 | * Returns the string, with after processing the following backslash escape sequences.
1525 | *
1526 | * attacklab: The polite way to do this is with the new escapeCharacters() function:
1527 | *
1528 | * text = escapeCharacters(text,"\\",true);
1529 | * text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
1530 | *
1531 | * ...but we're sidestepping its use of the (slow) RegExp constructor
1532 | * as an optimization for Firefox. This function gets called a LOT.
1533 | */
1534 | showdown.subParser('encodeBackslashEscapes', function (text) {
1535 | 'use strict';
1536 | text = text.replace(/\\(\\)/g, showdown.helper.escapeCharactersCallback);
1537 | text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, showdown.helper.escapeCharactersCallback);
1538 | return text;
1539 | });
1540 |
1541 | /**
1542 | * Encode/escape certain characters inside Markdown code runs.
1543 | * The point is that in code, these characters are literals,
1544 | * and lose their special Markdown meanings.
1545 | */
1546 | showdown.subParser('encodeCode', function (text) {
1547 | 'use strict';
1548 |
1549 | // Encode all ampersands; HTML entities are not
1550 | // entities within a Markdown code span.
1551 | text = text.replace(/&/g, '&');
1552 |
1553 | // Do the angle bracket song and dance:
1554 | text = text.replace(//g, '>');
1556 |
1557 | // Now, escape characters that are magic in Markdown:
1558 | text = showdown.helper.escapeCharacters(text, '*_{}[]\\', false);
1559 |
1560 | // jj the line above breaks this:
1561 | //---
1562 | //* Item
1563 | // 1. Subitem
1564 | // special char: *
1565 | // ---
1566 |
1567 | return text;
1568 | });
1569 |
1570 | /**
1571 | * Input: an email address, e.g. "foo@example.com"
1572 | *
1573 | * Output: the email address as a mailto link, with each character
1574 | * of the address encoded as either a decimal or hex entity, in
1575 | * the hopes of foiling most address harvesting spam bots. E.g.:
1576 | *
1577 | * foo
1579 | * @example.com
1580 | *
1581 | * Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
1582 | * mailing list:
1583 | *
1584 | */
1585 | showdown.subParser('encodeEmailAddress', function (addr) {
1586 | 'use strict';
1587 |
1588 | var encode = [
1589 | function (ch) {
1590 | return '' + ch.charCodeAt(0) + ';';
1591 | },
1592 | function (ch) {
1593 | return '' + ch.charCodeAt(0).toString(16) + ';';
1594 | },
1595 | function (ch) {
1596 | return ch;
1597 | }
1598 | ];
1599 |
1600 | addr = 'mailto:' + addr;
1601 |
1602 | addr = addr.replace(/./g, function (ch) {
1603 | if (ch === '@') {
1604 | // this *must* be encoded. I insist.
1605 | ch = encode[Math.floor(Math.random() * 2)](ch);
1606 | } else if (ch !== ':') {
1607 | // leave ':' alone (to spot mailto: later)
1608 | var r = Math.random();
1609 | // roughly 10% raw, 45% hex, 45% dec
1610 | ch = (
1611 | r > 0.9 ? encode[2](ch) : r > 0.45 ? encode[1](ch) : encode[0](ch)
1612 | );
1613 | }
1614 | return ch;
1615 | });
1616 |
1617 | addr = '' + addr + '';
1618 | addr = addr.replace(/">.+:/g, '">'); // strip the mailto: from the visible part
1619 |
1620 | return addr;
1621 | });
1622 |
1623 | /**
1624 | * Within tags -- meaning between < and > -- encode [\ ` * _] so they
1625 | * don't conflict with their use in Markdown for code, italics and strong.
1626 | */
1627 | showdown.subParser('escapeSpecialCharsWithinTagAttributes', function (text) {
1628 | 'use strict';
1629 |
1630 | // Build a regex to find HTML tags and comments. See Friedl's
1631 | // "Mastering Regular Expressions", 2nd Ed., pp. 200-201.
1632 | var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi;
1633 |
1634 | text = text.replace(regex, function (wholeMatch) {
1635 | var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, '$1`');
1636 | tag = showdown.helper.escapeCharacters(tag, '\\`*_', false);
1637 | return tag;
1638 | });
1639 |
1640 | return text;
1641 | });
1642 |
1643 | /**
1644 | * Handle github codeblocks prior to running HashHTML so that
1645 | * HTML contained within the codeblock gets escaped properly
1646 | * Example:
1647 | * ```ruby
1648 | * def hello_world(x)
1649 | * puts "Hello, #{x}"
1650 | * end
1651 | * ```
1652 | */
1653 | showdown.subParser('githubCodeBlocks', function (text, options, globals) {
1654 | 'use strict';
1655 |
1656 | // early exit if option is not enabled
1657 | if (!options.ghCodeBlocks) {
1658 | return text;
1659 | }
1660 |
1661 | text = globals.converter._dispatch('githubCodeBlocks.before', text, options, globals);
1662 |
1663 | text += '~0';
1664 |
1665 | text = text.replace(/(?:^|\n)```(.*)\n([\s\S]*?)\n```/g, function (wholeMatch, language, codeblock) {
1666 | var end = (options.omitExtraWLInCodeBlocks) ? '' : '\n';
1667 |
1668 | // First parse the github code block
1669 | codeblock = showdown.subParser('encodeCode')(codeblock);
1670 | codeblock = showdown.subParser('detab')(codeblock);
1671 | codeblock = codeblock.replace(/^\n+/g, ''); // trim leading newlines
1672 | codeblock = codeblock.replace(/\n+$/g, ''); // trim trailing whitespace
1673 |
1674 | codeblock = '' + codeblock + end + '
';
1675 |
1676 | codeblock = showdown.subParser('hashBlock')(codeblock, options, globals);
1677 |
1678 | // Since GHCodeblocks can be false positives, we need to
1679 | // store the primitive text and the parsed text in a global var,
1680 | // and then return a token
1681 | return '\n\n~G' + (globals.ghCodeBlocks.push({text: wholeMatch, codeblock: codeblock}) - 1) + 'G\n\n';
1682 | });
1683 |
1684 | // attacklab: strip sentinel
1685 | text = text.replace(/~0/, '');
1686 |
1687 | return globals.converter._dispatch('githubCodeBlocks.after', text, options, globals);
1688 | });
1689 |
1690 | showdown.subParser('hashBlock', function (text, options, globals) {
1691 | 'use strict';
1692 | text = text.replace(/(^\n+|\n+$)/g, '');
1693 | return '\n\n~K' + (globals.gHtmlBlocks.push(text) - 1) + 'K\n\n';
1694 | });
1695 |
1696 | showdown.subParser('hashElement', function (text, options, globals) {
1697 | 'use strict';
1698 |
1699 | return function (wholeMatch, m1) {
1700 | var blockText = m1;
1701 |
1702 | // Undo double lines
1703 | blockText = blockText.replace(/\n\n/g, '\n');
1704 | blockText = blockText.replace(/^\n/, '');
1705 |
1706 | // strip trailing blank lines
1707 | blockText = blockText.replace(/\n+$/g, '');
1708 |
1709 | // Replace the element text with a marker ("~KxK" where x is its key)
1710 | blockText = '\n\n~K' + (globals.gHtmlBlocks.push(blockText) - 1) + 'K\n\n';
1711 |
1712 | return blockText;
1713 | };
1714 | });
1715 |
1716 | showdown.subParser('hashHTMLBlocks', function (text, options, globals) {
1717 | 'use strict';
1718 |
1719 | var blockTags = [
1720 | 'pre',
1721 | 'div',
1722 | 'h1',
1723 | 'h2',
1724 | 'h3',
1725 | 'h4',
1726 | 'h5',
1727 | 'h6',
1728 | 'blockquote',
1729 | 'table',
1730 | 'dl',
1731 | 'ol',
1732 | 'ul',
1733 | 'script',
1734 | 'noscript',
1735 | 'form',
1736 | 'fieldset',
1737 | 'iframe',
1738 | 'math',
1739 | 'style',
1740 | 'section',
1741 | 'header',
1742 | 'footer',
1743 | 'nav',
1744 | 'article',
1745 | 'aside',
1746 | 'address',
1747 | 'audio',
1748 | 'canvas',
1749 | 'figure',
1750 | 'hgroup',
1751 | 'output',
1752 | 'video',
1753 | 'p'
1754 | ],
1755 | repFunc = function (wholeMatch, match, left, right) {
1756 | var txt = wholeMatch;
1757 | // check if this html element is marked as markdown
1758 | // if so, it's contents should be parsed as markdown
1759 | if (left.search(/\bmarkdown\b/) !== -1) {
1760 | txt = left + globals.converter.makeHtml(match) + right;
1761 | }
1762 | return '\n\n~K' + (globals.gHtmlBlocks.push(txt) - 1) + 'K\n\n';
1763 | };
1764 |
1765 | for (var i = 0; i < blockTags.length; ++i) {
1766 | text = showdown.helper.replaceRecursiveRegExp(text, repFunc, '^(?: |\\t){0,3}<' + blockTags[i] + '\\b[^>]*>', '' + blockTags[i] + '>', 'gim');
1767 | }
1768 |
1769 | // HR SPECIAL CASE
1770 | text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,
1771 | showdown.subParser('hashElement')(text, options, globals));
1772 |
1773 | // Special case for standalone HTML comments:
1774 | text = text.replace(/()/g,
1775 | showdown.subParser('hashElement')(text, options, globals));
1776 |
1777 | // PHP and ASP-style processor instructions (...?> and <%...%>)
1778 | text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,
1779 | showdown.subParser('hashElement')(text, options, globals));
1780 | return text;
1781 | });
1782 |
1783 | /**
1784 | * Hash span elements that should not be parsed as markdown
1785 | */
1786 | showdown.subParser('hashHTMLSpans', function (text, config, globals) {
1787 | 'use strict';
1788 |
1789 | var matches = showdown.helper.matchRecursiveRegExp(text, ']*>', '', 'gi');
1790 |
1791 | for (var i = 0; i < matches.length; ++i) {
1792 | text = text.replace(matches[i][0], '~L' + (globals.gHtmlSpans.push(matches[i][0]) - 1) + 'L');
1793 | }
1794 | return text;
1795 | });
1796 |
1797 | /**
1798 | * Unhash HTML spans
1799 | */
1800 | showdown.subParser('unhashHTMLSpans', function (text, config, globals) {
1801 | 'use strict';
1802 |
1803 | for (var i = 0; i < globals.gHtmlSpans.length; ++i) {
1804 | text = text.replace('~L' + i + 'L', globals.gHtmlSpans[i]);
1805 | }
1806 |
1807 | return text;
1808 | });
1809 |
1810 | /**
1811 | * Hash span elements that should not be parsed as markdown
1812 | */
1813 | showdown.subParser('hashPreCodeTags', function (text, config, globals) {
1814 | 'use strict';
1815 |
1816 | var repFunc = function (wholeMatch, match, left, right) {
1817 | // encode html entities
1818 | var codeblock = left + showdown.subParser('encodeCode')(match) + right;
1819 | return '\n\n~G' + (globals.ghCodeBlocks.push({text: wholeMatch, codeblock: codeblock}) - 1) + 'G\n\n';
1820 | };
1821 |
1822 | text = showdown.helper.replaceRecursiveRegExp(text, repFunc, '^(?: |\\t){0,3}]*>\\s*]*>', '^(?: |\\t){0,3}\\s*
', 'gim');
1823 | return text;
1824 | });
1825 |
1826 | showdown.subParser('headers', function (text, options, globals) {
1827 | 'use strict';
1828 |
1829 | text = globals.converter._dispatch('headers.before', text, options, globals);
1830 |
1831 | var prefixHeader = options.prefixHeaderId,
1832 | headerLevelStart = (isNaN(parseInt(options.headerLevelStart))) ? 1 : parseInt(options.headerLevelStart),
1833 |
1834 | // Set text-style headers:
1835 | // Header 1
1836 | // ========
1837 | //
1838 | // Header 2
1839 | // --------
1840 | //
1841 | setextRegexH1 = (options.smoothLivePreview) ? /^(.+)[ \t]*\n={2,}[ \t]*\n+/gm : /^(.+)[ \t]*\n=+[ \t]*\n+/gm,
1842 | setextRegexH2 = (options.smoothLivePreview) ? /^(.+)[ \t]*\n-{2,}[ \t]*\n+/gm : /^(.+)[ \t]*\n-+[ \t]*\n+/gm;
1843 |
1844 | text = text.replace(setextRegexH1, function (wholeMatch, m1) {
1845 |
1846 | var spanGamut = showdown.subParser('spanGamut')(m1, options, globals),
1847 | hID = (options.noHeaderId) ? '' : ' id="' + headerId(m1) + '"',
1848 | hLevel = headerLevelStart,
1849 | hashBlock = '' + spanGamut + ' ';
1850 | return showdown.subParser('hashBlock')(hashBlock, options, globals);
1851 | });
1852 |
1853 | text = text.replace(setextRegexH2, function (matchFound, m1) {
1854 | var spanGamut = showdown.subParser('spanGamut')(m1, options, globals),
1855 | hID = (options.noHeaderId) ? '' : ' id="' + headerId(m1) + '"',
1856 | hLevel = headerLevelStart + 1,
1857 | hashBlock = '' + spanGamut + ' ';
1858 | return showdown.subParser('hashBlock')(hashBlock, options, globals);
1859 | });
1860 |
1861 | // atx-style headers:
1862 | // # Header 1
1863 | // ## Header 2
1864 | // ## Header 2 with closing hashes ##
1865 | // ...
1866 | // ###### Header 6
1867 | //
1868 | text = text.replace(/^(#{1,6})[ \t]*(.+?)[ \t]*#*\n+/gm, function (wholeMatch, m1, m2) {
1869 | var span = showdown.subParser('spanGamut')(m2, options, globals),
1870 | hID = (options.noHeaderId) ? '' : ' id="' + headerId(m2) + '"',
1871 | hLevel = headerLevelStart - 1 + m1.length,
1872 | header = '' + span + ' ';
1873 |
1874 | return showdown.subParser('hashBlock')(header, options, globals);
1875 | });
1876 |
1877 | function headerId(m) {
1878 | var title, escapedId = m.replace(/[^\w]/g, '').toLowerCase();
1879 |
1880 | if (globals.hashLinkCounts[escapedId]) {
1881 | title = escapedId + '-' + (globals.hashLinkCounts[escapedId]++);
1882 | } else {
1883 | title = escapedId;
1884 | globals.hashLinkCounts[escapedId] = 1;
1885 | }
1886 |
1887 | // Prefix id to prevent causing inadvertent pre-existing style matches.
1888 | if (prefixHeader === true) {
1889 | prefixHeader = 'section';
1890 | }
1891 |
1892 | if (showdown.helper.isString(prefixHeader)) {
1893 | return prefixHeader + title;
1894 | }
1895 | return title;
1896 | }
1897 |
1898 | text = globals.converter._dispatch('headers.after', text, options, globals);
1899 | return text;
1900 | });
1901 |
1902 | /**
1903 | * Turn Markdown image shortcuts into
tags.
1904 | */
1905 | showdown.subParser('images', function (text, options, globals) {
1906 | 'use strict';
1907 |
1908 | text = globals.converter._dispatch('images.before', text, options, globals);
1909 |
1910 | var inlineRegExp = /!\[(.*?)]\s?\([ \t]*()(\S+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(['"])(.*?)\6[ \t]*)?\)/g,
1911 | referenceRegExp = /!\[([^\]]*?)] ?(?:\n *)?\[(.*?)]()()()()()/g;
1912 |
1913 | function writeImageTag (wholeMatch, altText, linkId, url, width, height, m5, title) {
1914 |
1915 | var gUrls = globals.gUrls,
1916 | gTitles = globals.gTitles,
1917 | gDims = globals.gDimensions;
1918 |
1919 | linkId = linkId.toLowerCase();
1920 |
1921 | if (!title) {
1922 | title = '';
1923 | }
1924 |
1925 | if (url === '' || url === null) {
1926 | if (linkId === '' || linkId === null) {
1927 | // lower-case and turn embedded newlines into spaces
1928 | linkId = altText.toLowerCase().replace(/ ?\n/g, ' ');
1929 | }
1930 | url = '#' + linkId;
1931 |
1932 | if (!showdown.helper.isUndefined(gUrls[linkId])) {
1933 | url = gUrls[linkId];
1934 | if (!showdown.helper.isUndefined(gTitles[linkId])) {
1935 | title = gTitles[linkId];
1936 | }
1937 | if (!showdown.helper.isUndefined(gDims[linkId])) {
1938 | width = gDims[linkId].width;
1939 | height = gDims[linkId].height;
1940 | }
1941 | } else {
1942 | return wholeMatch;
1943 | }
1944 | }
1945 |
1946 | altText = altText.replace(/"/g, '"');
1947 | altText = showdown.helper.escapeCharacters(altText, '*_', false);
1948 | url = showdown.helper.escapeCharacters(url, '*_', false);
1949 | var result = '
';
1966 | return result;
1967 | }
1968 |
1969 | // First, handle reference-style labeled images: ![alt text][id]
1970 | text = text.replace(referenceRegExp, writeImageTag);
1971 |
1972 | // Next, handle inline images: 
1973 | text = text.replace(inlineRegExp, writeImageTag);
1974 |
1975 | text = globals.converter._dispatch('images.after', text, options, globals);
1976 | return text;
1977 | });
1978 |
1979 | showdown.subParser('italicsAndBold', function (text, options, globals) {
1980 | 'use strict';
1981 |
1982 | text = globals.converter._dispatch('italicsAndBold.before', text, options, globals);
1983 |
1984 | if (options.literalMidWordUnderscores) {
1985 | //underscores
1986 | // Since we are consuming a \s character, we need to add it
1987 | text = text.replace(/(^|\s|>|\b)__(?=\S)([\s\S]+?)__(?=\b|<|\s|$)/gm, '$1$2');
1988 | text = text.replace(/(^|\s|>|\b)_(?=\S)([\s\S]+?)_(?=\b|<|\s|$)/gm, '$1$2');
1989 | //asterisks
1990 | text = text.replace(/(\*\*)(?=\S)([^\r]*?\S[*]*)\1/g, '$2');
1991 | text = text.replace(/(\*)(?=\S)([^\r]*?\S)\1/g, '$2');
1992 |
1993 | } else {
1994 | // must go first:
1995 | text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g, '$2');
1996 | text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g, '$2');
1997 | }
1998 |
1999 | text = globals.converter._dispatch('italicsAndBold.after', text, options, globals);
2000 | return text;
2001 | });
2002 |
2003 | /**
2004 | * Form HTML ordered (numbered) and unordered (bulleted) lists.
2005 | */
2006 | showdown.subParser('lists', function (text, options, globals) {
2007 | 'use strict';
2008 |
2009 | text = globals.converter._dispatch('lists.before', text, options, globals);
2010 | /**
2011 | * Process the contents of a single ordered or unordered list, splitting it
2012 | * into individual list items.
2013 | * @param {string} listStr
2014 | * @param {boolean} trimTrailing
2015 | * @returns {string}
2016 | */
2017 | function processListItems (listStr, trimTrailing) {
2018 | // The $g_list_level global keeps track of when we're inside a list.
2019 | // Each time we enter a list, we increment it; when we leave a list,
2020 | // we decrement. If it's zero, we're not in a list anymore.
2021 | //
2022 | // We do this because when we're not inside a list, we want to treat
2023 | // something like this:
2024 | //
2025 | // I recommend upgrading to version
2026 | // 8. Oops, now this line is treated
2027 | // as a sub-list.
2028 | //
2029 | // As a single paragraph, despite the fact that the second line starts
2030 | // with a digit-period-space sequence.
2031 | //
2032 | // Whereas when we're inside a list (or sub-list), that line will be
2033 | // treated as the start of a sub-list. What a kludge, huh? This is
2034 | // an aspect of Markdown's syntax that's hard to parse perfectly
2035 | // without resorting to mind-reading. Perhaps the solution is to
2036 | // change the syntax rules such that sub-lists must start with a
2037 | // starting cardinal number; e.g. "1." or "a.".
2038 | globals.gListLevel++;
2039 |
2040 | // trim trailing blank lines:
2041 | listStr = listStr.replace(/\n{2,}$/, '\n');
2042 |
2043 | // attacklab: add sentinel to emulate \z
2044 | listStr += '~0';
2045 |
2046 | var rgx = /(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,
2047 | isParagraphed = (/\n[ \t]*\n(?!~0)/.test(listStr));
2048 |
2049 | listStr = listStr.replace(rgx, function (wholeMatch, m1, m2, m3, m4, taskbtn, checked) {
2050 | checked = (checked && checked.trim() !== '');
2051 | var item = showdown.subParser('outdent')(m4, options, globals),
2052 | bulletStyle = '';
2053 |
2054 | // Support for github tasklists
2055 | if (taskbtn && options.tasklists) {
2056 | bulletStyle = ' class="task-list-item" style="list-style-type: none;"';
2057 | item = item.replace(/^[ \t]*\[(x|X| )?]/m, function () {
2058 | var otp = '';
2063 | return otp;
2064 | });
2065 | }
2066 | // m1 - Leading line or
2067 | // Has a double return (multi paragraph) or
2068 | // Has sublist
2069 | if (m1 || (item.search(/\n{2,}/) > -1)) {
2070 | item = showdown.subParser('githubCodeBlocks')(item, options, globals);
2071 | item = showdown.subParser('blockGamut')(item, options, globals);
2072 | } else {
2073 | // Recursion for sub-lists:
2074 | item = showdown.subParser('lists')(item, options, globals);
2075 | item = item.replace(/\n$/, ''); // chomp(item)
2076 | if (isParagraphed) {
2077 | item = showdown.subParser('paragraphs')(item, options, globals);
2078 | } else {
2079 | item = showdown.subParser('spanGamut')(item, options, globals);
2080 | }
2081 | }
2082 | item = '\n' + item + ' \n';
2083 | return item;
2084 | });
2085 |
2086 | // attacklab: strip sentinel
2087 | listStr = listStr.replace(/~0/g, '');
2088 |
2089 | globals.gListLevel--;
2090 |
2091 | if (trimTrailing) {
2092 | listStr = listStr.replace(/\s+$/, '');
2093 | }
2094 |
2095 | return listStr;
2096 | }
2097 |
2098 | /**
2099 | * Check and parse consecutive lists (better fix for issue #142)
2100 | * @param {string} list
2101 | * @param {string} listType
2102 | * @param {boolean} trimTrailing
2103 | * @returns {string}
2104 | */
2105 | function parseConsecutiveLists(list, listType, trimTrailing) {
2106 | // check if we caught 2 or more consecutive lists by mistake
2107 | // we use the counterRgx, meaning if listType is UL we look for UL and vice versa
2108 | var counterRxg = (listType === 'ul') ? /^ {0,2}\d+\.[ \t]/gm : /^ {0,2}[*+-][ \t]/gm,
2109 | subLists = [],
2110 | result = '';
2111 |
2112 | if (list.search(counterRxg) !== -1) {
2113 | (function parseCL(txt) {
2114 | var pos = txt.search(counterRxg);
2115 | if (pos !== -1) {
2116 | // slice
2117 | result += '\n\n<' + listType + '>' + processListItems(txt.slice(0, pos), !!trimTrailing) + '' + listType + '>\n\n';
2118 |
2119 | // invert counterType and listType
2120 | listType = (listType === 'ul') ? 'ol' : 'ul';
2121 | counterRxg = (listType === 'ul') ? /^ {0,2}\d+\.[ \t]/gm : /^ {0,2}[*+-][ \t]/gm;
2122 |
2123 | //recurse
2124 | parseCL(txt.slice(pos));
2125 | } else {
2126 | result += '\n\n<' + listType + '>' + processListItems(txt, !!trimTrailing) + '' + listType + '>\n\n';
2127 | }
2128 | })(list);
2129 | for (var i = 0; i < subLists.length; ++i) {
2130 |
2131 | }
2132 | } else {
2133 | result = '\n\n<' + listType + '>' + processListItems(list, !!trimTrailing) + '' + listType + '>\n\n';
2134 | }
2135 |
2136 | return result;
2137 | }
2138 |
2139 | // attacklab: add sentinel to hack around khtml/safari bug:
2140 | // http://bugs.webkit.org/show_bug.cgi?id=11231
2141 | text += '~0';
2142 |
2143 | // Re-usable pattern to match any entire ul or ol list:
2144 | var wholeList = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
2145 |
2146 | if (globals.gListLevel) {
2147 | text = text.replace(wholeList, function (wholeMatch, list, m2) {
2148 | var listType = (m2.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
2149 | return parseConsecutiveLists(list, listType, true);
2150 | });
2151 | } else {
2152 | wholeList = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
2153 | //wholeList = /(\n\n|^\n?)( {0,3}([*+-]|\d+\.)[ \t]+[\s\S]+?)(?=(~0)|(\n\n(?!\t| {2,}| {0,3}([*+-]|\d+\.)[ \t])))/g;
2154 | text = text.replace(wholeList, function (wholeMatch, m1, list, m3) {
2155 |
2156 | var listType = (m3.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
2157 | return parseConsecutiveLists(list, listType);
2158 | });
2159 | }
2160 |
2161 | // attacklab: strip sentinel
2162 | text = text.replace(/~0/, '');
2163 |
2164 | text = globals.converter._dispatch('lists.after', text, options, globals);
2165 | return text;
2166 | });
2167 |
2168 | /**
2169 | * Remove one level of line-leading tabs or spaces
2170 | */
2171 | showdown.subParser('outdent', function (text) {
2172 | 'use strict';
2173 |
2174 | // attacklab: hack around Konqueror 3.5.4 bug:
2175 | // "----------bug".replace(/^-/g,"") == "bug"
2176 | text = text.replace(/^(\t|[ ]{1,4})/gm, '~0'); // attacklab: g_tab_width
2177 |
2178 | // attacklab: clean up hack
2179 | text = text.replace(/~0/g, '');
2180 |
2181 | return text;
2182 | });
2183 |
2184 | /**
2185 | *
2186 | */
2187 | showdown.subParser('paragraphs', function (text, options, globals) {
2188 | 'use strict';
2189 |
2190 | text = globals.converter._dispatch('paragraphs.before', text, options, globals);
2191 | // Strip leading and trailing lines:
2192 | text = text.replace(/^\n+/g, '');
2193 | text = text.replace(/\n+$/g, '');
2194 |
2195 | var grafs = text.split(/\n{2,}/g),
2196 | grafsOut = [],
2197 | end = grafs.length; // Wrap tags
2198 |
2199 | for (var i = 0; i < end; i++) {
2200 | var str = grafs[i];
2201 | // if this is an HTML marker, copy it
2202 | if (str.search(/~(K|G)(\d+)\1/g) >= 0) {
2203 | grafsOut.push(str);
2204 | } else {
2205 | str = showdown.subParser('spanGamut')(str, options, globals);
2206 | str = str.replace(/^([ \t]*)/g, '
');
2207 | str += '
';
2208 | grafsOut.push(str);
2209 | }
2210 | }
2211 |
2212 | /** Unhashify HTML blocks */
2213 | end = grafsOut.length;
2214 | for (i = 0; i < end; i++) {
2215 | var blockText = '',
2216 | grafsOutIt = grafsOut[i],
2217 | codeFlag = false;
2218 | // if this is a marker for an html block...
2219 | while (grafsOutIt.search(/~(K|G)(\d+)\1/) >= 0) {
2220 | var delim = RegExp.$1,
2221 | num = RegExp.$2;
2222 |
2223 | if (delim === 'K') {
2224 | blockText = globals.gHtmlBlocks[num];
2225 | } else {
2226 | // we need to check if ghBlock is a false positive
2227 | if (codeFlag) {
2228 | // use encoded version of all text
2229 | blockText = showdown.subParser('encodeCode')(globals.ghCodeBlocks[num].text);
2230 | } else {
2231 | blockText = globals.ghCodeBlocks[num].codeblock;
2232 | }
2233 | }
2234 | blockText = blockText.replace(/\$/g, '$$$$'); // Escape any dollar signs
2235 |
2236 | grafsOutIt = grafsOutIt.replace(/(\n\n)?~(K|G)\d+\2(\n\n)?/, blockText);
2237 | // Check if grafsOutIt is a pre->code
2238 | if (/^]*>\s*]*>/.test(grafsOutIt)) {
2239 | codeFlag = true;
2240 | }
2241 | }
2242 | grafsOut[i] = grafsOutIt;
2243 | }
2244 | text = grafsOut.join('\n\n');
2245 | // Strip leading and trailing lines:
2246 | text = text.replace(/^\n+/g, '');
2247 | text = text.replace(/\n+$/g, '');
2248 | return globals.converter._dispatch('paragraphs.after', text, options, globals);
2249 | });
2250 |
2251 | /**
2252 | * Run extension
2253 | */
2254 | showdown.subParser('runExtension', function (ext, text, options, globals) {
2255 | 'use strict';
2256 |
2257 | if (ext.filter) {
2258 | text = ext.filter(text, globals.converter, options);
2259 |
2260 | } else if (ext.regex) {
2261 | // TODO remove this when old extension loading mechanism is deprecated
2262 | var re = ext.regex;
2263 | if (!re instanceof RegExp) {
2264 | re = new RegExp(re, 'g');
2265 | }
2266 | text = text.replace(re, ext.replace);
2267 | }
2268 |
2269 | return text;
2270 | });
2271 |
2272 | /**
2273 | * These are all the transformations that occur *within* block-level
2274 | * tags like paragraphs, headers, and list items.
2275 | */
2276 | showdown.subParser('spanGamut', function (text, options, globals) {
2277 | 'use strict';
2278 |
2279 | text = globals.converter._dispatch('spanGamut.before', text, options, globals);
2280 | text = showdown.subParser('codeSpans')(text, options, globals);
2281 | text = showdown.subParser('escapeSpecialCharsWithinTagAttributes')(text, options, globals);
2282 | text = showdown.subParser('encodeBackslashEscapes')(text, options, globals);
2283 |
2284 | // Process anchor and image tags. Images must come first,
2285 | // because ![foo][f] looks like an anchor.
2286 | text = showdown.subParser('images')(text, options, globals);
2287 | text = showdown.subParser('anchors')(text, options, globals);
2288 |
2289 | // Make links out of things like ` `
2290 | // Must come after _DoAnchors(), because you can use < and >
2291 | // delimiters in inline links like [this]().
2292 | text = showdown.subParser('autoLinks')(text, options, globals);
2293 | text = showdown.subParser('encodeAmpsAndAngles')(text, options, globals);
2294 | text = showdown.subParser('italicsAndBold')(text, options, globals);
2295 | text = showdown.subParser('strikethrough')(text, options, globals);
2296 |
2297 | // Do hard breaks:
2298 | text = text.replace(/ +\n/g, '
\n');
2299 |
2300 | text = globals.converter._dispatch('spanGamut.after', text, options, globals);
2301 | return text;
2302 | });
2303 |
2304 | showdown.subParser('strikethrough', function (text, options, globals) {
2305 | 'use strict';
2306 |
2307 | if (options.strikethrough) {
2308 | text = globals.converter._dispatch('strikethrough.before', text, options, globals);
2309 | text = text.replace(/(?:~T){2}([\s\S]+?)(?:~T){2}/g, '$1');
2310 | text = globals.converter._dispatch('strikethrough.after', text, options, globals);
2311 | }
2312 |
2313 | return text;
2314 | });
2315 |
2316 | /**
2317 | * Strip any lines consisting only of spaces and tabs.
2318 | * This makes subsequent regexs easier to write, because we can
2319 | * match consecutive blank lines with /\n+/ instead of something
2320 | * contorted like /[ \t]*\n+/
2321 | */
2322 | showdown.subParser('stripBlankLines', function (text) {
2323 | 'use strict';
2324 | return text.replace(/^[ \t]+$/mg, '');
2325 | });
2326 |
2327 | /**
2328 | * Strips link definitions from text, stores the URLs and titles in
2329 | * hash references.
2330 | * Link defs are in the form: ^[id]: url "optional title"
2331 | *
2332 | * ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1
2333 | * [ \t]*
2334 | * \n? // maybe *one* newline
2335 | * [ \t]*
2336 | * (\S+?)>? // url = $2
2337 | * [ \t]*
2338 | * \n? // maybe one newline
2339 | * [ \t]*
2340 | * (?:
2341 | * (\n*) // any lines skipped = $3 attacklab: lookbehind removed
2342 | * ["(]
2343 | * (.+?) // title = $4
2344 | * [")]
2345 | * [ \t]*
2346 | * )? // title is optional
2347 | * (?:\n+|$)
2348 | * /gm,
2349 | * function(){...});
2350 | *
2351 | */
2352 | showdown.subParser('stripLinkDefinitions', function (text, options, globals) {
2353 | 'use strict';
2354 |
2355 | var 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;
2356 |
2357 | // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
2358 | text += '~0';
2359 |
2360 | text = text.replace(regex, function (wholeMatch, linkId, url, width, height, blankLines, title) {
2361 | linkId = linkId.toLowerCase();
2362 | globals.gUrls[linkId] = showdown.subParser('encodeAmpsAndAngles')(url); // Link IDs are case-insensitive
2363 |
2364 | if (blankLines) {
2365 | // Oops, found blank lines, so it's not a title.
2366 | // Put back the parenthetical statement we stole.
2367 | return blankLines + title;
2368 |
2369 | } else {
2370 | if (title) {
2371 | globals.gTitles[linkId] = title.replace(/"|'/g, '"');
2372 | }
2373 | if (options.parseImgDimensions && width && height) {
2374 | globals.gDimensions[linkId] = {
2375 | width: width,
2376 | height: height
2377 | };
2378 | }
2379 | }
2380 | // Completely remove the definition from the text
2381 | return '';
2382 | });
2383 |
2384 | // attacklab: strip sentinel
2385 | text = text.replace(/~0/, '');
2386 |
2387 | return text;
2388 | });
2389 |
2390 | showdown.subParser('tables', function (text, options, globals) {
2391 | 'use strict';
2392 |
2393 | if (!options.tables) {
2394 | return text;
2395 | }
2396 |
2397 | var tableRgx = /^[ \t]{0,3}\|?.+\|.+\n[ \t]{0,3}\|?[ \t]*:?[ \t]*(?:-|=){2,}[ \t]*:?[ \t]*\|[ \t]*:?[ \t]*(?:-|=){2,}[\s\S]+?(?:\n\n|~0)/gm;
2398 |
2399 | function parseStyles(sLine) {
2400 | if (/^:[ \t]*--*$/.test(sLine)) {
2401 | return ' style="text-align:left;"';
2402 | } else if (/^--*[ \t]*:[ \t]*$/.test(sLine)) {
2403 | return ' style="text-align:right;"';
2404 | } else if (/^:[ \t]*--*[ \t]*:$/.test(sLine)) {
2405 | return ' style="text-align:center;"';
2406 | } else {
2407 | return '';
2408 | }
2409 | }
2410 |
2411 | function parseHeaders(header, style) {
2412 | var id = '';
2413 | header = header.trim();
2414 | if (options.tableHeaderId) {
2415 | id = ' id="' + header.replace(/ /g, '_').toLowerCase() + '"';
2416 | }
2417 | header = showdown.subParser('spanGamut')(header, options, globals);
2418 |
2419 | return '' + header + ' \n';
2420 | }
2421 |
2422 | function parseCells(cell, style) {
2423 | var subText = showdown.subParser('spanGamut')(cell, options, globals);
2424 | return '' + subText + ' \n';
2425 | }
2426 |
2427 | function buildTable(headers, cells) {
2428 | var tb = '\n\n\n',
2429 | tblLgn = headers.length;
2430 |
2431 | for (var i = 0; i < tblLgn; ++i) {
2432 | tb += headers[i];
2433 | }
2434 | tb += ' \n\n\n';
2435 |
2436 | for (i = 0; i < cells.length; ++i) {
2437 | tb += '\n';
2438 | for (var ii = 0; ii < tblLgn; ++ii) {
2439 | tb += cells[i][ii];
2440 | }
2441 | tb += ' \n';
2442 | }
2443 | tb += '\n
\n';
2444 | return tb;
2445 | }
2446 |
2447 | text = globals.converter._dispatch('tables.before', text, options, globals);
2448 |
2449 | text = text.replace(tableRgx, function (rawTable) {
2450 |
2451 | var i, tableLines = rawTable.split('\n');
2452 |
2453 | // strip wrong first and last column if wrapped tables are used
2454 | for (i = 0; i < tableLines.length; ++i) {
2455 | if (/^[ \t]{0,3}\|/.test(tableLines[i])) {
2456 | tableLines[i] = tableLines[i].replace(/^[ \t]{0,3}\|/, '');
2457 | }
2458 | if (/\|[ \t]*$/.test(tableLines[i])) {
2459 | tableLines[i] = tableLines[i].replace(/\|[ \t]*$/, '');
2460 | }
2461 | }
2462 |
2463 | var rawHeaders = tableLines[0].split('|').map(function (s) { return s.trim();}),
2464 | rawStyles = tableLines[1].split('|').map(function (s) { return s.trim();}),
2465 | rawCells = [],
2466 | headers = [],
2467 | styles = [],
2468 | cells = [];
2469 |
2470 | tableLines.shift();
2471 | tableLines.shift();
2472 |
2473 | for (i = 0; i < tableLines.length; ++i) {
2474 | if (tableLines[i].trim() === '') {
2475 | continue;
2476 | }
2477 | rawCells.push(
2478 | tableLines[i]
2479 | .split('|')
2480 | .map(function (s) {
2481 | return s.trim();
2482 | })
2483 | );
2484 | }
2485 |
2486 | if (rawHeaders.length < rawStyles.length) {
2487 | return rawTable;
2488 | }
2489 |
2490 | for (i = 0; i < rawStyles.length; ++i) {
2491 | styles.push(parseStyles(rawStyles[i]));
2492 | }
2493 |
2494 | for (i = 0; i < rawHeaders.length; ++i) {
2495 | if (showdown.helper.isUndefined(styles[i])) {
2496 | styles[i] = '';
2497 | }
2498 | headers.push(parseHeaders(rawHeaders[i], styles[i]));
2499 | }
2500 |
2501 | for (i = 0; i < rawCells.length; ++i) {
2502 | var row = [];
2503 | for (var ii = 0; ii < headers.length; ++ii) {
2504 | if (showdown.helper.isUndefined(rawCells[i][ii])) {
2505 |
2506 | }
2507 | row.push(parseCells(rawCells[i][ii], styles[ii]));
2508 | }
2509 | cells.push(row);
2510 | }
2511 |
2512 | return buildTable(headers, cells);
2513 | });
2514 |
2515 | text = globals.converter._dispatch('tables.after', text, options, globals);
2516 |
2517 | return text;
2518 | });
2519 |
2520 | /**
2521 | * Swap back in all the special characters we've hidden.
2522 | */
2523 | showdown.subParser('unescapeSpecialChars', function (text) {
2524 | 'use strict';
2525 |
2526 | text = text.replace(/~E(\d+)E/g, function (wholeMatch, m1) {
2527 | var charCodeToReplace = parseInt(m1);
2528 | return String.fromCharCode(charCodeToReplace);
2529 | });
2530 | return text;
2531 | });
2532 | module.exports = showdown;
2533 |
--------------------------------------------------------------------------------