Merge branch 'showdown2'

This commit is contained in:
Estevão Soares dos Santos 2015-05-14 03:01:03 +01:00
commit 612c8ce661
107 changed files with 2634 additions and 644 deletions

15
.editorconfig Normal file
View File

@ -0,0 +1,15 @@
[*.js]
indent_style = space;
indent_size = 2;
continuation_indent_size = 2;
insert_final_newline = true;
quote_type = single;
space_after_anonymous_functions = true;
space_after_control_statements = true;
spaces_around_operators = true;
trim_trailing_whitespace = true;
spaces_in_brackets = false;
curly_bracket_next_line = true;
indent_brace_style = 1TBS;
end_of_line = lf;
charset = utf-8;

7
.gitattributes vendored
View File

@ -1,7 +1,6 @@
/test export-ignore /test export-ignore
.gitattributes export-ignore .gitattributes export-ignore
.gitignore export-ignore .gitignore export-ignore
/perlMarkdown export-ignore .jshintignore export-ignore
/example export-ignore .travis.yml export-ignore
grunt.js export-ignore Gruntfile.js export-ignore

3
.gitignore vendored
View File

@ -1,4 +1,5 @@
.idea/
build/
.DS_Store .DS_Store
node_modules node_modules
npm-debug.log npm-debug.log
.idea/

90
.jscs.json Normal file
View File

@ -0,0 +1,90 @@
{
"validateIndentation": 2,
"requireCurlyBraces": [
"if",
"else",
"for",
"while",
"do",
"try",
"catch"
],
"requireOperatorBeforeLineBreak": true,
"requireCamelCaseOrUpperCaseIdentifiers": true,
"validateIndentation": 2,
"validateQuoteMarks": "'",
"disallowMultipleLineStrings": true,
"disallowMixedSpacesAndTabs": true,
"disallowTrailingWhitespace": true,
"disallowSpaceAfterPrefixUnaryOperators": true,
"requireMultipleVarDecl": true,
"disallowKeywordsOnNewLine": ["else"],
"requireSpaceAfterKeywords": [
"if",
"else",
"for",
"while",
"do",
"switch",
"return",
"try",
"catch"
],
"requireSpaceBeforeBinaryOperators": [
"=",
"+=",
"-=",
"*=",
"/=",
"%=",
"<<=",
">>=",
">>>=",
"&=",
"|=",
"^=",
"+=",
"+",
"-",
"*",
"/",
"%",
"<<",
">>",
">>>",
"&",
"|",
"^",
"&&",
"||",
"===",
"==",
">=",
"<=",
"<",
">",
"!=",
"!=="
],
"requireSpaceAfterBinaryOperators": true,
"requireSpacesInConditionalExpression": true,
"requireSpaceBeforeBlockStatements": true,
"requireSpacesInForStatement": true,
"requireLineFeedAtFileEnd": true,
"requireSpacesInFunctionExpression": {
"beforeOpeningCurlyBrace": true
},
"requireSpacesInAnonymousFunctionExpression": {
"beforeOpeningRoundBrace": true,
"beforeOpeningCurlyBrace": true
},
"disallowSpacesInsideObjectBrackets": "all",
"disallowSpacesInsideArrayBrackets": "all",
"disallowSpacesInsideParentheses": true,
"validateJSDoc": {
"checkParamNames": true,
"requireParamTypes": true
},
"disallowMultipleLineBreaks": true,
"disallowNewlineBeforeBlockStatements": true
}

View File

@ -1,2 +1,3 @@
Gruntfile.js Gruntfile.js
dist/**/*.js dist/**/*.js
build/**/*.js

28
.jshintrc Normal file
View File

@ -0,0 +1,28 @@
{
"node": true,
"browser": true,
"esnext": true,
"bitwise": true,
"camelcase": true,
"curly": true,
"eqeqeq": true,
"immed": true,
"indent": 2,
"latedef": "nofunc",
"newcap": true,
"noarg": true,
"quotmark": "single",
"undef": false,
"unused": true,
"strict": true,
"trailing": true,
"smarttabs": true,
"onevar": true,
"globals": {
"angular": true,
"module": true,
"define": true,
"window": true,
"showdown": true
}
}

View File

@ -6,3 +6,9 @@ node_js:
before_install: before_install:
- '[ "${TRAVIS_NODE_VERSION}" != "0.8" ] || npm install -g npm@1.4.28' - '[ "${TRAVIS_NODE_VERSION}" != "0.8" ] || npm install -g npm@1.4.28'
- npm install -g npm@latest - npm install -g npm@latest
#travis build speed up
sudo: false
cache:
directories:
- node_modules

30
CHANGELOG.md Normal file
View File

