markdown.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813
  1. // CodeMirror, copyright (c) by Marijn Haverbeke and others
  2. // Distributed under an MIT license: http://codemirror.net/LICENSE
  3. (function(mod) {
  4. if (typeof exports == "object" && typeof module == "object") // CommonJS
  5. mod(require("../../lib/codemirror"), require("../xml/xml"), require("../meta"));
  6. else if (typeof define == "function" && define.amd) // AMD
  7. define(["../../lib/codemirror", "../xml/xml", "../meta"], mod);
  8. else // Plain browser env
  9. mod(CodeMirror);
  10. })(function(CodeMirror) {
  11. "use strict";
  12. CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
  13. var htmlMode = CodeMirror.getMode(cmCfg, "text/html");
  14. var htmlModeMissing = htmlMode.name == "null"
  15. function getMode(name) {
  16. if (CodeMirror.findModeByName) {
  17. var found = CodeMirror.findModeByName(name);
  18. if (found) name = found.mime || found.mimes[0];
  19. }
  20. var mode = CodeMirror.getMode(cmCfg, name);
  21. return mode.name == "null" ? null : mode;
  22. }
  23. // Should characters that affect highlighting be highlighted separate?
  24. // Does not include characters that will be output (such as `1.` and `-` for lists)
  25. if (modeCfg.highlightFormatting === undefined)
  26. modeCfg.highlightFormatting = false;
  27. // Maximum number of nested blockquotes. Set to 0 for infinite nesting.
  28. // Excess `>` will emit `error` token.
  29. if (modeCfg.maxBlockquoteDepth === undefined)
  30. modeCfg.maxBlockquoteDepth = 0;
  31. // Should underscores in words open/close em/strong?
  32. if (modeCfg.underscoresBreakWords === undefined)
  33. modeCfg.underscoresBreakWords = true;
  34. // Use `fencedCodeBlocks` to configure fenced code blocks. false to
  35. // disable, string to specify a precise regexp that the fence should
  36. // match, and true to allow three or more backticks or tildes (as
  37. // per CommonMark).
  38. // Turn on task lists? ("- [ ] " and "- [x] ")
  39. if (modeCfg.taskLists === undefined) modeCfg.taskLists = false;
  40. // Turn on strikethrough syntax
  41. if (modeCfg.strikethrough === undefined)
  42. modeCfg.strikethrough = false;
  43. // Allow token types to be overridden by user-provided token types.
  44. if (modeCfg.tokenTypeOverrides === undefined)
  45. modeCfg.tokenTypeOverrides = {};
  46. var tokenTypes = {
  47. header: "header",
  48. code: "comment",
  49. quote: "quote",
  50. list1: "variable-2",
  51. list2: "variable-3",
  52. list3: "keyword",
  53. hr: "hr",
  54. image: "image",
  55. imageAltText: "image-alt-text",
  56. imageMarker: "image-marker",
  57. formatting: "formatting",
  58. linkInline: "link",
  59. linkEmail: "link",
  60. linkText: "link",
  61. linkHref: "string",
  62. em: "em",
  63. strong: "strong",
  64. strikethrough: "strikethrough"
  65. };
  66. for (var tokenType in tokenTypes) {
  67. if (tokenTypes.hasOwnProperty(tokenType) && modeCfg.tokenTypeOverrides[tokenType]) {
  68. tokenTypes[tokenType] = modeCfg.tokenTypeOverrides[tokenType];
  69. }
  70. }
  71. var hrRE = /^([*\-_])(?:\s*\1){2,}\s*$/
  72. , listRE = /^(?:[*\-+]|^[0-9]+([.)]))\s+/
  73. , taskListRE = /^\[(x| )\](?=\s)/ // Must follow listRE
  74. , atxHeaderRE = modeCfg.allowAtxHeaderWithoutSpace ? /^(#+)/ : /^(#+)(?: |$)/
  75. , setextHeaderRE = /^ *(?:\={1,}|-{1,})\s*$/
  76. , textRE = /^[^#!\[\]*_\\<>` "'(~]+/
  77. , fencedCodeRE = new RegExp("^(" + (modeCfg.fencedCodeBlocks === true ? "~~~+|```+" : modeCfg.fencedCodeBlocks) +
  78. ")[ \\t]*([\\w+#\-]*)");
  79. function switchInline(stream, state, f) {
  80. state.f = state.inline = f;
  81. return f(stream, state);
  82. }
  83. function switchBlock(stream, state, f) {
  84. state.f = state.block = f;
  85. return f(stream, state);
  86. }
  87. function lineIsEmpty(line) {
  88. return !line || !/\S/.test(line.string)
  89. }
  90. // Blocks
  91. function blankLine(state) {
  92. // Reset linkTitle state
  93. state.linkTitle = false;
  94. // Reset EM state
  95. state.em = false;
  96. // Reset STRONG state
  97. state.strong = false;
  98. // Reset strikethrough state
  99. state.strikethrough = false;
  100. // Reset state.quote
  101. state.quote = 0;
  102. // Reset state.indentedCode
  103. state.indentedCode = false;
  104. if (htmlModeMissing && state.f == htmlBlock) {
  105. state.f = inlineNormal;
  106. state.block = blockNormal;
  107. }
  108. // Reset state.trailingSpace
  109. state.trailingSpace = 0;
  110. state.trailingSpaceNewLine = false;
  111. // Mark this line as blank
  112. state.prevLine = state.thisLine
  113. state.thisLine = null
  114. return null;
  115. }
  116. function blockNormal(stream, state) {
  117. var sol = stream.sol();
  118. var prevLineIsList = state.list !== false,
  119. prevLineIsIndentedCode = state.indentedCode;
  120. state.indentedCode = false;
  121. if (prevLineIsList) {
  122. if (state.indentationDiff >= 0) { // Continued list
  123. if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block
  124. state.indentation -= state.indentationDiff;
  125. }
  126. state.list = null;
  127. } else if (state.indentation > 0) {
  128. state.list = null;
  129. } else { // No longer a list
  130. state.list = false;
  131. }
  132. }
  133. var match = null;
  134. if (state.indentationDiff >= 4) {
  135. stream.skipToEnd();
  136. if (prevLineIsIndentedCode || lineIsEmpty(state.prevLine)) {
  137. state.indentation -= 4;
  138. state.indentedCode = true;
  139. return tokenTypes.code;
  140. } else {
  141. return null;
  142. }
  143. } else if (stream.eatSpace()) {
  144. return null;
  145. } else if ((match = stream.match(atxHeaderRE)) && match[1].length <= 6) {
  146. state.header = match[1].length;
  147. if (modeCfg.highlightFormatting) state.formatting = "header";
  148. state.f = state.inline;
  149. return getType(state);
  150. } else if (!lineIsEmpty(state.prevLine) && !state.quote && !prevLineIsList &&
  151. !prevLineIsIndentedCode && (match = stream.match(setextHeaderRE))) {
  152. state.header = match[0].charAt(0) == '=' ? 1 : 2;
  153. if (modeCfg.highlightFormatting) state.formatting = "header";
  154. state.f = state.inline;
  155. return getType(state);
  156. } else if (stream.eat('>')) {
  157. state.quote = sol ? 1 : state.quote + 1;
  158. if (modeCfg.highlightFormatting) state.formatting = "quote";
  159. stream.eatSpace();
  160. return getType(state);
  161. } else if (stream.peek() === '[') {
  162. return switchInline(stream, state, footnoteLink);
  163. } else if (stream.match(hrRE, true)) {
  164. state.hr = true;
  165. return tokenTypes.hr;
  166. } else if (match = stream.match(listRE)) {
  167. var listType = match[1] ? "ol" : "ul";
  168. state.indentation = stream.column() + stream.current().length;
  169. state.list = true;
  170. // While this list item's marker's indentation
  171. // is less than the deepest list item's content's indentation,
  172. // pop the deepest list item indentation off the stack.
  173. while (state.listStack && stream.column() < state.listStack[state.listStack.length - 1]) {
  174. state.listStack.pop();
  175. }
  176. // Add this list item's content's indentation to the stack
  177. state.listStack.push(state.indentation);
  178. if (modeCfg.taskLists && stream.match(taskListRE, false)) {
  179. state.taskList = true;
  180. }
  181. state.f = state.inline;
  182. if (modeCfg.highlightFormatting) state.formatting = ["list", "list-" + listType];
  183. return getType(state);
  184. } else if (modeCfg.fencedCodeBlocks && (match = stream.match(fencedCodeRE, true))) {
  185. state.fencedChars = match[1]
  186. // try switching mode
  187. state.localMode = getMode(match[2]);
  188. if (state.localMode) state.localState = CodeMirror.startState(state.localMode);
  189. state.f = state.block = local;
  190. if (modeCfg.highlightFormatting) state.formatting = "code-block";
  191. state.code = -1
  192. return getType(state);
  193. }
  194. return switchInline(stream, state, state.inline);
  195. }
  196. function htmlBlock(stream, state) {
  197. var style = htmlMode.token(stream, state.htmlState);
  198. if (!htmlModeMissing) {
  199. var inner = CodeMirror.innerMode(htmlMode, state.htmlState)
  200. if ((inner.mode.name == "xml" && inner.state.tagStart === null &&
  201. (!inner.state.context && inner.state.tokenize.isInText)) ||
  202. (state.md_inside && stream.current().indexOf(">") > -1)) {
  203. state.f = inlineNormal;
  204. state.block = blockNormal;
  205. state.htmlState = null;
  206. }
  207. }
  208. return style;
  209. }
  210. function local(stream, state) {
  211. if (state.fencedChars && stream.match(state.fencedChars, false)) {
  212. state.localMode = state.localState = null;
  213. state.f = state.block = leavingLocal;
  214. return null;
  215. } else if (state.localMode) {
  216. return state.localMode.token(stream, state.localState);
  217. } else {
  218. stream.skipToEnd();
  219. return tokenTypes.code;
  220. }
  221. }
  222. function leavingLocal(stream, state) {
  223. stream.match(state.fencedChars);
  224. state.block = blockNormal;
  225. state.f = inlineNormal;
  226. state.fencedChars = null;
  227. if (modeCfg.highlightFormatting) state.formatting = "code-block";
  228. state.code = 1
  229. var returnType = getType(state);
  230. state.code = 0
  231. return returnType;
  232. }
  233. // Inline
  234. function getType(state) {
  235. var styles = [];
  236. if (state.formatting) {
  237. styles.push(tokenTypes.formatting);
  238. if (typeof state.formatting === "string") state.formatting = [state.formatting];
  239. for (var i = 0; i < state.formatting.length; i++) {
  240. styles.push(tokenTypes.formatting + "-" + state.formatting[i]);
  241. if (state.formatting[i] === "header") {
  242. styles.push(tokenTypes.formatting + "-" + state.formatting[i] + "-" + state.header);
  243. }
  244. // Add `formatting-quote` and `formatting-quote-#` for blockquotes
  245. // Add `error` instead if the maximum blockquote nesting depth is passed
  246. if (state.formatting[i] === "quote") {
  247. if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) {
  248. styles.push(tokenTypes.formatting + "-" + state.formatting[i] + "-" + state.quote);
  249. } else {
  250. styles.push("error");
  251. }
  252. }
  253. }
  254. }
  255. if (state.taskOpen) {
  256. styles.push("meta");
  257. return styles.length ? styles.join(' ') : null;
  258. }
  259. if (state.taskClosed) {
  260. styles.push("property");
  261. return styles.length ? styles.join(' ') : null;
  262. }
  263. if (state.linkHref) {
  264. styles.push(tokenTypes.linkHref, "url");
  265. } else { // Only apply inline styles to non-url text
  266. if (state.strong) { styles.push(tokenTypes.strong); }
  267. if (state.em) { styles.push(tokenTypes.em); }
  268. if (state.strikethrough) { styles.push(tokenTypes.strikethrough); }
  269. if (state.linkText) { styles.push(tokenTypes.linkText); }
  270. if (state.code) { styles.push(tokenTypes.code); }
  271. if (state.image) { styles.push(tokenTypes.image); }
  272. if (state.imageAltText) { styles.push(tokenTypes.imageAltText, "link"); }
  273. if (state.imageMarker) { styles.push(tokenTypes.imageMarker); }
  274. }
  275. if (state.header) { styles.push(tokenTypes.header, tokenTypes.header + "-" + state.header); }
  276. if (state.quote) {
  277. styles.push(tokenTypes.quote);
  278. // Add `quote-#` where the maximum for `#` is modeCfg.maxBlockquoteDepth
  279. if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) {
  280. styles.push(tokenTypes.quote + "-" + state.quote);
  281. } else {
  282. styles.push(tokenTypes.quote + "-" + modeCfg.maxBlockquoteDepth);
  283. }
  284. }
  285. if (state.list !== false) {
  286. var listMod = (state.listStack.length - 1) % 3;
  287. if (!listMod) {
  288. styles.push(tokenTypes.list1);
  289. } else if (listMod === 1) {
  290. styles.push(tokenTypes.list2);
  291. } else {
  292. styles.push(tokenTypes.list3);
  293. }
  294. }
  295. if (state.trailingSpaceNewLine) {
  296. styles.push("trailing-space-new-line");
  297. } else if (state.trailingSpace) {
  298. styles.push("trailing-space-" + (state.trailingSpace % 2 ? "a" : "b"));
  299. }
  300. return styles.length ? styles.join(' ') : null;
  301. }
  302. function handleText(stream, state) {
  303. if (stream.match(textRE, true)) {
  304. return getType(state);
  305. }
  306. return undefined;
  307. }
  308. function inlineNormal(stream, state) {
  309. var style = state.text(stream, state);
  310. if (typeof style !== 'undefined')
  311. return style;
  312. if (state.list) { // List marker (*, +, -, 1., etc)
  313. state.list = null;
  314. return getType(state);
  315. }
  316. if (state.taskList) {
  317. var taskOpen = stream.match(taskListRE, true)[1] !== "x";
  318. if (taskOpen) state.taskOpen = true;
  319. else state.taskClosed = true;
  320. if (modeCfg.highlightFormatting) state.formatting = "task";
  321. state.taskList = false;
  322. return getType(state);
  323. }
  324. state.taskOpen = false;
  325. state.taskClosed = false;
  326. if (state.header && stream.match(/^#+$/, true)) {
  327. if (modeCfg.highlightFormatting) state.formatting = "header";
  328. return getType(state);
  329. }
  330. // Get sol() value now, before character is consumed
  331. var sol = stream.sol();
  332. var ch = stream.next();
  333. // Matches link titles present on next line
  334. if (state.linkTitle) {
  335. state.linkTitle = false;
  336. var matchCh = ch;
  337. if (ch === '(') {
  338. matchCh = ')';
  339. }
  340. matchCh = (matchCh+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
  341. var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh;
  342. if (stream.match(new RegExp(regex), true)) {
  343. return tokenTypes.linkHref;
  344. }
  345. }
  346. // If this block is changed, it may need to be updated in GFM mode
  347. if (ch === '`') {
  348. var previousFormatting = state.formatting;
  349. if (modeCfg.highlightFormatting) state.formatting = "code";
  350. stream.eatWhile('`');
  351. var count = stream.current().length
  352. if (state.code == 0) {
  353. state.code = count
  354. return getType(state)
  355. } else if (count == state.code) { // Must be exact
  356. var t = getType(state)
  357. state.code = 0
  358. return t
  359. } else {
  360. state.formatting = previousFormatting
  361. return getType(state)
  362. }
  363. } else if (state.code) {
  364. return getType(state);
  365. }
  366. if (ch === '\\') {
  367. stream.next();
  368. if (modeCfg.highlightFormatting) {
  369. var type = getType(state);
  370. var formattingEscape = tokenTypes.formatting + "-escape";
  371. return type ? type + " " + formattingEscape : formattingEscape;
  372. }
  373. }
  374. if (ch === '!' && stream.match(/\[[^\]]*\] ?(?:\(|\[)/, false)) {
  375. state.imageMarker = true;
  376. state.image = true;
  377. if (modeCfg.highlightFormatting) state.formatting = "image";
  378. return getType(state);
  379. }
  380. if (ch === '[' && state.imageMarker && stream.match(/[^\]]*\](\(.*?\)| ?\[.*?\])/, false)) {
  381. state.imageMarker = false;
  382. state.imageAltText = true
  383. if (modeCfg.highlightFormatting) state.formatting = "image";
  384. return getType(state);
  385. }
  386. if (ch === ']' && state.imageAltText) {
  387. if (modeCfg.highlightFormatting) state.formatting = "image";
  388. var type = getType(state);
  389. state.imageAltText = false;
  390. state.image = false;
  391. state.inline = state.f = linkHref;
  392. return type;
  393. }
  394. if (ch === '[' && stream.match(/[^\]]*\](\(.*\)| ?\[.*?\])/, false) && !state.image) {
  395. state.linkText = true;
  396. if (modeCfg.highlightFormatting) state.formatting = "link";
  397. return getType(state);
  398. }
  399. if (ch === ']' && state.linkText && stream.match(/\(.*?\)| ?\[.*?\]/, false)) {
  400. if (modeCfg.highlightFormatting) state.formatting = "link";
  401. var type = getType(state);
  402. state.linkText = false;
  403. state.inline = state.f = linkHref;
  404. return type;
  405. }
  406. if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, false)) {
  407. state.f = state.inline = linkInline;
  408. if (modeCfg.highlightFormatting) state.formatting = "link";
  409. var type = getType(state);
  410. if (type){
  411. type += " ";
  412. } else {
  413. type = "";
  414. }
  415. return type + tokenTypes.linkInline;
  416. }
  417. if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, false)) {
  418. state.f = state.inline = linkInline;
  419. if (modeCfg.highlightFormatting) state.formatting = "link";
  420. var type = getType(state);
  421. if (type){
  422. type += " ";
  423. } else {
  424. type = "";
  425. }
  426. return type + tokenTypes.linkEmail;
  427. }
  428. if (ch === '<' && stream.match(/^(!--|[a-z]+(?:\s+[a-z_:.\-]+(?:\s*=\s*[^ >]+)?)*\s*>)/i, false)) {
  429. var end = stream.string.indexOf(">", stream.pos);
  430. if (end != -1) {
  431. var atts = stream.string.substring(stream.start, end);
  432. if (/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(atts)) state.md_inside = true;
  433. }
  434. stream.backUp(1);
  435. state.htmlState = CodeMirror.startState(htmlMode);
  436. return switchBlock(stream, state, htmlBlock);
  437. }
  438. if (ch === '<' && stream.match(/^\/\w*?>/)) {
  439. state.md_inside = false;
  440. return "tag";
  441. }
  442. var ignoreUnderscore = false;
  443. if (!modeCfg.underscoresBreakWords) {
  444. if (ch === '_' && stream.peek() !== '_' && stream.match(/(\w)/, false)) {
  445. var prevPos = stream.pos - 2;
  446. if (prevPos >= 0) {
  447. var prevCh = stream.string.charAt(prevPos);
  448. if (prevCh !== '_' && prevCh.match(/(\w)/, false)) {
  449. ignoreUnderscore = true;
  450. }
  451. }
  452. }
  453. }
  454. if (ch === '*' || (ch === '_' && !ignoreUnderscore)) {
  455. if (sol && stream.peek() === ' ') {
  456. // Do nothing, surrounded by newline and space
  457. } else if (state.strong === ch && stream.eat(ch)) { // Remove STRONG
  458. if (modeCfg.highlightFormatting) state.formatting = "strong";
  459. var t = getType(state);
  460. state.strong = false;
  461. return t;
  462. } else if (!state.strong && stream.eat(ch)) { // Add STRONG
  463. state.strong = ch;
  464. if (modeCfg.highlightFormatting) state.formatting = "strong";
  465. return getType(state);
  466. } else if (state.em === ch) { // Remove EM
  467. if (modeCfg.highlightFormatting) state.formatting = "em";
  468. var t = getType(state);
  469. state.em = false;
  470. return t;
  471. } else if (!state.em) { // Add EM
  472. state.em = ch;
  473. if (modeCfg.highlightFormatting) state.formatting = "em";
  474. return getType(state);
  475. }
  476. } else if (ch === ' ') {
  477. if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces
  478. if (stream.peek() === ' ') { // Surrounded by spaces, ignore
  479. return getType(state);
  480. } else { // Not surrounded by spaces, back up pointer
  481. stream.backUp(1);
  482. }
  483. }
  484. }
  485. if (modeCfg.strikethrough) {
  486. if (ch === '~' && stream.eatWhile(ch)) {
  487. if (state.strikethrough) {// Remove strikethrough
  488. if (modeCfg.highlightFormatting) state.formatting = "strikethrough";
  489. var t = getType(state);
  490. state.strikethrough = false;
  491. return t;
  492. } else if (stream.match(/^[^\s]/, false)) {// Add strikethrough
  493. state.strikethrough = true;
  494. if (modeCfg.highlightFormatting) state.formatting = "strikethrough";
  495. return getType(state);
  496. }
  497. } else if (ch === ' ') {
  498. if (stream.match(/^~~/, true)) { // Probably surrounded by space
  499. if (stream.peek() === ' ') { // Surrounded by spaces, ignore
  500. return getType(state);
  501. } else { // Not surrounded by spaces, back up pointer
  502. stream.backUp(2);
  503. }
  504. }
  505. }
  506. }
  507. if (ch === ' ') {
  508. if (stream.match(/ +$/, false)) {
  509. state.trailingSpace++;
  510. } else if (state.trailingSpace) {
  511. state.trailingSpaceNewLine = true;
  512. }
  513. }
  514. return getType(state);
  515. }
  516. function linkInline(stream, state) {
  517. var ch = stream.next();
  518. if (ch === ">") {
  519. state.f = state.inline = inlineNormal;
  520. if (modeCfg.highlightFormatting) state.formatting = "link";
  521. var type = getType(state);
  522. if (type){
  523. type += " ";
  524. } else {
  525. type = "";
  526. }
  527. return type + tokenTypes.linkInline;
  528. }
  529. stream.match(/^[^>]+/, true);
  530. return tokenTypes.linkInline;
  531. }
  532. function linkHref(stream, state) {
  533. // Check if space, and return NULL if so (to avoid marking the space)
  534. if(stream.eatSpace()){
  535. return null;
  536. }
  537. var ch = stream.next();
  538. if (ch === '(' || ch === '[') {
  539. state.f = state.inline = getLinkHrefInside(ch === "(" ? ")" : "]", 0);
  540. if (modeCfg.highlightFormatting) state.formatting = "link-string";
  541. state.linkHref = true;
  542. return getType(state);
  543. }
  544. return 'error';
  545. }
  546. var linkRE = {
  547. ")": /^(?:[^\\\(\)]|\\.|\((?:[^\\\(\)]|\\.)*\))*?(?=\))/,
  548. "]": /^(?:[^\\\[\]]|\\.|\[(?:[^\\\[\\]]|\\.)*\])*?(?=\])/
  549. }
  550. function getLinkHrefInside(endChar) {
  551. return function(stream, state) {
  552. var ch = stream.next();
  553. if (ch === endChar) {
  554. state.f = state.inline = inlineNormal;
  555. if (modeCfg.highlightFormatting) state.formatting = "link-string";
  556. var returnState = getType(state);
  557. state.linkHref = false;
  558. return returnState;
  559. }
  560. stream.match(linkRE[endChar])
  561. state.linkHref = true;
  562. return getType(state);
  563. };
  564. }
  565. function footnoteLink(stream, state) {
  566. if (stream.match(/^([^\]\\]|\\.)*\]:/, false)) {
  567. state.f = footnoteLinkInside;
  568. stream.next(); // Consume [
  569. if (modeCfg.highlightFormatting) state.formatting = "link";
  570. state.linkText = true;
  571. return getType(state);
  572. }
  573. return switchInline(stream, state, inlineNormal);
  574. }
  575. function footnoteLinkInside(stream, state) {
  576. if (stream.match(/^\]:/, true)) {
  577. state.f = state.inline = footnoteUrl;
  578. if (modeCfg.highlightFormatting) state.formatting = "link";
  579. var returnType = getType(state);
  580. state.linkText = false;
  581. return returnType;
  582. }
  583. stream.match(/^([^\]\\]|\\.)+/, true);
  584. return tokenTypes.linkText;
  585. }
  586. function footnoteUrl(stream, state) {
  587. // Check if space, and return NULL if so (to avoid marking the space)
  588. if(stream.eatSpace()){
  589. return null;
  590. }
  591. // Match URL
  592. stream.match(/^[^\s]+/, true);
  593. // Check for link title
  594. if (stream.peek() === undefined) { // End of line, set flag to check next line
  595. state.linkTitle = true;
  596. } else { // More content on line, check if link title
  597. stream.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true);
  598. }
  599. state.f = state.inline = inlineNormal;
  600. return tokenTypes.linkHref + " url";
  601. }
  602. var mode = {
  603. startState: function() {
  604. return {
  605. f: blockNormal,
  606. prevLine: null,
  607. thisLine: null,
  608. block: blockNormal,
  609. htmlState: null,
  610. indentation: 0,
  611. inline: inlineNormal,
  612. text: handleText,
  613. formatting: false,
  614. linkText: false,
  615. linkHref: false,
  616. linkTitle: false,
  617. code: 0,
  618. em: false,
  619. strong: false,
  620. header: 0,
  621. hr: false,
  622. taskList: false,
  623. list: false,
  624. listStack: [],
  625. quote: 0,
  626. trailingSpace: 0,
  627. trailingSpaceNewLine: false,
  628. strikethrough: false,
  629. fencedChars: null
  630. };
  631. },
  632. copyState: function(s) {
  633. return {
  634. f: s.f,
  635. prevLine: s.prevLine,
  636. thisLine: s.thisLine,
  637. block: s.block,
  638. htmlState: s.htmlState && CodeMirror.copyState(htmlMode, s.htmlState),
  639. indentation: s.indentation,
  640. localMode: s.localMode,
  641. localState: s.localMode ? CodeMirror.copyState(s.localMode, s.localState) : null,
  642. inline: s.inline,
  643. text: s.text,
  644. formatting: false,
  645. linkTitle: s.linkTitle,
  646. code: s.code,
  647. em: s.em,
  648. strong: s.strong,
  649. strikethrough: s.strikethrough,
  650. header: s.header,
  651. hr: s.hr,
  652. taskList: s.taskList,
  653. list: s.list,
  654. listStack: s.listStack.slice(0),
  655. quote: s.quote,
  656. indentedCode: s.indentedCode,
  657. trailingSpace: s.trailingSpace,
  658. trailingSpaceNewLine: s.trailingSpaceNewLine,
  659. md_inside: s.md_inside,
  660. fencedChars: s.fencedChars
  661. };
  662. },
  663. token: function(stream, state) {
  664. // Reset state.formatting
  665. state.formatting = false;
  666. if (stream != state.thisLine) {
  667. var forceBlankLine = state.header || state.hr;
  668. // Reset state.header and state.hr
  669. state.header = 0;
  670. state.hr = false;
  671. if (stream.match(/^\s*$/, true) || forceBlankLine) {
  672. blankLine(state);
  673. if (!forceBlankLine) return null
  674. state.prevLine = null
  675. }
  676. state.prevLine = state.thisLine
  677. state.thisLine = stream
  678. // Reset state.taskList
  679. state.taskList = false;
  680. // Reset state.trailingSpace
  681. state.trailingSpace = 0;
  682. state.trailingSpaceNewLine = false;
  683. state.f = state.block;
  684. var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, ' ').length;
  685. state.indentationDiff = Math.min(indentation - state.indentation, 4);
  686. state.indentation = state.indentation + state.indentationDiff;
  687. if (indentation > 0) return null;
  688. }
  689. return state.f(stream, state);
  690. },
  691. innerMode: function(state) {
  692. if (state.block == htmlBlock) return {state: state.htmlState, mode: htmlMode};
  693. if (state.localState) return {state: state.localState, mode: state.localMode};
  694. return {state: state, mode: mode};
  695. },
  696. blankLine: blankLine,
  697. getType: getType,
  698. closeBrackets: "()[]{}''\"\"``",
  699. fold: "markdown"
  700. };
  701. return mode;
  702. }, "xml");
  703. CodeMirror.defineMIME("text/x-markdown", "markdown");
  704. });