houston-core 0.5.4 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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);