@ -0,0 +1,30 @@
<a name"1.0.0-alpha1"></a>
### 1.0.0-alpha1 (2015-05-13)
#### Release Information
This is a major code refactor with some big changes such as:
- showdown.js file was split in several files, called sub-parsers. This should improve code maintainability.
- angular integration was removed from core and move to its own repository, similar to what was done with extensions
- A new extension registering system is on the "cooks" that should reduce errors when using extensions. The old mechanism
is kept so old extensions should be compatible.
#### Bug Fixes
* **extensions:** support for old extension loading mechanism ([95ed7c68](http://github.com/showdownjs/showdown/commit/95ed7c68))
* **helpers:** fix wrong function call 'escapeCharacters' due to old strayed code ([18ba4e75](http://github.com/showdownjs/showdown/commit/18ba4e75))
* **showdown.js:**
- fix showdown extension loader ([a38c76d2](http://github.com/showdownjs/showdown/commit/a38c76d2)),
closes [#50](http://github.com/showdownjs/showdown/issues/50),[#56](http://github.com/showdownjs/showdown/issues/56),
[#104](http://github.com/showdownjs/showdown/issues/104), [#108](http://github.com/showdownjs/showdown/issues/108),
[#109](http://github.com/showdownjs/showdown/issues/109), [#111](http://github.com/showdownjs/showdown/issues/111),
[#118](http://github.com/showdownjs/showdown/issues/118), [#122](http://github.com/showdownjs/showdown/issues/122)
- add unique id prefix and suffix to headers ([c367a4b9](http://github.com/showdownjs/showdown/commit/c367a4b9), closes [#81](http://github.com/showdownjs/showdown/issues/81), [#82](http://github.com/showdownjs/showdown/issues/82))
#### Features
* **registerExtension():** new extension loading mechanism. Now extensions can be registered using this function.
The system, however, is not final and will probably be changed until the final version([0fd10cb] (http://github.com/showdownjs/showdown/commit/0fd10cb))
* **allowBlockIndents:** indented inline block elements can now be parsed as markdown ([f6326b84](http://github.com/showdownjs/showdown/commit/f6326b84))
#### Breaking Changes
* **angular:** angular integration was removed from core and now lives in it's own [repository](http://github.com/showdownjs/angular/).

39
CREDITS.md Normal file
View File

@ -0,0 +1,39 @@
Credits
=======
- Showdown v2
* [Estevão Santos](http://soares-dos-santos.com)<br/>
Code Refactoring and project maintainer
- Showdown v1
* [Corey Innis](http://github.com/coreyti):<br/>
GitHub project maintainer
* [Remy Sharp](https://github.com/remy/):<br/>
CommonJS-compatibility and more
* [Konstantin Käfer](https://github.com/kkaefer/):<br/>
CommonJS packaging
* [Roger Braun](https://github.com/rogerbraun):<br/>
Github-style code blocks
* [Dominic Tarr](https://github.com/dominictarr):<br/>
Documentation
* [Cat Chen](https://github.com/CatChen):<br/>
Export fix
* [Titus Stone](https://github.com/tstone):<br/>
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
* [Pascal Deschênes](https://github.com/pdeschen):<br/>
Grunt support, extension fixes + additions, packaging improvements, documentation
* [Estevão Santos](http://soares-dos-santos.com)<br/>
Bug fixing and later maintainer
- Original Project
* [John Gruber](http://daringfireball.net/projects/markdown/)<br/>
Author of Markdown
* [John Fraser](http://attacklab.net/)
Author of Showdown

View File

@ -4,97 +4,94 @@
module.exports = function (grunt) { module.exports = function (grunt) {
// Project configuration. // Project configuration.
grunt.initConfig({ var config = {
pkg: grunt.file.readJSON('package.json'), pkg: grunt.file.readJSON('package.json'),
concat: { concat: {
options: { options: {
separator: ';', sourceMap: true,
sourceMap: true banner: ';/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n(function(){\n',
}, footer: '}).call(this)'
dist: { },
src: ['src/showdown.js', 'src/*.js'], dist: {
dest: 'compressed/<%= pkg.name %>.js' src: [
}, 'src/showdown.js',
github_ext: { 'src/helpers.js',
src: ['src/extensions/github.js'], 'src/subParsers/*.js',
dest: 'compressed/extensions/github.min.js' 'src/loader.js'
}, ],
prettify_ext: { dest: 'dist/<%= pkg.name %>.js'
src: ['src/extensions/prettify.js'], }
dest: 'compressed/extensions/prettify.min.js' },
}, uglify: {
table_ext: { options: {
src: ['src/extensions/table.js'], sourceMap: true,
dest: 'compressed/extensions/table.min.js' banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n'
}, },
twitter_ext: { dist: {
src: ['src/extensions/twitter.js'], files: {
dest: 'compressed/extensions/twitter.min.js' 'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>']
}
},
uglify: {
options: {
banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n'
},
dist: {
files: {
'compressed/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>']
}
},
github_ext: {
files: {
'compressed/extensions/github.min.js': ['<%= concat.github_ext.dest %>']
}
},
prettify_ext: {
files: {
'compressed/extensions/prettify.min.js': ['<%= concat.prettify_ext.dest %>']
}
},
table_ext: {
files: {
'compressed/extensions/table.min.js': ['<%= concat.table_ext.dest %>']
}
},
twitter_ext: {
files: {
'compressed/extensions/twitter.min.js': ['<%= concat.twitter_ext.dest %>']
}
}
},
jshint: {
files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js']
},
simplemocha: {
all: {
src: 'test/run.js',
options: {
globals: ['should'],
timeout: 3000,
ignoreLeaks: false,
ui: 'bdd'
}
}
} }
}); }
},
jshint: {
files: [
'Gruntfile.js',
'src/**/*.js',
'test/**/*.js'
]
},
jscs: {
options: {
config: '.jscs.json',
},
files: {
src: [
'Gruntfile.js',
'src/**/*.js',
'test/**/*.js'
]
}
},
changelog: {
options: {
repository: 'http://github.com/showdownjs/showdown',
dest: 'CHANGELOG.md'
}
},
simplemocha: {
node: {
src: 'test/node/**/*.js',
options: {
globals: ['should'],
timeout: 3000,
ignoreLeaks: false,
reporter: 'spec'
}
},
browser: {
src: 'test/browser/**/*.js',
options: {
reporter: 'spec'
}
}
}
};
grunt.loadNpmTasks('grunt-contrib-concat'); grunt.initConfig(config);
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-simple-mocha');
// test grunt.loadNpmTasks('grunt-contrib-concat');
grunt.registerTask('lint', ['jshint']); grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.registerTask('test', ['simplemocha']); grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-simple-mocha');
grunt.loadNpmTasks('grunt-jscs');
grunt.loadNpmTasks('grunt-conventional-changelog');
// build with uglify grunt.registerTask('lint', ['jshint', 'jscs']);
grunt.registerTask('build', ['concat', 'uglify']); grunt.registerTask('test', ['lint', 'concat', 'simplemocha']);
grunt.registerTask('test-without-building', ['simplemocha']);
// Build with closure compiler grunt.registerTask('build', ['lint', 'test', 'uglify']);
grunt.registerTask('build-with-closure', ['test', 'concat', 'closure-compiler']);
// Default task(s).
grunt.registerTask('default', []);
// Default task(s).
grunt.registerTask('default', []);
}; };

615
README.md
View File

@ -1,317 +1,298 @@
# Showdown ![Showdown](https://raw.githubusercontent.com/showdownjs/logo/master/dist/logo.readme.png)
A JavaScript port of Markdown [![Build Status](https://travis-ci.org/showdownjs/showdown.svg?branch=master)](https://travis-ci.org/showdownjs/showdown)
## Note Showdown is a Javascript Markdown to HTML converter, based on the original works by John Gruber. Showdown can be used client side (in the browser) or server side (with NodeJs).
> Showdown is now maintained by the [showdownjs](https://github.com/showdownjs) organization on Github.
> ## Installation
> The organization needs members to maintain Showdown.
> ### Download tarball
> Please see [this issue](https://github.com/showdownjs/showdown/issues/114) to express interest or comment on this note.
You can download the latest release's tarball directly from https://github.com/showdownjs/showdown/releases
## Original Attributions
### Bower
Showdown Copyright (c) 2007 John Fraser.
<http://www.attacklab.net/> bower install showdown
Original Markdown Copyright (c) 2004-2005 John Gruber ### npm (server-side)
<http://daringfireball.net/projects/markdown/>
npm install showdown
Redistributable under a BSD-style open source license.
See license.txt for more information.
## Browser Compatibility
## Quick Example
Showdown has been tested successfully with:
```js
var Showdown = require('showdown'); * Firefox 1.5 and 2.0
var converter = new Showdown.converter(); * Internet Explorer 6 and 7
* Safari 2.0.4
converter.makeHtml('#hello markdown!'); * Opera 8.54 and 9.10
* Netscape 8.1.2
// <h1 id="hellomarkdown">hello markdown!</h1> * 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.
## What's it for?
Developers can use Showdown to: ## Node compatibility
* Add in-browser preview to existing Markdown apps Showdown has been tested with node 0.8 and 0.10. However, it should work with previous versions, such as node 0.6.
Showdown's output is (almost always) identical to
markdown.pl's, so the server can reproduce exactly ## Quick Example
the output that the user saw. (See below for
exceptions.) ### Node
* Add Markdown input to programs that don't support it ```js
var showdown = require('showdown'),
Any app that accepts HTML input can now be made to speak converter = new showdown.Converter(),
Markdown by modifying the input pages's HTML. If your text = '#hello, markdown!',
application lets users edit documents again later, html = converter.makeHtml(text);
then they won't have access to the original Markdown ```
text. But this should be good enough for many
uses -- and you can do it with just a two-line ### Browser
`onsubmit` function!
```js
* Add Markdown input to closed-source web apps var converter = new showdown.Converter(),
text = '#hello, markdown!',
You can write bookmarklets or userscripts to extend html = converter.makeHtml(text);
any standard textarea on the web so that it accepts ```
Markdown instead of HTML. With a little more hacking,
the same can probably be done with many rich edit ### Output
controls.
Both examples should output...
* Build new web apps from scratch
```html
A Showdown front-end can send back text in Markdown, <h1 id="hellomarkdown">hello, markdown!</h1>
HTML or both, so you can trade bandwidth for server ```
load to reduce your cost of operation. If your app
requires JavaScript, you won't need to do any
Markdown processing on the server at all. (For most ## Extensions
uses, you'll still need to sanitize the HTML before
showing it to other users -- but you'd need to do Showdown allows additional functionality to be loaded via extensions.
that anyway if you're allowing raw HTML in your
Markdown.) ### Client-side Extension Usage
```js
## Browser Compatibility <script src="showdown.js" />
<script src="twitter-extension.js" />
Showdown has been tested successfully with:
var converter = new showdown.Converter({ extensions: 'twitter' });
* Firefox 1.5 and 2.0 ```
* Internet Explorer 6 and 7
* Safari 2.0.4 ### Server-side Extension Usage
* Opera 8.54 and 9.10
* Netscape 8.1.2 ```js
* Konqueror 3.5.4 var showdown = require('showdown'),
myExtension = require('myExtension'),
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. converter = new showdown.Converter({ extensions: ['myExtension'] });
```
## Extensions ## Tests
Showdown allows additional functionality to be loaded via extensions. 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:
### Client-side Extension Usage npm install --dev
```js Once installed the tests can be run from the project root using:
<script src="src/showdown.js" />
<script src="src/extensions/twitter.js" /> npm test
var converter = new Showdown.converter({ extensions: ['twitter'] }); 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`.
```
### Server-side Extension Usage ## Known Differences in Output
```js 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.
// Using a bundled extension
var Showdown = require('showdown'); * This release uses the HTML parser from Markdown 1.0.2b2,
var converter = new Showdown.converter({ extensions: ['twitter'] }); which means it fails `Inline HTML (Advanced).text` from
the Markdown test suite:
// Using a custom extension
var mine = require('./custom-extensions/mine'); <div>
var converter = new Showdown.converter({ extensions: ['twitter', mine] }); <div>
``` unindented == broken
</div>
</div>
## Known Differences in Output
* Showdown doesn't support the markdown="1" attribute:
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.
<div markdown="1">
* This release uses the HTML parser from Markdown 1.0.2b2, Markdown does *not* work in here.
which means it fails `Inline HTML (Advanced).text` from </div>
the Markdown test suite:
This is half laziness on my part and half stubbornness.
<div> Markdown is smart enough to process the contents of span-
<div> level tags without screwing things up; shouldn't it be
unindented == broken able to do the same inside block elements? Let's find a
</div> way to make markdown="1" the default.
</div>
* Showdown doesn't support the markdown="1" attribute: * You can only nest square brackets in link titles to a
depth of two levels:
<div markdown="1">
Markdown does *not* work in here. [[fine]](http://www.attacklab.net/)
</div> [[[broken]]](http://www.attacklab.net/)
This is half laziness on my part and half stubbornness. If you need more, you can escape them with backslashes.
Markdown is smart enough to process the contents of span-
level tags without screwing things up; shouldn't it be
able to do the same inside block elements? Let's find a * When sublists have paragraphs, Showdown produces equivalent
way to make markdown="1" the default. HTML with a slightly different arrangement of newlines:
+ item
* You can only nest square brackets in link titles to a
depth of two levels: - subitem
[[fine]](http://www.attacklab.net/) The HTML has a superfluous newline before this
[[[broken]]](http://www.attacklab.net/) paragraph.
If you need more, you can escape them with backslashes. - subitem
The HTML here is unchanged.
* When sublists have paragraphs, Showdown produces equivalent
HTML with a slightly different arrangement of newlines: - subitem
+ item The HTML is missing a newline after this
list subitem.
- subitem
The HTML has a superfluous newline before this
paragraph. * Markdown.pl creates empty title attributes for
inline-style images:
- subitem
Here's an empty title on an inline-style
The HTML here is unchanged. ![image](http://w3.org/Icons/valid-xhtml10).
- subitem I tried to replicate this to clean up my diffs during
testing, but I went too far: now Showdown also makes
The HTML is missing a newline after this empty titles for reference-style images:
list subitem.
Showdown makes an empty title for
reference-style ![images][] too.
* Markdown.pl creates empty title attributes for [images]: http://w3.org/Icons/valid-xhtml10
inline-style images:
Here's an empty title on an inline-style * With crazy input, Markdown will mistakenly put
![image](http://w3.org/Icons/valid-xhtml10). `<strong>` or `<em>` tags in URLs:
I tried to replicate this to clean up my diffs during <a href="<*Markdown adds em tags in here*>">
testing, but I went too far: now Showdown also makes improbable URL
empty titles for reference-style images: </a>
Showdown makes an empty title for Showdown won't. But still, don't do that.
reference-style ![images][] too.
[images]: http://w3.org/Icons/valid-xhtml10 ## 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:
* With crazy input, Markdown will mistakenly put
`<strong>` or `<em>` tags in URLs: * 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.
<a href="<*Markdown adds em tags in here*>">
improbable URL Each extension can provide two combinations of interfaces for showdown.
</a>
### Regex/Replace
Showdown won't. But still, don't do that.
Regex/replace style extensions are very similar to Javascript's `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.
## Tests **Example:**
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: ```js
var demo = function(converter) {
npm install --dev return [
// Replace escaped @ symbols
Once installed the tests can be run from the project root using: { type: 'lang', regex: '\\@', replace: '@' }
];
npm test }
```
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`.
### Filter
## Creating Markdown Extensions 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).
A showdown extension is simply a function which returns an array of language extensions and/or output modifiers: **Example:**
* Language Extension -- Language extensions are specified with the `lang` type, and 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. ```js
* Output Modifiers -- Output Modifiers are specified with the `output` type. After showdown has generated HTML, an output modifier can make changes to the generated HTML. For example, if you wanted to change `<div class="header">` to be `<header>`, you could implement an output modifier. var demo = function(converter) {
return [
Each showdown extension can provide language extensions and/or output modifiers. // Replace escaped @ symbols
{ type: 'lang', filter: function(text) {
### Regex/Replace return text.replace(/\\@/g, '@');
}}
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. ];
}
**Example:** ```
```js ### Implementation Concerns
var demo = function(converter) {
return [ 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.
// Replace escaped @ symbols
{ type: 'lang', regex: '\\@', replace: '@' } ```js
]; (function(){
} // Your extension here
``` }());
```
### Filter
Second, client-side extensions should add a property onto `Showdown.extensions` which matches the name of the file. As an example, a file named `demo.js` should then add `Showdown.extensions.demo`. Server-side extensions can simply export themselves.
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).
```js
**Example:** (function(){
var demo = function(converter) {
```js // ... extension code here ...
var demo = function(converter) { };
return [
// Replace escaped @ symbols // Client-side export
{ type: 'lang', filter: function(text) { if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) { window.Showdown.extensions.demo = demo; }
return text.replace(/\\@/g, '@'); // Server-side export
}} if (typeof module !== 'undefined') module.exports = demo;
]; }());
} ```
```
### Testing Extensions
### Implementation Concerns
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 folder using the md/html format and they will automatically be run when tests are run.
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 ## Contributing
(function(){ The organization needs members to maintain Showdown.
// Your extension here Please see [this issue](https://github.com/showdownjs/showdown/issues/114) to express interest or comment on this note.
}());
```
## Credits
Second, client-side extensions should add a property onto `Showdown.extensions` which matches the name of the file. As an example, a file named `demo.js` should then add `Showdown.extensions.demo`. Server-side extensions can simply export themselves.
- Showdown
```js * [Estevão Santos](http://soares-dos-santos.com):<br/>
(function(){ GitHub project maintainer
var demo = function(converter) { * [Pascal Deschênes](https://github.com/pdeschen):<br/>
// ... extension code here ... Grunt support, extension fixes + additions, packaging improvements, documentation
}; * [Corey Innis](http://github.com/coreyti):<br/>
Original GitHub project maintainer
// Client-side export * [Remy Sharp](https://github.com/remy/):<br/>
if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) { window.Showdown.extensions.demo = demo; } CommonJS-compatibility and more
// Server-side export * [Konstantin Käfer](https://github.com/kkaefer/):<br/>
if (typeof module !== 'undefined') module.exports = demo; CommonJS packaging
}()); * [Roger Braun](https://github.com/rogerbraun):<br/>
``` Github-style code blocks
* [Dominic Tarr](https://github.com/dominictarr):<br/>
### Testing Extensions Documentation
* [Cat Chen](https://github.com/CatChen):<br/>
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. Export fix
* [Titus Stone](https://github.com/tstone):<br/>
Mocha tests, extension mechanism, and bug fixes
## Credits * [Rob Sutherland](https://github.com/roberocity):<br/>
The idea that lead to extensions
* Origins * [Pavel Lang](https://github.com/langpavel):<br/>
* [John Fraser](http://attacklab.net/):<br/> Code cleanup
Author of Showdown * [Ben Combee](https://github.com/unwiredben):<br/>
* [John Gruber](http://daringfireball.net/projects/markdown/):<br/> Regex optimization
Author of Markdown * [Adam Backstrom](https://github.com/abackstrom):<br/>
* Maintenance/Contributions (roughly chronologically) WebKit bugfix
* [Corey Innis](http://github.com/coreyti):<br/>
GitHub project maintainer
* [Remy Sharp](https://github.com/remy/):<br/> - Original Project
CommonJS-compatibility and more * [John Gruber](http://daringfireball.net/projects/markdown/)<br/>
* [Konstantin Käfer](https://github.com/kkaefer/):<br/> Author of Markdown
CommonJS packaging * [John Fraser](http://attacklab.net/)<br/>
* [Roger Braun](https://github.com/rogerbraun):<br/> Author of Showdown
Github-style code blocks
* [Dominic Tarr](https://github.com/dominictarr):<br/>
Documentation
* [Cat Chen](https://github.com/CatChen):<br/>
Export fix
* [Titus Stone](https://github.com/tstone):<br/>
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
* [Pascal Deschênes](https://github.com/pdeschen):<br/>
Grunt support, extension fixes + additions, packaging improvements, documentation
* [Estevão Santos](http://github.com/coreyti):<br/>
GitHub project maintainer

View File

@ -1,12 +1,12 @@
{ {
"name": "showdown", "name": "showdown",
"description": "JavaScript port of Markdown", "description": "A Markdown to HTML converter written in Javascript",
"homepage": "https://github.com/showdownjs/showdown", "homepage": "https://github.com/showdownjs/showdown",
"authors": [ "authors": [
"John Fraser", "Estevão Santos (https://github.com/tivie)",
"Corey Innis (https://github.com/coreyti)",
"Pascal Deschênes (https://github.com/pdeschen)", "Pascal Deschênes (https://github.com/pdeschen)",
"Estevão Santos (https://github.com/tivie)" "Corey Innis (https://github.com/coreyti)",
"John Fraser"
], ],
"main": ["src/showdown.js"], "main": ["src/showdown.js"],
"ignore": [ "ignore": [

BIN
dist/showdown.js vendored Normal file

Binary file not shown.

BIN
dist/showdown.js.map vendored Normal file

Binary file not shown.

BIN
dist/showdown.min.js vendored Normal file

Binary file not shown.

BIN
dist/showdown.min.js.map vendored Normal file

Binary file not shown.

View File

@ -1,9 +1,9 @@
Copyright (c) 2007, John Fraser Showdown Copyright (c) 2007, John Fraser
<http://www.attacklab.net/> <http://www.attacklab.net/>
All rights reserved. All rights reserved.
Original Markdown copyright (c) 2004, John Gruber Original Markdown copyright (c) 2004, John Gruber
<http://daringfireball.net/> <http://daringfireball.net/>
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without

View File

@ -1,7 +1,8 @@
{ {
"name": "showdown", "name": "showdown",
"version": "0.5.0", "version": "1.0.0-alpha1",
"author": "John Fraser", "description": "A Markdown to HTML converter written in Javascript",
"authors": "Estevão Santos",
"contributors": [ "contributors": [
"John Gruber", "John Gruber",
"John Fraser", "John Fraser",
@ -27,21 +28,25 @@
"licenses": [ "licenses": [
{ {
"type": "BSD", "type": "BSD",
"url": "https://github.com/coreyti/showdown/raw/master/license.txt" "url": "https://github.com/showdownjs/showdown/blob/master/license.txt"
} }
], ],
"main": "./src/showdown", "main": "./dist/showdown.js",
"scripts": { "scripts": {
"test": "mocha ./test/run.js" "test": "mocha ./test/**/*.js"
}, },
"devDependencies": { "devDependencies": {
"angular": "^1.3.2", "chai": "^1.10.0",
"grunt": "^0.4.5", "grunt": "^0.4.5",
"grunt-contrib-concat": "^0.5.0", "grunt-contrib-concat": "^0.5.0",
"grunt-contrib-jshint": "^0.10.0", "grunt-contrib-jshint": "^0.10.0",
"grunt-contrib-uglify": "^0.6.0", "grunt-contrib-uglify": "^0.6.0",
"grunt-conventional-changelog": "^1.1.0",
"grunt-jscs": "^1.2.0",
"grunt-simple-mocha": "^0.4.0", "grunt-simple-mocha": "^0.4.0",
"jscs": "^1.10.0",
"mocha": "*", "mocha": "*",
"should": "^4.4.2" "should": "^4.4.2",
"source-map-support": "^0.2.9"
} }
} }

107
src/helpers.js Normal file
View File

@ -0,0 +1,107 @@
/**
* showdownjs helper functions
*/
if (!showdown.hasOwnProperty('helper')) {
showdown.helper = {};
}
/**
* Check if var is string
* @static
* @param {string} a
* @returns {boolean}
*/
showdown.helper.isString = function isString(a) {
'use strict';
return (typeof a === 'string' || a instanceof String);
};
/**
* ForEach helper function
* @static
* @param {*} obj
* @param {function} callback
*/
showdown.helper.forEach = function forEach(obj, callback) {
'use strict';
if (typeof obj.forEach === 'function') {
obj.forEach(callback);
} else {
for (var i = 0; i < obj.length; i++) {
callback(obj[i], i, obj);
}
}
};
/**
* isArray helper function
* @static
* @param {*} a
* @returns {boolean}
*/
showdown.helper.isArray = function isArray(a) {
'use strict';
return a.constructor === Array;
};
/**
* Check if value is undefined
* @static
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.
*/
showdown.helper.isUndefined = function isUndefined(value) {
'use strict';
return typeof value === 'undefined';
};
/**
* Standardidize extension name
* @static
* @param {string} s extension name
* @returns {string}
*/
showdown.helper.stdExtName = function (s) {
'use strict';
return s.replace(/[_-]||\s/g, '').toLowerCase();
};
function escapeCharactersCallback(wholeMatch, m1) {
'use strict';
var charCodeToEscape = m1.charCodeAt(0);
return '~E' + charCodeToEscape + 'E';
}
/**
* Callback used to escape characters when passing through String.replace
* @static
* @param {string} wholeMatch
* @param {string} m1
* @returns {string}
*/
showdown.helper.escapeCharactersCallback = escapeCharactersCallback;
/**
* Escape characters in a string
* @static
* @param {string} text
* @param {string} charsToEscape
* @param {boolean} afterBackslash
* @returns {XML|string|void|*}
*/
showdown.helper.escapeCharacters = function escapeCharacters(text, charsToEscape, afterBackslash) {
'use strict';
// First we have to escape the escape characters so that
// we can build a character class out of them
var regexString = '([' + charsToEscape.replace(/([\[\]\\])/g, '\\$1') + '])';
if (afterBackslash) {
regexString = '\\\\' + regexString;
}
var regex = new RegExp(regexString, 'g');
text = text.replace(regex, escapeCharactersCallback);
return text;
};

17
src/loader.js Normal file
View File

@ -0,0 +1,17 @@
var root = this;
// CommonJS/nodeJS Loader
if (typeof module !== 'undefined' && module.exports) {
module.exports = showdown;
// AMD Loader
} else if (typeof define === 'function' && define.amd) {
define('showdown', function () {
'use strict';
return showdown;
});
// Regular Browser loader
} else {
root.showdown = showdown;
}

View File

@ -0,0 +1,280 @@
/**
* Created by Tivie on 06-01-2015.
*/
// Private properties
var showdown = {},
parsers = {},
extensions = {},
globalOptions = {
omitExtraWLInCodeBlocks: false,
prefixHeaderId: false
};
/**
* helper namespace
* @type {{}}
*/
showdown.helper = {};
// Public properties
showdown.extensions = {};
/**
* Set a global option
* @static
* @param {string} key
* @param {string} value
* @returns {showdown}
*/
showdown.setOption = function (key, value) {
'use strict';
globalOptions[key] = value;
return this;
};
/**
* Get a global option
* @static
* @param {string} key
* @returns {*}
*/
showdown.getOption = function (key) {
'use strict';
return globalOptions[key];
};
/**
* Get the global options
* @static
* @returns {{omitExtraWLInCodeBlocks: boolean, prefixHeaderId: boolean}}
*/
showdown.getOptions = function () {
'use strict';
return globalOptions;
};
/**
* Get or set a subParser
*
* subParser(name) - Get a registered subParser
* subParser(name, func) - Register a subParser
* @static
* @param {string} name
* @param {function} [func]
* @returns {*}
*/
showdown.subParser = function (name, func) {
'use strict';
if (showdown.helper.isString(name)) {
if (typeof func !== 'undefined') {
parsers[name] = func;
} else {
if (parsers.hasOwnProperty(name)) {
return parsers[name];
} else {
throw Error('SubParser named ' + name + ' not registered!');
}
}
}
};
showdown.extension = function (name, ext) {
'use strict';
if (!showdown.helper.isString(name)) {
throw Error('Extension \'name\' must be a string');
}
name = showdown.helper.stdExtName(name);
if (showdown.helper.isUndefined(ext)) {
return getExtension();
} else {
return setExtension();
}
};
function getExtension(name) {
'use strict';
if (!extensions.hasOwnProperty(name)) {
throw Error('Extension named ' + name + ' is not registered!');
}
return extensions[name];
}
function setExtension(name, ext) {
'use strict';
if (typeof ext !== 'object') {
throw Error('A Showdown Extension must be an object, ' + typeof ext + ' given');
}
if (!showdown.helper.isString(ext.type)) {
throw Error('When registering a showdown extension, "type" must be a string, ' + typeof ext.type + ' given');
}
ext.type = ext.type.toLowerCase();
extensions[name] = ext;
}
/**
* Showdown Converter class
*
* @param {object} [converterOptions]
* @returns {{makeHtml: Function}}
*/
showdown.Converter = function (converterOptions) {
'use strict';
converterOptions = converterOptions || {};
var options = globalOptions,
langExtensions = [],
outputModifiers = [],
parserOrder = [
'githubCodeBlocks',
'hashHTMLBlocks',
'stripLinkDefinitions',
'blockGamut',
'unescapeSpecialChars'
];
// Merge options
if (typeof converterOptions === 'object') {
for (var opt in converterOptions) {
if (converterOptions.hasOwnProperty(opt)) {
options[opt] = converterOptions[opt];
}
}
}
// This is a dirty workaround to maintain backwards extension compatibility
// We define a self var (which is a copy of this) and inject the makeHtml function
// directly to it. This ensures a full converter object is available when iterating over extensions
// We should rewrite the extension loading mechanism and use some kind of interface or decorator pattern
// and inject the object reference there instead.
var self = this;
self.makeHtml = makeHtml;
// Parse options
if (options.extensions) {
// Iterate over each plugin
showdown.helper.forEach(options.extensions, function (plugin) {
var pluginName = plugin;
// Assume it's a bundled plugin if a string is given
if (typeof plugin === 'string') {
var tPluginName = showdown.helper.stdExtName(plugin);
if (!showdown.helper.isUndefined(showdown.extensions[tPluginName]) && showdown.extensions[tPluginName]) {
//Trigger some kind of deprecated alert
plugin = showdown.extensions[tPluginName];
} else if (!showdown.helper.isUndefined(extensions[tPluginName])) {
plugin = extensions[tPluginName];
}
}
if (typeof plugin === 'function') {
// Iterate over each extension within that plugin
showdown.helper.forEach(plugin(self), function (ext) {
// Sort extensions by type
if (ext.type) {
if (ext.type === 'language' || ext.type === 'lang') {
langExtensions.push(ext);
} else if (ext.type === 'output' || ext.type === 'html') {
outputModifiers.push(ext);
}
} else {
// Assume language extension
outputModifiers.push(ext);
}
});
} else {
var errMsg = 'An extension could not be loaded. It was either not found or is not a valid extension.';
if (typeof pluginName === 'string') {
errMsg = 'Extension "' + pluginName + '" could not be loaded. It was either not found or is not a valid extension.';
}
throw errMsg;
}
});
}
/**
* Converts a markdown string into HTML
* @param {string} text
* @returns {*}
*/
function makeHtml(text) {
//check if text is not falsy
if (!text) {
return text;
}
var globals = {
gHtmlBlocks: [],
gUrls: {},
gTitles: {},
gListLevel: 0,
hashLinkCounts: {},
langExtensions: langExtensions,
outputModifiers: outputModifiers
};
// attacklab: Replace ~ with ~T
// This lets us use tilde as an escape char to avoid md5 hashes
// The choice of character is arbitrary; anything that isn't
// magic in Markdown will work.
text = text.replace(/~/g, '~T');
// attacklab: Replace $ with ~D
// RegExp interprets $ as a special character
// when it's in a replacement string
text = text.replace(/\$/g, '~D');
// Standardize line endings
text = text.replace(/\r\n/g, '\n'); // DOS to Unix
text = text.replace(/\r/g, '\n'); // Mac to Unix
// Make sure text begins and ends with a couple of newlines:
text = '\n\n' + text + '\n\n';
// detab
text = parsers.detab(text, options, globals);
// stripBlankLines
text = parsers.stripBlankLines(text, options, globals);
//run languageExtensions
text = parsers.languageExtensions(text, options, globals);
// Run all registered parsers
for (var i = 0; i < parserOrder.length; ++i) {
var name = parserOrder[i];
text = parsers[name](text, options, globals);
}
// attacklab: Restore dollar signs
text = text.replace(/~D/g, '$$');
// attacklab: Restore tildes
text = text.replace(/~T/g, '~');
// Run output modifiers
showdown.helper.forEach(globals.outputModifiers, function (ext) {
text = showdown.subParser('runExtension')(ext, text);
});
text = parsers.outputModifiers(text, options, globals);
return text;
}
return {
makeHtml: makeHtml
};
};

131
src/subParsers/anchors.js Normal file
View File

@ -0,0 +1,131 @@
/**
* Turn Markdown link shortcuts into XHTML <a> tags.
*/
showdown.subParser('anchors', function (text, config, globals) {
'use strict';
var writeAnchorTag = function (wholeMatch, m1, m2, m3, m4, m5, m6, m7) {
if (showdown.helper.isUndefined(m7)) {
m7 = '';
}
wholeMatch = m1;
var linkText = m2,
linkId = m3.toLowerCase(),
url = m4,
title = m7;
if (!url) {
if (!linkId) {
// lower-case and turn embedded newlines into spaces
linkId = linkText.toLowerCase().replace(/ ?\n/g, ' ');
}
url = '#' + linkId;
if (!showdown.helper.isUndefined(globals.gUrls[linkId])) {
url = globals.gUrls[linkId];
if (!showdown.helper.isUndefined(globals.gTitles[linkId])) {
title = globals.gTitles[linkId];
}
} else {
if (wholeMatch.search(/\(\s*\)$/m) > -1) {
// Special case for explicit empty url
url = '';
} else {
return wholeMatch;
}
}
}
url = showdown.helper.escapeCharacters(url, '*_', false);
var result = '<a href="' + url + '"';
if (title !== '' && title !== null) {
title = title.replace(/"/g, '&quot;');
title = showdown.helper.escapeCharacters(title, '*_', false);
result += ' title="' + title + '"';
}
result += '>' + linkText + '</a>';
return result;
};
// First, handle reference-style links: [link text] [id]
/*
text = text.replace(/
( // wrap whole match in $1
\[
(
(?:
\[[^\]]*\] // allow brackets nested one level
|
[^\[] // or anything else
)*
)
\]
[ ]? // one optional space
(?:\n[ ]*)? // one optional newline followed by spaces
\[
(.*?) // id = $3
\]
)()()()() // pad remaining backreferences
/g,_DoAnchors_callback);
*/
text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeAnchorTag);
//
// Next, inline-style links: [link text](url "optional title")
//
/*
text = text.replace(/
( // wrap whole match in $1
\[
(
(?:
\[[^\]]*\] // allow brackets nested one level
|
[^\[\]] // or anything else
)
)
\]
\( // literal paren
[ \t]*
() // no id, so leave $3 empty
<?(.*?)>? // href = $4
[ \t]*
( // $5
(['"]) // quote char = $6
(.*?) // Title = $7
\6 // matching quote
[ \t]* // ignore any spaces/tabs between closing quote and )
)? // title is optional
\)
)
/g,writeAnchorTag);
*/
text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?(?:\(.*?\).*?)?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,
writeAnchorTag);
//
// Last, handle reference-style shortcuts: [link text]
// These must come last in case you've also got [link test][1]
// or [link test](/foo)
//
/*
text = text.replace(/
( // wrap whole match in $1
\[
([^\[\]]+) // link text = $2; can't contain '[' or ']'
\]
)()()()()() // pad rest of backreferences
/g, writeAnchorTag);
*/
text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);
return text;
});

View File

@ -0,0 +1,28 @@
showdown.subParser('autoLinks', function (text) {
'use strict';
text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi, '<a href=\"$1\">$1</a>');
// Email addresses: <address@domain.foo>
/*
text = text.replace(/
<
(?:mailto:)?
(
[-.\w]+
\@
[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
)
>
/gi);
*/
var pattern = /<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi;
text = text.replace(pattern, function (wholeMatch, m1) {
var unescapedStr = showdown.subParser('unescapeSpecialChars')(m1);
return showdown.subParser('encodeEmailAddress')(unescapedStr);
});
return text;
});

View File

@ -0,0 +1,29 @@
/**
* These are all the transformations that form block-level
* tags like paragraphs, headers, and list items.
*/
showdown.subParser('blockGamut', function (text, options, globals) {
'use strict';
text = showdown.subParser('headers')(text, options, globals);
// Do Horizontal Rules:
var key = showdown.subParser('hashBlock')('<hr />', options, globals);
text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, key);
text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm, key);
text = text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm, key);
text = showdown.subParser('lists')(text, options, globals);
text = showdown.subParser('codeBlocks')(text, options, globals);
text = showdown.subParser('blockQuotes')(text, options, globals);
// We already ran _HashHTMLBlocks() before, in Markdown(), but that
// was to escape raw HTML in the original Markdown source. This time,
// we're escaping the markup we've just created, so that we don't wrap
// <p> tags around block-level tags.
text = showdown.subParser('hashHTMLBlocks')(text, options, globals);
text = showdown.subParser('paragraphs')(text, options, globals);
return text;
});

View File

@ -0,0 +1,43 @@
showdown.subParser('blockQuotes', function (text, options, globals) {
'use strict';
/*
text = text.replace(/
( // Wrap whole match in $1
(
^[ \t]*>[ \t]? // '>' at the start of a line
.+\n // rest of the first line
(.+\n)* // subsequent consecutive lines
\n* // blanks
)+
)
/gm, function(){...});
*/
text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm, function (wholeMatch, m1) {
var bq = m1;
// attacklab: hack around Konqueror 3.5.4 bug:
// "----------bug".replace(/^-/g,"") == "bug"
bq = bq.replace(/^[ \t]*>[ \t]?/gm, '~0'); // trim one level of quoting
// attacklab: clean up hack
bq = bq.replace(/~0/g, '');
bq = bq.replace(/^[ \t]+$/gm, ''); // trim whitespace-only lines
bq = showdown.subParser('blockGamut')(bq, options, globals); // recurse
bq = bq.replace(/(^|\n)/g, '$1 ');
// These leading spaces screw with <pre> content, so we need to fix that:
bq = bq.replace(/(\s*<pre>[^\r]+?<\/pre>)/gm, function (wholeMatch, m1) {
var pre = m1;
// attacklab: hack around Konqueror 3.5.4 bug:
pre = pre.replace(/^ /mg, '~0');
pre = pre.replace(/~0/g, '');
return pre;
});
return showdown.subParser('hashBlock')('<blockquote>\n' + bq + '\n</blockquote>', options, globals);
});
return text;
});

View File

@ -0,0 +1,42 @@
/**
* Process Markdown `<pre><code>` blocks.
*/
showdown.subParser('codeBlocks', function (text, options, globals) {
'use strict';
/*
text = text.replace(text,
/(?:\n\n|^)
( // $1 = the code block -- one or more lines, starting with a space/tab
(?:
(?:[ ]{4}|\t) // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
.*\n+
)+
)
(\n*[ ]{0,3}[^ \t\n]|(?=~0)) // attacklab: g_tab_width
/g,function(){...});
*/
// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
text += '~0';
var pattern = /(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g;
text = text.replace(pattern, function (wholeMatch, m1, m2) {
var codeblock = m1, nextChar = m2;
codeblock = showdown.subParser('outdent')(codeblock);
codeblock = showdown.subParser('encodeCode')(codeblock);
codeblock = showdown.subParser('detab')(codeblock);
codeblock = codeblock.replace(/^\n+/g, ''); // trim leading newlines
codeblock = codeblock.replace(/\n+$/g, ''); // trim trailing whitespace
codeblock = '<pre><code>' + codeblock + '\n</code></pre>';
return showdown.subParser('hashBlock')(codeblock, options, globals) + nextChar;
});
// attacklab: strip sentinel
text = text.replace(/~0/, '');
return text;
});

View File

@ -0,0 +1,52 @@
/**
*
* * Backtick quotes are used for <code></code> spans.
*
* * You can use multiple backticks as the delimiters if you want to
* include literal backticks in the code span. So, this input:
*
* Just type ``foo `bar` baz`` at the prompt.
*
* Will translate to:
*
* <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
*
* There's no arbitrary limit to the number of backticks you
* can use as delimters. If you need three consecutive backticks
* in your code, use four for delimiters, etc.
*
* * You can use spaces to get literal backticks at the edges:
*
* ... type `` `bar` `` ...
*
* Turns to:
*
* ... type <code>`bar`</code> ...
*/
showdown.subParser('codeSpans', function (text) {
'use strict';
/*
text = text.replace(/
(^|[^\\]) // Character before opening ` can't be a backslash
(`+) // $2 = Opening run of `
( // $3 = The code block
[^\r]*?
[^`] // attacklab: work around lack of lookbehind
)
\2 // Matching closer
(?!`)
/gm, function(){...});
*/
text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm, function (wholeMatch, m1, m2, m3) {
var c = m3;
c = c.replace(/^([ \t]*)/g, ''); // leading whitespace
c = c.replace(/[ \t]*$/g, ''); // trailing whitespace
c = showdown.subParser('encodeCode')(c);
return m1 + '<code>' + c + '</code>';
});
return text;
});

32
src/subParsers/detab.js Normal file
View File

@ -0,0 +1,32 @@
/**
* Convert all tabs to spaces
*/
showdown.subParser('detab', function (text) {
'use strict';
// expand first n-1 tabs
text = text.replace(/\t(?=\t)/g, ' '); // g_tab_width
// replace the nth with two sentinels
text = text.replace(/\t/g, '~A~B');
// use the sentinel to anchor our regex so it doesn't explode
text = text.replace(/~B(.+?)~A/g, function (wholeMatch, m1) {
var leadingText = m1,
numSpaces = 4 - leadingText.length % 4; // g_tab_width
// there *must* be a better way to do this:
for (var i = 0; i < numSpaces; i++) {
leadingText += ' ';
}
return leadingText;
});
// clean up sentinels
text = text.replace(/~A/g, ' '); // g_tab_width
text = text.replace(/~B/g, '');
return text;
});

View File

@ -0,0 +1,14 @@
/**
* Smart processing for ampersands and angle brackets that need to be encoded.
*/
showdown.subParser('encodeAmpsAndAngles', function (text) {
'use strict';
// Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
// http://bumppo.net/projects/amputator/
text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, '&amp;');
// Encode naked <'s
text = text.replace(/<(?![a-z\/?\$!])/gi, '&lt;');
return text;
});

View File

@ -0,0 +1,17 @@
/**
* Returns the string, with after processing the following backslash escape sequences.
*
* attacklab: The polite way to do this is with the new escapeCharacters() function:
*
* text = escapeCharacters(text,"\\",true);
* text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
*
* ...but we're sidestepping its use of the (slow) RegExp constructor
* as an optimization for Firefox. This function gets called a LOT.
*/
showdown.subParser('encodeBackslashEscapes', function (text) {
'use strict';
text = text.replace(/\\(\\)/g, showdown.helper.escapeCharactersCallback);
text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, showdown.helper.escapeCharactersCallback);
return text;
});

View File

@ -0,0 +1,28 @@
/**
* Encode/escape certain characters inside Markdown code runs.
* The point is that in code, these characters are literals,
* and lose their special Markdown meanings.
*/
showdown.subParser('encodeCode', function (text) {
'use strict';
// Encode all ampersands; HTML entities are not
// entities within a Markdown code span.
text = text.replace(/&/g, '&amp;');
// Do the angle bracket song and dance:
text = text.replace(/</g, '&lt;');
text = text.replace(/>/g, '&gt;');
// Now, escape characters that are magic in Markdown:
text = showdown.helper.escapeCharacters(text, '*_{}[]\\', false);
// jj the line above breaks this:
//---
//* Item
// 1. Subitem
// special char: *
// ---
return text;
});

View File

@ -0,0 +1,52 @@
/**
* Input: an email address, e.g. "foo@example.com"
*
* Output: the email address as a mailto link, with each character
* of the address encoded as either a decimal or hex entity, in
* the hopes of foiling most address harvesting spam bots. E.g.:
*
* <a href="&#x6D;&#97;&#105;&#108;&#x74;&#111;:&#102;&#111;&#111;&#64;&#101;
* x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;">&#102;&#111;&#111;
* &#64;&#101;x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;</a>
*
* Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
* mailing list: <http://tinyurl.com/yu7ue>
*
*/
showdown.subParser('encodeEmailAddress', function (addr) {
'use strict';
var encode = [
function (ch) {
return '&#' + ch.charCodeAt(0) + ';';
},
function (ch) {
return '&#x' + ch.charCodeAt(0).toString(16) + ';';
},
function (ch) {
return ch;
}
];
addr = 'mailto:' + addr;
addr = addr.replace(/./g, function (ch) {
if (ch === '@') {
// this *must* be encoded. I insist.
ch = encode[Math.floor(Math.random() * 2)](ch);
} else if (ch !== ':') {
// leave ':' alone (to spot mailto: later)
var r = Math.random();
// roughly 10% raw, 45% hex, 45% dec
ch = (
r > 0.9 ? encode[2](ch) : r > 0.45 ? encode[1](ch) : encode[0](ch)
);
}
return ch;
});
addr = '<a href="' + addr + '">' + addr + '</a>';
addr = addr.replace(/">.+:/g, '">'); // strip the mailto: from the visible part
return addr;
});

View File

@ -0,0 +1,19 @@
/**
* Within tags -- meaning between < and > -- encode [\ ` * _] so they
* don't conflict with their use in Markdown for code, italics and strong.
*/
showdown.subParser('escapeSpecialCharsWithinTagAttributes', function (text) {
'use strict';
// Build a regex to find HTML tags and comments. See Friedl's
// "Mastering Regular Expressions", 2nd Ed., pp. 200-201.
var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi;
text = text.replace(regex, function (wholeMatch) {
var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, '$1`');
tag = showdown.helper.escapeCharacters(tag, '\\`*_', false);
return tag;
});
return text;
});

View File

@ -0,0 +1,40 @@
/**
* Handle github codeblocks prior to running HashHTML so that
* HTML contained within the codeblock gets escaped properly
* Example:
* ```ruby
* def hello_world(x)
* puts "Hello, #{x}"
* end
* ```
*/
showdown.subParser('githubCodeBlocks', function (text, options, globals) {
'use strict';
text += '~0';
text = text.replace(/(?:^|\n)```(.*)\n([\s\S]*?)\n```/g, function (wholeMatch, m1, m2) {
var language = m1,
codeblock = m2,
end = '\n';
if (options.omitExtraWLInCodeBlocks) {
end = '';
}
codeblock = showdown.subParser('encodeCode')(codeblock);
codeblock = showdown.subParser('detab')(codeblock);
codeblock = codeblock.replace(/^\n+/g, ''); // trim leading newlines
codeblock = codeblock.replace(/\n+$/g, ''); // trim trailing whitespace
codeblock = '<pre><code' + (language ? ' class="' + language + '"' : '') + '>' + codeblock + end + '</code></pre>';
return showdown.subParser('hashBlock')(codeblock, options, globals);
});
// attacklab: strip sentinel
text = text.replace(/~0/, '');
return text;
});

View File

@ -0,0 +1,5 @@
showdown.subParser('hashBlock', function (text, options, globals) {
'use strict';
text = text.replace(/(^\n+|\n+$)/g, '');
return '\n\n~K' + (globals.gHtmlBlocks.push(text) - 1) + 'K\n\n';
});

View File

@ -0,0 +1,19 @@
showdown.subParser('hashElement', function (text, options, globals) {
'use strict';
return function (wholeMatch, m1) {
var blockText = m1;
// Undo double lines
blockText = blockText.replace(/\n\n/g, '\n');
blockText = blockText.replace(/^\n/, '');
// strip trailing blank lines
blockText = blockText.replace(/\n+$/g, '');
// Replace the element text with a marker ("~KxK" where x is its key)
blockText = '\n\n~K' + (globals.gHtmlBlocks.push(blockText) - 1) + 'K\n\n';
return blockText;
};
});

View File

@ -0,0 +1,133 @@
showdown.subParser('hashHTMLBlocks', function (text, options, globals) {
'use strict';
// attacklab: Double up blank lines to reduce lookaround
text = text.replace(/\n/g, '\n\n');
// Hashify HTML blocks:
// We only want to do this for block-level HTML tags, such as headers,
// lists, and tables. That's because we still want to wrap <p>s around
// "paragraphs" that are wrapped in non-block-level tags, such as anchors,
// phrase emphasis, and spans. The list of tags we're looking for is
// hard-coded:
//var block_tags_a =
// 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del|style|section|header|footer|nav|article|aside';
// var block_tags_b =
// 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|style|section|header|footer|nav|article|aside';
// First, look for nested blocks, e.g.:
// <div>
// <div>
// tags for inner block must be indented.
// </div>
// </div>
//
// The outermost tags must start at the left margin for this to match, and
// the inner nested divs must be indented.
// We need to do this before the next, more liberal match, because the next
// match will start at the first `<div>` and stop at the first `</div>`.
// attacklab: This regex can be expensive when it fails.
/*
var text = text.replace(/
( // save in $1
^ // start of line (with /m)
<($block_tags_a) // start tag = $2
\b // word break
// attacklab: hack around khtml/pcre bug...
[^\r]*?\n // any number of lines, minimally matching
</\2> // the matching end tag
[ \t]* // trailing spaces/tabs
(?=\n+) // followed by a newline
) // attacklab: there are sentinel newlines at end of document
/gm,function(){...}};
*/
text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,
showdown.subParser('hashElement')(text, options, globals));
//
// Now match more liberally, simply from `\n<tag>` to `</tag>\n`
//
/*
var text = text.replace(/
( // save in $1
^ // start of line (with /m)
<($block_tags_b) // start tag = $2
\b // word break
// attacklab: hack around khtml/pcre bug...
[^\r]*? // any number of lines, minimally matching
</\2> // the matching end tag
[ \t]* // trailing spaces/tabs
(?=\n+) // followed by a newline
) // attacklab: there are sentinel newlines at end of document
/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|address|audio|canvas|figure|hgroup|output|video)\b[^\r]*?<\/\2>[ \t]*(?=\n+)\n)/gm,
showdown.subParser('hashElement')(text, options, globals));
// Special case just for <hr />. It was easier to make a special case than
// to make the other regex more complicated.
/*
text = text.replace(/
( // save in $1
\n\n // Starting after a blank line
[ ]{0,3}
(<(hr) // start tag = $2
\b // word break
([^<>])*? //
\/?>) // the matching end tag
[ \t]*
(?=\n{2,}) // followed by a blank line
)
/g,showdown.subParser('hashElement')(text, options, globals));
*/
text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,
showdown.subParser('hashElement')(text, options, globals));
// Special case for standalone HTML comments:
/*
text = text.replace(/
( // save in $1
\n\n // Starting after a blank line
[ ]{0,3} // attacklab: g_tab_width - 1
<!
(--[^\r]*?--\s*)+
>
[ \t]*
(?=\n{2,}) // followed by a blank line
)
/g,showdown.subParser('hashElement')(text, options, globals));
*/
text = text.replace(/(\n\n[ ]{0,3}<!(--[^\r]*?--\s*)+>[ \t]*(?=\n{2,}))/g,
showdown.subParser('hashElement')(text, options, globals));
// PHP and ASP-style processor instructions (<?...?> and <%...%>)
/*
text = text.replace(/
(?:
\n\n // Starting after a blank line
)
( // save in $1
[ ]{0,3} // attacklab: g_tab_width - 1
(?:
<([?%]) // $2
[^\r]*?
\2>
)
[ \t]*
(?=\n{2,}) // followed by a blank line
)
/g,showdown.subParser('hashElement')(text, options, globals));
*/
text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,
showdown.subParser('hashElement')(text, options, globals));
// attacklab: Undo double lines (see comment at top of this function)
text = text.replace(/\n\n/g, '\n');
return text;
});

73
src/subParsers/headers.js Normal file
View File

@ -0,0 +1,73 @@
showdown.subParser('headers', function (text, options, globals) {
'use strict';
var prefixHeader = options.prefixHeaderId;
// Set text-style headers:
// Header 1
// ========
//
// Header 2
// --------
//
text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm, function (wholeMatch, m1) {
var spanGamut = showdown.subParser('spanGamut')(m1, options, globals),
hashBlock = '<h1 id="' + headerId(m1) + '">' + spanGamut + '</h1>';
return showdown.subParser('hashBlock')(hashBlock, options, globals);
});
text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, function (matchFound, m1) {
var spanGamut = showdown.subParser('spanGamut')(m1, options, globals),
hashBlock = '<h2 id="' + headerId(m1) + '">' + spanGamut + '</h2>';
return showdown.subParser('hashBlock')(hashBlock, options, globals);
});
// atx-style headers:
// # Header 1
// ## Header 2
// ## Header 2 with closing hashes ##
// ...
// ###### Header 6
//
/*
text = text.replace(/
^(\#{1,6}) // $1 = string of #'s
[ \t]*
(.+?) // $2 = Header text
[ \t]*
\#* // optional closing #'s (not counted)
\n+
/gm, function() {...});
*/
text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm, function (wholeMatch, m1, m2) {
var span = showdown.subParser('spanGamut')(m2, options, globals),
header = '<h' + m1.length + ' id="' + headerId(m2) + '">' + span + '</h' + m1.length + '>';
return showdown.subParser('hashBlock')(header, options, globals);
});
function headerId(m) {
var title, escapedId = m.replace(/[^\w]/g, '').toLowerCase();
if (globals.hashLinkCounts[escapedId]) {
title = escapedId + '-' + (globals.hashLinkCounts[escapedId]++);
} else {
title = escapedId;
globals.hashLinkCounts[escapedId] = 1;
}
// Prefix id to prevent causing inadvertent pre-existing style matches.
if (prefixHeader === true) {
prefixHeader = 'section';
}
if (showdown.helper.isString(prefixHeader)) {
return prefixHeader + title;
}
return title;
}
return text;
});

102
src/subParsers/images.js Normal file
View File

@ -0,0 +1,102 @@
/**
* Turn Markdown image shortcuts into <img> tags.
*/
showdown.subParser('images', function (text, options, globals) {
'use strict';
var writeImageTag = function (wholeMatch, m1, m2, m3, m4, m5, m6, m7) {
wholeMatch = m1;
var altText = m2,
linkId = m3.toLowerCase(),
url = m4,
title = m7,
gUrls = globals.gUrls,
gTitles = globals.gTitles;
if (!title) {
title = '';
}
if (url === '' || url === null) {
if (linkId === '' || linkId === null) {
// lower-case and turn embedded newlines into spaces
linkId = altText.toLowerCase().replace(/ ?\n/g, ' ');
}
url = '#' + linkId;
if (typeof gUrls[linkId] !== 'undefined') {
url = gUrls[linkId];
if (typeof gTitles[linkId] !== 'undefined') {
title = gTitles[linkId];
}
} else {
return wholeMatch;
}
}
altText = altText.replace(/"/g, '&quot;');
url = showdown.helper.escapeCharacters(url, '*_', false);
var result = '<img src="' + url + '" alt="' + altText + '"';
// attacklab: Markdown.pl adds empty title attributes to images.
// Replicate this bug.
//if (title != "") {
title = title.replace(/"/g, '&quot;');
title = showdown.helper.escapeCharacters(title, '*_', false);
result += ' title="' + title + '"';
//}
result += ' />';
return result;
};
// First, handle reference-style labeled images: ![alt text][id]
/*
text = text.replace(/
( // wrap whole match in $1
!\[
(.*?) // alt text = $2
\]
[ ]? // one optional space
(?:\n[ ]*)? // one optional newline followed by spaces
\[
(.*?) // id = $3
\]
)()()()() // pad rest of backreferences
/g,writeImageTag);
*/
text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeImageTag);
// Next, handle inline images: ![alt text](url "optional title")
// Don't forget: encode * and _
/*
text = text.replace(/
( // wrap whole match in $1
!\[
(.*?) // alt text = $2
\]
\s? // One optional whitespace character
\( // literal paren
[ \t]*
() // no id, so leave $3 empty
<?(\S+?)>? // src url = $4
[ \t]*
( // $5
(['"]) // quote char = $6
(.*?) // title = $7
\6 // matching quote
[ \t]*
)? // title is optional
\)
)
/g,writeImageTag);
*/
text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeImageTag);
return text;
});

View File

@ -0,0 +1,9 @@
showdown.subParser('italicsAndBold', function (text) {
'use strict';
// <strong> must go first:
text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g, '<strong>$2</strong>');
text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g, '<em>$2</em>');
return text;
});

View File

@ -0,0 +1,11 @@
/**
* Run language extensions
*/
showdown.subParser('languageExtensions', function (text, config, globals) {
'use strict';
showdown.helper.forEach(globals.langExtensions, function (ext) {
text = showdown.subParser('runExtension')(ext, text);
});
return text;
});

144
src/subParsers/lists.js Normal file
View File

@ -0,0 +1,144 @@
/**
* Form HTML ordered (numbered) and unordered (bulleted) lists.
*/
showdown.subParser('lists', function (text, options, globals) {
'use strict';
/**
* Process the contents of a single ordered or unordered list, splitting it
* into individual list items.
* @param {string} listStr
* @returns {string|*}
*/
var processListItems = function (listStr) {
// The $g_list_level global keeps track of when we're inside a list.
// Each time we enter a list, we increment it; when we leave a list,
// we decrement. If it's zero, we're not in a list anymore.
//
// We do this because when we're not inside a list, we want to treat
// something like this:
//
// I recommend upgrading to version
// 8. Oops, now this line is treated
// as a sub-list.
//
// As a single paragraph, despite the fact that the second line starts
// with a digit-period-space sequence.
//
// Whereas when we're inside a list (or sub-list), that line will be
// treated as the start of a sub-list. What a kludge, huh? This is
// an aspect of Markdown's syntax that's hard to parse perfectly
// without resorting to mind-reading. Perhaps the solution is to
// change the syntax rules such that sub-lists must start with a
// starting cardinal number; e.g. "1." or "a.".
globals.gListLevel++;
// trim trailing blank lines:
listStr = listStr.replace(/\n{2,}$/, '\n');
// attacklab: add sentinel to emulate \z
listStr += '~0';
/*
list_str = list_str.replace(/
(\n)? // leading line = $1
(^[ \t]*) // leading whitespace = $2
([*+-]|\d+[.]) [ \t]+ // list marker = $3
([^\r]+? // list item text = $4
(\n{1,2}))
(?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+))
/gm, function(){...});
*/
listStr = listStr.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,
function (wholeMatch, m1, m2, m3, m4) {
var item = showdown.subParser('outdent')(m4, options, globals);
//m1 - LeadingLine
if (m1 || (item.search(/\n{2,}/) > -1)) {
item = showdown.subParser('blockGamut')(item, options, globals);
} else {
// Recursion for sub-lists:
item = showdown.subParser('lists')(item, options, globals);
item = item.replace(/\n$/, ''); // chomp(item)
item = showdown.subParser('spanGamut')(item, options, globals);
}
return '<li>' + item + '</li>\n';
});
// attacklab: strip sentinel
listStr = listStr.replace(/~0/g, '');
globals.gListLevel--;
return listStr;
};
// attacklab: add sentinel to hack around khtml/safari bug:
// http://bugs.webkit.org/show_bug.cgi?id=11231
text += '~0';
// Re-usable pattern to match any entirel ul or ol list:
/*
var whole_list = /
( // $1 = whole list
( // $2
[ ]{0,3} // attacklab: g_tab_width - 1
([*+-]|\d+[.]) // $3 = first list item marker
[ \t]+
)
[^\r]+?
( // $4
~0 // sentinel for workaround; should be $
|
\n{2,}
(?=\S)
(?! // Negative lookahead for another list item marker
[ \t]*
(?:[*+-]|\d+[.])[ \t]+
)
)
)/g
*/
var wholeList = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
if (globals.gListLevel) {
text = text.replace(wholeList, function (wholeMatch, m1, m2) {
var list = m1,
listType = (m2.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
// Turn double returns into triple returns, so that we can make a
// paragraph for the last item in a list, if necessary:
list = list.replace(/\n{2,}/g, '\n\n\n');
var result = processListItems(list);
// Trim any trailing whitespace, to put the closing `</$list_type>`
// up on the preceding line, to get it past the current stupid
// HTML block parser. This is a hack to work around the terrible
// hack that is the HTML block parser.
result = result.replace(/\s+$/, '');
result = '<' + listType + '>' + result + '</' + listType + '>\n';
return result;
});
} else {
wholeList = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;
text = text.replace(wholeList, function (wholeMatch, m1, m2, m3) {
// Turn double returns into triple returns, so that we can make a
// paragraph for the last item in a list, if necessary:
var list = m2.replace(/\n{2,}/g, '\n\n\n'),
listType = (m3.search(/[*+-]/g) > -1) ? 'ul' : 'ol',
result = processListItems(list);
return m1 + '<' + listType + '>\n' + result + '</' + listType + '>\n';
});
}
// attacklab: strip sentinel
text = text.replace(/~0/, '');
return text;
});

15
src/subParsers/outdent.js Normal file
View File

@ -0,0 +1,15 @@
/**
* Remove one level of line-leading tabs or spaces
*/
showdown.subParser('outdent', function (text) {
'use strict';
// attacklab: hack around Konqueror 3.5.4 bug:
// "----------bug".replace(/^-/g,"") == "bug"
text = text.replace(/^(\t|[ ]{1,4})/gm, '~0'); // attacklab: g_tab_width
// attacklab: clean up hack
text = text.replace(/~0/g, '');
return text;
});

View File

@ -0,0 +1,11 @@
/**
* Run language extensions
*/
showdown.subParser('outputModifiers', function (text, config, globals) {
'use strict';
showdown.helper.forEach(globals.outputModifiers, function (ext) {
text = showdown.subParser('runExtension')(ext, text);
});
return text;
});

View File

@ -0,0 +1,41 @@
/**
*
*/
showdown.subParser('paragraphs', function (text, options, globals) {
'use strict';
// Strip leading and trailing lines:
text = text.replace(/^\n+/g, '');
text = text.replace(/\n+$/g, '');
var grafs = text.split(/\n{2,}/g),
grafsOut = [],
end = grafs.length; // Wrap <p> tags
for (var i = 0; i < end; i++) {
var str = grafs[i];
// if this is an HTML marker, copy it
if (str.search(/~K(\d+)K/g) >= 0) {
grafsOut.push(str);
} else if (str.search(/\S/) >= 0) {
str = showdown.subParser('spanGamut')(str, options, globals);
str = str.replace(/^([ \t]*)/g, '<p>');
str += '</p>';
grafsOut.push(str);
}
}
/** Unhashify HTML blocks */
end = grafsOut.length;
for (i = 0; i < end; i++) {
// if this is a marker for an html block...
while (grafsOut[i].search(/~K(\d+)K/) >= 0) {
var blockText = globals.gHtmlBlocks[RegExp.$1];
blockText = blockText.replace(/\$/g, '$$$$'); // Escape any dollar signs
grafsOut[i] = grafsOut[i].replace(/~K\d+K/, blockText);
}
}
return grafsOut.join('\n\n');
});

View File

@ -0,0 +1,13 @@
/**
* Run language extensions
*/
showdown.subParser('runExtension', function (ext, text) {
'use strict';
if (ext.regex) {
var re = new RegExp(ext.regex, 'g');
return text.replace(re, ext.replace);
} else if (ext.filter) {
return ext.filter(text);
}
});

View File

@ -0,0 +1,29 @@
/**
* These are all the transformations that occur *within* block-level
* tags like paragraphs, headers, and list items.
*/
showdown.subParser('spanGamut', function (text, options, globals) {
'use strict';
text = showdown.subParser('codeSpans')(text, options, globals);
text = showdown.subParser('escapeSpecialCharsWithinTagAttributes')(text, options, globals);
text = showdown.subParser('encodeBackslashEscapes')(text, options, globals);
// Process anchor and image tags. Images must come first,
// because ![foo][f] looks like an anchor.
text = showdown.subParser('images')(text, options, globals);
text = showdown.subParser('anchors')(text, options, globals);
// Make links out of things like `<http://example.com/>`
// Must come after _DoAnchors(), because you can use < and >
// delimiters in inline links like [this](<url>).
text = showdown.subParser('autoLinks')(text, options, globals);
text = showdown.subParser('encodeAmpsAndAngles')(text, options, globals);
text = showdown.subParser('italicsAndBold')(text, options, globals);
// Do hard breaks:
text = text.replace(/ +\n/g, ' <br />\n');
return text;
});

View File

@ -0,0 +1,10 @@
/**
* Strip any lines consisting only of spaces and tabs.
* This makes subsequent regexs easier to write, because we can
* match consecutive blank lines with /\n+/ instead of something
* contorted like /[ \t]*\n+/
*/
showdown.subParser('stripBlankLines', function (text) {
'use strict';
return text.replace(/^[ \t]+$/mg, '');
});

View File

@ -0,0 +1,54 @@
/**
* Strips link definitions from text, stores the URLs and titles in
* hash references.
* Link defs are in the form: ^[id]: url "optional title"
*
* ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1
* [ \t]*
* \n? // maybe *one* newline
* [ \t]*
* <?(\S+?)>? // url = $2
* [ \t]*
* \n? // maybe one newline
* [ \t]*
* (?:
* (\n*) // any lines skipped = $3 attacklab: lookbehind removed
* ["(]
* (.+?) // title = $4
* [")]
* [ \t]*
* )? // title is optional
* (?:\n+|$)
* /gm,
* function(){...});
*
*/
showdown.subParser('stripLinkDefinitions', function (text, options, globals) {
'use strict';
var regex = /^[ ]{0,3}\[(.+)]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|(?=~0))/gm;
// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
text += '~0';
text = text.replace(regex, function (wholeMatch, m1, m2, m3, m4) {
m1 = m1.toLowerCase();
globals.gUrls[m1] = showdown.subParser('encodeAmpsAndAngles')(m2); // Link IDs are case-insensitive
if (m3) {
// Oops, found blank lines, so it's not a title.
// Put back the parenthetical statement we stole.
return m3 + m4;
} else if (m4) {
globals.gTitles[m1] = m4.replace(/"/g, '&quot;');
}
// Completely remove the definition from the text
return '';
});
// attacklab: strip sentinel
text = text.replace(/~0/, '');
return text;
});

View File

@ -0,0 +1,12 @@
/**
* Swap back in all the special characters we've hidden.
*/
showdown.subParser('unescapeSpecialChars', function (text) {
'use strict';
text = text.replace(/~E(\d+)E/g, function (wholeMatch, m1) {
var charCodeToReplace = parseInt(m1);
return String.fromCharCode(charCodeToReplace);
});
return text;
});

View File

@ -1,5 +1,4 @@
<p>This is <a href="http://example.com/" title="Optional Title Here">an example</a> reference-style link. <p>This is <a href="http://example.com/" title="Optional Title Here">an example</a> reference-style link.
This is <a href="http://example.com/" title="Optional Title Here">another</a> reference-style link. This is <a href="http://example.com/" title="Optional Title Here">another</a> reference-style link.
This is <a href="http://example.com/" title="Optional Title Here">a third</a> reference-style link. This is <a href="http://example.com/" title="Optional Title Here">a third</a> reference-style link.
This is <a href="http://example.com/" title="Optional Title Here">a fourth</a> reference-style link.</p> This is <a href="http://example.com/" title="Optional Title Here">a fourth</a> reference-style link.</p>

View File

@ -1,2 +1 @@
<p><a href="http://example.com/">http://example.com/</a></p>
<p><a href="http://example.com/">http://example.com/</a></p>

View File

@ -1,12 +1,12 @@
<blockquote> <blockquote>
<h2 id="thisisaheader">This is a header.</h2> <h2 id="thisisaheader">This is a header.</h2>
<ol> <ol>
<li>This is the first list item.</li> <li>This is the first list item.</li>
<li>This is the second list item.</li> <li>This is the second list item.</li>
</ol> </ol>
<p>Here's some example code:</p> <p>Here's some example code:</p>
<pre><code>return shell_exec("echo $input | $markdown_script"); <pre><code>return shell_exec("echo $input | $markdown_script");
</code></pre> </code></pre>

View File

@ -1,5 +1,5 @@
<blockquote> <blockquote>
<p>This is a multi line blockquote test</p> <p>This is a multi line blockquote test</p>
<p>With more than one line.</p> <p>With more than one line.</p>
</blockquote> </blockquote>

View File

@ -1,5 +1,4 @@
<p>This is some HTML:</p> <p>This is some HTML:</p>
<pre><code>&lt;h1&gt;Heading&lt;/h1&gt; <pre><code>&lt;h1&gt;Heading&lt;/h1&gt;
</code></pre> </code></pre>

View File

@ -1,5 +1,4 @@
<p>This is a normal paragraph:</p> <p>This is a normal paragraph:</p>
<pre><code>This is a code block. <pre><code>This is a code block.
</code></pre> </code></pre>

View File

@ -1,5 +1,4 @@
<ul> <ul>
<li><p>Bird</p></li> <li><p>Bird</p></li>
<li><p>Magic</p></li> <li><p>Magic</p></li>
</ul> </ul>

View File

@ -1,8 +1,66 @@
<p><em>single asterisks</em></p>
<p><em>important</em></p> <p><em>single underscores</em></p>
<p><em>important</em></p> <p><strong>double asterisks</strong></p>
<p>this mid<em>important</em>sentence</p> <p><strong>double underscores</strong></p>
<p>*not important*</p> <p>text <em>with italic sentence</em> in middle</p>
<p>text <strong>with bold sentence</strong> in middle</p>
<p>text with <strong>bold text that
spans across multiple</strong> lines</p>
<p>underscored_word</p>
<p>doubleunderscore__word</p>
<p>asterix*word</p>
<p>doubleasterix**word</p>
<p>line with_underscored word</p>
<p>line with__doubleunderscored word</p>
<p>line with*asterixed word</p>
<p>line with**doubleasterixed word</p>
<p>some line<em>with</em>inner underscores</p>
<p>some line<strong>with</strong>inner double underscores</p>
<p>some line<em>with</em>inner asterixs</p>
<p>some line<strong>with</strong>inner double asterixs</p>
<p>another line with just _one underscore</p>
<p>another line with just __one double underscore</p>
<p>another line with just *one asterix</p>
<p>another line with just **one double asterix</p>
<p>a sentence with<em>underscore and another</em>underscore</p>
<p>a sentence with<strong>doubleunderscore and another</strong>doubleunderscore</p>
<p>a sentence with<em>asterix and another</em>asterix</p>
<p>a sentence with<strong>doubleasterix and another</strong>doubleasterix</p>
<p>escaped word_with_underscores</p>
<p>escaped word__with__double underscores</p>
<p>escaped word<em>_with_</em>single italic underscore</p>
<p>escaped word*with*asterixs</p>
<p>escaped word**with**asterixs</p>
<p>escaped word<strong>*with*</strong>bold asterixs</p>

View File

@ -1,8 +1,66 @@
*single asterisks*
*important* _single underscores_
_important_ **double asterisks**
this mid*important*sentence __double underscores__
\*not important\* text *with italic sentence* in middle
text __with bold sentence__ in middle
text with __bold text that
spans across multiple__ lines
underscored_word
doubleunderscore__word
asterix*word
doubleasterix**word
line with_underscored word
line with__doubleunderscored word
line with*asterixed word
line with**doubleasterixed word
some line_with_inner underscores
some line__with__inner double underscores
some line*with*inner asterixs
some line**with**inner double asterixs
another line with just _one underscore
another line with just __one double underscore
another line with just *one asterix
another line with just **one double asterix
a sentence with_underscore and another_underscore
a sentence with__doubleunderscore and another__doubleunderscore
a sentence with*asterix and another*asterix
a sentence with**doubleasterix and another**doubleasterix
escaped word\_with\_underscores
escaped word\_\_with\_\_double underscores
escaped word_\_with\__single italic underscore
escaped word\*with*asterixs
escaped word\*\*with\*\*asterixs
escaped word**\*with\***bold asterixs

View File

@ -1 +1 @@
<p>It happened in 1986. What a great season.</p> <p>It happened in 1986. What a great season.</p>

View File

@ -1 +1 @@
It happened in 1986\. What a great season. It happened in 1986\. What a great season.

View File

@ -1,4 +1,3 @@
<p>These should all be escaped:</p> <p>These should all be escaped:</p>
<p>\</p> <p>\</p>
@ -29,4 +28,4 @@
<p>.</p> <p>.</p>
<p>!</p> <p>!</p>

View File

@ -1,7 +1,6 @@
<pre><code>function MyFunc(a) { <pre><code>function MyFunc(a) {
// ... // ...
} }
</code></pre> </code></pre>
<p>That is some code!</p> <p>That is some code!</p>

View File

@ -1,13 +1,11 @@
<p>Define a function in javascript:</p> <p>Define a function in javascript:</p>
<pre><code>function MyFunc(a) { <pre><code>function MyFunc(a) {
var s = '`'; var s = '`';
} }
</code></pre> </code></pre>
<p>And some HTML</p> <p>And some HTML</p>
<pre><code class="html">&lt;div&gt;HTML!&lt;/div&gt; <pre><code class="html">&lt;div&gt;HTML!&lt;/div&gt;
</code></pre> </code></pre>

View File

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

View File

@ -1 +1 @@
<h1 id="thisisanh1">This is an H1</h1> <h1 id="thisisanh1">This is an H1</h1>

View File

@ -1 +1 @@
<h1 id="thisisanh1">This is an H1</h1> <h1 id="thisisanh1">This is an H1</h1>

View File

@ -1 +1 @@
<h1 id="thisisanh1">This is an H1</h1> <h1 id="thisisanh1">This is an H1</h1>

View File

@ -1 +1 @@
<h2 id="thisisanh2">This is an H2</h2> <h2 id="thisisanh2">This is an H2</h2>

View File

@ -1 +1 @@
<h2 id="thisisanh2">This is an H2</h2> <h2 id="thisisanh2">This is an H2</h2>

View File

@ -1 +1 @@
<h2 id="thisisanh2">This is an H2</h2> <h2 id="thisisanh2">This is an H2</h2>

View File

@ -1 +1 @@
<h3 id="thisisanh3">This is an H3</h3> <h3 id="thisisanh3">This is an H3</h3>

View File

@ -1 +1 @@
<h3 id="thisisanh3">This is an H3</h3> <h3 id="thisisanh3">This is an H3</h3>

View File

@ -1 +1 @@
<h4 id="thisisanh4">This is an H4</h4> <h4 id="thisisanh4">This is an H4</h4>

View File

@ -1 +1 @@
<h5 id="thisisanh5">This is an H5</h5> <h5 id="thisisanh5">This is an H5</h5>

View File

@ -1 +1 @@
<h6 id="thisisanh6">This is an H6</h6> <h6 id="thisisanh6">This is an H6</h6>

View File

@ -1,4 +1,3 @@
<hr /> <hr />
<hr /> <hr />
@ -7,4 +6,4 @@
<hr /> <hr />
<hr /> <hr />

View File

@ -1,4 +1,3 @@
<p>These HTML5 tags should pass through just fine.</p> <p>These HTML5 tags should pass through just fine.</p>
<section>hello</section> <section>hello</section>
@ -14,10 +13,63 @@
<aside>ignore me</aside> <aside>ignore me</aside>
<article>read <article>read
me</article> me</article>
<aside> <aside>
ignore me ignore me
</aside> </aside>
<p>the end</p> <p>the end</p>
<table class="test">
<tr>
<td>Foo</td>
</tr>
<tr>
<td>Bar</td>
</tr>
</table>
<table class="test">
<thead>
<tr>
<td>Foo</td>
</tr>
</thead>
<tr>
<td>Bar</td>
</tr>
<tfoot>
<tr>
<td>Bar</td>
</tr>
</tfoot>
</table>
<audio class="podcastplayer" controls>
<source src="foobar.mp3" type="audio/mp3" preload="none"></source>
<source src="foobar.off" type="audio/ogg" preload="none"></source>
</audio>
<video src="foo.ogg">
<track kind="subtitles" src="foo.en.vtt" srclang="en" label="English">
<track kind="subtitles" src="foo.sv.vtt" srclang="sv" label="Svenska">
</video>
<address>My street</address>
<canvas id="canvas" width="300" height="300">
Sorry, your browser doesn't support the &lt;canvas&gt; element.
</canvas>
<figure>
<img src="mypic.png" alt="An awesome picture">
<figcaption>Caption for the awesome picture</figcaption>
</figure>
<hgroup>
<h1>Main title</h1>
<h2>Secondary title</h2>
</hgroup>
<output name="result"></output>

View File

@ -1,6 +1,5 @@
<p><img src="/path/to/img.jpg" alt="Alt text" title="" /></p> <p><img src="/path/to/img.jpg" alt="Alt text" title="" /></p>
<p><img src="/path/to/img.jpg" alt="Alt text" title="Optional title" /></p> <p><img src="/path/to/img.jpg" alt="Alt text" title="Optional title" /></p>
<p><img src="url/to/image" alt="Alt text" title="Optional title attribute" /></p> <p><img src="url/to/image" alt="Alt text" title="Optional title attribute" /></p>

View File

@ -1,2 +1 @@
<p>Search the web at <a href="http://google.com/">Google</a> or <a href="http://daringfireball.net/">Daring Fireball</a>.</p>
<p>Search the web at <a href="http://google.com/">Google</a> or <a href="http://daringfireball.net/">Daring Fireball</a>.</p>

View File

@ -1,4 +1,3 @@
<p>This is <a href="http://example.com/" title="Title">an example</a> inline link.</p> <p>This is <a href="http://example.com/" title="Title">an example</a> inline link.</p>
<p><a href="http://example.net/">This link</a> has no title attribute.</p> <p><a href="http://example.net/">This link</a> has no title attribute.</p>

View File

@ -1,4 +1,3 @@
<p>Create a new <code>function</code>.</p> <p>Create a new <code>function</code>.</p>
<p>Use the backtick in MySQL syntax <code>SELECT `column` FROM whatever</code>.</p> <p>Use the backtick in MySQL syntax <code>SELECT `column` FROM whatever</code>.</p>
@ -9,4 +8,4 @@
<p>Please don't use any <code>&lt;blink&gt;</code> tags.</p> <p>Please don't use any <code>&lt;blink&gt;</code> tags.</p>
<p><code>&amp;#8212;</code> is the decimal-encoded equivalent of <code>&amp;mdash;</code>.</p> <p><code>&amp;#8212;</code> is the decimal-encoded equivalent of <code>&amp;mdash;</code>.</p>

View File

@ -1,3 +1,2 @@
<p>Hello.this_is_a_variable <p>Hello.this_is_a_variable
and.this.is.another_one</p> and.this.is.another_one</p>

View File

@ -1,6 +1,5 @@
<style> <style>
p { line-height: 20px; } p { line-height: 20px; }
</style> </style>
<p>An exciting sentence.</p> <p>An exciting sentence.</p>

View File

@ -1,5 +1,5 @@
<blockquote> <blockquote>
<p>This is a multi line blockquote test</p> <p>This is a multi line blockquote test</p>
<p>With more than one line.</p> <p>With more than one line.</p>
</blockquote> </blockquote>

View File

@ -1,8 +1,8 @@
<ul> <ul>
<li><p>A list item with a blockquote:</p> <li><p>A list item with a blockquote:</p>
<blockquote> <blockquote>
<p>This is a blockquote <p>This is a blockquote
inside a list item.</p> inside a list item.</p>
</blockquote></li> </blockquote></li>
</ul> </ul>

View File

@ -1,6 +1,6 @@
<ul> <ul>
<li><p>A list item with code:</p> <li><p>A list item with code:</p>
<pre><code>alert('Hello world!'); <pre><code>alert('Hello world!');
</code></pre></li> </code></pre></li>
</ul> </ul>

View File

@ -1,6 +1,6 @@
<ol> <ol>
<li><p>This is a major bullet point.</p> <li><p>This is a major bullet point.</p>
<p>That contains multiple paragraphs.</p></li> <p>That contains multiple paragraphs.</p></li>
<li><p>And another line</p></li> <li><p>And another line</p></li>
</ol> </ol>

View File

@ -1,5 +1,5 @@
<ul> <ul>
<li>This line spans <li>This line spans
more than one line and is lazy</li> more than one line and is lazy</li>
<li>Similar to this line</li> <li>Similar to this line</li>
</ul> </ul>

View File

@ -1,9 +1,9 @@
<blockquote> <blockquote>
<p>This is a multi line blockquote test</p> <p>This is a multi line blockquote test</p>
<blockquote> <blockquote>
<p>And nesting!</p> <p>And nesting!</p>
</blockquote> </blockquote>
<p>With more than one line.</p> <p>With more than one line.</p>
</blockquote> </blockquote>

View File

@ -1,5 +1,5 @@
<ol> <ol>
<li>Red</li> <li>Red</li>
<li>Green</li> <li>Green</li>
<li>Blue</li> <li>Blue</li>
</ol> </ol>

View File

@ -1,5 +1,5 @@
<ol> <ol>
<li>Red</li> <li>Red</li>
<li>Green</li> <li>Green</li>
<li>Blue</li> <li>Blue</li>
</ol> </ol>

View File

@ -1,5 +1,5 @@
<ol> <ol>
<li>Red</li> <li>Red</li>
<li>Green</li> <li>Green</li>
<li>Blue</li> <li>Blue</li>
</ol> </ol>

View File

@ -1,2 +1 @@
<p>See my <a href="/about/">About</a> page for details.</p>
<p>See my <a href="/about/">About</a> page for details.</p>

View File

@ -0,0 +1,5 @@
<h1 id="sametitle">Same Title</h1>
<p>some text</p>
<h1 id="sametitle-1">Same Title</h1>

View File

@ -0,0 +1,5 @@
# Same Title
some text
# Same Title

View File

@ -1 +1 @@
<p>Hello, world!</p> <p>Hello, world!</p>

View File

@ -1,6 +1,5 @@
<p><strong>important</strong></p> <p><strong>important</strong></p>
<p><strong>important</strong></p> <p><strong>important</strong></p>
<p>really <strong>freaking</strong>strong</p> <p>really <strong>freaking</strong>strong</p>

View File

@ -1,5 +1,5 @@
<ul> <ul>
<li>Red</li> <li>Red</li>
<li>Green</li> <li>Green</li>
<li>Blue</li> <li>Blue</li>
</ul> </ul>

View File

@ -1,5 +1,5 @@
<ul> <ul>
<li>Red</li> <li>Red</li>
<li>Green</li> <li>Green</li>
<li>Blue</li> <li>Blue</li>
</ul> </ul>

Some files were not shown because too many files have changed in this diff Show More