showdown.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  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 = {},
  114. langExtensions = [],
  115. outputModifiers = [],
  116. parserOrder = [
  117. 'githubCodeBlocks',
  118. 'hashHTMLBlocks',
  119. 'stripLinkDefinitions',
  120. 'blockGamut',
  121. 'unescapeSpecialChars'
  122. ];
  123. for (var gOpt in globalOptions) {
  124. if (globalOptions.hasOwnProperty(gOpt)) {
  125. options[gOpt] = globalOptions[gOpt];
  126. }
  127. }
  128. // Merge options
  129. if (typeof converterOptions === 'object') {
  130. for (var opt in converterOptions) {
  131. if (converterOptions.hasOwnProperty(opt)) {
  132. options[opt] = converterOptions[opt];
  133. }
  134. }
  135. }
  136. // This is a dirty workaround to maintain backwards extension compatibility
  137. // We define a self var (which is a copy of this) and inject the makeHtml function
  138. // directly to it. This ensures a full converter object is available when iterating over extensions
  139. // We should rewrite the extension loading mechanism and use some kind of interface or decorator pattern
  140. // and inject the object reference there instead.
  141. var self = this;
  142. self.makeHtml = makeHtml;
  143. // Parse options
  144. if (options.extensions) {
  145. // Iterate over each plugin
  146. showdown.helper.forEach(options.extensions, function (plugin) {
  147. var pluginName = plugin;
  148. // Assume it's a bundled plugin if a string is given
  149. if (typeof plugin === 'string') {
  150. var tPluginName = showdown.helper.stdExtName(plugin);
  151. if (!showdown.helper.isUndefined(showdown.extensions[tPluginName]) && showdown.extensions[tPluginName]) {
  152. //Trigger some kind of deprecated alert
  153. plugin = showdown.extensions[tPluginName];
  154. } else if (!showdown.helper.isUndefined(extensions[tPluginName])) {
  155. plugin = extensions[tPluginName];
  156. }
  157. }
  158. if (typeof plugin === 'function') {
  159. // Iterate over each extension within that plugin
  160. showdown.helper.forEach(plugin(self), function (ext) {
  161. // Sort extensions by type
  162. if (ext.type) {
  163. if (ext.type === 'language' || ext.type === 'lang') {
  164. langExtensions.push(ext);
  165. } else if (ext.type === 'output' || ext.type === 'html') {
  166. outputModifiers.push(ext);
  167. }
  168. } else {
  169. // Assume language extension
  170. outputModifiers.push(ext);
  171. }
  172. });
  173. } else {
  174. var errMsg = 'An extension could not be loaded. It was either not found or is not a valid extension.';
  175. if (typeof pluginName === 'string') {
  176. errMsg = 'Extension "' + pluginName + '" could not be loaded. It was either not found or is not a valid extension.';
  177. }
  178. throw errMsg;
  179. }
  180. });
  181. }
  182. /**
  183. * Converts a markdown string into HTML
  184. * @param {string} text
  185. * @returns {*}
  186. */
  187. function makeHtml(text) {
  188. //check if text is not falsy
  189. if (!text) {
  190. return text;
  191. }
  192. var globals = {
  193. gHtmlBlocks: [],
  194. gUrls: {},
  195. gTitles: {},
  196. gListLevel: 0,
  197. hashLinkCounts: {},
  198. langExtensions: langExtensions,
  199. outputModifiers: outputModifiers
  200. };
  201. // attacklab: Replace ~ with ~T
  202. // This lets us use tilde as an escape char to avoid md5 hashes
  203. // The choice of character is arbitrary; anything that isn't
  204. // magic in Markdown will work.
  205. text = text.replace(/~/g, '~T');
  206. // attacklab: Replace $ with ~D
  207. // RegExp interprets $ as a special character
  208. // when it's in a replacement string
  209. text = text.replace(/\$/g, '~D');
  210. // Standardize line endings
  211. text = text.replace(/\r\n/g, '\n'); // DOS to Unix
  212. text = text.replace(/\r/g, '\n'); // Mac to Unix
  213. // Make sure text begins and ends with a couple of newlines:
  214. text = '\n\n' + text + '\n\n';
  215. // detab
  216. text = parsers.detab(text, options, globals);
  217. // stripBlankLines
  218. text = parsers.stripBlankLines(text, options, globals);
  219. //run languageExtensions
  220. text = parsers.languageExtensions(text, options, globals);
  221. // Run all registered parsers
  222. for (var i = 0; i < parserOrder.length; ++i) {
  223. var name = parserOrder[i];
  224. text = parsers[name](text, options, globals);
  225. }
  226. // attacklab: Restore dollar signs
  227. text = text.replace(/~D/g, '$$');
  228. // attacklab: Restore tildes
  229. text = text.replace(/~T/g, '~');
  230. // Run output modifiers
  231. showdown.helper.forEach(globals.outputModifiers, function (ext) {
  232. text = showdown.subParser('runExtension')(ext, text);
  233. });
  234. text = parsers.outputModifiers(text, options, globals);
  235. return text;
  236. }
  237. return {
  238. makeHtml: makeHtml
  239. };
  240. };