Created
November 5, 2013 11:00
-
-
Save royletron/7317367 to your computer and use it in GitHub Desktop.
A CodeMirror markdown type that will allow for different header types as follows: # Header 1 = .cm-header1
## Header 2 = .cm-header2
### Header 3 = .cm-header3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { | |
var htmlFound = CodeMirror.modes.hasOwnProperty("xml"); | |
var htmlMode = CodeMirror.getMode(cmCfg, htmlFound ? {name: "xml", htmlMode: true} : "text/plain"); | |
var aliases = { | |
html: "htmlmixed", | |
js: "javascript", | |
json: "application/json", | |
c: "text/x-csrc", | |
"c++": "text/x-c++src", | |
java: "text/x-java", | |
csharp: "text/x-csharp", | |
"c#": "text/x-csharp", | |
scala: "text/x-scala" | |
}; | |
var getMode = (function () { | |
var i, modes = {}, mimes = {}, mime; | |
var list = []; | |
for (var m in CodeMirror.modes) | |
if (CodeMirror.modes.propertyIsEnumerable(m)) list.push(m); | |
for (i = 0; i < list.length; i++) { | |
modes[list[i]] = list[i]; | |
} | |
var mimesList = []; | |
for (var m in CodeMirror.mimeModes) | |
if (CodeMirror.mimeModes.propertyIsEnumerable(m)) | |
mimesList.push({mime: m, mode: CodeMirror.mimeModes[m]}); | |
for (i = 0; i < mimesList.length; i++) { | |
mime = mimesList[i].mime; | |
mimes[mime] = mimesList[i].mime; | |
} | |
for (var a in aliases) { | |
if (aliases[a] in modes || aliases[a] in mimes) | |
modes[a] = aliases[a]; | |
} | |
return function (lang) { | |
return modes[lang] ? CodeMirror.getMode(cmCfg, modes[lang]) : null; | |
}; | |
}()); | |
// Should underscores in words open/close em/strong? | |
if (modeCfg.underscoresBreakWords === undefined) | |
modeCfg.underscoresBreakWords = true; | |
// Turn on fenced code blocks? ("```" to start/end) | |
if (modeCfg.fencedCodeBlocks === undefined) modeCfg.fencedCodeBlocks = false; | |
// Turn on task lists? ("- [ ] " and "- [x] ") | |
if (modeCfg.taskLists === undefined) modeCfg.taskLists = false; | |
var codeDepth = 0; | |
var header1 = 'header1' | |
, header2 = 'header2' | |
, header3 = 'header3' | |
, code = 'comment' | |
, quote1 = 'atom' | |
, quote2 = 'number' | |
, list1 = 'variable-2' | |
, list2 = 'variable-3' | |
, list3 = 'keyword' | |
, hr = 'hr' | |
, image = 'tag' | |
, linkinline = 'link' | |
, linkemail = 'link' | |
, linktext = 'link' | |
, linkhref = 'string' | |
, em = 'em' | |
, strong = 'strong'; | |
var hrRE = /^([*\-=_])(?:\s*\1){2,}\s*$/ | |
, ulRE = /^[*\-+]\s+/ | |
, olRE = /^[0-9]+\.\s+/ | |
, taskListRE = /^\[(x| )\](?=\s)/ // Must follow ulRE or olRE | |
, headerRE = /^(?:\={1,}|-{1,})$/ | |
, textRE = /^[^!\[\]*_\\<>` "'(]+/; | |
function switchInline(stream, state, f) { | |
state.f = state.inline = f; | |
return f(stream, state); | |
} | |
function switchBlock(stream, state, f) { | |
state.f = state.block = f; | |
return f(stream, state); | |
} | |
// Blocks | |
function blankLine(state) { | |
// Reset linkTitle state | |
state.linkTitle = false; | |
// Reset EM state | |
state.em = false; | |
// Reset STRONG state | |
state.strong = false; | |
// Reset state.quote | |
state.quote = 0; | |
if (!htmlFound && state.f == htmlBlock) { | |
state.f = inlineNormal; | |
state.block = blockNormal; | |
} | |
// Reset state.trailingSpace | |
state.trailingSpace = 0; | |
state.trailingSpaceNewLine = false; | |
// Mark this line as blank | |
state.thisLineHasContent = false; | |
return null; | |
} | |
function blockNormal(stream, state) { | |
var prevLineIsList = (state.list !== false); | |
if (state.list !== false && state.indentationDiff >= 0) { // Continued list | |
if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block | |
state.indentation -= state.indentationDiff; | |
} | |
state.list = null; | |
} else if (state.list !== false && state.indentation > 0) { | |
state.list = null; | |
state.listDepth = Math.floor(state.indentation / 4); | |
} else if (state.list !== false) { // No longer a list | |
state.list = false; | |
state.listDepth = 0; | |
} | |
if (state.indentationDiff >= 4) { | |
state.indentation -= 4; | |
stream.skipToEnd(); | |
return code; | |
} else if (stream.eatSpace()) { | |
return null; | |
} else if (stream.peek() === '#' || (state.prevLineHasContent && stream.match(headerRE)) ) { | |
if(stream.string.trim().substr(0, 3) === '###') | |
{ | |
state.header3 = true; | |
} | |
else if(stream.string.trim().substr(0, 2) === '##'){ | |
state.header2 = true; | |
} | |
else{ | |
state.header1 = true; | |
} | |
} else if (stream.eat('>')) { | |
state.indentation++; | |
state.quote = 1; | |
stream.eatSpace(); | |
while (stream.eat('>')) { | |
stream.eatSpace(); | |
state.quote++; | |
} | |
} else if (stream.peek() === '[') { | |
return switchInline(stream, state, footnoteLink); | |
} else if (stream.match(hrRE, true)) { | |
return hr; | |
} else if ((!state.prevLineHasContent || prevLineIsList) && (stream.match(ulRE, true) || stream.match(olRE, true))) { | |
state.indentation += 4; | |
state.list = true; | |
state.listDepth++; | |
if (modeCfg.taskLists && stream.match(taskListRE, false)) { | |
state.taskList = true; | |
} | |
} else if (modeCfg.fencedCodeBlocks && stream.match(/^```([\w+#]*)/, true)) { | |
// try switching mode | |
state.localMode = getMode(RegExp.$1); | |
if (state.localMode) state.localState = state.localMode.startState(); | |
switchBlock(stream, state, local); | |
return code; | |
} | |
return switchInline(stream, state, state.inline); | |
} | |
function htmlBlock(stream, state) { | |
var style = htmlMode.token(stream, state.htmlState); | |
if (htmlFound && style === 'tag' && state.htmlState.type !== 'openTag' && !state.htmlState.context) { | |
state.f = inlineNormal; | |
state.block = blockNormal; | |
} | |
if (state.md_inside && stream.current().indexOf(">")!=-1) { | |
state.f = inlineNormal; | |
state.block = blockNormal; | |
state.htmlState.context = undefined; | |
} | |
return style; | |
} | |
function local(stream, state) { | |
if (stream.sol() && stream.match(/^```/, true)) { | |
state.localMode = state.localState = null; | |
state.f = inlineNormal; | |
state.block = blockNormal; | |
return code; | |
} else if (state.localMode) { | |
return state.localMode.token(stream, state.localState); | |
} else { | |
stream.skipToEnd(); | |
return code; | |
} | |
} | |
// Inline | |
function getType(state) { | |
var styles = []; | |
if (state.taskOpen) { return "meta"; } | |
if (state.taskClosed) { return "property"; } | |
if (state.strong) { styles.push(strong); } | |
if (state.em) { styles.push(em); } | |
if (state.linkText) { styles.push(linktext); } | |
if (state.code) { styles.push(code); } | |
if (state.header1) { styles.push(header1); } | |
if (state.header2) { styles.push(header2); } | |
if (state.header3) { styles.push(header3); } | |
if (state.quote) { styles.push(state.quote % 2 ? quote1 : quote2); } | |
if (state.list !== false) { | |
var listMod = (state.listDepth - 1) % 3; | |
if (!listMod) { | |
styles.push(list1); | |
} else if (listMod === 1) { | |
styles.push(list2); | |
} else { | |
styles.push(list3); | |
} | |
} | |
if (state.trailingSpaceNewLine) { | |
styles.push("trailing-space-new-line"); | |
} else if (state.trailingSpace) { | |
styles.push("trailing-space-" + (state.trailingSpace % 2 ? "a" : "b")); | |
} | |
return styles.length ? styles.join(' ') : null; | |
} | |
function handleText(stream, state) { | |
if (stream.match(textRE, true)) { | |
return getType(state); | |
} | |
return undefined; | |
} | |
function inlineNormal(stream, state) { | |
var style = state.text(stream, state); | |
if (typeof style !== 'undefined') | |
return style; | |
if (state.list) { // List marker (*, +, -, 1., etc) | |
state.list = null; | |
return getType(state); | |
} | |
if (state.taskList) { | |
var taskOpen = stream.match(taskListRE, true)[1] !== "x"; | |
if (taskOpen) state.taskOpen = true; | |
else state.taskClosed = true; | |
state.taskList = false; | |
return getType(state); | |
} | |
state.taskOpen = false; | |
state.taskClosed = false; | |
var ch = stream.next(); | |
if (ch === '\\') { | |
stream.next(); | |
return getType(state); | |
} | |
// Matches link titles present on next line | |
if (state.linkTitle) { | |
state.linkTitle = false; | |
var matchCh = ch; | |
if (ch === '(') { | |
matchCh = ')'; | |
} | |
matchCh = (matchCh+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); | |
var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh; | |
if (stream.match(new RegExp(regex), true)) { | |
return linkhref; | |
} | |
} | |
// If this block is changed, it may need to be updated in GFM mode | |
if (ch === '`') { | |
var t = getType(state); | |
var before = stream.pos; | |
stream.eatWhile('`'); | |
var difference = 1 + stream.pos - before; | |
if (!state.code) { | |
codeDepth = difference; | |
state.code = true; | |
return getType(state); | |
} else { | |
if (difference === codeDepth) { // Must be exact | |
state.code = false; | |
return t; | |
} | |
return getType(state); | |
} | |
} else if (state.code) { | |
return getType(state); | |
} | |
if (ch === '!' && stream.match(/\[[^\]]*\] ?(?:\(|\[)/, false)) { | |
stream.match(/\[[^\]]*\]/); | |
state.inline = state.f = linkHref; | |
return image; | |
} | |
if (ch === '[' && stream.match(/.*\](\(| ?\[)/, false)) { | |
state.linkText = true; | |
return getType(state); | |
} | |
if (ch === ']' && state.linkText) { | |
var type = getType(state); | |
state.linkText = false; | |
state.inline = state.f = linkHref; | |
return type; | |
} | |
if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, false)) { | |
return switchInline(stream, state, inlineElement(linkinline, '>')); | |
} | |
if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, false)) { | |
return switchInline(stream, state, inlineElement(linkemail, '>')); | |
} | |
if (ch === '<' && stream.match(/^\w/, false)) { | |
if (stream.string.indexOf(">")!=-1) { | |
var atts = stream.string.substring(1,stream.string.indexOf(">")); | |
if (/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(atts)) { | |
state.md_inside = true; | |
} | |
} | |
stream.backUp(1); | |
return switchBlock(stream, state, htmlBlock); | |
} | |
if (ch === '<' && stream.match(/^\/\w*?>/)) { | |
state.md_inside = false; | |
return "tag"; | |
} | |
var ignoreUnderscore = false; | |
if (!modeCfg.underscoresBreakWords) { | |
if (ch === '_' && stream.peek() !== '_' && stream.match(/(\w)/, false)) { | |
var prevPos = stream.pos - 2; | |
if (prevPos >= 0) { | |
var prevCh = stream.string.charAt(prevPos); | |
if (prevCh !== '_' && prevCh.match(/(\w)/, false)) { | |
ignoreUnderscore = true; | |
} | |
} | |
} | |
} | |
var t = getType(state); | |
if (ch === '*' || (ch === '_' && !ignoreUnderscore)) { | |
if (state.strong === ch && stream.eat(ch)) { // Remove STRONG | |
state.strong = false; | |
return t; | |
} else if (!state.strong && stream.eat(ch)) { // Add STRONG | |
state.strong = ch; | |
return getType(state); | |
} else if (state.em === ch) { // Remove EM | |
state.em = false; | |
return t; | |
} else if (!state.em) { // Add EM | |
state.em = ch; | |
return getType(state); | |
} | |
} else if (ch === ' ') { | |
if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces | |
if (stream.peek() === ' ') { // Surrounded by spaces, ignore | |
return getType(state); | |
} else { // Not surrounded by spaces, back up pointer | |
stream.backUp(1); | |
} | |
} | |
} | |
if (ch === ' ') { | |
if (stream.match(/ +$/, false)) { | |
state.trailingSpace++; | |
} else if (state.trailingSpace) { | |
state.trailingSpaceNewLine = true; | |
} | |
} | |
return getType(state); | |
} | |
function linkHref(stream, state) { | |
// Check if space, and return NULL if so (to avoid marking the space) | |
if(stream.eatSpace()){ | |
return null; | |
} | |
var ch = stream.next(); | |
if (ch === '(' || ch === '[') { | |
return switchInline(stream, state, inlineElement(linkhref, ch === '(' ? ')' : ']')); | |
} | |
return 'error'; | |
} | |
function footnoteLink(stream, state) { | |
if (stream.match(/^[^\]]*\]:/, true)) { | |
state.f = footnoteUrl; | |
return linktext; | |
} | |
return switchInline(stream, state, inlineNormal); | |
} | |
function footnoteUrl(stream, state) { | |
// Check if space, and return NULL if so (to avoid marking the space) | |
if(stream.eatSpace()){ | |
return null; | |
} | |
// Match URL | |
stream.match(/^[^\s]+/, true); | |
// Check for link title | |
if (stream.peek() === undefined) { // End of line, set flag to check next line | |
state.linkTitle = true; | |
} else { // More content on line, check if link title | |
stream.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true); | |
} | |
state.f = state.inline = inlineNormal; | |
return linkhref; | |
} | |
var savedInlineRE = []; | |
function inlineRE(endChar) { | |
if (!savedInlineRE[endChar]) { | |
// Escape endChar for RegExp (taken from http://stackoverflow.com/a/494122/526741) | |
endChar = (endChar+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); | |
// Match any non-endChar, escaped character, as well as the closing | |
// endChar. | |
savedInlineRE[endChar] = new RegExp('^(?:[^\\\\]|\\\\.)*?(' + endChar + ')'); | |
} | |
return savedInlineRE[endChar]; | |
} | |
function inlineElement(type, endChar, next) { | |
next = next || inlineNormal; | |
return function(stream, state) { | |
stream.match(inlineRE(endChar)); | |
state.inline = state.f = next; | |
return type; | |
}; | |
} | |
return { | |
startState: function() { | |
return { | |
f: blockNormal, | |
prevLineHasContent: false, | |
thisLineHasContent: false, | |
block: blockNormal, | |
htmlState: CodeMirror.startState(htmlMode), | |
indentation: 0, | |
inline: inlineNormal, | |
text: handleText, | |
linkText: false, | |
linkTitle: false, | |
em: false, | |
strong: false, | |
header: false, | |
taskList: false, | |
list: false, | |
listDepth: 0, | |
quote: 0, | |
trailingSpace: 0, | |
trailingSpaceNewLine: false | |
}; | |
}, | |
copyState: function(s) { | |
return { | |
f: s.f, | |
prevLineHasContent: s.prevLineHasContent, | |
thisLineHasContent: s.thisLineHasContent, | |
block: s.block, | |
htmlState: CodeMirror.copyState(htmlMode, s.htmlState), | |
indentation: s.indentation, | |
localMode: s.localMode, | |
localState: s.localMode ? CodeMirror.copyState(s.localMode, s.localState) : null, | |
inline: s.inline, | |
text: s.text, | |
linkTitle: s.linkTitle, | |
em: s.em, | |
strong: s.strong, | |
header1: s.header1, | |
header2: s.header2, | |
header3: s.header3, | |
taskList: s.taskList, | |
list: s.list, | |
listDepth: s.listDepth, | |
quote: s.quote, | |
trailingSpace: s.trailingSpace, | |
trailingSpaceNewLine: s.trailingSpaceNewLine, | |
md_inside: s.md_inside | |
}; | |
}, | |
token: function(stream, state) { | |
if (stream.sol()) { | |
if (stream.match(/^\s*$/, true)) { | |
state.prevLineHasContent = false; | |
return blankLine(state); | |
} else { | |
state.prevLineHasContent = state.thisLineHasContent; | |
state.thisLineHasContent = true; | |
} | |
// Reset state.header | |
state.header1 = false; | |
state.header2 = false; | |
state.header3 = false; | |
// Reset state.taskList | |
state.taskList = false; | |
// Reset state.code | |
state.code = false; | |
// Reset state.trailingSpace | |
state.trailingSpace = 0; | |
state.trailingSpaceNewLine = false; | |
state.f = state.block; | |
var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, ' ').length; | |
var difference = Math.floor((indentation - state.indentation) / 4) * 4; | |
if (difference > 4) difference = 4; | |
var adjustedIndentation = state.indentation + difference; | |
state.indentationDiff = adjustedIndentation - state.indentation; | |
state.indentation = adjustedIndentation; | |
if (indentation > 0) return null; | |
} | |
return state.f(stream, state); | |
}, | |
blankLine: blankLine, | |
getType: getType | |
}; | |
}, "xml"); | |
CodeMirror.defineMIME("text/x-markdown", "markdown"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment