showdown.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. /**
  2. * Created by Tivie on 06-01-2015.
  3. */
  4. // Private properties
  5. var showdown = {},
  6. parsers = {},
  7. extensions = {},
  8. globalOptions = {
  9. omitExtraWLInCodeBlocks: false,
  10. prefixHeaderId: false
  11. };
  12. /**
  13. * helper namespace
  14. * @type {{}}
  15. */
  16. showdown.helper = {};
  17. // Public properties
  18. showdown.extensions = {};
  19. /**
  20. * Set a global option
  21. * @static
  22. * @param {string} key
  23. * @param {string} value
  24. * @returns {showdown}
  25. */
  26. showdown.setOption = function (key, value) {
  27. 'use strict';
  28. globalOptions[key] = value;
  29. return this;
  30. };
  31. /**
  32. * Get a global option
  33. * @static
  34. * @param {string} key
  35. * @returns {*}
  36. */
  37. showdown.getOption = function (key) {
  38. 'use strict';
  39. return globalOptions[key];
  40. };
  41. /**
  42. * Get the global options
  43. * @static
  44. * @returns {{omitExtraWLInCodeBlocks: boolean, prefixHeaderId: boolean}}
  45. */
  46. showdown.getOptions = function () {
  47. 'use strict';
  48. return globalOptions;
  49. };
  50. /**
  51. * Get or set a subParser
  52. *
  53. * subParser(name) - Get a registered subParser
  54. * subParser(name, func) - Register a subParser
  55. * @static
  56. * @param {string} name
  57. * @param {function} [func]
  58. * @returns {*}
  59. */
  60. showdown.subParser = function (name, func) {
  61. 'use strict';
  62. if (showdown.helper.isString(name)) {
  63. if (typeof func !== 'undefined') {
  64. parsers[name] = func;
  65. } else {
  66. if (parsers.hasOwnProperty(name)) {
  67. return parsers[name];
  68. } else {
  69. throw Error('SubParser named ' + name + ' not registered!');
  70. }
  71. }
  72. }
  73. };
  74. showdown.extension = function (name, ext) {
  75. 'use strict';
  76. if (!showdown.helper.isString(name)) {
  77. throw Error('Extension \'name\' must be a string');
  78. }
  79. name = showdown.helper.stdExtName(name);
  80. if (showdown.helper.isUndefined(ext)) {
  81. return getExtension();
  82. } else {
  83. return setExtension();
  84. }
  85. };
  86. function getExtension(name) {
  87. 'use strict';
  88. if (!extensions.hasOwnProperty(name)) {
  89. throw Error('Extension named ' + name + ' is not registered!');
  90. }
  91. return extensions[name];
  92. }
  93. function setExtension(name, ext) {
  94. 'use strict';
  95. if (typeof ext !== 'object') {
  96. throw Error('A Showdown Extension must be an object, ' + typeof ext + ' given');
  97. }
  98. if (!showdown.helper.isString(ext.type)) {
  99. throw Error('When registering a showdown extension, "type" must be a string, ' + typeof ext.type + ' given');
  100. }
  101. ext.type = ext.type.toLowerCase();
  102. extensions[name] = ext;
  103. }
  104. /**
  105. * Showdown Converter class
  106. *
  107. * @param {object} [converterOptions]
  108. * @returns {{makeHtml: Function}}
  109. */
  110. showdown.Converter = function (converterOptions) {
  111. 'use strict';
  112. converterOptions = converterOptions || {};
  113. var options = globalOptions,
  114. langExtensions = [],
  115. outputModifiers = [],
  116. parserOrder = [
  117. 'githubCodeBlocks',
  118. 'hashHTMLBlocks',
  119. 'stripLinkDefinitions',
  120. 'blockGamut',
  121. 'unescapeSpecialChars'
  122. ];
  123. // Merge options
  124. if (typeof converterOptions === 'object') {
  125. for (var opt in converterOptions) {
  126. if (converterOptions.hasOwnProperty(opt)) {
  127. options[opt] = converterOptions[opt];
  128. }
  129. }
  130. }
  131. // Parse options
  132. if (options.extensions) {
  133. // Iterate over each plugin
  134. showdown.helper.forEach(options.extensions, function (plugin) {
  135. // Assume it's a bundled plugin if a string is given
  136. if (typeof plugin === 'string') {
  137. plugin = extensions[showdown.helper.stdExtName(plugin)];
  138. }
  139. if (typeof plugin === 'function') {
  140. // Iterate over each extension within that plugin
  141. showdown.helper.forEach(plugin(self), function (ext) {
  142. // Sort extensions by type
  143. if (ext.type) {
  144. if (ext.type === 'language' || ext.type === 'lang') {
  145. langExtensions.push(ext);
  146. } else if (ext.type === 'output' || ext.type === 'html') {
  147. outputModifiers.push(ext);
  148. }
  149. } else {
  150. // Assume language extension
  151. outputModifiers.push(ext);
  152. }
  153. });
  154. } else {
  155. throw 'Extension "' + plugin + '" could not be loaded. It was either not found or is not a valid extension.';
  156. }
  157. });
  158. }
  159. /**
  160. * Converts a markdown string into HTML
  161. * @param {string} text
  162. * @returns {*}
  163. */
  164. function makeHtml(text) {
  165. //check if text is not falsy
  166. if (!text) {
  167. return text;
  168. }
  169. var globals = {
  170. gHtmlBlocks: [],
  171. gUrls: {},
  172. gTitles: {},
  173. gListLevel: 0,
  174. hashLinkCounts: {},
  175. langExtensions: langExtensions,
  176. outputModifiers: outputModifiers
  177. };
  178. // attacklab: Replace ~ with ~T
  179. // This lets us use tilde as an escape char to avoid md5 hashes
  180. // The choice of character is arbitrary; anything that isn't
  181. // magic in Markdown will work.
  182. text = text.replace(/~/g, '~T');
  183. // attacklab: Replace $ with ~D
  184. // RegExp interprets $ as a special character
  185. // when it's in a replacement string
  186. text = text.replace(/\$/g, '~D');
  187. // Standardize line endings
  188. text = text.replace(/\r\n/g, '\n'); // DOS to Unix
  189. text = text.replace(/\r/g, '\n'); // Mac to Unix
  190. // Make sure text begins and ends with a couple of newlines:
  191. text = '\n\n' + text + '\n\n';
  192. // detab
  193. text = parsers.detab(text, options, globals);
  194. // stripBlankLines
  195. text = parsers.stripBlankLines(text, options, globals);
  196. //run languageExtensions
  197. text = parsers.languageExtensions(text, options, globals);
  198. // Run all registered parsers
  199. for (var i = 0; i < parserOrder.length; ++i) {
  200. var name = parserOrder[i];
  201. text = parsers[name](text, options, globals);
  202. }
  203. // attacklab: Restore dollar signs
  204. text = text.replace(/~D/g, '$$');
  205. // attacklab: Restore tildes
  206. text = text.replace(/~T/g, '~');
  207. // Run output modifiers
  208. showdown.helper.forEach(globals.outputModifiers, function (ext) {
  209. text = showdown.subParser('runExtension')(ext, text);
  210. });
  211. text = parsers.outputModifiers(text, options, globals);
  212. return text;
  213. }
  214. return {
  215. makeHtml: makeHtml
  216. };
  217. };