forked from TryGhost/Ghost
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding Github Flavored Markdown support
closes TryGhost#422, issue TryGhost#295 - Added GFM mode to codemirror - Took the github.js extension for Showdown and added all useful behaviour - Now supports strikethrough, line breaking and multiple underscores, and auto linking urls & emails without breaking definition urls - Also added definition url handling in preparation for TryGhost#295 - Added unit tests for the extentions individually and integrated with showdown
- Loading branch information
Showing
15 changed files
with
919 additions
and
44 deletions.
There are no files selected for viewing
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
59 changes: 59 additions & 0 deletions
59
core/client/assets/vendor/codemirror/addon/mode/overlay.js
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// Utility function that allows modes to be combined. The mode given | ||
// as the base argument takes care of most of the normal mode | ||
// functionality, but a second (typically simple) mode is used, which | ||
// can override the style of text. Both modes get to parse all of the | ||
// text, but when both assign a non-null style to a piece of code, the | ||
// overlay wins, unless the combine argument was true, in which case | ||
// the styles are combined. | ||
|
||
// overlayParser is the old, deprecated name | ||
CodeMirror.overlayMode = CodeMirror.overlayParser = function(base, overlay, combine) { | ||
return { | ||
startState: function() { | ||
return { | ||
base: CodeMirror.startState(base), | ||
overlay: CodeMirror.startState(overlay), | ||
basePos: 0, baseCur: null, | ||
overlayPos: 0, overlayCur: null | ||
}; | ||
}, | ||
copyState: function(state) { | ||
return { | ||
base: CodeMirror.copyState(base, state.base), | ||
overlay: CodeMirror.copyState(overlay, state.overlay), | ||
basePos: state.basePos, baseCur: null, | ||
overlayPos: state.overlayPos, overlayCur: null | ||
}; | ||
}, | ||
|
||
token: function(stream, state) { | ||
if (stream.start == state.basePos) { | ||
state.baseCur = base.token(stream, state.base); | ||
state.basePos = stream.pos; | ||
} | ||
if (stream.start == state.overlayPos) { | ||
stream.pos = stream.start; | ||
state.overlayCur = overlay.token(stream, state.overlay); | ||
state.overlayPos = stream.pos; | ||
} | ||
stream.pos = Math.min(state.basePos, state.overlayPos); | ||
if (stream.eol()) state.basePos = state.overlayPos = 0; | ||
|
||
if (state.overlayCur == null) return state.baseCur; | ||
if (state.baseCur != null && combine) return state.baseCur + " " + state.overlayCur; | ||
else return state.overlayCur; | ||
}, | ||
|
||
indent: base.indent && function(state, textAfter) { | ||
return base.indent(state.base, textAfter); | ||
}, | ||
electricChars: base.electricChars, | ||
|
||
innerMode: function(state) { return {state: state.base, mode: base}; }, | ||
|
||
blankLine: function(state) { | ||
if (base.blankLine) base.blankLine(state.base); | ||
if (overlay.blankLine) overlay.blankLine(state.overlay); | ||
} | ||
}; | ||
}; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
CodeMirror.defineMode("gfm", function(config) { | ||
var codeDepth = 0; | ||
function blankLine(state) { | ||
state.code = false; | ||
return null; | ||
} | ||
var gfmOverlay = { | ||
startState: function() { | ||
return { | ||
code: false, | ||
codeBlock: false, | ||
ateSpace: false | ||
}; | ||
}, | ||
copyState: function(s) { | ||
return { | ||
code: s.code, | ||
codeBlock: s.codeBlock, | ||
ateSpace: s.ateSpace | ||
}; | ||
}, | ||
token: function(stream, state) { | ||
// Hack to prevent formatting override inside code blocks (block and inline) | ||
if (state.codeBlock) { | ||
if (stream.match(/^```/)) { | ||
state.codeBlock = false; | ||
return null; | ||
} | ||
stream.skipToEnd(); | ||
return null; | ||
} | ||
if (stream.sol()) { | ||
state.code = false; | ||
} | ||
if (stream.sol() && stream.match(/^```/)) { | ||
stream.skipToEnd(); | ||
state.codeBlock = true; | ||
return null; | ||
} | ||
// If this block is changed, it may need to be updated in Markdown mode | ||
if (stream.peek() === '`') { | ||
stream.next(); | ||
var before = stream.pos; | ||
stream.eatWhile('`'); | ||
var difference = 1 + stream.pos - before; | ||
if (!state.code) { | ||
codeDepth = difference; | ||
state.code = true; | ||
} else { | ||
if (difference === codeDepth) { // Must be exact | ||
state.code = false; | ||
} | ||
} | ||
return null; | ||
} else if (state.code) { | ||
stream.next(); | ||
return null; | ||
} | ||
// Check if space. If so, links can be formatted later on | ||
if (stream.eatSpace()) { | ||
state.ateSpace = true; | ||
return null; | ||
} | ||
if (stream.sol() || state.ateSpace) { | ||
state.ateSpace = false; | ||
if(stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+@)?(?:[a-f0-9]{7,40}\b)/)) { | ||
// User/Project@SHA | ||
// User@SHA | ||
// SHA | ||
return "link"; | ||
} else if (stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+)?#[0-9]+\b/)) { | ||
// User/Project#Num | ||
// User#Num | ||
// #Num | ||
return "link"; | ||
} | ||
} | ||
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)) { | ||
// URLs | ||
// Taken from http://daringfireball.net/2010/07/improved_regex_for_matching_urls | ||
// And then (issue #1160) simplified to make it not crash the Chrome Regexp engine | ||
return "link"; | ||
} | ||
stream.next(); | ||
return null; | ||
}, | ||
blankLine: blankLine | ||
}; | ||
CodeMirror.defineMIME("gfmBase", { | ||
name: "markdown", | ||
underscoresBreakWords: false, | ||
taskLists: true, | ||
fencedCodeBlocks: true | ||
}); | ||
return CodeMirror.overlayMode(CodeMirror.getMode(config, "gfmBase"), gfmOverlay); | ||
}, "markdown"); |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
<!doctype html> | ||
<html> | ||
<head> | ||
<meta charset="utf-8"> | ||
<title>CodeMirror: GFM mode</title> | ||
<link rel="stylesheet" href="../../lib/codemirror.css"> | ||
<script src="../../lib/codemirror.js"></script> | ||
<script src="../../addon/mode/overlay.js"></script> | ||
<script src="../xml/xml.js"></script> | ||
<script src="../markdown/markdown.js"></script> | ||
<script src="gfm.js"></script> | ||
|
||
<!-- Code block highlighting modes --> | ||
<script src="../javascript/javascript.js"></script> | ||
<script src="../css/css.js"></script> | ||
<script src="../htmlmixed/htmlmixed.js"></script> | ||
<script src="../clike/clike.js"></script> | ||
|
||
<style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style> | ||
<link rel="stylesheet" href="../../doc/docs.css"> | ||
</head> | ||
<body> | ||
<h1>CodeMirror: GFM mode</h1> | ||
|
||
<form><textarea id="code" name="code"> | ||
GitHub Flavored Markdown | ||
======================== | ||
|
||
Everything from markdown plus GFM features: | ||
|
||
## URL autolinking | ||
|
||
Underscores_are_allowed_between_words. | ||
|
||
## Fenced code blocks (and syntax highlighting) | ||
|
||
```javascript | ||
for (var i = 0; i < items.length; i++) { | ||
console.log(items[i], i); // log them | ||
} | ||
``` | ||
|
||
## Task Lists | ||
|
||
- [ ] Incomplete task list item | ||
- [x] **Completed** task list item | ||
|
||
## A bit of GitHub spice | ||
|
||
* SHA: be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2 | ||
* User@SHA ref: mojombo@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2 | ||
* User/Project@SHA: mojombo/god@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2 | ||
* \#Num: #1 | ||
* User/#Num: mojombo#1 | ||
* User/Project#Num: mojombo/god#1 | ||
|
||
See http://github.github.com/github-flavored-markdown/. | ||
|
||
</textarea></form> | ||
|
||
<script> | ||
var editor = CodeMirror.fromTextArea(document.getElementById("code"), { | ||
mode: 'gfm', | ||
lineNumbers: true, | ||
theme: "default" | ||
}); | ||
</script> | ||
|
||
<p>Optionally depends on other modes for properly highlighted code blocks.</p> | ||
|
||
<p><strong>Parsing/Highlighting Tests:</strong> <a href="../../test/index.html#gfm_*">normal</a>, <a href="../../test/index.html#verbose,gfm_*">verbose</a>.</p> | ||
|
||
</body> | ||
</html> |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
(function() { | ||
var mode = CodeMirror.getMode({tabSize: 4}, "gfm"); | ||
function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } | ||
|
||
MT("emInWordAsterisk", | ||
"foo[em *bar*]hello"); | ||
|
||
MT("emInWordUnderscore", | ||
"foo_bar_hello"); | ||
|
||
MT("emStrongUnderscore", | ||
"[strong __][em&strong _foo__][em _] bar"); | ||
|
||
MT("fencedCodeBlocks", | ||
"[comment ```]", | ||
"[comment foo]", | ||
"", | ||
"[comment ```]", | ||
"bar"); | ||
|
||
MT("fencedCodeBlockModeSwitching", | ||
"[comment ```javascript]", | ||
"[variable foo]", | ||
"", | ||
"[comment ```]", | ||
"bar"); | ||
|
||
MT("taskListAsterisk", | ||
"[variable-2 * []] foo]", // Invalid; must have space or x between [] | ||
"[variable-2 * [ ]]bar]", // Invalid; must have space after ] | ||
"[variable-2 * [x]]hello]", // Invalid; must have space after ] | ||
"[variable-2 * ][meta [ ]]][variable-2 [world]]]", // Valid; tests reference style links | ||
" [variable-3 * ][property [x]]][variable-3 foo]"); // Valid; can be nested | ||
|
||
MT("taskListPlus", | ||
"[variable-2 + []] foo]", // Invalid; must have space or x between [] | ||
"[variable-2 + [ ]]bar]", // Invalid; must have space after ] | ||
"[variable-2 + [x]]hello]", // Invalid; must have space after ] | ||
"[variable-2 + ][meta [ ]]][variable-2 [world]]]", // Valid; tests reference style links | ||
" [variable-3 + ][property [x]]][variable-3 foo]"); // Valid; can be nested | ||
|
||
MT("taskListDash", | ||
"[variable-2 - []] foo]", // Invalid; must have space or x between [] | ||
"[variable-2 - [ ]]bar]", // Invalid; must have space after ] | ||
"[variable-2 - [x]]hello]", // Invalid; must have space after ] | ||
"[variable-2 - ][meta [ ]]][variable-2 [world]]]", // Valid; tests reference style links | ||
" [variable-3 - ][property [x]]][variable-3 foo]"); // Valid; can be nested | ||
|
||
MT("taskListNumber", | ||
"[variable-2 1. []] foo]", // Invalid; must have space or x between [] | ||
"[variable-2 2. [ ]]bar]", // Invalid; must have space after ] | ||
"[variable-2 3. [x]]hello]", // Invalid; must have space after ] | ||
"[variable-2 4. ][meta [ ]]][variable-2 [world]]]", // Valid; tests reference style links | ||
" [variable-3 1. ][property [x]]][variable-3 foo]"); // Valid; can be nested | ||
|
||
MT("SHA", | ||
"foo [link be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2] bar"); | ||
|
||
MT("shortSHA", | ||
"foo [link be6a8cc] bar"); | ||
|
||
MT("tooShortSHA", | ||
"foo be6a8c bar"); | ||
|
||
MT("longSHA", | ||
"foo be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd22 bar"); | ||
|
||
MT("badSHA", | ||
"foo be6a8cc1c1ecfe9489fb51e4869af15a13fc2cg2 bar"); | ||
|
||
MT("userSHA", | ||
"foo [link bar@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2] hello"); | ||
|
||
MT("userProjectSHA", | ||
"foo [link bar/hello@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2] world"); | ||
|
||
MT("num", | ||
"foo [link #1] bar"); | ||
|
||
MT("badNum", | ||
"foo #1bar hello"); | ||
|
||
MT("userNum", | ||
"foo [link bar#1] hello"); | ||
|
||
MT("userProjectNum", | ||
"foo [link bar/hello#1] world"); | ||
|
||
MT("vanillaLink", | ||
"foo [link http://www.example.com/] bar"); | ||
|
||
MT("vanillaLinkPunctuation", | ||
"foo [link http://www.example.com/]. bar"); | ||
|
||
MT("vanillaLinkExtension", | ||
"foo [link http://www.example.com/index.html] bar"); | ||
|
||
MT("notALink", | ||
"[comment ```css]", | ||
"[tag foo] {[property color][operator :][keyword black];}", | ||
"[comment ```][link http://www.example.com/]"); | ||
|
||
MT("notALink", | ||
"[comment ``foo `bar` http://www.example.com/``] hello"); | ||
|
||
MT("notALink", | ||
"[comment `foo]", | ||
"[link http://www.example.com/]", | ||
"[comment `foo]", | ||
"", | ||
"[link http://www.example.com/]"); | ||
})(); |
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
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
Oops, something went wrong.