houston-core 0.5.4 → 0.5.5

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