showdown.js 9.3 KB

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