mirror of
https://github.com/showdownjs/showdown.git
synced 2024-03-22 13:30:55 +08:00
Merge branch 'showdown2'
This commit is contained in:
commit
612c8ce661
15
.editorconfig
Normal file
15
.editorconfig
Normal 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
7
.gitattributes
vendored
|
@ -1,7 +1,6 @@
|
|||
/test export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
/perlMarkdown export-ignore
|
||||
/example export-ignore
|
||||
grunt.js export-ignore
|
||||
|
||||
.jshintignore export-ignore
|
||||
.travis.yml export-ignore
|
||||
Gruntfile.js export-ignore
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
|||
.idea/
|
||||
build/
|
||||
.DS_Store
|
||||
node_modules
|
||||
npm-debug.log
|
||||
.idea/
|
||||
|
|
90
.jscs.json
Normal file
90
.jscs.json
Normal 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
|
||||
}
|
|
@ -1,2 +1,3 @@
|
|||
Gruntfile.js
|
||||
dist/**/*.js
|
||||
dist/**/*.js
|
||||
build/**/*.js
|
||||
|
|
28
.jshintrc
Normal file
28
.jshintrc
Normal 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
|
||||
}
|
||||
}
|
|
@ -6,3 +6,9 @@ node_js:
|
|||
before_install:
|
||||
- '[ "${TRAVIS_NODE_VERSION}" != "0.8" ] || npm install -g npm@1.4.28'
|
||||
- npm install -g npm@latest
|
||||
|
||||
#travis build speed up
|
||||
sudo: false
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
|
|
30
CHANGELOG.md
Normal file
30
CHANGELOG.md
Normal 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
39
CREDITS.md
Normal 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
|
173
Gruntfile.js
173
Gruntfile.js
|
@ -4,97 +4,94 @@
|
|||
|
||||
module.exports = function (grunt) {
|
||||
|
||||
// Project configuration.
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
concat: {
|
||||
options: {
|
||||
separator: ';',
|
||||
sourceMap: true
|
||||
},
|
||||
dist: {
|
||||
src: ['src/showdown.js', 'src/*.js'],
|
||||
dest: 'compressed/<%= pkg.name %>.js'
|
||||
},
|
||||
github_ext: {
|
||||
src: ['src/extensions/github.js'],
|
||||
dest: 'compressed/extensions/github.min.js'
|
||||
},
|
||||
prettify_ext: {
|
||||
src: ['src/extensions/prettify.js'],
|
||||
dest: 'compressed/extensions/prettify.min.js'
|
||||
},
|
||||
table_ext: {
|
||||
src: ['src/extensions/table.js'],
|
||||
dest: 'compressed/extensions/table.min.js'
|
||||
},
|
||||
twitter_ext: {
|
||||
src: ['src/extensions/twitter.js'],
|
||||
dest: 'compressed/extensions/twitter.min.js'
|
||||
}
|
||||
},
|
||||
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'
|
||||
}
|
||||
}
|
||||
// Project configuration.
|
||||
var config = {
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
concat: {
|
||||
options: {
|
||||
sourceMap: true,
|
||||
banner: ';/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n(function(){\n',
|
||||
footer: '}).call(this)'
|
||||
},
|
||||
dist: {
|
||||
src: [
|
||||
'src/showdown.js',
|
||||
'src/helpers.js',
|
||||
'src/subParsers/*.js',
|
||||
'src/loader.js'
|
||||
],
|
||||
dest: 'dist/<%= pkg.name %>.js'
|
||||
}
|
||||
},
|
||||
uglify: {
|
||||
options: {
|
||||
sourceMap: true,
|
||||
banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n'
|
||||
},
|
||||
dist: {
|
||||
files: {
|
||||
'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>']
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
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.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||
grunt.loadNpmTasks('grunt-simple-mocha');
|
||||
grunt.initConfig(config);
|
||||
|
||||
// test
|
||||
grunt.registerTask('lint', ['jshint']);
|
||||
grunt.registerTask('test', ['simplemocha']);
|
||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||
grunt.loadNpmTasks('grunt-simple-mocha');
|
||||
grunt.loadNpmTasks('grunt-jscs');
|
||||
grunt.loadNpmTasks('grunt-conventional-changelog');
|
||||
|
||||
// build with uglify
|
||||
grunt.registerTask('build', ['concat', 'uglify']);
|
||||
|
||||
// Build with closure compiler
|
||||
grunt.registerTask('build-with-closure', ['test', 'concat', 'closure-compiler']);
|
||||
|
||||
// Default task(s).
|
||||
grunt.registerTask('default', []);
|
||||
grunt.registerTask('lint', ['jshint', 'jscs']);
|
||||
grunt.registerTask('test', ['lint', 'concat', 'simplemocha']);
|
||||
grunt.registerTask('test-without-building', ['simplemocha']);
|
||||
grunt.registerTask('build', ['lint', 'test', 'uglify']);
|
||||
|
||||
// Default task(s).
|
||||
grunt.registerTask('default', []);
|
||||
};
|
||||
|
|
615
README.md
615
README.md
|
@ -1,317 +1,298 @@
|
|||
# Showdown
|
||||
|
||||
A JavaScript port of Markdown
|
||||
|
||||
## Note
|
||||
|
||||
> Showdown is now maintained by the [showdownjs](https://github.com/showdownjs) organization on Github.
|
||||
>
|
||||
> The organization needs members to maintain Showdown.
|
||||
>
|
||||
> Please see [this issue](https://github.com/showdownjs/showdown/issues/114) to express interest or comment on this note.
|
||||
|
||||
## Original Attributions
|
||||
|
||||
Showdown Copyright (c) 2007 John Fraser.
|
||||
<http://www.attacklab.net/>
|
||||
|
||||
Original Markdown Copyright (c) 2004-2005 John Gruber
|
||||
<http://daringfireball.net/projects/markdown/>
|
||||
|
||||
Redistributable under a BSD-style open source license.
|
||||
See license.txt for more information.
|
||||
|
||||
## Quick Example
|
||||
|
||||
```js
|
||||
var Showdown = require('showdown');
|
||||
var converter = new Showdown.converter();
|
||||
|
||||
converter.makeHtml('#hello markdown!');
|
||||
|
||||
// <h1 id="hellomarkdown">hello markdown!</h1>
|
||||
```
|
||||
|
||||
## What's it for?
|
||||
|
||||
Developers can use Showdown to:
|
||||
|
||||
* Add in-browser preview to existing Markdown apps
|
||||
|
||||
Showdown's output is (almost always) identical to
|
||||
markdown.pl's, so the server can reproduce exactly
|
||||
the output that the user saw. (See below for
|
||||
exceptions.)
|
||||
|
||||
* Add Markdown input to programs that don't support it
|
||||
|
||||
Any app that accepts HTML input can now be made to speak
|
||||
Markdown by modifying the input pages's HTML. If your
|
||||
application lets users edit documents again later,
|
||||
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
|
||||
`onsubmit` function!
|
||||
|
||||
* Add Markdown input to closed-source web apps
|
||||
|
||||
You can write bookmarklets or userscripts to extend
|
||||
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
|
||||
controls.
|
||||
|
||||
* Build new web apps from scratch
|
||||
|
||||
A Showdown front-end can send back text in Markdown,
|
||||
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
|
||||
uses, you'll still need to sanitize the HTML before
|
||||
showing it to other users -- but you'd need to do
|
||||
that anyway if you're allowing raw HTML in your
|
||||
Markdown.)
|
||||
|
||||
|
||||
## Browser Compatibility
|
||||
|
||||
Showdown has been tested successfully with:
|
||||
|
||||
* Firefox 1.5 and 2.0
|
||||
* Internet Explorer 6 and 7
|
||||
* Safari 2.0.4
|
||||
* Opera 8.54 and 9.10
|
||||
* Netscape 8.1.2
|
||||
* 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.
|
||||
|
||||
|
||||
## Extensions
|
||||
|
||||
Showdown allows additional functionality to be loaded via extensions.
|
||||
|
||||
### Client-side Extension Usage
|
||||
|
||||
```js
|
||||
<script src="src/showdown.js" />
|
||||
<script src="src/extensions/twitter.js" />
|
||||
|
||||
var converter = new Showdown.converter({ extensions: ['twitter'] });
|
||||
```
|
||||
|
||||
### Server-side Extension Usage
|
||||
|
||||
```js
|
||||
// Using a bundled extension
|
||||
var Showdown = require('showdown');
|
||||
var converter = new Showdown.converter({ extensions: ['twitter'] });
|
||||
|
||||
// Using a custom extension
|
||||
var mine = require('./custom-extensions/mine');
|
||||
var converter = new Showdown.converter({ extensions: ['twitter', mine] });
|
||||
```
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
* This release uses the HTML parser from Markdown 1.0.2b2,
|
||||
which means it fails `Inline HTML (Advanced).text` from
|
||||
the Markdown test suite:
|
||||
|
||||
<div>
|
||||
<div>
|
||||
unindented == broken
|
||||
</div>
|
||||
</div>
|
||||
|
||||
* Showdown doesn't support the markdown="1" attribute:
|
||||
|
||||
<div markdown="1">
|
||||
Markdown does *not* work in here.
|
||||
</div>
|
||||
|
||||
This is half laziness on my part and half stubbornness.
|
||||
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
|
||||
way to make markdown="1" the default.
|
||||
|
||||
|
||||
* You can only nest square brackets in link titles to a
|
||||
depth of two levels:
|
||||
|
||||
[[fine]](http://www.attacklab.net/)
|
||||
[[[broken]]](http://www.attacklab.net/)
|
||||
|
||||
If you need more, you can escape them with backslashes.
|
||||
|
||||
|
||||
* When sublists have paragraphs, Showdown produces equivalent
|
||||
HTML with a slightly different arrangement of newlines:
|
||||
|
||||
+ item
|
||||
|
||||
- subitem
|
||||
|
||||
The HTML has a superfluous newline before this
|
||||
paragraph.
|
||||
|
||||
- subitem
|
||||
|
||||
The HTML here is unchanged.
|
||||
|
||||
- subitem
|
||||
|
||||
The HTML is missing a newline after this
|
||||
list subitem.
|
||||
|
||||
|
||||
|
||||
* Markdown.pl creates empty title attributes for
|
||||
inline-style images:
|
||||
|
||||
Here's an empty title on an inline-style
|
||||
![image](http://w3.org/Icons/valid-xhtml10).
|
||||
|
||||
I tried to replicate this to clean up my diffs during
|
||||
testing, but I went too far: now Showdown also makes
|
||||
empty titles for reference-style images:
|
||||
|
||||
Showdown makes an empty title for
|
||||
reference-style ![images][] too.
|
||||
|
||||
[images]: http://w3.org/Icons/valid-xhtml10
|
||||
|
||||
|
||||
* With crazy input, Markdown will mistakenly put
|
||||
`<strong>` or `<em>` tags in URLs:
|
||||
|
||||
<a href="<*Markdown adds em tags in here*>">
|
||||
improbable URL
|
||||
</a>
|
||||
|
||||
Showdown won't. But still, don't do that.
|
||||
|
||||
|
||||
## 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:
|
||||
|
||||
npm install --dev
|
||||
|
||||
Once installed the tests can be run from the project root using:
|
||||
|
||||
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`.
|
||||
|
||||
|
||||
## Creating Markdown Extensions
|
||||
|
||||
A showdown extension is simply a function which returns an array of language extensions and/or output modifiers:
|
||||
|
||||
* 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.
|
||||
* 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.
|
||||
|
||||
Each showdown extension can provide language extensions and/or output modifiers.
|
||||
|
||||
### 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 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
|
||||
var demo = function(converter) {
|
||||
return [
|
||||
// Replace escaped @ symbols
|
||||
{ type: 'lang', regex: '\\@', replace: '@' }
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### 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).
|
||||
|
||||
**Example:**
|
||||
|
||||
```js
|
||||
var demo = function(converter) {
|
||||
return [
|
||||
// Replace escaped @ symbols
|
||||
{ type: 'lang', filter: function(text) {
|
||||
return text.replace(/\\@/g, '@');
|
||||
}}
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
```js
|
||||
(function(){
|
||||
// Your extension here
|
||||
}());
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
```js
|
||||
(function(){
|
||||
var demo = function(converter) {
|
||||
// ... extension code here ...
|
||||
};
|
||||
|
||||
// Client-side export
|
||||
if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) { window.Showdown.extensions.demo = demo; }
|
||||
// Server-side export
|
||||
if (typeof module !== 'undefined') module.exports = demo;
|
||||
}());
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
* Origins
|
||||
* [John Fraser](http://attacklab.net/):<br/>
|
||||
Author of Showdown
|
||||
* [John Gruber](http://daringfireball.net/projects/markdown/):<br/>
|
||||
Author of Markdown
|
||||
* Maintenance/Contributions (roughly chronologically)
|
||||
* [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://github.com/coreyti):<br/>
|
||||
GitHub project maintainer
|
||||
![Showdown](https://raw.githubusercontent.com/showdownjs/logo/master/dist/logo.readme.png)
|
||||
|
||||
[![Build Status](https://travis-ci.org/showdownjs/showdown.svg?branch=master)](https://travis-ci.org/showdownjs/showdown)
|
||||
|
||||
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).
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
### Download tarball
|
||||
|
||||
You can download the latest release's tarball directly from https://github.com/showdownjs/showdown/releases
|
||||
|
||||
### Bower
|
||||
|
||||
bower install showdown
|
||||
|
||||
### npm (server-side)
|
||||
|
||||
npm install showdown
|
||||
|
||||
|
||||
## Browser Compatibility
|
||||
|
||||
Showdown has been tested successfully with:
|
||||
|
||||
* Firefox 1.5 and 2.0
|
||||
* Internet Explorer 6 and 7
|
||||
* Safari 2.0.4
|
||||
* Opera 8.54 and 9.10
|
||||
* Netscape 8.1.2
|
||||
* 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.
|
||||
|
||||
|
||||
## Node compatibility
|
||||
|
||||
Showdown has been tested with node 0.8 and 0.10. However, it should work with previous versions, such as node 0.6.
|
||||
|
||||
|
||||
## Quick Example
|
||||
|
||||
### Node
|
||||
|
||||
```js
|
||||
var showdown = require('showdown'),
|
||||
converter = new showdown.Converter(),
|
||||
text = '#hello, markdown!',
|
||||
html = converter.makeHtml(text);
|
||||
```
|
||||
|
||||
### Browser
|
||||
|
||||
```js
|
||||
var converter = new showdown.Converter(),
|
||||
text = '#hello, markdown!',
|
||||
html = converter.makeHtml(text);
|
||||
```
|
||||
|
||||
### Output
|
||||
|
||||
Both examples should output...
|
||||
|
||||
```html
|
||||
<h1 id="hellomarkdown">hello, markdown!</h1>
|
||||
```
|
||||
|
||||
|
||||
## Extensions
|
||||
|
||||
Showdown allows additional functionality to be loaded via extensions.
|
||||
|
||||
### Client-side Extension Usage
|
||||
|
||||
```js
|
||||
<script src="showdown.js" />
|
||||
<script src="twitter-extension.js" />
|
||||
|
||||
var converter = new showdown.Converter({ extensions: 'twitter' });
|
||||
```
|
||||
|
||||
### Server-side Extension Usage
|
||||
|
||||
```js
|
||||
var showdown = require('showdown'),
|
||||
myExtension = require('myExtension'),
|
||||
converter = new showdown.Converter({ extensions: ['myExtension'] });
|
||||
```
|
||||
|
||||
## 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:
|
||||
|
||||
npm install --dev
|
||||
|
||||
Once installed the tests can be run from the project root using:
|
||||
|
||||
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`.
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
* This release uses the HTML parser from Markdown 1.0.2b2,
|
||||
which means it fails `Inline HTML (Advanced).text` from
|
||||
the Markdown test suite:
|
||||
|
||||
<div>
|
||||
<div>
|
||||
unindented == broken
|
||||
</div>
|
||||
</div>
|
||||
|
||||
* Showdown doesn't support the markdown="1" attribute:
|
||||
|
||||
<div markdown="1">
|
||||
Markdown does *not* work in here.
|
||||
</div>
|
||||
|
||||
This is half laziness on my part and half stubbornness.
|
||||
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
|
||||
way to make markdown="1" the default.
|
||||
|
||||
|
||||
* You can only nest square brackets in link titles to a
|
||||
depth of two levels:
|
||||
|
||||
[[fine]](http://www.attacklab.net/)
|
||||
[[[broken]]](http://www.attacklab.net/)
|
||||
|
||||
If you need more, you can escape them with backslashes.
|
||||
|
||||
|
||||
* When sublists have paragraphs, Showdown produces equivalent
|
||||
HTML with a slightly different arrangement of newlines:
|
||||
|
||||
+ item
|
||||
|
||||
- subitem
|
||||
|
||||
The HTML has a superfluous newline before this
|
||||
paragraph.
|
||||
|
||||
- subitem
|
||||
|
||||
The HTML here is unchanged.
|
||||
|
||||
- subitem
|
||||
|
||||
The HTML is missing a newline after this
|
||||
list subitem.
|
||||
|
||||
|
||||
|
||||
* Markdown.pl creates empty title attributes for
|
||||
inline-style images:
|
||||
|
||||
Here's an empty title on an inline-style
|
||||
![image](http://w3.org/Icons/valid-xhtml10).
|
||||
|
||||
I tried to replicate this to clean up my diffs during
|
||||
testing, but I went too far: now Showdown also makes
|
||||
empty titles for reference-style images:
|
||||
|
||||
Showdown makes an empty title for
|
||||
reference-style ![images][] too.
|
||||
|
||||
[images]: http://w3.org/Icons/valid-xhtml10
|
||||
|
||||
|
||||
* With crazy input, Markdown will mistakenly put
|
||||
`<strong>` or `<em>` tags in URLs:
|
||||
|
||||
<a href="<*Markdown adds em tags in here*>">
|
||||
improbable URL
|
||||
</a>
|
||||
|
||||
Showdown won't. But still, don't do that.
|
||||
|
||||
|
||||
## 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:
|
||||
|
||||
* 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.
|
||||
|
||||
Each extension can provide two combinations of interfaces for showdown.
|
||||
|
||||
### Regex/Replace
|
||||
|
||||
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.
|
||||
|
||||
**Example:**
|
||||
|
||||
```js
|
||||
var demo = function(converter) {
|
||||
return [
|
||||
// Replace escaped @ symbols
|
||||
{ type: 'lang', regex: '\\@', replace: '@' }
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### 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).
|
||||
|
||||
**Example:**
|
||||
|
||||
```js
|
||||
var demo = function(converter) {
|
||||
return [
|
||||
// Replace escaped @ symbols
|
||||
{ type: 'lang', filter: function(text) {
|
||||
return text.replace(/\\@/g, '@');
|
||||
}}
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
```js
|
||||
(function(){
|
||||
// Your extension here
|
||||
}());
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
```js
|
||||
(function(){
|
||||
var demo = function(converter) {
|
||||
// ... extension code here ...
|
||||
};
|
||||
|
||||
// Client-side export
|
||||
if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) { window.Showdown.extensions.demo = demo; }
|
||||
// Server-side export
|
||||
if (typeof module !== 'undefined') module.exports = demo;
|
||||
}());
|
||||
```
|
||||
|
||||
### 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 folder using the md/html format and they will automatically be run when tests are run.
|
||||
|
||||
|
||||
## Contributing
|
||||
The organization needs members to maintain Showdown.
|
||||
Please see [this issue](https://github.com/showdownjs/showdown/issues/114) to express interest or comment on this note.
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
- Showdown
|
||||
* [Estevão Santos](http://soares-dos-santos.com):<br/>
|
||||
GitHub project maintainer
|
||||
* [Pascal Deschênes](https://github.com/pdeschen):<br/>
|
||||
Grunt support, extension fixes + additions, packaging improvements, documentation
|
||||
* [Corey Innis](http://github.com/coreyti):<br/>
|
||||
Original 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
|
||||
|
||||
|
||||
- Original Project
|
||||
* [John Gruber](http://daringfireball.net/projects/markdown/)<br/>
|
||||
Author of Markdown
|
||||
* [John Fraser](http://attacklab.net/)<br/>
|
||||
Author of Showdown
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "showdown",
|
||||
"description": "JavaScript port of Markdown",
|
||||
"description": "A Markdown to HTML converter written in Javascript",
|
||||
"homepage": "https://github.com/showdownjs/showdown",
|
||||
"authors": [
|
||||
"John Fraser",
|
||||
"Corey Innis (https://github.com/coreyti)",
|
||||
"Estevão Santos (https://github.com/tivie)",
|
||||
"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"],
|
||||
"ignore": [
|
||||
|
|
BIN
dist/showdown.js
vendored
Normal file
BIN
dist/showdown.js
vendored
Normal file
Binary file not shown.
BIN
dist/showdown.js.map
vendored
Normal file
BIN
dist/showdown.js.map
vendored
Normal file
Binary file not shown.
BIN
dist/showdown.min.js
vendored
Normal file
BIN
dist/showdown.min.js
vendored
Normal file
Binary file not shown.
BIN
dist/showdown.min.js.map
vendored
Normal file
BIN
dist/showdown.min.js.map
vendored
Normal file
Binary file not shown.
|
@ -1,9 +1,9 @@
|
|||
Copyright (c) 2007, John Fraser
|
||||
<http://www.attacklab.net/>
|
||||
Showdown Copyright (c) 2007, John Fraser
|
||||
<http://www.attacklab.net/>
|
||||
All rights reserved.
|
||||
|
||||
Original Markdown copyright (c) 2004, John Gruber
|
||||
<http://daringfireball.net/>
|
||||
Original Markdown copyright (c) 2004, John Gruber
|
||||
<http://daringfireball.net/>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
19
package.json
19
package.json
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"name": "showdown",
|
||||
"version": "0.5.0",
|
||||
"author": "John Fraser",
|
||||
"version": "1.0.0-alpha1",
|
||||
"description": "A Markdown to HTML converter written in Javascript",
|
||||
"authors": "Estevão Santos",
|
||||
"contributors": [
|
||||
"John Gruber",
|
||||
"John Fraser",
|
||||
|
@ -27,21 +28,25 @@
|
|||
"licenses": [
|
||||
{
|
||||
"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": {
|
||||
"test": "mocha ./test/run.js"
|
||||
"test": "mocha ./test/**/*.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular": "^1.3.2",
|
||||
"chai": "^1.10.0",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-contrib-concat": "^0.5.0",
|
||||
"grunt-contrib-jshint": "^0.10.0",
|
||||
"grunt-contrib-uglify": "^0.6.0",
|
||||
"grunt-conventional-changelog": "^1.1.0",
|
||||
"grunt-jscs": "^1.2.0",
|
||||
"grunt-simple-mocha": "^0.4.0",
|
||||
"jscs": "^1.10.0",
|
||||
"mocha": "*",
|
||||
"should": "^4.4.2"
|
||||
"should": "^4.4.2",
|
||||
"source-map-support": "^0.2.9"
|
||||
}
|
||||
}
|
||||
|
|
107
src/helpers.js
Normal file
107
src/helpers.js
Normal 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
17
src/loader.js
Normal 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;
|
||||
}
|
280
src/showdown.js
280
src/showdown.js
|
@ -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
131
src/subParsers/anchors.js
Normal 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, '"');
|
||||
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;
|
||||
|
||||
});
|
28
src/subParsers/autoLinks.js
Normal file
28
src/subParsers/autoLinks.js
Normal 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;
|
||||
|
||||
});
|
29
src/subParsers/blockGamut.js
Normal file
29
src/subParsers/blockGamut.js
Normal 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;
|
||||
|
||||
});
|
43
src/subParsers/blockQuotes.js
Normal file
43
src/subParsers/blockQuotes.js
Normal 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;
|
||||
});
|
42
src/subParsers/codeBlocks.js
Normal file
42
src/subParsers/codeBlocks.js
Normal 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;
|
||||
});
|
52
src/subParsers/codeSpans.js
Normal file
52
src/subParsers/codeSpans.js
Normal 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
32
src/subParsers/detab.js
Normal 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;
|
||||
|
||||
});
|
14
src/subParsers/encodeAmpsAndAngles.js
Normal file
14
src/subParsers/encodeAmpsAndAngles.js
Normal 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, '&');
|
||||
|
||||
// Encode naked <'s
|
||||
text = text.replace(/<(?![a-z\/?\$!])/gi, '<');
|
||||
|
||||
return text;
|
||||
});
|
17
src/subParsers/encodeBackslashEscapes.js
Normal file
17
src/subParsers/encodeBackslashEscapes.js
Normal 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;
|
||||
});
|
28
src/subParsers/encodeCode.js
Normal file
28
src/subParsers/encodeCode.js
Normal 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, '&');
|
||||
|
||||
// Do the angle bracket song and dance:
|
||||
text = text.replace(/</g, '<');
|
||||
text = text.replace(/>/g, '>');
|
||||
|
||||
// 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;
|
||||
});
|
52
src/subParsers/encodeEmailAddress.js
Normal file
52
src/subParsers/encodeEmailAddress.js
Normal 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="mailto:foo@e
|
||||
* xample.com">foo
|
||||
* @example.com</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;
|
||||
});
|
19
src/subParsers/escapeSpecialCharsWithinTagAttributes.js
Normal file
19
src/subParsers/escapeSpecialCharsWithinTagAttributes.js
Normal 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;
|
||||
});
|
40
src/subParsers/githubCodeBlocks.js
Normal file
40
src/subParsers/githubCodeBlocks.js
Normal 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;
|
||||
|
||||
});
|
5
src/subParsers/hashBlock.js
Normal file
5
src/subParsers/hashBlock.js
Normal 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';
|
||||
});
|
19
src/subParsers/hashElement.js
Normal file
19
src/subParsers/hashElement.js
Normal 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;
|
||||
};
|
||||
});
|
133
src/subParsers/hashHTMLBlocks.js
Normal file
133
src/subParsers/hashHTMLBlocks.js
Normal 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
73
src/subParsers/headers.js
Normal 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
102
src/subParsers/images.js
Normal 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, '"');
|
||||
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, '"');
|
||||
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;
|
||||
});
|
9
src/subParsers/italicsAndBold.js
Normal file
9
src/subParsers/italicsAndBold.js
Normal 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;
|
||||
});
|
11
src/subParsers/languageExtensions.js
Normal file
11
src/subParsers/languageExtensions.js
Normal 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
144
src/subParsers/lists.js
Normal 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
15
src/subParsers/outdent.js
Normal 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;
|
||||
});
|
11
src/subParsers/outputModifiers.js
Normal file
11
src/subParsers/outputModifiers.js
Normal 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;
|
||||
});
|
41
src/subParsers/paragraphs.js
Normal file
41
src/subParsers/paragraphs.js
Normal 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');
|
||||
});
|
13
src/subParsers/runExtension.js
Normal file
13
src/subParsers/runExtension.js
Normal 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);
|
||||
}
|
||||
});
|
29
src/subParsers/spanGamut.js
Normal file
29
src/subParsers/spanGamut.js
Normal 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;
|
||||
|
||||
});
|
10
src/subParsers/stripBlankLines.js
Normal file
10
src/subParsers/stripBlankLines.js
Normal 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, '');
|
||||
});
|
54
src/subParsers/stripLinkDefinitions.js
Normal file
54
src/subParsers/stripLinkDefinitions.js
Normal 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, '"');
|
||||
}
|
||||
|
||||
// Completely remove the definition from the text
|
||||
return '';
|
||||
});
|
||||
|
||||
// attacklab: strip sentinel
|
||||
text = text.replace(/~0/, '');
|
||||
|
||||
return text;
|
||||
});
|
12
src/subParsers/unescapeSpecialChars.js
Normal file
12
src/subParsers/unescapeSpecialChars.js
Normal 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;
|
||||
});
|
|
@ -1,5 +1,4 @@
|
|||
|
||||
<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">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">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 fourth</a> reference-style link.</p>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<blockquote>
|
||||
<h2 id="thisisaheader">This is a header.</h2>
|
||||
<h2 id="thisisaheader">This is a header.</h2>
|
||||
|
||||
<ol>
|
||||
<li>This is the first list item.</li>
|
||||
<li>This is the second list item.</li>
|
||||
</ol>
|
||||
<ol>
|
||||
<li>This is the first list item.</li>
|
||||
<li>This is the second list item.</li>
|
||||
</ol>
|
||||
|
||||
<p>Here's some example code:</p>
|
||||
<p>Here's some example code:</p>
|
||||
|
||||
<pre><code>return shell_exec("echo $input | $markdown_script");
|
||||
</code></pre>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<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>
|
||||
</blockquote>
|
||||
<p>With more than one line.</p>
|
||||
</blockquote>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
|
||||
<p>This is some HTML:</p>
|
||||
|
||||
<pre><code><h1>Heading</h1>
|
||||
</code></pre>
|
||||
</code></pre>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
|
||||
<p>This is a normal paragraph:</p>
|
||||
|
||||
<pre><code>This is a code block.
|
||||
</code></pre>
|
||||
</code></pre>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
|
||||
<ul>
|
||||
<li><p>Bird</p></li>
|
||||
<li><p>Magic</p></li>
|
||||
</ul>
|
||||
<li><p>Bird</p></li>
|
||||
<li><p>Magic</p></li>
|
||||
</ul>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1 +1 @@
|
|||
<p>It happened in 1986. What a great season.</p>
|
||||
<p>It happened in 1986. What a great season.</p>
|
||||
|
|
|
@ -1 +1 @@
|
|||
It happened in 1986\. What a great season.
|
||||
It happened in 1986\. What a great season.
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
<p>These should all be escaped:</p>
|
||||
|
||||
<p>\</p>
|
||||
|
@ -29,4 +28,4 @@
|
|||
|
||||
<p>.</p>
|
||||
|
||||
<p>!</p>
|
||||
<p>!</p>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
<pre><code>function MyFunc(a) {
|
||||
// ...
|
||||
}
|
||||
// ...
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<p>That is some code!</p>
|
||||
<p>That is some code!</p>
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
|
||||
|
||||
<p>Define a function in javascript:</p>
|
||||
|
||||
<pre><code>function MyFunc(a) {
|
||||
var s = '`';
|
||||
}
|
||||
var s = '`';
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<p>And some HTML</p>
|
||||
|
||||
<pre><code class="html"><div>HTML!</div>
|
||||
</code></pre>
|
||||
</code></pre>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
<pre><code>code can go here
|
||||
this is rendered on a second line
|
||||
</code></pre>
|
||||
this is rendered on a second line
|
||||
</code></pre>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<h1 id="thisisanh1">This is an H1</h1>
|
||||
<h1 id="thisisanh1">This is an H1</h1>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<h1 id="thisisanh1">This is an H1</h1>
|
||||
<h1 id="thisisanh1">This is an H1</h1>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<h1 id="thisisanh1">This is an H1</h1>
|
||||
<h1 id="thisisanh1">This is an H1</h1>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<h2 id="thisisanh2">This is an H2</h2>
|
||||
<h2 id="thisisanh2">This is an H2</h2>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<h2 id="thisisanh2">This is an H2</h2>
|
||||
<h2 id="thisisanh2">This is an H2</h2>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<h2 id="thisisanh2">This is an H2</h2>
|
||||
<h2 id="thisisanh2">This is an H2</h2>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<h3 id="thisisanh3">This is an H3</h3>
|
||||
<h3 id="thisisanh3">This is an H3</h3>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<h3 id="thisisanh3">This is an H3</h3>
|
||||
<h3 id="thisisanh3">This is an H3</h3>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<h4 id="thisisanh4">This is an H4</h4>
|
||||
<h4 id="thisisanh4">This is an H4</h4>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<h5 id="thisisanh5">This is an H5</h5>
|
||||
<h5 id="thisisanh5">This is an H5</h5>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<h6 id="thisisanh6">This is an H6</h6>
|
||||
<h6 id="thisisanh6">This is an H6</h6>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
<hr />
|
||||
|
||||
<hr />
|
||||
|
@ -7,4 +6,4 @@
|
|||
|
||||
<hr />
|
||||
|
||||
<hr />
|
||||
<hr />
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
<p>These HTML5 tags should pass through just fine.</p>
|
||||
|
||||
<section>hello</section>
|
||||
|
@ -14,10 +13,63 @@
|
|||
<aside>ignore me</aside>
|
||||
|
||||
<article>read
|
||||
me</article>
|
||||
me</article>
|
||||
|
||||
<aside>
|
||||
ignore me
|
||||
ignore me
|
||||
</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 <canvas> 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>
|
||||
|
|
|
@ -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="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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
<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>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
<p>Create a new <code>function</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><blink></code> tags.</p>
|
||||
|
||||
<p><code>&#8212;</code> is the decimal-encoded equivalent of <code>&mdash;</code>.</p>
|
||||
<p><code>&#8212;</code> is the decimal-encoded equivalent of <code>&mdash;</code>.</p>
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
|
||||
<p>Hello.this_is_a_variable
|
||||
and.this.is.another_one</p>
|
||||
and.this.is.another_one</p>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
|
||||
<style>
|
||||
p { line-height: 20px; }
|
||||
p { line-height: 20px; }
|
||||
</style>
|
||||
|
||||
<p>An exciting sentence.</p>
|
||||
<p>An exciting sentence.</p>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<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>
|
||||
</blockquote>
|
||||
<p>With more than one line.</p>
|
||||
</blockquote>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<ul>
|
||||
<li><p>A list item with a blockquote:</p>
|
||||
<li><p>A list item with a blockquote:</p>
|
||||
|
||||
<blockquote>
|
||||
<p>This is a blockquote
|
||||
<p>This is a blockquote
|
||||
inside a list item.</p>
|
||||
</blockquote></li>
|
||||
</ul>
|
||||
</ul>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<ul>
|
||||
<li><p>A list item with code:</p>
|
||||
<li><p>A list item with code:</p>
|
||||
|
||||
<pre><code>alert('Hello world!');
|
||||
</code></pre></li>
|
||||
</ul>
|
||||
</ul>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<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>
|
||||
<li><p>And another line</p></li>
|
||||
</ol>
|
||||
<li><p>And another line</p></li>
|
||||
</ol>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<ul>
|
||||
<li>This line spans
|
||||
more than one line and is lazy</li>
|
||||
<li>Similar to this line</li>
|
||||
</ul>
|
||||
<li>This line spans
|
||||
more than one line and is lazy</li>
|
||||
<li>Similar to this line</li>
|
||||
</ul>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<blockquote>
|
||||
<p>This is a multi line blockquote test</p>
|
||||
<p>This is a multi line blockquote test</p>
|
||||
|
||||
<blockquote>
|
||||
<p>And nesting!</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>And nesting!</p>
|
||||
</blockquote>
|
||||
|
||||
<p>With more than one line.</p>
|
||||
</blockquote>
|
||||
<p>With more than one line.</p>
|
||||
</blockquote>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<ol>
|
||||
<li>Red</li>
|
||||
<li>Green</li>
|
||||
<li>Blue</li>
|
||||
</ol>
|
||||
<li>Red</li>
|
||||
<li>Green</li>
|
||||
<li>Blue</li>
|
||||
</ol>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<ol>
|
||||
<li>Red</li>
|
||||
<li>Green</li>
|
||||
<li>Blue</li>
|
||||
</ol>
|
||||
<li>Red</li>
|
||||
<li>Green</li>
|
||||
<li>Blue</li>
|
||||
</ol>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<ol>
|
||||
<li>Red</li>
|
||||
<li>Green</li>
|
||||
<li>Blue</li>
|
||||
</ol>
|
||||
<li>Red</li>
|
||||
<li>Green</li>
|
||||
<li>Blue</li>
|
||||
</ol>
|
||||
|
|
|
@ -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>
|
||||
|
|
5
test/cases/repeated-headers.html
Normal file
5
test/cases/repeated-headers.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
<h1 id="sametitle">Same Title</h1>
|
||||
|
||||
<p>some text</p>
|
||||
|
||||
<h1 id="sametitle-1">Same Title</h1>
|
5
test/cases/repeated-headers.md
Normal file
5
test/cases/repeated-headers.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Same Title
|
||||
|
||||
some text
|
||||
|
||||
# Same Title
|
|
@ -1 +1 @@
|
|||
<p>Hello, world!</p>
|
||||
<p>Hello, world!</p>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
|
||||
<p><strong>important</strong></p>
|
||||
|
||||
<p><strong>important</strong></p>
|
||||
|
||||
<p>really <strong>freaking</strong>strong</p>
|
||||
<p>really <strong>freaking</strong>strong</p>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<ul>
|
||||
<li>Red</li>
|
||||
<li>Green</li>
|
||||
<li>Blue</li>
|
||||
</ul>
|
||||
<li>Red</li>
|
||||
<li>Green</li>
|
||||
<li>Blue</li>
|
||||
</ul>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<ul>
|
||||
<li>Red</li>
|
||||
<li>Green</li>
|
||||
<li>Blue</li>
|
||||
</ul>
|
||||
<li>Red</li>
|
||||
<li>Green</li>
|
||||
<li>Blue</li>
|
||||
</ul>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user