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
|
/test export-ignore
|
||||||
.gitattributes export-ignore
|
.gitattributes export-ignore
|
||||||
.gitignore export-ignore
|
.gitignore export-ignore
|
||||||
/perlMarkdown export-ignore
|
.jshintignore export-ignore
|
||||||
/example export-ignore
|
.travis.yml export-ignore
|
||||||
grunt.js export-ignore
|
Gruntfile.js export-ignore
|
||||||
|
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
||||||
|
.idea/
|
||||||
|
build/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
.idea/
|
|
||||||
|
|
90
.jscs.json
Normal file
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
|
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:
|
before_install:
|
||||||
- '[ "${TRAVIS_NODE_VERSION}" != "0.8" ] || npm install -g npm@1.4.28'
|
- '[ "${TRAVIS_NODE_VERSION}" != "0.8" ] || npm install -g npm@1.4.28'
|
||||||
- npm install -g npm@latest
|
- npm install -g npm@latest
|
||||||
|
|
||||||
|
#travis build speed up
|
||||||
|
sudo: false
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- node_modules
|
||||||
|
|
30
CHANGELOG.md
Normal file
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) {
|
module.exports = function (grunt) {
|
||||||
|
|
||||||
// Project configuration.
|
// Project configuration.
|
||||||
grunt.initConfig({
|
var config = {
|
||||||
pkg: grunt.file.readJSON('package.json'),
|
pkg: grunt.file.readJSON('package.json'),
|
||||||
concat: {
|
concat: {
|
||||||
options: {
|
options: {
|
||||||
separator: ';',
|
sourceMap: true,
|
||||||
sourceMap: true
|
banner: ';/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n(function(){\n',
|
||||||
},
|
footer: '}).call(this)'
|
||||||
dist: {
|
},
|
||||||
src: ['src/showdown.js', 'src/*.js'],
|
dist: {
|
||||||
dest: 'compressed/<%= pkg.name %>.js'
|
src: [
|
||||||
},
|
'src/showdown.js',
|
||||||
github_ext: {
|
'src/helpers.js',
|
||||||
src: ['src/extensions/github.js'],
|
'src/subParsers/*.js',
|
||||||
dest: 'compressed/extensions/github.min.js'
|
'src/loader.js'
|
||||||
},
|
],
|
||||||
prettify_ext: {
|
dest: 'dist/<%= pkg.name %>.js'
|
||||||
src: ['src/extensions/prettify.js'],
|
}
|
||||||
dest: 'compressed/extensions/prettify.min.js'
|
},
|
||||||
},
|
uglify: {
|
||||||
table_ext: {
|
options: {
|
||||||
src: ['src/extensions/table.js'],
|
sourceMap: true,
|
||||||
dest: 'compressed/extensions/table.min.js'
|
banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n'
|
||||||
},
|
},
|
||||||
twitter_ext: {
|
dist: {
|
||||||
src: ['src/extensions/twitter.js'],
|
files: {
|
||||||
dest: 'compressed/extensions/twitter.min.js'
|
'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>']
|
||||||
}
|
|
||||||
},
|
|
||||||
uglify: {
|
|
||||||
options: {
|
|
||||||
banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n'
|
|
||||||
},
|
|
||||||
dist: {
|
|
||||||
files: {
|
|
||||||
'compressed/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
github_ext: {
|
|
||||||
files: {
|
|
||||||
'compressed/extensions/github.min.js': ['<%= concat.github_ext.dest %>']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
prettify_ext: {
|
|
||||||
files: {
|
|
||||||
'compressed/extensions/prettify.min.js': ['<%= concat.prettify_ext.dest %>']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
table_ext: {
|
|
||||||
files: {
|
|
||||||
'compressed/extensions/table.min.js': ['<%= concat.table_ext.dest %>']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
twitter_ext: {
|
|
||||||
files: {
|
|
||||||
'compressed/extensions/twitter.min.js': ['<%= concat.twitter_ext.dest %>']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
jshint: {
|
|
||||||
files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js']
|
|
||||||
},
|
|
||||||
simplemocha: {
|
|
||||||
all: {
|
|
||||||
src: 'test/run.js',
|
|
||||||
options: {
|
|
||||||
globals: ['should'],
|
|
||||||
timeout: 3000,
|
|
||||||
ignoreLeaks: false,
|
|
||||||
ui: 'bdd'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
},
|
||||||
|
jshint: {
|
||||||
|
files: [
|
||||||
|
'Gruntfile.js',
|
||||||
|
'src/**/*.js',
|
||||||
|
'test/**/*.js'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
jscs: {
|
||||||
|
options: {
|
||||||
|
config: '.jscs.json',
|
||||||
|
},
|
||||||
|
files: {
|
||||||
|
src: [
|
||||||
|
'Gruntfile.js',
|
||||||
|
'src/**/*.js',
|
||||||
|
'test/**/*.js'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
changelog: {
|
||||||
|
options: {
|
||||||
|
repository: 'http://github.com/showdownjs/showdown',
|
||||||
|
dest: 'CHANGELOG.md'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
simplemocha: {
|
||||||
|
node: {
|
||||||
|
src: 'test/node/**/*.js',
|
||||||
|
options: {
|
||||||
|
globals: ['should'],
|
||||||
|
timeout: 3000,
|
||||||
|
ignoreLeaks: false,
|
||||||
|
reporter: 'spec'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
browser: {
|
||||||
|
src: 'test/browser/**/*.js',
|
||||||
|
options: {
|
||||||
|
reporter: 'spec'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
grunt.initConfig(config);
|
||||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
|
||||||
grunt.loadNpmTasks('grunt-simple-mocha');
|
|
||||||
|
|
||||||
// test
|
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||||
grunt.registerTask('lint', ['jshint']);
|
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||||
grunt.registerTask('test', ['simplemocha']);
|
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||||
|
grunt.loadNpmTasks('grunt-simple-mocha');
|
||||||
|
grunt.loadNpmTasks('grunt-jscs');
|
||||||
|
grunt.loadNpmTasks('grunt-conventional-changelog');
|
||||||
|
|
||||||
// build with uglify
|
grunt.registerTask('lint', ['jshint', 'jscs']);
|
||||||
grunt.registerTask('build', ['concat', 'uglify']);
|
grunt.registerTask('test', ['lint', 'concat', 'simplemocha']);
|
||||||
|
grunt.registerTask('test-without-building', ['simplemocha']);
|
||||||
// Build with closure compiler
|
grunt.registerTask('build', ['lint', 'test', 'uglify']);
|
||||||
grunt.registerTask('build-with-closure', ['test', 'concat', 'closure-compiler']);
|
|
||||||
|
|
||||||
// Default task(s).
|
|
||||||
grunt.registerTask('default', []);
|
|
||||||
|
|
||||||
|
// Default task(s).
|
||||||
|
grunt.registerTask('default', []);
|
||||||
};
|
};
|
||||||
|
|
615
README.md
615
README.md
|
@ -1,317 +1,298 @@
|
||||||
# Showdown
|
![Showdown](https://raw.githubusercontent.com/showdownjs/logo/master/dist/logo.readme.png)
|
||||||
|
|
||||||
A JavaScript port of Markdown
|
[![Build Status](https://travis-ci.org/showdownjs/showdown.svg?branch=master)](https://travis-ci.org/showdownjs/showdown)
|
||||||
|
|
||||||
## Note
|
Showdown is a Javascript Markdown to HTML converter, based on the original works by John Gruber. Showdown can be used client side (in the browser) or server side (with NodeJs).
|
||||||
|
|
||||||
> Showdown is now maintained by the [showdownjs](https://github.com/showdownjs) organization on Github.
|
|
||||||
>
|
## Installation
|
||||||
> The organization needs members to maintain Showdown.
|
|
||||||
>
|
### Download tarball
|
||||||
> Please see [this issue](https://github.com/showdownjs/showdown/issues/114) to express interest or comment on this note.
|
|
||||||
|
You can download the latest release's tarball directly from https://github.com/showdownjs/showdown/releases
|
||||||
## Original Attributions
|
|
||||||
|
### Bower
|
||||||
Showdown Copyright (c) 2007 John Fraser.
|
|
||||||
<http://www.attacklab.net/>
|
bower install showdown
|
||||||
|
|
||||||
Original Markdown Copyright (c) 2004-2005 John Gruber
|
### npm (server-side)
|
||||||
<http://daringfireball.net/projects/markdown/>
|
|
||||||
|
npm install showdown
|
||||||
Redistributable under a BSD-style open source license.
|
|
||||||
See license.txt for more information.
|
|
||||||
|
## Browser Compatibility
|
||||||
## Quick Example
|
|
||||||
|
Showdown has been tested successfully with:
|
||||||
```js
|
|
||||||
var Showdown = require('showdown');
|
* Firefox 1.5 and 2.0
|
||||||
var converter = new Showdown.converter();
|
* Internet Explorer 6 and 7
|
||||||
|
* Safari 2.0.4
|
||||||
converter.makeHtml('#hello markdown!');
|
* Opera 8.54 and 9.10
|
||||||
|
* Netscape 8.1.2
|
||||||
// <h1 id="hellomarkdown">hello markdown!</h1>
|
* Konqueror 3.5.4
|
||||||
```
|
|
||||||
|
In theory, Showdown will work in any browser that supports ECMA 262 3rd Edition (JavaScript 1.5). The converter itself might even work in things that aren't web browsers, like Acrobat. No promises.
|
||||||
## What's it for?
|
|
||||||
|
|
||||||
Developers can use Showdown to:
|
## Node compatibility
|
||||||
|
|
||||||
* Add in-browser preview to existing Markdown apps
|
Showdown has been tested with node 0.8 and 0.10. However, it should work with previous versions, such as node 0.6.
|
||||||
|
|
||||||
Showdown's output is (almost always) identical to
|
|
||||||
markdown.pl's, so the server can reproduce exactly
|
## Quick Example
|
||||||
the output that the user saw. (See below for
|
|
||||||
exceptions.)
|
### Node
|
||||||
|
|
||||||
* Add Markdown input to programs that don't support it
|
```js
|
||||||
|
var showdown = require('showdown'),
|
||||||
Any app that accepts HTML input can now be made to speak
|
converter = new showdown.Converter(),
|
||||||
Markdown by modifying the input pages's HTML. If your
|
text = '#hello, markdown!',
|
||||||
application lets users edit documents again later,
|
html = converter.makeHtml(text);
|
||||||
then they won't have access to the original Markdown
|
```
|
||||||
text. But this should be good enough for many
|
|
||||||
uses -- and you can do it with just a two-line
|
### Browser
|
||||||
`onsubmit` function!
|
|
||||||
|
```js
|
||||||
* Add Markdown input to closed-source web apps
|
var converter = new showdown.Converter(),
|
||||||
|
text = '#hello, markdown!',
|
||||||
You can write bookmarklets or userscripts to extend
|
html = converter.makeHtml(text);
|
||||||
any standard textarea on the web so that it accepts
|
```
|
||||||
Markdown instead of HTML. With a little more hacking,
|
|
||||||
the same can probably be done with many rich edit
|
### Output
|
||||||
controls.
|
|
||||||
|
Both examples should output...
|
||||||
* Build new web apps from scratch
|
|
||||||
|
```html
|
||||||
A Showdown front-end can send back text in Markdown,
|
<h1 id="hellomarkdown">hello, markdown!</h1>
|
||||||
HTML or both, so you can trade bandwidth for server
|
```
|
||||||
load to reduce your cost of operation. If your app
|
|
||||||
requires JavaScript, you won't need to do any
|
|
||||||
Markdown processing on the server at all. (For most
|
## Extensions
|
||||||
uses, you'll still need to sanitize the HTML before
|
|
||||||
showing it to other users -- but you'd need to do
|
Showdown allows additional functionality to be loaded via extensions.
|
||||||
that anyway if you're allowing raw HTML in your
|
|
||||||
Markdown.)
|
### Client-side Extension Usage
|
||||||
|
|
||||||
|
```js
|
||||||
## Browser Compatibility
|
<script src="showdown.js" />
|
||||||
|
<script src="twitter-extension.js" />
|
||||||
Showdown has been tested successfully with:
|
|
||||||
|
var converter = new showdown.Converter({ extensions: 'twitter' });
|
||||||
* Firefox 1.5 and 2.0
|
```
|
||||||
* Internet Explorer 6 and 7
|
|
||||||
* Safari 2.0.4
|
### Server-side Extension Usage
|
||||||
* Opera 8.54 and 9.10
|
|
||||||
* Netscape 8.1.2
|
```js
|
||||||
* Konqueror 3.5.4
|
var showdown = require('showdown'),
|
||||||
|
myExtension = require('myExtension'),
|
||||||
In theory, Showdown will work in any browser that supports ECMA 262 3rd Edition (JavaScript 1.5). The converter itself might even work in things that aren't web browsers, like Acrobat. No promises.
|
converter = new showdown.Converter({ extensions: ['myExtension'] });
|
||||||
|
```
|
||||||
|
|
||||||
## Extensions
|
## Tests
|
||||||
|
|
||||||
Showdown allows additional functionality to be loaded via extensions.
|
A suite of tests is available which require node.js. Once node is installed, run the following command from the project root to install the development dependencies:
|
||||||
|
|
||||||
### Client-side Extension Usage
|
npm install --dev
|
||||||
|
|
||||||
```js
|
Once installed the tests can be run from the project root using:
|
||||||
<script src="src/showdown.js" />
|
|
||||||
<script src="src/extensions/twitter.js" />
|
npm test
|
||||||
|
|
||||||
var converter = new Showdown.converter({ extensions: ['twitter'] });
|
New test cases can easily be added. Create a markdown file (ending in `.md`) which contains the markdown to test. Create a `.html` file of the exact same name. It will automatically be tested when the tests are executed with `mocha`.
|
||||||
```
|
|
||||||
|
|
||||||
### Server-side Extension Usage
|
## Known Differences in Output
|
||||||
|
|
||||||
```js
|
In most cases, Showdown's output is identical to that of Perl Markdown v1.0.2b7. What follows is a list of all known deviations. Please file an issue if you find more.
|
||||||
// Using a bundled extension
|
|
||||||
var Showdown = require('showdown');
|
* This release uses the HTML parser from Markdown 1.0.2b2,
|
||||||
var converter = new Showdown.converter({ extensions: ['twitter'] });
|
which means it fails `Inline HTML (Advanced).text` from
|
||||||
|
the Markdown test suite:
|
||||||
// Using a custom extension
|
|
||||||
var mine = require('./custom-extensions/mine');
|
<div>
|
||||||
var converter = new Showdown.converter({ extensions: ['twitter', mine] });
|
<div>
|
||||||
```
|
unindented == broken
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
## Known Differences in Output
|
|
||||||
|
* Showdown doesn't support the markdown="1" attribute:
|
||||||
In most cases, Showdown's output is identical to that of Perl Markdown v1.0.2b7. What follows is a list of all known deviations. Please file an issue if you find more.
|
|
||||||
|
<div markdown="1">
|
||||||
* This release uses the HTML parser from Markdown 1.0.2b2,
|
Markdown does *not* work in here.
|
||||||
which means it fails `Inline HTML (Advanced).text` from
|
</div>
|
||||||
the Markdown test suite:
|
|
||||||
|
This is half laziness on my part and half stubbornness.
|
||||||
<div>
|
Markdown is smart enough to process the contents of span-
|
||||||
<div>
|
level tags without screwing things up; shouldn't it be
|
||||||
unindented == broken
|
able to do the same inside block elements? Let's find a
|
||||||
</div>
|
way to make markdown="1" the default.
|
||||||
</div>
|
|
||||||
|
|
||||||
* Showdown doesn't support the markdown="1" attribute:
|
* You can only nest square brackets in link titles to a
|
||||||
|
depth of two levels:
|
||||||
<div markdown="1">
|
|
||||||
Markdown does *not* work in here.
|
[[fine]](http://www.attacklab.net/)
|
||||||
</div>
|
[[[broken]]](http://www.attacklab.net/)
|
||||||
|
|
||||||
This is half laziness on my part and half stubbornness.
|
If you need more, you can escape them with backslashes.
|
||||||
Markdown is smart enough to process the contents of span-
|
|
||||||
level tags without screwing things up; shouldn't it be
|
|
||||||
able to do the same inside block elements? Let's find a
|
* When sublists have paragraphs, Showdown produces equivalent
|
||||||
way to make markdown="1" the default.
|
HTML with a slightly different arrangement of newlines:
|
||||||
|
|
||||||
|
+ item
|
||||||
* You can only nest square brackets in link titles to a
|
|
||||||
depth of two levels:
|
- subitem
|
||||||
|
|
||||||
[[fine]](http://www.attacklab.net/)
|
The HTML has a superfluous newline before this
|
||||||
[[[broken]]](http://www.attacklab.net/)
|
paragraph.
|
||||||
|
|
||||||
If you need more, you can escape them with backslashes.
|
- subitem
|
||||||
|
|
||||||
|
The HTML here is unchanged.
|
||||||
* When sublists have paragraphs, Showdown produces equivalent
|
|
||||||
HTML with a slightly different arrangement of newlines:
|
- subitem
|
||||||
|
|
||||||
+ item
|
The HTML is missing a newline after this
|
||||||
|
list subitem.
|
||||||
- subitem
|
|
||||||
|
|
||||||
The HTML has a superfluous newline before this
|
|
||||||
paragraph.
|
* Markdown.pl creates empty title attributes for
|
||||||
|
inline-style images:
|
||||||
- subitem
|
|
||||||
|
Here's an empty title on an inline-style
|
||||||
The HTML here is unchanged.
|
![image](http://w3.org/Icons/valid-xhtml10).
|
||||||
|
|
||||||
- subitem
|
I tried to replicate this to clean up my diffs during
|
||||||
|
testing, but I went too far: now Showdown also makes
|
||||||
The HTML is missing a newline after this
|
empty titles for reference-style images:
|
||||||
list subitem.
|
|
||||||
|
Showdown makes an empty title for
|
||||||
|
reference-style ![images][] too.
|
||||||
|
|
||||||
* Markdown.pl creates empty title attributes for
|
[images]: http://w3.org/Icons/valid-xhtml10
|
||||||
inline-style images:
|
|
||||||
|
|
||||||
Here's an empty title on an inline-style
|
* With crazy input, Markdown will mistakenly put
|
||||||
![image](http://w3.org/Icons/valid-xhtml10).
|
`<strong>` or `<em>` tags in URLs:
|
||||||
|
|
||||||
I tried to replicate this to clean up my diffs during
|
<a href="<*Markdown adds em tags in here*>">
|
||||||
testing, but I went too far: now Showdown also makes
|
improbable URL
|
||||||
empty titles for reference-style images:
|
</a>
|
||||||
|
|
||||||
Showdown makes an empty title for
|
Showdown won't. But still, don't do that.
|
||||||
reference-style ![images][] too.
|
|
||||||
|
|
||||||
[images]: http://w3.org/Icons/valid-xhtml10
|
## Creating Markdown Extensions
|
||||||
|
|
||||||
|
A showdown extension is simply a function which returns an array of extensions. Each single extension can be one of two types:
|
||||||
* With crazy input, Markdown will mistakenly put
|
|
||||||
`<strong>` or `<em>` tags in URLs:
|
* Language Extension -- Language extensions are ones that that add new markdown syntax to showdown. For example, say you wanted `^^youtube http://www.youtube.com/watch?v=oHg5SJYRHA0` to automatically render as an embedded YouTube video, that would be a language extension.
|
||||||
|
* Output Modifiers -- After showdown has run, and generated HTML, an output modifier would change that HTML. For example, say you wanted to change `<div class="header">` to be `<header>`, that would be an output modifier.
|
||||||
<a href="<*Markdown adds em tags in here*>">
|
|
||||||
improbable URL
|
Each extension can provide two combinations of interfaces for showdown.
|
||||||
</a>
|
|
||||||
|
### Regex/Replace
|
||||||
Showdown won't. But still, don't do that.
|
|
||||||
|
Regex/replace style extensions are very similar to Javascript's `string.replace` function. Two properties are given, `regex` and `replace`. `regex` is a string and `replace` can be either a string or a function. If `replace` is a string, it can use the `$1` syntax for group substitution, exactly as if it were making use of `string.replace` (internally it does this actually); The value of `regex` is assumed to be a global replacement.
|
||||||
|
|
||||||
## Tests
|
**Example:**
|
||||||
|
|
||||||
A suite of tests is available which require node.js. Once node is installed, run the following command from the project root to install the development dependencies:
|
```js
|
||||||
|
var demo = function(converter) {
|
||||||
npm install --dev
|
return [
|
||||||
|
// Replace escaped @ symbols
|
||||||
Once installed the tests can be run from the project root using:
|
{ type: 'lang', regex: '\\@', replace: '@' }
|
||||||
|
];
|
||||||
npm test
|
}
|
||||||
|
```
|
||||||
New test cases can easily be added. Create a markdown file (ending in `.md`) which contains the markdown to test. Create a `.html` file of the exact same name. It will automatically be tested when the tests are executed with `mocha`.
|
|
||||||
|
### Filter
|
||||||
|
|
||||||
## Creating Markdown Extensions
|
Alternately, if you'd just like to do everything yourself, you can specify a filter which is a callback with a single input parameter, text (the current source text within the showdown engine).
|
||||||
|
|
||||||
A showdown extension is simply a function which returns an array of language extensions and/or output modifiers:
|
**Example:**
|
||||||
|
|
||||||
* Language Extension -- Language extensions are specified with the `lang` type, and add new markdown syntax to showdown. For example, say you wanted `^^youtube http://www.youtube.com/watch?v=oHg5SJYRHA0` to automatically render as an embedded YouTube video, that would be a language extension.
|
```js
|
||||||
* Output Modifiers -- Output Modifiers are specified with the `output` type. After showdown has generated HTML, an output modifier can make changes to the generated HTML. For example, if you wanted to change `<div class="header">` to be `<header>`, you could implement an output modifier.
|
var demo = function(converter) {
|
||||||
|
return [
|
||||||
Each showdown extension can provide language extensions and/or output modifiers.
|
// Replace escaped @ symbols
|
||||||
|
{ type: 'lang', filter: function(text) {
|
||||||
### Regex/Replace
|
return text.replace(/\\@/g, '@');
|
||||||
|
}}
|
||||||
Regex/replace style extensions are very similar to javascripts `string.replace` function. Two properties are given, `regex` and `replace`. `regex` is a string and `replace` can be either a string or a function. If `replace` is a string, it can use the `$1` syntax for group substitution, exactly as if it were making use of `string.replace` (internally it does this actually); The value of `regex` is assumed to be a global replacement.
|
];
|
||||||
|
}
|
||||||
**Example:**
|
```
|
||||||
|
|
||||||
```js
|
### Implementation Concerns
|
||||||
var demo = function(converter) {
|
|
||||||
return [
|
One bit which should be taken into account is maintaining both client-side and server-side compatibility. This can be achieved with a few lines of boilerplate code. First, to prevent polluting the global scope for client-side code, the extension definition should be wrapped in a self-executing function.
|
||||||
// Replace escaped @ symbols
|
|
||||||
{ type: 'lang', regex: '\\@', replace: '@' }
|
```js
|
||||||
];
|
(function(){
|
||||||
}
|
// Your extension here
|
||||||
```
|
}());
|
||||||
|
```
|
||||||
### Filter
|
|
||||||
|
Second, client-side extensions should add a property onto `Showdown.extensions` which matches the name of the file. As an example, a file named `demo.js` should then add `Showdown.extensions.demo`. Server-side extensions can simply export themselves.
|
||||||
Alternately, if you'd just like to do everything yourself, you can specify a filter which is a callback with a single input parameter, text (the current source text within the showdown engine).
|
|
||||||
|
```js
|
||||||
**Example:**
|
(function(){
|
||||||
|
var demo = function(converter) {
|
||||||
```js
|
// ... extension code here ...
|
||||||
var demo = function(converter) {
|
};
|
||||||
return [
|
|
||||||
// Replace escaped @ symbols
|
// Client-side export
|
||||||
{ type: 'lang', filter: function(text) {
|
if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) { window.Showdown.extensions.demo = demo; }
|
||||||
return text.replace(/\\@/g, '@');
|
// Server-side export
|
||||||
}}
|
if (typeof module !== 'undefined') module.exports = demo;
|
||||||
];
|
}());
|
||||||
}
|
```
|
||||||
```
|
|
||||||
|
### Testing Extensions
|
||||||
### Implementation Concerns
|
|
||||||
|
The showdown test runner is setup to automatically test cases for extensions. To add test cases for an extension, create a new folder under `./test/extensions` which matches the name of the `.js` file in `./src/extensions`. Place any test cases into the folder using the md/html format and they will automatically be run when tests are run.
|
||||||
One bit which should be taken into account is maintaining both client-side and server-side compatibility. This can be achieved with a few lines of boilerplate code. First, to prevent polluting the global scope for client-side code, the extension definition should be wrapped in a self-executing function.
|
|
||||||
|
|
||||||
```js
|
## Contributing
|
||||||
(function(){
|
The organization needs members to maintain Showdown.
|
||||||
// Your extension here
|
Please see [this issue](https://github.com/showdownjs/showdown/issues/114) to express interest or comment on this note.
|
||||||
}());
|
|
||||||
```
|
|
||||||
|
## Credits
|
||||||
Second, client-side extensions should add a property onto `Showdown.extensions` which matches the name of the file. As an example, a file named `demo.js` should then add `Showdown.extensions.demo`. Server-side extensions can simply export themselves.
|
|
||||||
|
- Showdown
|
||||||
```js
|
* [Estevão Santos](http://soares-dos-santos.com):<br/>
|
||||||
(function(){
|
GitHub project maintainer
|
||||||
var demo = function(converter) {
|
* [Pascal Deschênes](https://github.com/pdeschen):<br/>
|
||||||
// ... extension code here ...
|
Grunt support, extension fixes + additions, packaging improvements, documentation
|
||||||
};
|
* [Corey Innis](http://github.com/coreyti):<br/>
|
||||||
|
Original GitHub project maintainer
|
||||||
// Client-side export
|
* [Remy Sharp](https://github.com/remy/):<br/>
|
||||||
if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) { window.Showdown.extensions.demo = demo; }
|
CommonJS-compatibility and more
|
||||||
// Server-side export
|
* [Konstantin Käfer](https://github.com/kkaefer/):<br/>
|
||||||
if (typeof module !== 'undefined') module.exports = demo;
|
CommonJS packaging
|
||||||
}());
|
* [Roger Braun](https://github.com/rogerbraun):<br/>
|
||||||
```
|
Github-style code blocks
|
||||||
|
* [Dominic Tarr](https://github.com/dominictarr):<br/>
|
||||||
### Testing Extensions
|
Documentation
|
||||||
|
* [Cat Chen](https://github.com/CatChen):<br/>
|
||||||
The showdown test runner is setup to automatically test cases for extensions. To add test cases for an extension, create a new folder under `./test/extensions` which matches the name of the `.js` file in `./src/extensions`. Place any test cases into the filder using the md/html format and they will automatically be run when tests are run.
|
Export fix
|
||||||
|
* [Titus Stone](https://github.com/tstone):<br/>
|
||||||
|
Mocha tests, extension mechanism, and bug fixes
|
||||||
## Credits
|
* [Rob Sutherland](https://github.com/roberocity):<br/>
|
||||||
|
The idea that lead to extensions
|
||||||
* Origins
|
* [Pavel Lang](https://github.com/langpavel):<br/>
|
||||||
* [John Fraser](http://attacklab.net/):<br/>
|
Code cleanup
|
||||||
Author of Showdown
|
* [Ben Combee](https://github.com/unwiredben):<br/>
|
||||||
* [John Gruber](http://daringfireball.net/projects/markdown/):<br/>
|
Regex optimization
|
||||||
Author of Markdown
|
* [Adam Backstrom](https://github.com/abackstrom):<br/>
|
||||||
* Maintenance/Contributions (roughly chronologically)
|
WebKit bugfix
|
||||||
* [Corey Innis](http://github.com/coreyti):<br/>
|
|
||||||
GitHub project maintainer
|
|
||||||
* [Remy Sharp](https://github.com/remy/):<br/>
|
- Original Project
|
||||||
CommonJS-compatibility and more
|
* [John Gruber](http://daringfireball.net/projects/markdown/)<br/>
|
||||||
* [Konstantin Käfer](https://github.com/kkaefer/):<br/>
|
Author of Markdown
|
||||||
CommonJS packaging
|
* [John Fraser](http://attacklab.net/)<br/>
|
||||||
* [Roger Braun](https://github.com/rogerbraun):<br/>
|
Author of Showdown
|
||||||
Github-style code blocks
|
|
||||||
* [Dominic Tarr](https://github.com/dominictarr):<br/>
|
|
||||||
Documentation
|
|
||||||
* [Cat Chen](https://github.com/CatChen):<br/>
|
|
||||||
Export fix
|
|
||||||
* [Titus Stone](https://github.com/tstone):<br/>
|
|
||||||
Mocha tests, extension mechanism, and bug fixes
|
|
||||||
* [Rob Sutherland](https://github.com/roberocity):<br/>
|
|
||||||
The idea that lead to extensions
|
|
||||||
* [Pavel Lang](https://github.com/langpavel):<br/>
|
|
||||||
Code cleanup
|
|
||||||
* [Ben Combee](https://github.com/unwiredben):<br/>
|
|
||||||
Regex optimization
|
|
||||||
* [Adam Backstrom](https://github.com/abackstrom):<br/>
|
|
||||||
WebKit bugfix
|
|
||||||
* [Pascal Deschênes](https://github.com/pdeschen):<br/>
|
|
||||||
Grunt support, extension fixes + additions, packaging improvements, documentation
|
|
||||||
* [Estevão Santos](http://github.com/coreyti):<br/>
|
|
||||||
GitHub project maintainer
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "showdown",
|
"name": "showdown",
|
||||||
"description": "JavaScript port of Markdown",
|
"description": "A Markdown to HTML converter written in Javascript",
|
||||||
"homepage": "https://github.com/showdownjs/showdown",
|
"homepage": "https://github.com/showdownjs/showdown",
|
||||||
"authors": [
|
"authors": [
|
||||||
"John Fraser",
|
"Estevão Santos (https://github.com/tivie)",
|
||||||
"Corey Innis (https://github.com/coreyti)",
|
|
||||||
"Pascal Deschênes (https://github.com/pdeschen)",
|
"Pascal Deschênes (https://github.com/pdeschen)",
|
||||||
"Estevão Santos (https://github.com/tivie)"
|
"Corey Innis (https://github.com/coreyti)",
|
||||||
|
"John Fraser"
|
||||||
],
|
],
|
||||||
"main": ["src/showdown.js"],
|
"main": ["src/showdown.js"],
|
||||||
"ignore": [
|
"ignore": [
|
||||||
|
|
BIN
dist/showdown.js
vendored
Normal file
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
|
Showdown Copyright (c) 2007, John Fraser
|
||||||
<http://www.attacklab.net/>
|
<http://www.attacklab.net/>
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Original Markdown copyright (c) 2004, John Gruber
|
Original Markdown copyright (c) 2004, John Gruber
|
||||||
<http://daringfireball.net/>
|
<http://daringfireball.net/>
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
|
19
package.json
19
package.json
|
@ -1,7 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "showdown",
|
"name": "showdown",
|
||||||
"version": "0.5.0",
|
"version": "1.0.0-alpha1",
|
||||||
"author": "John Fraser",
|
"description": "A Markdown to HTML converter written in Javascript",
|
||||||
|
"authors": "Estevão Santos",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
"John Gruber",
|
"John Gruber",
|
||||||
"John Fraser",
|
"John Fraser",
|
||||||
|
@ -27,21 +28,25 @@
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
"type": "BSD",
|
"type": "BSD",
|
||||||
"url": "https://github.com/coreyti/showdown/raw/master/license.txt"
|
"url": "https://github.com/showdownjs/showdown/blob/master/license.txt"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"main": "./src/showdown",
|
"main": "./dist/showdown.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha ./test/run.js"
|
"test": "mocha ./test/**/*.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"angular": "^1.3.2",
|
"chai": "^1.10.0",
|
||||||
"grunt": "^0.4.5",
|
"grunt": "^0.4.5",
|
||||||
"grunt-contrib-concat": "^0.5.0",
|
"grunt-contrib-concat": "^0.5.0",
|
||||||
"grunt-contrib-jshint": "^0.10.0",
|
"grunt-contrib-jshint": "^0.10.0",
|
||||||
"grunt-contrib-uglify": "^0.6.0",
|
"grunt-contrib-uglify": "^0.6.0",
|
||||||
|
"grunt-conventional-changelog": "^1.1.0",
|
||||||
|
"grunt-jscs": "^1.2.0",
|
||||||
"grunt-simple-mocha": "^0.4.0",
|
"grunt-simple-mocha": "^0.4.0",
|
||||||
|
"jscs": "^1.10.0",
|
||||||
"mocha": "*",
|
"mocha": "*",
|
||||||
"should": "^4.4.2"
|
"should": "^4.4.2",
|
||||||
|
"source-map-support": "^0.2.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
107
src/helpers.js
Normal file
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.
|
<p>This is <a href="http://example.com/" title="Optional Title Here">an example</a> reference-style link.
|
||||||
This is <a href="http://example.com/" title="Optional Title Here">another</a> reference-style link.
|
This is <a href="http://example.com/" title="Optional Title Here">another</a> reference-style link.
|
||||||
This is <a href="http://example.com/" title="Optional Title Here">a third</a> reference-style link.
|
This is <a href="http://example.com/" title="Optional Title Here">a third</a> reference-style link.
|
||||||
This is <a href="http://example.com/" title="Optional Title Here">a fourth</a> reference-style link.</p>
|
This is <a href="http://example.com/" title="Optional Title Here">a fourth</a> reference-style link.</p>
|
||||||
|
|
|
@ -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>
|
<blockquote>
|
||||||
<h2 id="thisisaheader">This is a header.</h2>
|
<h2 id="thisisaheader">This is a header.</h2>
|
||||||
|
|
||||||
<ol>
|
<ol>
|
||||||
<li>This is the first list item.</li>
|
<li>This is the first list item.</li>
|
||||||
<li>This is the second list item.</li>
|
<li>This is the second list item.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<p>Here's some example code:</p>
|
<p>Here's some example code:</p>
|
||||||
|
|
||||||
<pre><code>return shell_exec("echo $input | $markdown_script");
|
<pre><code>return shell_exec("echo $input | $markdown_script");
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<blockquote>
|
<blockquote>
|
||||||
<p>This is a multi line blockquote test</p>
|
<p>This is a multi line blockquote test</p>
|
||||||
|
|
||||||
<p>With more than one line.</p>
|
<p>With more than one line.</p>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
|
|
||||||
<p>This is some HTML:</p>
|
<p>This is some HTML:</p>
|
||||||
|
|
||||||
<pre><code><h1>Heading</h1>
|
<pre><code><h1>Heading</h1>
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
|
|
||||||
<p>This is a normal paragraph:</p>
|
<p>This is a normal paragraph:</p>
|
||||||
|
|
||||||
<pre><code>This is a code block.
|
<pre><code>This is a code block.
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><p>Bird</p></li>
|
<li><p>Bird</p></li>
|
||||||
<li><p>Magic</p></li>
|
<li><p>Magic</p></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -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>These should all be escaped:</p>
|
||||||
|
|
||||||
<p>\</p>
|
<p>\</p>
|
||||||
|
@ -29,4 +28,4 @@
|
||||||
|
|
||||||
<p>.</p>
|
<p>.</p>
|
||||||
|
|
||||||
<p>!</p>
|
<p>!</p>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
|
|
||||||
<pre><code>function MyFunc(a) {
|
<pre><code>function MyFunc(a) {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
|
||||||
<p>That is some code!</p>
|
<p>That is some code!</p>
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
|
|
||||||
|
|
||||||
<p>Define a function in javascript:</p>
|
<p>Define a function in javascript:</p>
|
||||||
|
|
||||||
<pre><code>function MyFunc(a) {
|
<pre><code>function MyFunc(a) {
|
||||||
var s = '`';
|
var s = '`';
|
||||||
}
|
}
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
|
||||||
<p>And some HTML</p>
|
<p>And some HTML</p>
|
||||||
|
|
||||||
<pre><code class="html"><div>HTML!</div>
|
<pre><code class="html"><div>HTML!</div>
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
<pre><code>code can go here
|
<pre><code>code can go here
|
||||||
this is rendered on a second line
|
this is rendered on a second line
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
|
|
@ -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 />
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
@ -7,4 +6,4 @@
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
<p>These HTML5 tags should pass through just fine.</p>
|
<p>These HTML5 tags should pass through just fine.</p>
|
||||||
|
|
||||||
<section>hello</section>
|
<section>hello</section>
|
||||||
|
@ -14,10 +13,63 @@
|
||||||
<aside>ignore me</aside>
|
<aside>ignore me</aside>
|
||||||
|
|
||||||
<article>read
|
<article>read
|
||||||
me</article>
|
me</article>
|
||||||
|
|
||||||
<aside>
|
<aside>
|
||||||
ignore me
|
ignore me
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<p>the end</p>
|
<p>the end</p>
|
||||||
|
|
||||||
|
<table class="test">
|
||||||
|
<tr>
|
||||||
|
<td>Foo</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Bar</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table class="test">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Foo</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tr>
|
||||||
|
<td>Bar</td>
|
||||||
|
</tr>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td>Bar</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<audio class="podcastplayer" controls>
|
||||||
|
<source src="foobar.mp3" type="audio/mp3" preload="none"></source>
|
||||||
|
<source src="foobar.off" type="audio/ogg" preload="none"></source>
|
||||||
|
</audio>
|
||||||
|
|
||||||
|
<video src="foo.ogg">
|
||||||
|
<track kind="subtitles" src="foo.en.vtt" srclang="en" label="English">
|
||||||
|
<track kind="subtitles" src="foo.sv.vtt" srclang="sv" label="Svenska">
|
||||||
|
</video>
|
||||||
|
|
||||||
|
<address>My street</address>
|
||||||
|
|
||||||
|
<canvas id="canvas" width="300" height="300">
|
||||||
|
Sorry, your browser doesn't support the <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="" /></p>
|
||||||
|
|
||||||
<p><img src="/path/to/img.jpg" alt="Alt text" title="Optional title" /></p>
|
<p><img src="/path/to/img.jpg" alt="Alt text" title="Optional title" /></p>
|
||||||
|
|
||||||
<p><img src="url/to/image" alt="Alt text" title="Optional title attribute" /></p>
|
<p><img src="url/to/image" alt="Alt text" title="Optional title attribute" /></p>
|
||||||
|
|
|
@ -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>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>Create a new <code>function</code>.</p>
|
||||||
|
|
||||||
<p>Use the backtick in MySQL syntax <code>SELECT `column` FROM whatever</code>.</p>
|
<p>Use the backtick in MySQL syntax <code>SELECT `column` FROM whatever</code>.</p>
|
||||||
|
@ -9,4 +8,4 @@
|
||||||
|
|
||||||
<p>Please don't use any <code><blink></code> tags.</p>
|
<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
|
<p>Hello.this_is_a_variable
|
||||||
and.this.is.another_one</p>
|
and.this.is.another_one</p>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
p { line-height: 20px; }
|
p { line-height: 20px; }
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<p>An exciting sentence.</p>
|
<p>An exciting sentence.</p>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<blockquote>
|
<blockquote>
|
||||||
<p>This is a multi line blockquote test</p>
|
<p>This is a multi line blockquote test</p>
|
||||||
|
|
||||||
<p>With more than one line.</p>
|
<p>With more than one line.</p>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<ul>
|
<ul>
|
||||||
<li><p>A list item with a blockquote:</p>
|
<li><p>A list item with a blockquote:</p>
|
||||||
|
|
||||||
<blockquote>
|
<blockquote>
|
||||||
<p>This is a blockquote
|
<p>This is a blockquote
|
||||||
inside a list item.</p>
|
inside a list item.</p>
|
||||||
</blockquote></li>
|
</blockquote></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<ul>
|
<ul>
|
||||||
<li><p>A list item with code:</p>
|
<li><p>A list item with code:</p>
|
||||||
|
|
||||||
<pre><code>alert('Hello world!');
|
<pre><code>alert('Hello world!');
|
||||||
</code></pre></li>
|
</code></pre></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<ol>
|
<ol>
|
||||||
<li><p>This is a major bullet point.</p>
|
<li><p>This is a major bullet point.</p>
|
||||||
|
|
||||||
<p>That contains multiple paragraphs.</p></li>
|
<p>That contains multiple paragraphs.</p></li>
|
||||||
<li><p>And another line</p></li>
|
<li><p>And another line</p></li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<ul>
|
<ul>
|
||||||
<li>This line spans
|
<li>This line spans
|
||||||
more than one line and is lazy</li>
|
more than one line and is lazy</li>
|
||||||
<li>Similar to this line</li>
|
<li>Similar to this line</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<blockquote>
|
<blockquote>
|
||||||
<p>This is a multi line blockquote test</p>
|
<p>This is a multi line blockquote test</p>
|
||||||
|
|
||||||
<blockquote>
|
<blockquote>
|
||||||
<p>And nesting!</p>
|
<p>And nesting!</p>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
|
|
||||||
<p>With more than one line.</p>
|
<p>With more than one line.</p>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<ol>
|
<ol>
|
||||||
<li>Red</li>
|
<li>Red</li>
|
||||||
<li>Green</li>
|
<li>Green</li>
|
||||||
<li>Blue</li>
|
<li>Blue</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<ol>
|
<ol>
|
||||||
<li>Red</li>
|
<li>Red</li>
|
||||||
<li>Green</li>
|
<li>Green</li>
|
||||||
<li>Blue</li>
|
<li>Blue</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<ol>
|
<ol>
|
||||||
<li>Red</li>
|
<li>Red</li>
|
||||||
<li>Green</li>
|
<li>Green</li>
|
||||||
<li>Blue</li>
|
<li>Blue</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
|
@ -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><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>
|
<ul>
|
||||||
<li>Red</li>
|
<li>Red</li>
|
||||||
<li>Green</li>
|
<li>Green</li>
|
||||||
<li>Blue</li>
|
<li>Blue</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<ul>
|
<ul>
|
||||||
<li>Red</li>
|
<li>Red</li>
|
||||||
<li>Green</li>
|
<li>Green</li>
|
||||||
<li>Blue</li>
|
<li>Blue</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user