File modules/editor/codemirror/mode/markdown/markdown.js

Last commit: Sun Dec 17 01:14:09 2017 +0100	Jan Dankert	Integration eines weiteren Code-Editors: Codemirror. Demnächst müssen wir hier mal aufräumen und andere Editoren rauswerfen.
1 // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 // Distributed under an MIT license: http://codemirror.net/LICENSE 3 4 (function(mod) { 5 if (typeof exports == "object" && typeof module == "object") // CommonJS 6 mod(require("../../lib/codemirror"), require("../xml/xml"), require("../meta")); 7 else if (typeof define == "function" && define.amd) // AMD 8 define(["../../lib/codemirror", "../xml/xml", "../meta"], mod); 9 else // Plain browser env 10 mod(CodeMirror); 11 })(function(CodeMirror) { 12 "use strict"; 13 14 CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { 15 16 var htmlMode = CodeMirror.getMode(cmCfg, "text/html"); 17 var htmlModeMissing = htmlMode.name == "null" 18 19 function getMode(name) { 20 if (CodeMirror.findModeByName) { 21 var found = CodeMirror.findModeByName(name); 22 if (found) name = found.mime || found.mimes[0]; 23 } 24 var mode = CodeMirror.getMode(cmCfg, name); 25 return mode.name == "null" ? null : mode; 26 } 27 28 // Should characters that affect highlighting be highlighted separate? 29 // Does not include characters that will be output (such as `1.` and `-` for lists) 30 if (modeCfg.highlightFormatting === undefined) 31 modeCfg.highlightFormatting = false; 32 33 // Maximum number of nested blockquotes. Set to 0 for infinite nesting. 34 // Excess `>` will emit `error` token. 35 if (modeCfg.maxBlockquoteDepth === undefined) 36 modeCfg.maxBlockquoteDepth = 0; 37 38 // Turn on task lists? ("- [ ] " and "- [x] ") 39 if (modeCfg.taskLists === undefined) modeCfg.taskLists = false; 40 41 // Turn on strikethrough syntax 42 if (modeCfg.strikethrough === undefined) 43 modeCfg.strikethrough = false; 44 45 if (modeCfg.emoji === undefined) 46 modeCfg.emoji = false; 47 48 if (modeCfg.fencedCodeBlockHighlighting === undefined) 49 modeCfg.fencedCodeBlockHighlighting = true; 50 51 if (modeCfg.xml === undefined) 52 modeCfg.xml = true; 53 54 // Allow token types to be overridden by user-provided token types. 55 if (modeCfg.tokenTypeOverrides === undefined) 56 modeCfg.tokenTypeOverrides = {}; 57 58 var tokenTypes = { 59 header: "header", 60 code: "comment", 61 quote: "quote", 62 list1: "variable-2", 63 list2: "variable-3", 64 list3: "keyword", 65 hr: "hr", 66 image: "image", 67 imageAltText: "image-alt-text", 68 imageMarker: "image-marker", 69 formatting: "formatting", 70 linkInline: "link", 71 linkEmail: "link", 72 linkText: "link", 73 linkHref: "string", 74 em: "em", 75 strong: "strong", 76 strikethrough: "strikethrough", 77 emoji: "builtin" 78 }; 79 80 for (var tokenType in tokenTypes) { 81 if (tokenTypes.hasOwnProperty(tokenType) && modeCfg.tokenTypeOverrides[tokenType]) { 82 tokenTypes[tokenType] = modeCfg.tokenTypeOverrides[tokenType]; 83 } 84 } 85 86 var hrRE = /^([*\-_])(?:\s*\1){2,}\s*$/ 87 , listRE = /^(?:[*\-+]|^[0-9]+([.)]))\s+/ 88 , taskListRE = /^\[(x| )\](?=\s)/i // Must follow listRE 89 , atxHeaderRE = modeCfg.allowAtxHeaderWithoutSpace ? /^(#+)/ : /^(#+)(?: |$)/ 90 , setextHeaderRE = /^ *(?:\={1,}|-{1,})\s*$/ 91 , textRE = /^[^#!\[\]*_\\<>` "'(~:]+/ 92 , fencedCodeRE = /^(~~~+|```+)[ \t]*([\w+#-]*)[^\n`]*$/ 93 , linkDefRE = /^\s*\[[^\]]+?\]:\s*\S+(\s*\S*\s*)?$/ // naive link-definition 94 , punctuation = /[!\"#$%&\'()*+,\-\.\/:;<=>?@\[\\\]^_`{|}~—]/ 95 , expandedTab = " " // CommonMark specifies tab as 4 spaces 96 97 function switchInline(stream, state, f) { 98 state.f = state.inline = f; 99 return f(stream, state); 100 } 101 102 function switchBlock(stream, state, f) { 103 state.f = state.block = f; 104 return f(stream, state); 105 } 106 107 function lineIsEmpty(line) { 108 return !line || !/\S/.test(line.string) 109 } 110 111 // Blocks 112 113 function blankLine(state) { 114 // Reset linkTitle state 115 state.linkTitle = false; 116 // Reset EM state 117 state.em = false; 118 // Reset STRONG state 119 state.strong = false; 120 // Reset strikethrough state 121 state.strikethrough = false; 122 // Reset state.quote 123 state.quote = 0; 124 // Reset state.indentedCode 125 state.indentedCode = false; 126 if (state.f == htmlBlock) { 127 state.f = inlineNormal; 128 state.block = blockNormal; 129 } 130 // Reset state.trailingSpace 131 state.trailingSpace = 0; 132 state.trailingSpaceNewLine = false; 133 // Mark this line as blank 134 state.prevLine = state.thisLine 135 state.thisLine = {stream: null} 136 return null; 137 } 138 139 function blockNormal(stream, state) { 140 var firstTokenOnLine = stream.column() === state.indentation; 141 var prevLineLineIsEmpty = lineIsEmpty(state.prevLine.stream); 142 var prevLineIsIndentedCode = state.indentedCode; 143 var prevLineIsHr = state.prevLine.hr; 144 var prevLineIsList = state.list !== false; 145 var maxNonCodeIndentation = (state.listStack[state.listStack.length - 1] || 0) + 3; 146 147 state.indentedCode = false; 148 149 var lineIndentation = state.indentation; 150 // compute once per line (on first token) 151 if (state.indentationDiff === null) { 152 state.indentationDiff = state.indentation; 153 if (prevLineIsList) { 154 state.list = null; 155 // While this list item's marker's indentation is less than the deepest 156 // list item's content's indentation,pop the deepest list item 157 // indentation off the stack, and update block indentation state 158 while (lineIndentation < state.listStack[state.listStack.length - 1]) { 159 state.listStack.pop(); 160 if (state.listStack.length) { 161 state.indentation = state.listStack[state.listStack.length - 1]; 162 // less than the first list's indent -> the line is no longer a list 163 } else { 164 state.list = false; 165 } 166 } 167 if (state.list !== false) { 168 state.indentationDiff = lineIndentation - state.listStack[state.listStack.length - 1] 169 } 170 } 171 } 172 173 // not comprehensive (currently only for setext detection purposes) 174 var allowsInlineContinuation = ( 175 !prevLineLineIsEmpty && !prevLineIsHr && !state.prevLine.header && 176 (!prevLineIsList || !prevLineIsIndentedCode) && 177 !state.prevLine.fencedCodeEnd 178 ); 179 180 var isHr = (state.list === false || prevLineIsHr || prevLineLineIsEmpty) && 181 state.indentation <= maxNonCodeIndentation && stream.match(hrRE); 182 183 var match = null; 184 if (state.indentationDiff >= 4 && (prevLineIsIndentedCode || state.prevLine.fencedCodeEnd || 185 state.prevLine.header || prevLineLineIsEmpty)) { 186 stream.skipToEnd(); 187 state.indentedCode = true; 188 return tokenTypes.code; 189 } else if (stream.eatSpace()) { 190 return null; 191 } else if (firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(atxHeaderRE)) && match[1].length <= 6) { 192 state.quote = 0; 193 state.header = match[1].length; 194 state.thisLine.header = true; 195 if (modeCfg.highlightFormatting) state.formatting = "header"; 196 state.f = state.inline; 197 return getType(state); 198 } else if (state.indentation <= maxNonCodeIndentation && stream.eat('>')) { 199 state.quote = firstTokenOnLine ? 1 : state.quote + 1; 200 if (modeCfg.highlightFormatting) state.formatting = "quote"; 201 stream.eatSpace(); 202 return getType(state); 203 } else if (!isHr && !state.setext && firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(listRE))) { 204 var listType = match[1] ? "ol" : "ul"; 205 206 state.indentation = lineIndentation + stream.current().length; 207 state.list = true; 208 state.quote = 0; 209 210 // Add this list item's content's indentation to the stack 211 state.listStack.push(state.indentation); 212 213 if (modeCfg.taskLists && stream.match(taskListRE, false)) { 214 state.taskList = true; 215 } 216 state.f = state.inline; 217 if (modeCfg.highlightFormatting) state.formatting = ["list", "list-" + listType]; 218 return getType(state); 219 } else if (firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(fencedCodeRE, true))) { 220 state.quote = 0; 221 state.fencedEndRE = new RegExp(match[1] + "+ *$"); 222 // try switching mode 223 state.localMode = modeCfg.fencedCodeBlockHighlighting && getMode(match[2]); 224 if (state.localMode) state.localState = CodeMirror.startState(state.localMode); 225 state.f = state.block = local; 226 if (modeCfg.highlightFormatting) state.formatting = "code-block"; 227 state.code = -1 228 return getType(state); 229 // SETEXT has lowest block-scope precedence after HR, so check it after 230 // the others (code, blockquote, list...) 231 } else if ( 232 // if setext set, indicates line after ---/=== 233 state.setext || ( 234 // line before ---/=== 235 (!allowsInlineContinuation || !prevLineIsList) && !state.quote && state.list === false && 236 !state.code && !isHr && !linkDefRE.test(stream.string) && 237 (match = stream.lookAhead(1)) && (match = match.match(setextHeaderRE)) 238 ) 239 ) { 240 if ( !state.setext ) { 241 state.header = match[0].charAt(0) == '=' ? 1 : 2; 242 state.setext = state.header; 243 } else { 244 state.header = state.setext; 245 // has no effect on type so we can reset it now 246 state.setext = 0; 247 stream.skipToEnd(); 248 if (modeCfg.highlightFormatting) state.formatting = "header"; 249 } 250 state.thisLine.header = true; 251 state.f = state.inline; 252 return getType(state); 253 } else if (isHr) { 254 stream.skipToEnd(); 255 state.hr = true; 256 state.thisLine.hr = true; 257 return tokenTypes.hr; 258 } else if (stream.peek() === '[') { 259 return switchInline(stream, state, footnoteLink); 260 } 261 262 return switchInline(stream, state, state.inline); 263 } 264 265 function htmlBlock(stream, state) { 266 var style = htmlMode.token(stream, state.htmlState); 267 if (!htmlModeMissing) { 268 var inner = CodeMirror.innerMode(htmlMode, state.htmlState) 269 if ((inner.mode.name == "xml" && inner.state.tagStart === null && 270 (!inner.state.context && inner.state.tokenize.isInText)) || 271 (state.md_inside && stream.current().indexOf(">") > -1)) { 272 state.f = inlineNormal; 273 state.block = blockNormal; 274 state.htmlState = null; 275 } 276 } 277 return style; 278 } 279 280 function local(stream, state) { 281 var currListInd = state.listStack[state.listStack.length - 1] || 0; 282 var hasExitedList = state.indentation < currListInd; 283 var maxFencedEndInd = currListInd + 3; 284 if (state.fencedEndRE && state.indentation <= maxFencedEndInd && (hasExitedList || stream.match(state.fencedEndRE))) { 285 if (modeCfg.highlightFormatting) state.formatting = "code-block"; 286 var returnType; 287 if (!hasExitedList) returnType = getType(state) 288 state.localMode = state.localState = null; 289 state.block = blockNormal; 290 state.f = inlineNormal; 291 state.fencedEndRE = null; 292 state.code = 0 293 state.thisLine.fencedCodeEnd = true; 294 if (hasExitedList) return switchBlock(stream, state, state.block); 295 return returnType; 296 } else if (state.localMode) { 297 return state.localMode.token(stream, state.localState); 298 } else { 299 stream.skipToEnd(); 300 return tokenTypes.code; 301 } 302 } 303 304 // Inline 305 function getType(state) { 306 var styles = []; 307 308 if (state.formatting) { 309 styles.push(tokenTypes.formatting); 310 311 if (typeof state.formatting === "string") state.formatting = [state.formatting]; 312 313 for (var i = 0; i < state.formatting.length; i++) { 314 styles.push(tokenTypes.formatting + "-" + state.formatting[i]); 315 316 if (state.formatting[i] === "header") { 317 styles.push(tokenTypes.formatting + "-" + state.formatting[i] + "-" + state.header); 318 } 319 320 // Add `formatting-quote` and `formatting-quote-#` for blockquotes 321 // Add `error` instead if the maximum blockquote nesting depth is passed 322 if (state.formatting[i] === "quote") { 323 if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) { 324 styles.push(tokenTypes.formatting + "-" + state.formatting[i] + "-" + state.quote); 325 } else { 326 styles.push("error"); 327 } 328 } 329 } 330 } 331 332 if (state.taskOpen) { 333 styles.push("meta"); 334 return styles.length ? styles.join(' ') : null; 335 } 336 if (state.taskClosed) { 337 styles.push("property"); 338 return styles.length ? styles.join(' ') : null; 339 } 340 341 if (state.linkHref) { 342 styles.push(tokenTypes.linkHref, "url"); 343 } else { // Only apply inline styles to non-url text 344 if (state.strong) { styles.push(tokenTypes.strong); } 345 if (state.em) { styles.push(tokenTypes.em); } 346 if (state.strikethrough) { styles.push(tokenTypes.strikethrough); } 347 if (state.emoji) { styles.push(tokenTypes.emoji); } 348 if (state.linkText) { styles.push(tokenTypes.linkText); } 349 if (state.code) { styles.push(tokenTypes.code); } 350 if (state.image) { styles.push(tokenTypes.image); } 351 if (state.imageAltText) { styles.push(tokenTypes.imageAltText, "link"); } 352 if (state.imageMarker) { styles.push(tokenTypes.imageMarker); } 353 } 354 355 if (state.header) { styles.push(tokenTypes.header, tokenTypes.header + "-" + state.header); } 356 357 if (state.quote) { 358 styles.push(tokenTypes.quote); 359 360 // Add `quote-#` where the maximum for `#` is modeCfg.maxBlockquoteDepth 361 if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) { 362 styles.push(tokenTypes.quote + "-" + state.quote); 363 } else { 364 styles.push(tokenTypes.quote + "-" + modeCfg.maxBlockquoteDepth); 365 } 366 } 367 368 if (state.list !== false) { 369 var listMod = (state.listStack.length - 1) % 3; 370 if (!listMod) { 371 styles.push(tokenTypes.list1); 372 } else if (listMod === 1) { 373 styles.push(tokenTypes.list2); 374 } else { 375 styles.push(tokenTypes.list3); 376 } 377 } 378 379 if (state.trailingSpaceNewLine) { 380 styles.push("trailing-space-new-line"); 381 } else if (state.trailingSpace) { 382 styles.push("trailing-space-" + (state.trailingSpace % 2 ? "a" : "b")); 383 } 384 385 return styles.length ? styles.join(' ') : null; 386 } 387 388 function handleText(stream, state) { 389 if (stream.match(textRE, true)) { 390 return getType(state); 391 } 392 return undefined; 393 } 394 395 function inlineNormal(stream, state) { 396 var style = state.text(stream, state); 397 if (typeof style !== 'undefined') 398 return style; 399 400 if (state.list) { // List marker (*, +, -, 1., etc) 401 state.list = null; 402 return getType(state); 403 } 404 405 if (state.taskList) { 406 var taskOpen = stream.match(taskListRE, true)[1] === " "; 407 if (taskOpen) state.taskOpen = true; 408 else state.taskClosed = true; 409 if (modeCfg.highlightFormatting) state.formatting = "task"; 410 state.taskList = false; 411 return getType(state); 412 } 413 414 state.taskOpen = false; 415 state.taskClosed = false; 416 417 if (state.header && stream.match(/^#+$/, true)) { 418 if (modeCfg.highlightFormatting) state.formatting = "header"; 419 return getType(state); 420 } 421 422 var ch = stream.next(); 423 424 // Matches link titles present on next line 425 if (state.linkTitle) { 426 state.linkTitle = false; 427 var matchCh = ch; 428 if (ch === '(') { 429 matchCh = ')'; 430 } 431 matchCh = (matchCh+'').replace(/([.?*+^\[\]\\(){}|-])/g, "\\$1"); 432 var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh; 433 if (stream.match(new RegExp(regex), true)) { 434 return tokenTypes.linkHref; 435 } 436 } 437 438 // If this block is changed, it may need to be updated in GFM mode 439 if (ch === '`') { 440 var previousFormatting = state.formatting; 441 if (modeCfg.highlightFormatting) state.formatting = "code"; 442 stream.eatWhile('`'); 443 var count = stream.current().length 444 if (state.code == 0 && (!state.quote || count == 1)) { 445 state.code = count 446 return getType(state) 447 } else if (count == state.code) { // Must be exact 448 var t = getType(state) 449 state.code = 0 450 return t 451 } else { 452 state.formatting = previousFormatting 453 return getType(state) 454 } 455 } else if (state.code) { 456 return getType(state); 457 } 458 459 if (ch === '\\') { 460 stream.next(); 461 if (modeCfg.highlightFormatting) { 462 var type = getType(state); 463 var formattingEscape = tokenTypes.formatting + "-escape"; 464 return type ? type + " " + formattingEscape : formattingEscape; 465 } 466 } 467 468 if (ch === '!' && stream.match(/\[[^\]]*\] ?(?:\(|\[)/, false)) { 469 state.imageMarker = true; 470 state.image = true; 471 if (modeCfg.highlightFormatting) state.formatting = "image"; 472 return getType(state); 473 } 474 475 if (ch === '[' && state.imageMarker && stream.match(/[^\]]*\](\(.*?\)| ?\[.*?\])/, false)) { 476 state.imageMarker = false; 477 state.imageAltText = true 478 if (modeCfg.highlightFormatting) state.formatting = "image"; 479 return getType(state); 480 } 481 482 if (ch === ']' && state.imageAltText) { 483 if (modeCfg.highlightFormatting) state.formatting = "image"; 484 var type = getType(state); 485 state.imageAltText = false; 486 state.image = false; 487 state.inline = state.f = linkHref; 488 return type; 489 } 490 491 if (ch === '[' && !state.image) { 492 state.linkText = true; 493 if (modeCfg.highlightFormatting) state.formatting = "link"; 494 return getType(state); 495 } 496 497 if (ch === ']' && state.linkText) { 498 if (modeCfg.highlightFormatting) state.formatting = "link"; 499 var type = getType(state); 500 state.linkText = false; 501 state.inline = state.f = stream.match(/\(.*?\)| ?\[.*?\]/, false) ? linkHref : inlineNormal 502 return type; 503 } 504 505 if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, false)) { 506 state.f = state.inline = linkInline; 507 if (modeCfg.highlightFormatting) state.formatting = "link"; 508 var type = getType(state); 509 if (type){ 510 type += " "; 511 } else { 512 type = ""; 513 } 514 return type + tokenTypes.linkInline; 515 } 516 517 if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, false)) { 518 state.f = state.inline = linkInline; 519 if (modeCfg.highlightFormatting) state.formatting = "link"; 520 var type = getType(state); 521 if (type){ 522 type += " "; 523 } else { 524 type = ""; 525 } 526 return type + tokenTypes.linkEmail; 527 } 528 529 if (modeCfg.xml && ch === '<' && stream.match(/^(!--|[a-z]+(?:\s+[a-z_:.\-]+(?:\s*=\s*[^ >]+)?)*\s*>)/i, false)) { 530 var end = stream.string.indexOf(">", stream.pos); 531 if (end != -1) { 532 var atts = stream.string.substring(stream.start, end); 533 if (/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(atts)) state.md_inside = true; 534 } 535 stream.backUp(1); 536 state.htmlState = CodeMirror.startState(htmlMode); 537 return switchBlock(stream, state, htmlBlock); 538 } 539 540 if (modeCfg.xml && ch === '<' && stream.match(/^\/\w*?>/)) { 541 state.md_inside = false; 542 return "tag"; 543 } else if (ch === "*" || ch === "_") { 544 var len = 1, before = stream.pos == 1 ? " " : stream.string.charAt(stream.pos - 2) 545 while (len < 3 && stream.eat(ch)) len++ 546 var after = stream.peek() || " " 547 // See http://spec.commonmark.org/0.27/#emphasis-and-strong-emphasis 548 var leftFlanking = !/\s/.test(after) && (!punctuation.test(after) || /\s/.test(before) || punctuation.test(before)) 549 var rightFlanking = !/\s/.test(before) && (!punctuation.test(before) || /\s/.test(after) || punctuation.test(after)) 550 var setEm = null, setStrong = null 551 if (len % 2) { // Em 552 if (!state.em && leftFlanking && (ch === "*" || !rightFlanking || punctuation.test(before))) 553 setEm = true 554 else if (state.em == ch && rightFlanking && (ch === "*" || !leftFlanking || punctuation.test(after))) 555 setEm = false 556 } 557 if (len > 1) { // Strong 558 if (!state.strong && leftFlanking && (ch === "*" || !rightFlanking || punctuation.test(before))) 559 setStrong = true 560 else if (state.strong == ch && rightFlanking && (ch === "*" || !leftFlanking || punctuation.test(after))) 561 setStrong = false 562 } 563 if (setStrong != null || setEm != null) { 564 if (modeCfg.highlightFormatting) state.formatting = setEm == null ? "strong" : setStrong == null ? "em" : "strong em" 565 if (setEm === true) state.em = ch 566 if (setStrong === true) state.strong = ch 567 var t = getType(state) 568 if (setEm === false) state.em = false 569 if (setStrong === false) state.strong = false 570 return t 571 } 572 } else if (ch === ' ') { 573 if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces 574 if (stream.peek() === ' ') { // Surrounded by spaces, ignore 575 return getType(state); 576 } else { // Not surrounded by spaces, back up pointer 577 stream.backUp(1); 578 } 579 } 580 } 581 582 if (modeCfg.strikethrough) { 583 if (ch === '~' && stream.eatWhile(ch)) { 584 if (state.strikethrough) {// Remove strikethrough 585 if (modeCfg.highlightFormatting) state.formatting = "strikethrough"; 586 var t = getType(state); 587 state.strikethrough = false; 588 return t; 589 } else if (stream.match(/^[^\s]/, false)) {// Add strikethrough 590 state.strikethrough = true; 591 if (modeCfg.highlightFormatting) state.formatting = "strikethrough"; 592 return getType(state); 593 } 594 } else if (ch === ' ') { 595 if (stream.match(/^~~/, true)) { // Probably surrounded by space 596 if (stream.peek() === ' ') { // Surrounded by spaces, ignore 597 return getType(state); 598 } else { // Not surrounded by spaces, back up pointer 599 stream.backUp(2); 600 } 601 } 602 } 603 } 604 605 if (modeCfg.emoji && ch === ":" && stream.match(/^[a-z_\d+-]+:/)) { 606 state.emoji = true; 607 if (modeCfg.highlightFormatting) state.formatting = "emoji"; 608 var retType = getType(state); 609 state.emoji = false; 610 return retType; 611 } 612 613 if (ch === ' ') { 614 if (stream.match(/ +$/, false)) { 615 state.trailingSpace++; 616 } else if (state.trailingSpace) { 617 state.trailingSpaceNewLine = true; 618 } 619 } 620 621 return getType(state); 622 } 623 624 function linkInline(stream, state) { 625 var ch = stream.next(); 626 627 if (ch === ">") { 628 state.f = state.inline = inlineNormal; 629 if (modeCfg.highlightFormatting) state.formatting = "link"; 630 var type = getType(state); 631 if (type){ 632 type += " "; 633 } else { 634 type = ""; 635 } 636 return type + tokenTypes.linkInline; 637 } 638 639 stream.match(/^[^>]+/, true); 640 641 return tokenTypes.linkInline; 642 } 643 644 function linkHref(stream, state) { 645 // Check if space, and return NULL if so (to avoid marking the space) 646 if(stream.eatSpace()){ 647 return null; 648 } 649 var ch = stream.next(); 650 if (ch === '(' || ch === '[') { 651 state.f = state.inline = getLinkHrefInside(ch === "(" ? ")" : "]"); 652 if (modeCfg.highlightFormatting) state.formatting = "link-string"; 653 state.linkHref = true; 654 return getType(state); 655 } 656 return 'error'; 657 } 658 659 var linkRE = { 660 ")": /^(?:[^\\\(\)]|\\.|\((?:[^\\\(\)]|\\.)*\))*?(?=\))/, 661 "]": /^(?:[^\\\[\]]|\\.|\[(?:[^\\\[\]]|\\.)*\])*?(?=\])/ 662 } 663 664 function getLinkHrefInside(endChar) { 665 return function(stream, state) { 666 var ch = stream.next(); 667 668 if (ch === endChar) { 669 state.f = state.inline = inlineNormal; 670 if (modeCfg.highlightFormatting) state.formatting = "link-string"; 671 var returnState = getType(state); 672 state.linkHref = false; 673 return returnState; 674 } 675 676 stream.match(linkRE[endChar]) 677 state.linkHref = true; 678 return getType(state); 679 }; 680 } 681 682 function footnoteLink(stream, state) { 683 if (stream.match(/^([^\]\\]|\\.)*\]:/, false)) { 684 state.f = footnoteLinkInside; 685 stream.next(); // Consume [ 686 if (modeCfg.highlightFormatting) state.formatting = "link"; 687 state.linkText = true; 688 return getType(state); 689 } 690 return switchInline(stream, state, inlineNormal); 691 } 692 693 function footnoteLinkInside(stream, state) { 694 if (stream.match(/^\]:/, true)) { 695 state.f = state.inline = footnoteUrl; 696 if (modeCfg.highlightFormatting) state.formatting = "link"; 697 var returnType = getType(state); 698 state.linkText = false; 699 return returnType; 700 } 701 702 stream.match(/^([^\]\\]|\\.)+/, true); 703 704 return tokenTypes.linkText; 705 } 706 707 function footnoteUrl(stream, state) { 708 // Check if space, and return NULL if so (to avoid marking the space) 709 if(stream.eatSpace()){ 710 return null; 711 } 712 // Match URL 713 stream.match(/^[^\s]+/, true); 714 // Check for link title 715 if (stream.peek() === undefined) { // End of line, set flag to check next line 716 state.linkTitle = true; 717 } else { // More content on line, check if link title 718 stream.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true); 719 } 720 state.f = state.inline = inlineNormal; 721 return tokenTypes.linkHref + " url"; 722 } 723 724 var mode = { 725 startState: function() { 726 return { 727 f: blockNormal, 728 729 prevLine: {stream: null}, 730 thisLine: {stream: null}, 731 732 block: blockNormal, 733 htmlState: null, 734 indentation: 0, 735 736 inline: inlineNormal, 737 text: handleText, 738 739 formatting: false, 740 linkText: false, 741 linkHref: false, 742 linkTitle: false, 743 code: 0, 744 em: false, 745 strong: false, 746 header: 0, 747 setext: 0, 748 hr: false, 749 taskList: false, 750 list: false, 751 listStack: [], 752 quote: 0, 753 trailingSpace: 0, 754 trailingSpaceNewLine: false, 755 strikethrough: false, 756 emoji: false, 757 fencedEndRE: null 758 }; 759 }, 760 761 copyState: function(s) { 762 return { 763 f: s.f, 764 765 prevLine: s.prevLine, 766 thisLine: s.thisLine, 767 768 block: s.block, 769 htmlState: s.htmlState && CodeMirror.copyState(htmlMode, s.htmlState), 770 indentation: s.indentation, 771 772 localMode: s.localMode, 773 localState: s.localMode ? CodeMirror.copyState(s.localMode, s.localState) : null, 774 775 inline: s.inline, 776 text: s.text, 777 formatting: false, 778 linkText: s.linkText, 779 linkTitle: s.linkTitle, 780 code: s.code, 781 em: s.em, 782 strong: s.strong, 783 strikethrough: s.strikethrough, 784 emoji: s.emoji, 785 header: s.header, 786 setext: s.setext, 787 hr: s.hr, 788 taskList: s.taskList, 789 list: s.list, 790 listStack: s.listStack.slice(0), 791 quote: s.quote, 792 indentedCode: s.indentedCode, 793 trailingSpace: s.trailingSpace, 794 trailingSpaceNewLine: s.trailingSpaceNewLine, 795 md_inside: s.md_inside, 796 fencedEndRE: s.fencedEndRE 797 }; 798 }, 799 800 token: function(stream, state) { 801 802 // Reset state.formatting 803 state.formatting = false; 804 805 if (stream != state.thisLine.stream) { 806 state.header = 0; 807 state.hr = false; 808 809 if (stream.match(/^\s*$/, true)) { 810 blankLine(state); 811 return null; 812 } 813 814 state.prevLine = state.thisLine 815 state.thisLine = {stream: stream} 816 817 // Reset state.taskList 818 state.taskList = false; 819 820 // Reset state.trailingSpace 821 state.trailingSpace = 0; 822 state.trailingSpaceNewLine = false; 823 824 if (!state.localState) { 825 state.f = state.block; 826 if (state.f != htmlBlock) { 827 var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, expandedTab).length; 828 state.indentation = indentation; 829 state.indentationDiff = null; 830 if (indentation > 0) return null; 831 } 832 } 833 } 834 return state.f(stream, state); 835 }, 836 837 innerMode: function(state) { 838 if (state.block == htmlBlock) return {state: state.htmlState, mode: htmlMode}; 839 if (state.localState) return {state: state.localState, mode: state.localMode}; 840 return {state: state, mode: mode}; 841 }, 842 843 indent: function(state, textAfter, line) { 844 if (state.block == htmlBlock && htmlMode.indent) return htmlMode.indent(state.htmlState, textAfter, line) 845 if (state.localState && state.localMode.indent) return state.localMode.indent(state.localState, textAfter, line) 846 return CodeMirror.Pass 847 }, 848 849 blankLine: blankLine, 850 851 getType: getType, 852 853 closeBrackets: "()[]{}''\"\"``", 854 fold: "markdown" 855 }; 856 return mode; 857 }, "xml"); 858 859 CodeMirror.defineMIME("text/x-markdown", "markdown"); 860 861 });
Download modules/editor/codemirror/mode/markdown/markdown.js
History Sun, 17 Dec 2017 01:14:09 +0100 Jan Dankert Integration eines weiteren Code-Editors: Codemirror. Demnächst müssen wir hier mal aufräumen und andere Editoren rauswerfen.