showdown.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. /**
  2. * Created by Tivie on 06-01-2015.
  3. */
  4. // Private properties
  5. var showdown = {},
  6. parsers = {},
  7. extensions = {},
  8. globalOptions = getDefaultOpts(true),
  9. setFlavor = 'vanilla',
  10. flavor = {
  11. github: {
  12. omitExtraWLInCodeBlocks: true,
  13. prefixHeaderId: 'user-content-',
  14. simplifiedAutoLink: true,
  15. excludeTrailingPunctuationFromURLs: true,
  16. literalMidWordUnderscores: true,
  17. strikethrough: true,
  18. tables: true,
  19. tablesHeaderId: true,
  20. ghCodeBlocks: true,
  21. tasklists: true,
  22. disableForced4SpacesIndentedSublists: true,
  23. simpleLineBreaks: true,
  24. requireSpaceBeforeHeadingText: true,
  25. ghCompatibleHeaderId: true,
  26. ghMentions: true
  27. },
  28. vanilla: getDefaultOpts(true),
  29. allOn: allOptionsOn()
  30. };
  31. /**
  32. * helper namespace
  33. * @type {{}}
  34. */
  35. showdown.helper = {};
  36. /**
  37. * TODO LEGACY SUPPORT CODE
  38. * @type {{}}
  39. */
  40. showdown.extensions = {};
  41. /**
  42. * Set a global option
  43. * @static
  44. * @param {string} key
  45. * @param {*} value
  46. * @returns {showdown}
  47. */
  48. showdown.setOption = function (key, value) {
  49. 'use strict';
  50. globalOptions[key] = value;
  51. return this;
  52. };
  53. /**
  54. * Get a global option
  55. * @static
  56. * @param {string} key
  57. * @returns {*}
  58. */
  59. showdown.getOption = function (key) {
  60. 'use strict';
  61. return globalOptions[key];
  62. };
  63. /**
  64. * Get the global options
  65. * @static
  66. * @returns {{}}
  67. */
  68. showdown.getOptions = function () {
  69. 'use strict';
  70. return globalOptions;
  71. };
  72. /**
  73. * Reset global options to the default values
  74. * @static
  75. */
  76. showdown.resetOptions = function () {
  77. 'use strict';
  78. globalOptions = getDefaultOpts(true);
  79. };
  80. /**
  81. * Set the flavor showdown should use as default
  82. * @param {string} name
  83. */
  84. showdown.setFlavor = function (name) {
  85. 'use strict';
  86. if (!flavor.hasOwnProperty(name)) {
  87. throw Error(name + ' flavor was not found');
  88. }
  89. var preset = flavor[name];
  90. setFlavor = name;
  91. for (var option in preset) {
  92. if (preset.hasOwnProperty(option)) {
  93. globalOptions[option] = preset[option];
  94. }
  95. }
  96. };
  97. /**
  98. * Get the currently set flavor
  99. * @returns {string}
  100. */
  101. showdown.getFlavor = function () {
  102. 'use strict';
  103. return setFlavor;
  104. };
  105. /**
  106. * Get the options of a specified flavor. Returns undefined if the flavor was not found
  107. * @param {string} name Name of the flavor
  108. * @returns {{}|undefined}
  109. */
  110. showdown.getFlavorOptions = function (name) {
  111. 'use strict';
  112. if (flavor.hasOwnProperty(name)) {
  113. return flavor[name];
  114. }
  115. };
  116. /**
  117. * Get the default options
  118. * @static
  119. * @param {boolean} [simple=true]
  120. * @returns {{}}
  121. */
  122. showdown.getDefaultOptions = function (simple) {
  123. 'use strict';
  124. return getDefaultOpts(simple);
  125. };
  126. /**
  127. * Get or set a subParser
  128. *
  129. * subParser(name) - Get a registered subParser
  130. * subParser(name, func) - Register a subParser
  131. * @static
  132. * @param {string} name
  133. * @param {function} [func]
  134. * @returns {*}
  135. */
  136. showdown.subParser = function (name, func) {
  137. 'use strict';
  138. if (showdown.helper.isString(name)) {
  139. if (typeof func !== 'undefined') {
  140. parsers[name] = func;
  141. } else {
  142. if (parsers.hasOwnProperty(name)) {
  143. return parsers[name];
  144. } else {
  145. throw Error('SubParser named ' + name + ' not registered!');
  146. }
  147. }
  148. }
  149. };
  150. /**
  151. * Gets or registers an extension
  152. * @static
  153. * @param {string} name
  154. * @param {object|function=} ext
  155. * @returns {*}
  156. */
  157. showdown.extension = function (name, ext) {
  158. 'use strict';
  159. if (!showdown.helper.isString(name)) {
  160. throw Error('Extension \'name\' must be a string');
  161. }
  162. name = showdown.helper.stdExtName(name);
  163. // Getter
  164. if (showdown.helper.isUndefined(ext)) {
  165. if (!extensions.hasOwnProperty(name)) {
  166. throw Error('Extension named ' + name + ' is not registered!');
  167. }
  168. return extensions[name];
  169. // Setter
  170. } else {
  171. // Expand extension if it's wrapped in a function
  172. if (typeof ext === 'function') {
  173. ext = ext();
  174. }
  175. // Ensure extension is an array
  176. if (!showdown.helper.isArray(ext)) {
  177. ext = [ext];
  178. }
  179. var validExtension = validate(ext, name);
  180. if (validExtension.valid) {
  181. extensions[name] = ext;
  182. } else {
  183. throw Error(validExtension.error);
  184. }
  185. }
  186. };
  187. /**
  188. * Gets all extensions registered
  189. * @returns {{}}
  190. */
  191. showdown.getAllExtensions = function () {
  192. 'use strict';
  193. return extensions;
  194. };
  195. /**
  196. * Remove an extension
  197. * @param {string} name
  198. */
  199. showdown.removeExtension = function (name) {
  200. 'use strict';
  201. delete extensions[name];
  202. };
  203. /**
  204. * Removes all extensions
  205. */
  206. showdown.resetExtensions = function () {
  207. 'use strict';
  208. extensions = {};
  209. };
  210. /**
  211. * Validate extension
  212. * @param {array} extension
  213. * @param {string} name
  214. * @returns {{valid: boolean, error: string}}
  215. */
  216. function validate (extension, name) {
  217. 'use strict';
  218. var errMsg = (name) ? 'Error in ' + name + ' extension->' : 'Error in unnamed extension',
  219. ret = {
  220. valid: true,
  221. error: ''
  222. };
  223. if (!showdown.helper.isArray(extension)) {
  224. extension = [extension];
  225. }
  226. for (var i = 0; i < extension.length; ++i) {
  227. var baseMsg = errMsg + ' sub-extension ' + i + ': ',
  228. ext = extension[i];
  229. if (typeof ext !== 'object') {
  230. ret.valid = false;
  231. ret.error = baseMsg + 'must be an object, but ' + typeof ext + ' given';
  232. return ret;
  233. }
  234. if (!showdown.helper.isString(ext.type)) {
  235. ret.valid = false;
  236. ret.error = baseMsg + 'property "type" must be a string, but ' + typeof ext.type + ' given';
  237. return ret;
  238. }
  239. var type = ext.type = ext.type.toLowerCase();
  240. // normalize extension type
  241. if (type === 'language') {
  242. type = ext.type = 'lang';
  243. }
  244. if (type === 'html') {
  245. type = ext.type = 'output';
  246. }
  247. if (type !== 'lang' && type !== 'output' && type !== 'listener') {
  248. ret.valid = false;
  249. ret.error = baseMsg + 'type ' + type + ' is not recognized. Valid values: "lang/language", "output/html" or "listener"';
  250. return ret;
  251. }
  252. if (type === 'listener') {
  253. if (showdown.helper.isUndefined(ext.listeners)) {
  254. ret.valid = false;
  255. ret.error = baseMsg + '. Extensions of type "listener" must have a property called "listeners"';
  256. return ret;
  257. }
  258. } else {
  259. if (showdown.helper.isUndefined(ext.filter) && showdown.helper.isUndefined(ext.regex)) {
  260. ret.valid = false;
  261. ret.error = baseMsg + type + ' extensions must define either a "regex" property or a "filter" method';
  262. return ret;
  263. }
  264. }
  265. if (ext.listeners) {
  266. if (typeof ext.listeners !== 'object') {
  267. ret.valid = false;
  268. ret.error = baseMsg + '"listeners" property must be an object but ' + typeof ext.listeners + ' given';
  269. return ret;
  270. }
  271. for (var ln in ext.listeners) {
  272. if (ext.listeners.hasOwnProperty(ln)) {
  273. if (typeof ext.listeners[ln] !== 'function') {
  274. ret.valid = false;
  275. ret.error = baseMsg + '"listeners" property must be an hash of [event name]: [callback]. listeners.' + ln +
  276. ' must be a function but ' + typeof ext.listeners[ln] + ' given';
  277. return ret;
  278. }
  279. }
  280. }
  281. }
  282. if (ext.filter) {
  283. if (typeof ext.filter !== 'function') {
  284. ret.valid = false;
  285. ret.error = baseMsg + '"filter" must be a function, but ' + typeof ext.filter + ' given';
  286. return ret;
  287. }
  288. } else if (ext.regex) {
  289. if (showdown.helper.isString(ext.regex)) {
  290. ext.regex = new RegExp(ext.regex, 'g');
  291. }
  292. if (!(ext.regex instanceof RegExp)) {
  293. ret.valid = false;
  294. ret.error = baseMsg + '"regex" property must either be a string or a RegExp object, but ' + typeof ext.regex + ' given';
  295. return ret;
  296. }
  297. if (showdown.helper.isUndefined(ext.replace)) {
  298. ret.valid = false;
  299. ret.error = baseMsg + '"regex" extensions must implement a replace string or function';
  300. return ret;
  301. }
  302. }
  303. }
  304. return ret;
  305. }
  306. /**
  307. * Validate extension
  308. * @param {object} ext
  309. * @returns {boolean}
  310. */
  311. showdown.validateExtension = function (ext) {
  312. 'use strict';
  313. var validateExtension = validate(ext, null);
  314. if (!validateExtension.valid) {
  315. console.warn(validateExtension.error);
  316. return false;
  317. }
  318. return true;
  319. };