gfm.js 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. CodeMirror.defineMode("gfm", function(config) {
  2. var codeDepth = 0;
  3. function blankLine(state) {
  4. state.code = false;
  5. return null;
  6. }
  7. var gfmOverlay = {
  8. startState: function() {
  9. return {
  10. code: false,
  11. codeBlock: false,
  12. ateSpace: false
  13. };
  14. },
  15. copyState: function(s) {
  16. return {
  17. code: s.code,
  18. codeBlock: s.codeBlock,
  19. ateSpace: s.ateSpace
  20. };
  21. },
  22. token: function(stream, state) {
  23. // Hack to prevent formatting override inside code blocks (block and inline)
  24. if (state.codeBlock) {
  25. if (stream.match(/^```/)) {
  26. state.codeBlock = false;
  27. return null;
  28. }
  29. stream.skipToEnd();
  30. return null;
  31. }
  32. if (stream.sol()) {
  33. state.code = false;
  34. }
  35. if (stream.sol() && stream.match(/^```/)) {
  36. stream.skipToEnd();
  37. state.codeBlock = true;
  38. return null;
  39. }
  40. // If this block is changed, it may need to be updated in Markdown mode
  41. if (stream.peek() === '`') {
  42. stream.next();
  43. var before = stream.pos;
  44. stream.eatWhile('`');
  45. var difference = 1 + stream.pos - before;
  46. if (!state.code) {
  47. codeDepth = difference;
  48. state.code = true;
  49. } else {
  50. if (difference === codeDepth) { // Must be exact
  51. state.code = false;
  52. }
  53. }
  54. return null;
  55. } else if (state.code) {
  56. stream.next();
  57. return null;
  58. }
  59. // Check if space. If so, links can be formatted later on
  60. if (stream.eatSpace()) {
  61. state.ateSpace = true;
  62. return null;
  63. }
  64. if (stream.sol() || state.ateSpace) {
  65. state.ateSpace = false;
  66. if(stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+@)?(?:[a-f0-9]{7,40}\b)/)) {
  67. // User/Project@SHA
  68. // User@SHA
  69. // SHA
  70. return "link";
  71. } else if (stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+)?#[0-9]+\b/)) {
  72. // User/Project#Num
  73. // User#Num
  74. // #Num
  75. return "link";
  76. }
  77. }
  78. if (stream.match(/^((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\([^\s()<>]*\))+(?:\([^\s()<>]*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/i)) {
  79. // URLs
  80. // Taken from http://daringfireball.net/2010/07/improved_regex_for_matching_urls
  81. // And then (issue #1160) simplified to make it not crash the Chrome Regexp engine
  82. return "link";
  83. }
  84. stream.next();
  85. return null;
  86. },
  87. blankLine: blankLine
  88. };
  89. CodeMirror.defineMIME("gfmBase", {
  90. name: "markdown",
  91. underscoresBreakWords: false,
  92. taskLists: true,
  93. fencedCodeBlocks: true
  94. });
  95. return CodeMirror.overlayMode(CodeMirror.getMode(config, "gfmBase"), gfmOverlay);
  96. }, "markdown");