Merge branch 'master' into feature-table-extension

Conflicts:
	README.md
	src/showdown.js
This commit is contained in:
Pascal Deschenes 2012-11-01 14:21:39 -04:00
commit dc28410a3b
17 changed files with 150 additions and 77 deletions

View File

@ -1,10 +1,6 @@
# NOTE -- Showdown on GitHub
[![Build Status](https://travis-ci.org/pdeschen/showdown.png)](https://travis-ci.org/pdeschen/showdown]) **Please note** that I, Corey, am not the author of Showdown. Rather, I found it
NOTE -- Showdown on GitHub
==========================
Please note that I, Corey, am not the author of Showdown. Rather, I found it
some time back at <http://attacklab.net/showdown/> some time back at <http://attacklab.net/showdown/>
(website removed, see: <http://wayback.archive.org/web/*/http://attacklab.net/showdown>) (website removed, see: <http://wayback.archive.org/web/*/http://attacklab.net/showdown>)
and wanted to see it available on GitHub. and wanted to see it available on GitHub.
@ -23,8 +19,7 @@ Cheers,<br/>
Corey Corey
Showdown -- A JavaScript port of Markdown # Showdown -- A JavaScript port of Markdown
=========================================
Showdown Copyright (c) 2007 John Fraser. Showdown Copyright (c) 2007 John Fraser.
<http://www.attacklab.net/> <http://www.attacklab.net/>
@ -36,21 +31,18 @@ Redistributable under a BSD-style open source license.
See license.txt for more information. See license.txt for more information.
Quick Example ## Quick Example
-------------
``` js ```js
var Showdown = require('showdown'); var Showdown = require('showdown');
var converter = new Showdown.converter(); var converter = new Showdown.converter();
converter.makeHtml('#hello markdown!'); converter.makeHtml('#hello markdown!');
// <h1 id="hellomarkdown">hello, markdown</h1> // <h1 id="hellomarkdown">hello, markdown</h1>
``` ```
What's it for? ## What's it for?
--------------
Developers can use Showdown to: Developers can use Showdown to:
@ -92,23 +84,21 @@ Developers can use Showdown to:
Markdown.) Markdown.)
Browser Compatibility ## Browser Compatibility
---------------------
Showdown has been tested successfully with: Showdown has been tested successfully with:
- Firefox 1.5 and 2.0 * Firefox 1.5 and 2.0
- Internet Explorer 6 and 7 * Internet Explorer 6 and 7
- Safari 2.0.4 * Safari 2.0.4
- Opera 8.54 and 9.10 * Opera 8.54 and 9.10
- Netscape 8.1.2 * Netscape 8.1.2
- Konqueror 3.5.4 * Konqueror 3.5.4
In theory, Showdown will work in any browser that supports ECMA 262 3rd Edition (JavaScript 1.5). The converter itself might even work in things that aren't web browsers, like Acrobat. No promises. In theory, Showdown will work in any browser that supports ECMA 262 3rd Edition (JavaScript 1.5). The converter itself might even work in things that aren't web browsers, like Acrobat. No promises.
Extensions ## Extensions
----------
Showdown allows additional functionality to be loaded via extensions. Showdown allows additional functionality to be loaded via extensions.
@ -118,7 +108,7 @@ Showdown allows additional functionality to be loaded via extensions.
<script src="src/showdown.js" /> <script src="src/showdown.js" />
<script src="src/extensions/twitter.js" /> <script src="src/extensions/twitter.js" />
var converter = new Showdown().converter({ extensions: ['twitter'] }); var converter = new Showdown.converter({ extensions: 'twitter' });
``` ```
### Server-side Extension Usage ### Server-side Extension Usage
@ -126,20 +116,17 @@ var converter = new Showdown().converter({ extensions: ['twitter'] });
```js ```js
// Using a bundled extension // Using a bundled extension
var Showdown = require('showdown'); var Showdown = require('showdown');
var converter = new Showdown().converter({ extensions: ['twitter'] }); var converter = new Showdown.converter({ extensions: ['twitter'] });
// Using a custom extension // Using a custom extension
var mine = require('./custom-extensions/mine'); var mine = require('./custom-extensions/mine');
var converter = new Showdown().converter({ extensions: ['twitter', mine] }); var converter = new Showdown.converter({ extensions: ['twitter', mine] });
``` ```
## Known Differences in Output
Known Differences in Output In most cases, Showdown's output is identical to that of Perl Markdown v1.0.2b7. What follows is a list of all known deviations. Please file an issue if you find more.
---------------------------
In most cases, Showdown's output is identical to that of Perl Markdown v1.0.2b7. What follows is a list of all known deviations. Please email me if you find more.
* This release uses the HTML parser from Markdown 1.0.2b2, * This release uses the HTML parser from Markdown 1.0.2b2,
which means it fails `Inline HTML (Advanced).text` from which means it fails `Inline HTML (Advanced).text` from
@ -151,7 +138,6 @@ In most cases, Showdown's output is identical to that of Perl Markdown v1.0.2b7.
</div> </div>
</div> </div>
* Showdown doesn't support the markdown="1" attribute: * Showdown doesn't support the markdown="1" attribute:
<div markdown="1"> <div markdown="1">
@ -221,8 +207,7 @@ In most cases, Showdown's output is identical to that of Perl Markdown v1.0.2b7.
Showdown won't. But still, don't do that. Showdown won't. But still, don't do that.
Tests ## Tests
---------------------------
A suite of tests is available which require node.js. Once node is installed, run the following command from the project root to install the development dependencies: A suite of tests is available which require node.js. Once node is installed, run the following command from the project root to install the development dependencies:
npm install --dev npm install --dev
@ -234,21 +219,20 @@ Once installed the tests can be run from the project root using:
New test cases can easily be added. Create a markdown file (ending in `.md`) which contains the markdown to test. Create a `.html` file of the exact same name. It will automatically be tested when the tests are executed with `mocha`. New test cases can easily be added. Create a markdown file (ending in `.md`) which contains the markdown to test. Create a `.html` file of the exact same name. It will automatically be tested when the tests are executed with `mocha`.
Creating Markdown Extensions ## Creating Markdown Extensions
----------------------------
A showdown extension is simply a function which returns an array of extensions. Each single extension can be one of two types: A showdown extension is simply a function which returns an array of extensions. Each single extension can be one of two types:
- Language Extension -- Language extensions are ones that that add new markdown syntax to showdown. For example, say you wanted `^^youtube http://www.youtube.com/watch?v=oHg5SJYRHA0` to automatically render as an embedded YouTube video, that would be a language extension. * Language Extension -- Language extensions are ones that that add new markdown syntax to showdown. For example, say you wanted `^^youtube http://www.youtube.com/watch?v=oHg5SJYRHA0` to automatically render as an embedded YouTube video, that would be a language extension.
- Output Modifiers -- After showdown has run, and generated HTML, an output modifier would change that HTML. For example, say you wanted to change `<div class="header">` to be `<header>`, that would be an output modifier. * Output Modifiers -- After showdown has run, and generated HTML, an output modifier would change that HTML. For example, say you wanted to change `<div class="header">` to be `<header>`, that would be an output modifier.
Each extension can provide two combinations of interfaces for showdown. Each extension can provide two combinations of interfaces for showdown.
#### Regex/Replace ### Regex/Replace
Regex/replace style extensions are very similar to javascripts `string.replace` function. Two properties are given, `regex` and `replace`. `regex` is a string and `replace` can be either a string or a function. If `replace` is a string, it can use the `$1` syntax for group substituation, exactly as if it were making use of `string.replace` (internally it does this actually); The value of `regex` is assumed to be a global replacement. Regex/replace style extensions are very similar to javascripts `string.replace` function. Two properties are given, `regex` and `replace`. `regex` is a string and `replace` can be either a string or a function. If `replace` is a string, it can use the `$1` syntax for group substitution, exactly as if it were making use of `string.replace` (internally it does this actually); The value of `regex` is assumed to be a global replacement.
#### Regex/Replace Example **Example:**
``` js ``` js
var demo = function(converter) { var demo = function(converter) {
@ -259,11 +243,11 @@ var demo = function(converter) {
} }
``` ```
#### Filter ### Filter
Alternately, if you'd just like to do everything yourself, you can specify a filter which is a callback with a single input parameter, text (the current source text within the showdown engine). Alternately, if you'd just like to do everything yourself, you can specify a filter which is a callback with a single input parameter, text (the current source text within the showdown engine).
#### Filter Example **Example:**
``` js ``` js
var demo = function(converter) { var demo = function(converter) {
@ -276,9 +260,9 @@ var demo = function(converter) {
} }
``` ```
#### Implementation Concerns ### Implementation Concerns
One bit which should be taken into account is maintaining both client-side and server-side compatibility. This can be achieved with a few lines of boilerplate code. First, to prevent polluting the global scope for client-side code, the extension definition should be wrapped in a self executing function. One bit which should be taken into account is maintaining both client-side and server-side compatibility. This can be achieved with a few lines of boilerplate code. First, to prevent polluting the global scope for client-side code, the extension definition should be wrapped in a self-executing function.
``` js ``` js
(function(){ (function(){
@ -301,7 +285,7 @@ Second, client-side extensions should add a property onto `Showdown.extensions`
}()); }());
``` ```
#### Testing Extensions ### Testing Extensions
The showdown test runner is setup to automatically test cases for extensions. To add test cases for an extension, create a new folder under `./test/extensions` which matches the name of the `.js` file in `./src/extensions`. Place any test cases into the filder using the md/html format and they will automatically be run when tests are run. The showdown test runner is setup to automatically test cases for extensions. To add test cases for an extension, create a new folder under `./test/extensions` which matches the name of the `.js` file in `./src/extensions`. Place any test cases into the filder using the md/html format and they will automatically be run when tests are run.
@ -314,11 +298,13 @@ Credits
Author of Showdown Author of Showdown
* [John Gruber](http://daringfireball.net/projects/markdown/):<br/> * [John Gruber](http://daringfireball.net/projects/markdown/):<br/>
Author of Markdown Author of Markdown
* Maintenance * Maintenance/Contributions
* [Corey Innis](http://github.com/coreyti):<br/> * [Corey Innis](http://github.com/coreyti):<br/>
GitHub project maintainer GitHub project maintainer
* [Remy Sharp](http://remysharp.com/):<br/> * [Remy Sharp](https://github.com/remy/):<br/>
CommonJS-compatibility and more CommonJS-compatibility and more
* [Konstantin Käfer](https://github.com/kkaefer/):<br/>
CommonJS packaging
* [Roger Braun](https://github.com/rogerbraun):<br/> * [Roger Braun](https://github.com/rogerbraun):<br/>
Github-style code blocks Github-style code blocks
* [Dominic Tarr](https://github.com/dominictarr):<br/> * [Dominic Tarr](https://github.com/dominictarr):<br/>
@ -326,5 +312,13 @@ Credits
* [Cat Chen](https://github.com/CatChen):<br/> * [Cat Chen](https://github.com/CatChen):<br/>
Export fix Export fix
* [Titus Stone](https://github.com/tstone):<br/> * [Titus Stone](https://github.com/tstone):<br/>
Mocha tests + bug fixes **Mocha tests**, **extension mechanism**, and bug fixes
* [Rob Sutherland](https://github.com/roberocity):<br/>
The idea that lead to extensions
* [Pavel Lang](https://github.com/langpavel):<br/>
Code cleanup
* [Ben Combee](https://github.com/unwiredben):<br/>
Regex optimization
* [Adam Backstrom](https://github.com/abackstrom):<br/>
WebKit bugfix

View File

@ -0,0 +1,5 @@
//
// Github Extension (WIP)
// ~~strike-through~~ -> <del>strike-through</del>
//
(function(){var a=function(a){return[{type:"lang",regex:"(~T){2}([^~]+)(~T){2}",replace:function(a,b,c,d){return"<del>"+c+"</del>"}}]};typeof window!="undefined"&&window.Showdown&&window.Showdown.extensions&&(window.Showdown.extensions.github=a),typeof module!="undefined"&&(module.exports=a)})();

View File

@ -0,0 +1,6 @@
//
// Google Prettify
// A showdown extension to add Google Prettify (http://code.google.com/p/google-code-prettify/)
// hints to showdown's HTML output.
//
(function(){var a=function(a){return[{type:"output",filter:function(a){return a.replace(/(<pre>)?<code>/gi,function(a,b){return b?'<pre class="prettyprint linenums" tabIndex="0"><code data-inner="1">':'<code class="prettyprint">'})}}]};typeof window!="undefined"&&window.Showdown&&window.Showdown.extensions&&(window.Showdown.extensions.googlePrettify=a),typeof module!="undefined"&&(module.exports=a)})();

View File

@ -0,0 +1,6 @@
//
// Twitter Extension
// @username -> <a href="http://twitter.com/username">@username</a>
// #hashtag -> <a href="http://twitter.com/search/%23hashtag">#hashtag</a>
//
(function(){var a=function(a){return[{type:"lang",regex:"\\B(\\\\)?@([\\S]+)\\b",replace:function(a,b,c){return b==="\\"?a:'<a href="http://twitter.com/'+c+'">@'+c+"</a>"}},{type:"lang",regex:"\\B(\\\\)?#([\\S]+)\\b",replace:function(a,b,c){return b==="\\"?a:'<a href="http://twitter.com/search/%23'+c+'">#'+c+"</a>"}},{type:"lang",regex:"\\\\@",replace:"@"}]};typeof window!="undefined"&&window.Showdown&&window.Showdown.extensions&&(window.Showdown.extensions.twitter=a),typeof module!="undefined"&&(module.exports=a)})();

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{ {
"name": "showdown", "name": "showdown",
"version": "0.2.0", "version": "0.3.0",
"author": "John Fraser", "author": "John Fraser",
"scripts": { "scripts": {
"test": "mocha ./test/run.js" "test": "mocha ./test/run.js"
@ -14,7 +14,11 @@
"Roger Braun", "Roger Braun",
"Dominic Tarr", "Dominic Tarr",
"Cat Chen", "Cat Chen",
"Titus Stone" "Titus Stone",
"Rob Sutherland",
"Pavel Lang",
"Ben Combee",
"Adam Backstrom"
], ],
"repository": { "repository": {
"type": "git", "type": "git",

25
src/extensions/github.js Normal file
View File

@ -0,0 +1,25 @@
//
// Github Extension (WIP)
// ~~strike-through~~ -> <del>strike-through</del>
//
(function(){
var github = function(converter) {
return [
{
// strike-through
// NOTE: showdown already replaced "~" with "~T", so we need to adjust accordingly.
type : 'lang',
regex : '(~T){2}([^~]+)(~T){2}',
replace : function(match, prefix, content, suffix) {
return '<del>' + content + '</del>';
}
}
];
};
// Client-side export
if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) { window.Showdown.extensions.github = github; }
// Server-side export
if (typeof module !== 'undefined') module.exports = github;
}());

View File

@ -1,4 +1,3 @@
// //
// Google Prettify // Google Prettify
// A showdown extension to add Google Prettify (http://code.google.com/p/google-code-prettify/) // A showdown extension to add Google Prettify (http://code.google.com/p/google-code-prettify/)

View File

@ -1,4 +1,3 @@
// //
// Twitter Extension // Twitter Extension
// @username -> <a href="http://twitter.com/username">@username</a> // @username -> <a href="http://twitter.com/username">@username</a>

View File

@ -128,7 +128,7 @@ if (typeof module !== 'undefind' && typeof exports !== 'undefined' && typeof req
return file.replace(/\.js$/, ''); return file.replace(/\.js$/, '');
}); });
// Load extensions into Showdown namespace // Load extensions into Showdown namespace
extensions.forEach(function(ext){ Showdown.forEach(extensions, function(ext){
var name = stdExtName(ext); var name = stdExtName(ext);
Showdown.extensions[name] = require('./extensions/' + ext); Showdown.extensions[name] = require('./extensions/' + ext);
}); });
@ -139,11 +139,11 @@ if (typeof module !== 'undefind' && typeof exports !== 'undefined' && typeof req
// Options: // Options:
// //
// Parse extensinos options into separate arrays // Parse extensions options into separate arrays
if (converter_options && converter_options.extensions) { if (converter_options && converter_options.extensions) {
// Iterate over each plugin // Iterate over each plugin
forEach(converter_options.extensions, function(plugin){ Showdown.forEach(converter_options.extensions, function(plugin){
// Assume it's a bundled plugin if a string is given // Assume it's a bundled plugin if a string is given
if (typeof plugin === 'string') { if (typeof plugin === 'string') {
@ -152,7 +152,7 @@ if (converter_options && converter_options.extensions) {
if (typeof plugin === 'function') { if (typeof plugin === 'function') {
// Iterate over each extension within that plugin // Iterate over each extension within that plugin
plugin(this).forEach(function(ext){ Showdown.forEach(plugin(this), function(ext){
// Sort extensions by type // Sort extensions by type
if (ext.type) { if (ext.type) {
if (ext.type === 'language' || ext.type === 'lang') { if (ext.type === 'language' || ext.type === 'lang') {
@ -215,7 +215,7 @@ this.makeHtml = function(text) {
text = text.replace(/^[ \t]+$/mg,""); text = text.replace(/^[ \t]+$/mg,"");
// Run language extensions // Run language extensions
g_lang_extensions.forEach(function(x){ Showdown.forEach(g_lang_extensions, function(x){
text = _ExecuteExtension(x, text); text = _ExecuteExtension(x, text);
}); });
@ -240,7 +240,7 @@ this.makeHtml = function(text) {
text = text.replace(/~T/g,"~"); text = text.replace(/~T/g,"~");
// Run output modifiers // Run output modifiers
g_output_modifiers.forEach(function(x){ Showdown.forEach(g_output_modifiers, function(x){
text = _ExecuteExtension(x, text); text = _ExecuteExtension(x, text);
}); });
@ -286,7 +286,11 @@ var _StripLinkDefinitions = function(text) {
/gm, /gm,
function(){...}); function(){...});
*/ */
var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,
// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
text += "~0";
text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|(?=~0))/gm,
function (wholeMatch,m1,m2,m3,m4) { function (wholeMatch,m1,m2,m3,m4) {
m1 = m1.toLowerCase(); m1 = m1.toLowerCase();
g_urls[m1] = _EncodeAmpsAndAngles(m2); // Link IDs are case-insensitive g_urls[m1] = _EncodeAmpsAndAngles(m2); // Link IDs are case-insensitive
@ -303,6 +307,9 @@ var _StripLinkDefinitions = function(text) {
} }
); );
// attacklab: strip sentinel
text = text.replace(/~0/,"");
return text; return text;
} }
@ -361,13 +368,13 @@ var _HashHTMLBlocks = function(text) {
\b // word break \b // word break
// attacklab: hack around khtml/pcre bug... // attacklab: hack around khtml/pcre bug...
[^\r]*? // any number of lines, minimally matching [^\r]*? // any number of lines, minimally matching
.*</\2> // the matching end tag </\2> // the matching end tag
[ \t]* // trailing spaces/tabs [ \t]* // trailing spaces/tabs
(?=\n+) // followed by a newline (?=\n+) // followed by a newline
) // attacklab: there are sentinel newlines at end of document ) // attacklab: there are sentinel newlines at end of document
/gm,function(){...}}; /gm,function(){...}};
*/ */
text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|style|section|header|footer|nav|article|aside)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,hashElement); text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|style|section|header|footer|nav|article|aside)\b[^\r]*?<\/\2>[ \t]*(?=\n+)\n)/gm,hashElement);
// Special case just for <hr />. It was easier to make a special case than // Special case just for <hr />. It was easier to make a special case than
// to make the other regex more complicated. // to make the other regex more complicated.

View File

@ -0,0 +1,4 @@
<pre><code>code can go here
this is rendered on a second line
</code></pre>

View File

@ -0,0 +1,4 @@
```
code can go here
this is rendered on a second line
```

View File

@ -13,4 +13,11 @@
<aside>ignore me</aside> <aside>ignore me</aside>
<article>read
me</article>
<aside>
ignore me
</aside>
<p>the end</p> <p>the end</p>

View File

@ -7,5 +7,10 @@ These HTML5 tags should pass through just fine.
<nav>navigation</nav> <nav>navigation</nav>
<article>read me</article> <article>read me</article>
<aside>ignore me</aside> <aside>ignore me</aside>
<article>read
me</article>
<aside>
ignore me
</aside>
the end the end

View File

@ -0,0 +1,5 @@
<p><a href="http://github.github.com/github-flavored-markdown/">github-flavored markdown</a> adds support for:</p>
<ul>
<li><del>strike-through text</del></li>
</ul>

View File

@ -0,0 +1,3 @@
[github-flavored markdown](http://github.github.com/github-flavored-markdown/) adds support for:
* ~~strike-through text~~

View File

@ -14,7 +14,7 @@ var runTestsInDir = function(dir, converter) {
}); });
// Run each test case (markdown -> html) // Run each test case (markdown -> html)
cases.forEach(function(test){ showdown.forEach(cases, function(test){
var name = test.replace(/[-.]/g, ' '); var name = test.replace(/[-.]/g, ' ');
it (name, function(){ it (name, function(){
var mdpath = path.join(dir, test + '.md'), var mdpath = path.join(dir, test + '.md'),
@ -70,7 +70,7 @@ if (path.existsSync('test/extensions')) {
}); });
// Run tests in each extension sub-folder // Run tests in each extension sub-folder
extensions.forEach(function(ext){ showdown.forEach(extensions, function(ext){
// Make sure extension exists // Make sure extension exists
var src = 'src/extensions/' + ext + '.js'; var src = 'src/extensions/' + ext + '.js';
if (!path.existsSync(src)) { if (!path.existsSync(src)) {