gfm.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. (function(mod) {
  2. if (typeof exports == "object" && typeof module == "object") // CommonJS
  3. mod(require("../../lib/codemirror"), require("../markdown/markdown"), require("../../addon/mode/overlay"));
  4. else if (typeof define == "function" && define.amd) // AMD
  5. define(["../../lib/codemirror", "../markdown/markdown", "../../addon/mode/overlay"], mod);
  6. else // Plain browser env
  7. mod(CodeMirror);
  8. })(function(CodeMirror) {
  9. "use strict";
  10. CodeMirror.defineMode("gfm", function(config, modeConfig) {
  11. var codeDepth = 0;
  12. function blankLine(state) {
  13. state.code = false;
  14. return null;
  15. }
  16. var gfmOverlay = {
  17. startState: function() {
  18. return {
  19. code: false,
  20. codeBlock: false,
  21. ateSpace: false
  22. };
  23. },
  24. copyState: function(s) {
  25. return {
  26. code: s.code,
  27. codeBlock: s.codeBlock,
  28. ateSpace: s.ateSpace
  29. };
  30. },
  31. token: function(stream, state) {
  32. // Hack to prevent formatting override inside code blocks (block and inline)
  33. if (state.codeBlock) {
  34. if (stream.match(/^```/)) {
  35. state.codeBlock = false;
  36. return null;
  37. }
  38. stream.skipToEnd();
  39. return null;
  40. }
  41. if (stream.sol()) {
  42. state.code = false;
  43. }
  44. if (stream.sol() && stream.match(/^```/)) {
  45. stream.skipToEnd();
  46. state.codeBlock = true;
  47. return null;
  48. }
  49. // If this block is changed, it may need to be updated in Markdown mode
  50. if (stream.peek() === '`') {
  51. stream.next();
  52. var before = stream.pos;
  53. stream.eatWhile('`');
  54. var difference = 1 + stream.pos - before;
  55. if (!state.code) {
  56. codeDepth = difference;
  57. state.code = true;
  58. } else {
  59. if (difference === codeDepth) { // Must be exact
  60. state.code = false;
  61. }
  62. }
  63. return null;
  64. } else if (state.code) {
  65. stream.next();
  66. return null;
  67. }
  68. // Check if space. If so, links can be formatted later on
  69. if (stream.eatSpace()) {
  70. state.ateSpace = true;
  71. return null;
  72. }
  73. if (stream.sol() || state.ateSpace) {
  74. state.ateSpace = false;
  75. if(stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+@)?(?:[a-f0-9]{7,40}\b)/)) {
  76. // User/Project@SHA
  77. // User@SHA
  78. // SHA
  79. return "link";
  80. } else if (stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+)?#[0-9]+\b/)) {
  81. // User/Project#Num
  82. // User#Num
  83. // #Num
  84. return "link";
  85. }
  86. }
  87. 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) &&
  88. stream.string.slice(stream.start - 2, stream.start) != "](") {
  89. // URLs
  90. // Taken from http://daringfireball.net/2010/07/improved_regex_for_matching_urls
  91. // And then (issue #1160) simplified to make it not crash the Chrome Regexp engine
  92. return "link";
  93. }
  94. stream.next();
  95. return null;
  96. },
  97. blankLine: blankLine
  98. };
  99. var markdownConfig = {
  100. underscoresBreakWords: false,
  101. taskLists: true,
  102. fencedCodeBlocks: true
  103. };
  104. for (var attr in modeConfig) {
  105. markdownConfig[attr] = modeConfig[attr];
  106. }
  107. markdownConfig.name = "markdown";
  108. CodeMirror.defineMIME("gfmBase", markdownConfig);
  109. return CodeMirror.overlayMode(CodeMirror.getMode(config, "gfmBase"), gfmOverlay);
  110. }, "markdown");
  111. });