mirror of
https://github.com/showdownjs/showdown.git
synced 2024-03-22 13:30:55 +08:00
Merge remote-tracking branch 'remotes/upstream/master'
Conflicts: src/extensions/table.js
This commit is contained in:
commit
8efe006160
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
|
11
.gitattributes
vendored
11
.gitattributes
vendored
|
@ -1,7 +1,10 @@
|
|||
/test export-ignore
|
||||
.editorconfig export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
/perlMarkdown export-ignore
|
||||
/example export-ignore
|
||||
grunt.js export-ignore
|
||||
|
||||
.jscs.json export-ignore
|
||||
.jshintignore export-ignore
|
||||
.jshintrc
|
||||
.travis.yml export-ignore
|
||||
bower.json
|
||||
Gruntfile.js export-ignore
|
||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1,3 +1,6 @@
|
|||
.idea/
|
||||
.build/
|
||||
.DS_Store
|
||||
node_modules
|
||||
npm-debug.log
|
||||
npm-debug.log
|
||||
/*.test.*
|
||||
|
|
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
|
||||
}
|
5
.jshintignore
Normal file
5
.jshintignore
Normal file
|
@ -0,0 +1,5 @@
|
|||
Gruntfile.js
|
||||
dist/**/*.js
|
||||
build/**/*.js
|
||||
src/options.js
|
||||
bin/*
|
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
|
||||
}
|
||||
}
|
25
.travis.yml
25
.travis.yml
|
@ -1,4 +1,25 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- 0.6
|
||||
- 0.8
|
||||
- "0.8"
|
||||
- "0.10"
|
||||
- "0.12"
|
||||
|
||||
before_install:
|
||||
- '[ "${TRAVIS_NODE_VERSION}" != "0.8" ] || npm install -g npm@1.4.28'
|
||||
- npm install -g npm@latest
|
||||
- npm install -g grunt-cli
|
||||
|
||||
#travis build speed up
|
||||
sudo: false
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
|
||||
# hooks
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/e369617839852624aa69
|
||||
on_success: change # options: [always|never|change] default: always
|
||||
on_failure: always # options: [always|never|change] default: always
|
||||
on_start: false # default: false
|
||||
|
|
162
CHANGELOG.md
Normal file
162
CHANGELOG.md
Normal file
|
@ -0,0 +1,162 @@
|
|||
<a name"1.2.2"></a>
|
||||
### 1.2.2 (2015-08-02)
|
||||
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
* **lists:** fix github code blocks not being parsed inside lists ([7720c88b](http://github.com/showdownjs/showdown/commit/7720c88b), closes [#142](http://github.com/showdownjs/showdown/issues/142), [#183](http://github.com/showdownjs/showdown/issues/183), [#184](http://github.com/showdownjs/showdown/issues/184))
|
||||
|
||||
|
||||
<a name"1.2.1"></a>
|
||||
### 1.2.1 (2015-07-22)
|
||||
|
||||
|
||||
#### Features
|
||||
|
||||
* **smoothLivePreview:** fix weird effects due to parsing incomplete input ([62ba3733](http://github.com/showdownjs/showdown/commit/62ba3733))
|
||||
* **subParsers/githubCodeBlock:** add extra language class to conform to html5 spec ([b7f5e32](http://github.com/showdownjs/showdown/commit/b7f5e32))
|
||||
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
* **tables:**
|
||||
|
||||
* fix undefined error in malformed tables ([6176977](http://github.com/showdownjs/showdown/commit/6176977))
|
||||
|
||||
Cannot read property 'trim' of undefined happens when the parser is fed a malformed table.
|
||||
This happens in live previews (for instance, when using Angularjs).
|
||||
|
||||
* add support for md span elements in table headers ([789dc18](http://github.com/showdownjs/showdown/commit/789dc18))
|
||||
|
||||
Closes #179
|
||||
|
||||
* **italicsAndBold:**
|
||||
|
||||
* fix broken em/strong tags when used with literalMidWordUnderscores ([7ee2017](http://github.com/showdownjs/showdown/commit/7ee2017))
|
||||
|
||||
When literalMidWordUnderscoresis set to true, em and strong tags that start or end a paragraph don't get parsed as such.
|
||||
This fixes this issue.
|
||||
|
||||
Closes #174
|
||||
|
||||
* fix underscores not being correctly parsed when used in conjunction with literalMidWordsUnderscores option ([c9e85f1](http://github.com/showdownjs/showdown/commit/c9e85f1))
|
||||
|
||||
* **codeSpans:** Fix issue with code html tags not being correctly escaped ([5f043ca](http://github.com/showdownjs/showdown/commit/5f043ca))
|
||||
|
||||
* **images:** fix alt attribute not being escaped correctly ([542194e](http://github.com/showdownjs/showdown/commit/542194e))
|
||||
|
||||
|
||||
<a name"1.2.0"></a>
|
||||
## 1.2.0 (2015-07-13)
|
||||
|
||||
This release moves some of the most popular extensions (such as table-extension and github-extension) to core.
|
||||
Also introduces a simple cli tool that you can use to quickly convert markdown files into html.
|
||||
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
* **headerLevelStart:** fix for NaN error when specifying a non number as headerLevelStart param ([be72b487](http://github.com/showdownjs/showdown/commit/be72b487))
|
||||
|
||||
|
||||
#### Features
|
||||
|
||||
* **CLI:** simple cli tool (ALPHA) ([f6a33e40](http://github.com/showdownjs/showdown/commit/f6a33e40))
|
||||
* **flavours:** add markdown presets/flavors ([7e55bceb](http://github.com/showdownjs/showdown/commit/7e55bceb), closes [#164](http://github.com/showdownjs/showdown/issues/164))
|
||||
* **ghCodeBlocks:** add option to disable GH codeblocks ([c33f9888](http://github.com/showdownjs/showdown/commit/c33f9888))
|
||||
* **literalMidWordUnderscores:** add support for GFM literal midword underscores ([0c0cd7db](http://github.com/showdownjs/showdown/commit/0c0cd7db))
|
||||
* **simplifiedAutoLink:** add support for GFM autolinks ([cff02372](http://github.com/showdownjs/showdown/commit/cff02372))
|
||||
* **strikethrough:** add support for GFM strikethrough ([43e9448d](http://github.com/showdownjs/showdown/commit/43e9448d))
|
||||
* **tables:** add support for GFM tables ([3a924e3c](http://github.com/showdownjs/showdown/commit/3a924e3c))
|
||||
* **tasklists:** add support for GFM tasklists ([dc72403a](http://github.com/showdownjs/showdown/commit/dc72403a))
|
||||
|
||||
|
||||
<a name"1.1.0"></a>
|
||||
## 1.1.0 (2015-06-18)
|
||||
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
* **converter.js:** add error if the passed constructor argument is not an object ([d86ed450](http://github.com/showdownjs/showdown/commit/d86ed450))
|
||||
* **output modifiers:** fix for output modifiers running twice ([dcbdc61e](http://github.com/showdownjs/showdown/commit/dcbdc61e))
|
||||
|
||||
|
||||
#### Features
|
||||
|
||||
* **headerLevelStart:** add support for setting the header starting level ([b84ac67d](http://github.com/showdownjs/showdown/commit/b84ac67d), closes [#69](http://github.com/showdownjs/showdown/issues/69))
|
||||
* **image dimensions:** add support for setting image dimensions within markdown syntax ([af82c2b6](http://github.com/showdownjs/showdown/commit/af82c2b6), closes [#143](http://github.com/showdownjs/showdown/issues/143))
|
||||
* **noHeaderId:** add option to suppress automatic generation of ids in headers ([7ac893e9](http://github.com/showdownjs/showdown/commit/7ac893e9))
|
||||
* **showdown.getDefaultOptions:** add method to retrieve default global options keypairs ([2de53a7d](http://github.com/showdownjs/showdown/commit/2de53a7d))
|
||||
|
||||
|
||||
#### Breaking Changes
|
||||
|
||||
* Deprecates `showdown.extensions` property. To migrate, extensions should use the new method `showdown.extension(<ext name>, <extension>)` to register.
|
||||
For more information on the new extension loading mechanism, please check the wiki pages.
|
||||
([4ebd0caa](http://github.com/showdownjs/showdown/commit/4ebd0caa))
|
||||
|
||||
|
||||
<a name"1.0.2"></a>
|
||||
### 1.0.2 (2015-05-28)
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
* **Gruntfile.js** add missing comma in footer. This bug prevented concatenating other js scripts and libraries
|
||||
with showdown([5315508](http://github.com/showdownjs/showdown/commit/5315508). Credits to Alexandre Courtiol.
|
||||
|
||||
|
||||
<a name"1.0.1"></a>
|
||||
### 1.0.1 (2015-05-27)
|
||||
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
* **bower.json:** update bower.json main attribute to point to dist directory ([bc3a092f](http://github.com/showdownjs/showdown/commit/bc3a092f))
|
||||
|
||||
|
||||
<a name"1.0.0"></a>
|
||||
## 1.0.0 (2015-05-27)
|
||||
|
||||
#### 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))
|
||||
* **options.omitExtraWLInCodeBlocks:** fix for options.omitExtraWLInCodeBlocks only applying in gitHub flavoured code b ([e6f40e19](http://github.com/showdownjs/showdown/commit/e6f40e19))
|
||||
* **showdown:** fix for options merging into globalOptions ([ddd6011d](http://github.com/showdownjs/showdown/commit/ddd6011d), closes [#153](http://github.com/showdownjs/showdown/issues/153))
|
||||
|
||||
#### 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))
|
||||
* **omitExtraWLInCodeBlocks:** add option to omit extra newline at the end of codeblocks ([141e3f5](http://github.com/showdownjs/showdown/commit/141e3f5))
|
||||
* **prefixHeaderId:** add options to prefix header ids to prevent id clash ([141e3f5](http://github.com/showdownjs/showdown/commit/141e3f5))
|
||||
* **Converter.options:** add getOption(), setOption() and getOptions() to Converter object ([db6f79b0](http://github.com/showdownjs/showdown/commit/db6f79b0))
|
||||
|
||||
#### Breaking Changes
|
||||
* **NAMESPACE:** showdown's namespace changed.
|
||||
|
||||
To migrate your code you should update all references to `Showdown` with `showdown`.
|
||||
|
||||
* **Converter:** converter reference changed from `converter` to `Converter`.
|
||||
|
||||
To migrate you should update all references to `Showdown.converter` with `showdown.Converter`
|
||||
|
||||
* **angular:** angular integration was removed from core and now lives in it's own [repository](http://github.com/showdownjs/angular/).
|
||||
|
||||
If you're using angular integration, you should install ng-showdown. Ex: `bower install ng-showdown`
|
||||
|
||||
* **extensions:** showdown extensions were removed from core package and now live in their own repository. See the [project's github page](https://github.com/showdownjs) for available extensions
|
48
CONTRIBUTING.md
Normal file
48
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,48 @@
|
|||
Contributing
|
||||
============
|
||||
|
||||
If you wish to contribute please read the following quick guide.
|
||||
|
||||
# Want a Feature?
|
||||
You can request a new feature by submitting an issue. If you would like to implement a new feature feel free to issue a
|
||||
Pull Request.
|
||||
|
||||
|
||||
# Pull requests (PRs)
|
||||
PRs are awesome. However, before you submit your pull request consider the following guidelines:
|
||||
|
||||
- Search GitHub for an open or closed Pull Request that relates to your submission. You don't want to duplicate effort.
|
||||
- When issuing PRs that change code, make your changes in a new git branch based on master:
|
||||
|
||||
```bash
|
||||
git checkout -b my-fix-branch master
|
||||
```
|
||||
|
||||
- Documentation (i.e: README.md) changes can be made directly against master.
|
||||
- Run the full test suite before submitting and make sure all tests pass (obviously =P).
|
||||
- Try to follow our [**coding style rules**](https://github.com/showdownjs/code-style/blob/master/README.md).
|
||||
Breaking them prevents the PR to pass the tests.
|
||||
- Refrain from fixing multiple issues in the same pull request. It's preferable to open multiple small PRs instead of one
|
||||
hard to review big one. Also, don't reuse old forks (or PRs) to fix new issues.
|
||||
- If the PR introduces a new feature or fixes an issue, please add the appropriate test case.
|
||||
- We use commit notes to generate the changelog. It's extremely helpful if your commit messages adhere to the
|
||||
[**AngularJS Git Commit Guidelines**](https://github.com/showdownjs/code-style/blob/master/README.md#commit-message-convention).
|
||||
- If we suggest changes then:
|
||||
- Make the required updates.
|
||||
- Re-run the Angular test suite to ensure tests are still passing.
|
||||
- Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
|
||||
|
||||
```bash
|
||||
git rebase master -i
|
||||
git push origin my-fix-branch -f
|
||||
```
|
||||
- After your pull request is merged, you can safely delete your branch.
|
||||
|
||||
If you have time to contribute to this project, we feel obliged that you get credit for it.
|
||||
These rules enable us to review your PR faster and will give you appropriate credit in your GitHub profile.
|
||||
We thank you in advance for your contribution!
|
||||
|
||||
|
||||
# Joining the team
|
||||
We're looking for members to help maintaining Showdown.
|
||||
Please see [this issue](https://github.com/showdownjs/showdown/issues/114) to express interest or comment on this note.
|
48
CREDITS.md
Normal file
48
CREDITS.md
Normal file
|
@ -0,0 +1,48 @@
|
|||
Credits
|
||||
=======
|
||||
- Showdown v1
|
||||
* [Estevão Santos](https://github.com/tivie)
|
||||
* [Pascal Deschênes](https://github.com/pdeschen)
|
||||
|
||||
- Showdown v0
|
||||
* [Corey Innis](http://github.com/coreyti):<br/>
|
||||
Original GitHub project maintainer
|
||||
* [Remy Sharp](https://github.com/remy/):<br/>
|
||||
CommonJS-compatibility and more
|
||||
* [Konstantin Käfer](https://github.com/kkaefer/):<br/>
|
||||
CommonJS packaging
|
||||
* [Roger Braun](https://github.com/rogerbraun):<br/>
|
||||
Github-style code blocks
|
||||
* [Dominic Tarr](https://github.com/dominictarr):<br/>
|
||||
Documentation
|
||||
* [Cat Chen](https://github.com/CatChen):<br/>
|
||||
Export fix
|
||||
* [Titus Stone](https://github.com/tstone):<br/>
|
||||
Mocha tests, extension mechanism, and bug fixes
|
||||
* [Rob Sutherland](https://github.com/roberocity):<br/>
|
||||
The idea that lead to extensions
|
||||
* [Pavel Lang](https://github.com/langpavel):<br/>
|
||||
Code cleanup
|
||||
* [Ben Combee](https://github.com/unwiredben):<br/>
|
||||
Regex optimization
|
||||
* [Adam Backstrom](https://github.com/abackstrom):<br/>
|
||||
WebKit bugfix
|
||||
* [Pascal Deschênes](https://github.com/pdeschen):<br/>
|
||||
Grunt support, extension fixes + additions, packaging improvements, documentation
|
||||
* [Estevão Santos](https://github.com/tivie)<br/>
|
||||
Bug fixing and late maintainer
|
||||
* [Hannah Wolfe](https://github.com/ErisDS)<br/>
|
||||
Bug fixes
|
||||
* [Alexandre Courtiol](https://github.com/acourtiol)<br/>
|
||||
Bug fixes and build optimization
|
||||
* [Karthik Balakrishnan](https://github.com/torcellite)<br/>
|
||||
Support for table alignment
|
||||
* [rheber](https://github.com/rheber)<br/>
|
||||
Cli
|
||||
|
||||
|
||||
- Original Project
|
||||
* [John Gruber](http://daringfireball.net/projects/markdown/)<br/>
|
||||
Author of Markdown
|
||||
* [John Fraser](http://attacklab.net/)<br/>
|
||||
Author of Showdown
|
170
Gruntfile.js
Normal file
170
Gruntfile.js
Normal file
|
@ -0,0 +1,170 @@
|
|||
/**
|
||||
* Created by Tivie on 12-11-2014.
|
||||
*/
|
||||
|
||||
module.exports = function (grunt) {
|
||||
|
||||
if (grunt.option('q') || grunt.option('quiet')) {
|
||||
require('quiet-grunt');
|
||||
}
|
||||
|
||||
// Project configuration.
|
||||
var config = {
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
|
||||
concat: {
|
||||
options: {
|
||||
sourceMap: true,
|
||||
banner: ';/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n(function(){\n',
|
||||
footer: '}).call(this);'
|
||||
},
|
||||
dist: {
|
||||
src: [
|
||||
'src/options.js',
|
||||
'src/showdown.js',
|
||||
'src/helpers.js',
|
||||
'src/converter.js',
|
||||
'src/subParsers/*.js',
|
||||
'src/loader.js'
|
||||
],
|
||||
dest: 'dist/<%= pkg.name %>.js'
|
||||
},
|
||||
test: {
|
||||
src: '<%= concat.dist.src %>',
|
||||
dest: '.build/<%= pkg.name %>.js',
|
||||
options: {
|
||||
sourceMap: false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
clean: ['.build/'],
|
||||
|
||||
uglify: {
|
||||
options: {
|
||||
sourceMap: true,
|
||||
banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n'
|
||||
},
|
||||
dist: {
|
||||
files: {
|
||||
'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
jshint: {
|
||||
options: {
|
||||
jshintrc: '.jshintrc'
|
||||
},
|
||||
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: true,
|
||||
reporter: 'spec'
|
||||
}
|
||||
},
|
||||
karlcow: {
|
||||
src: 'test/node/testsuite.karlcow.js',
|
||||
options: {
|
||||
globals: ['should'],
|
||||
timeout: 3000,
|
||||
ignoreLeaks: false,
|
||||
reporter: 'spec'
|
||||
}
|
||||
},
|
||||
issues: {
|
||||
src: 'test/node/testsuite.issues.js',
|
||||
options: {
|
||||
globals: ['should'],
|
||||
timeout: 3000,
|
||||
ignoreLeaks: false,
|
||||
reporter: 'spec'
|
||||
}
|
||||
},
|
||||
standard: {
|
||||
src: 'test/node/testsuite.standard.js',
|
||||
options: {
|
||||
globals: ['should'],
|
||||
timeout: 3000,
|
||||
ignoreLeaks: false,
|
||||
reporter: 'spec'
|
||||
}
|
||||
},
|
||||
features: {
|
||||
src: 'test/node/testsuite.features.js',
|
||||
options: {
|
||||
globals: ['should'],
|
||||
timeout: 3000,
|
||||
ignoreLeaks: false,
|
||||
reporter: 'spec'
|
||||
}
|
||||
},
|
||||
single: {
|
||||
src: 'test/node/**/*.js',
|
||||
options: {
|
||||
globals: ['should'],
|
||||
timeout: 3000,
|
||||
ignoreLeaks: false,
|
||||
reporter: 'spec'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
grunt.initConfig(config);
|
||||
|
||||
require('load-grunt-tasks')(grunt);
|
||||
|
||||
grunt.registerTask('single-test', function (grep) {
|
||||
'use strict';
|
||||
grunt.config.merge({
|
||||
simplemocha: {
|
||||
single: {
|
||||
options: {
|
||||
grep: grep
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
grunt.task.run(['lint', 'concat:test', 'simplemocha:single', 'clean']);
|
||||
});
|
||||
|
||||
grunt.registerTask('lint', ['jshint', 'jscs']);
|
||||
grunt.registerTask('test', ['clean', 'lint', 'concat:test', 'simplemocha:node', 'clean']);
|
||||
grunt.registerTask('build', ['test', 'concat:dist', 'uglify']);
|
||||
grunt.registerTask('prep-release', ['build', 'changelog']);
|
||||
|
||||
// Default task(s).
|
||||
grunt.registerTask('default', ['test']);
|
||||
};
|
701
README.md
701
README.md
|
@ -1,327 +1,374 @@
|
|||
# Showdown [![build status](https://secure.travis-ci.org/coreyti/showdown.png)](http://travis-ci.org/coreyti/showdown)
|
||||
|
||||
A JavaScript port of Markdown
|
||||
|
||||
## Note
|
||||
|
||||
> **Please note** that I, [Corey](https://github.com/coreyti), am not the author
|
||||
> of Showdown. Rather, I found it some time back at <http://attacklab.net/showdown/>
|
||||
> (website removed, see: <http://wayback.archive.org/web/*/http://attacklab.net/showdown>)
|
||||
> and wanted to see it available on GitHub.
|
||||
>
|
||||
> All credit and praise for authoring this library should go to John Fraser.
|
||||
>
|
||||
> Oh, and John Gruber of course.
|
||||
>
|
||||
> That said, Showdown *is* evolving. See below for a list of contributors and an
|
||||
> overview of their contributions to the project.
|
||||
>
|
||||
> Apologies for any confusion or perceived misinformation.
|
||||
>
|
||||
> Cheers,<br/>
|
||||
> Corey
|
||||
|
||||
|
||||
## Original Attributions
|
||||
|
||||
Showdown Copyright (c) 2007 John Fraser.
|
||||
<http://www.attacklab.net/>
|
||||
|
||||
Original Markdown Copyright (c) 2004-2005 John Gruber
|
||||
<http://daringfireball.net/projects/markdown/>
|
||||
|
||||
Redistributable under a BSD-style open source license.
|
||||
See license.txt for more information.
|
||||
|
||||
## Quick Example
|
||||
|
||||
```js
|
||||
var Showdown = require('showdown');
|
||||
var converter = new Showdown.converter();
|
||||
|
||||
converter.makeHtml('#hello markdown!');
|
||||
|
||||
// <h1 id="hellomarkdown">hello, markdown</h1>
|
||||
```
|
||||
|
||||
## What's it for?
|
||||
|
||||
Developers can use Showdown to:
|
||||
|
||||
* Add in-browser preview to existing Markdown apps
|
||||
|
||||
Showdown's output is (almost always) identical to
|
||||
markdown.pl's, so the server can reproduce exactly
|
||||
the output that the user saw. (See below for
|
||||
exceptions.)
|
||||
|
||||
* Add Markdown input to programs that don't support it
|
||||
|
||||
Any app that accepts HTML input can now be made to speak
|
||||
Markdown by modifying the input pages's HTML. If your
|
||||
application lets users edit documents again later,
|
||||
then they won't have access to the original Markdown
|
||||
text. But this should be good enough for many
|
||||
uses -- and you can do it with just a two-line
|
||||
`onsubmit` function!
|
||||
|
||||
* Add Markdown input to closed-source web apps
|
||||
|
||||
You can write bookmarklets or userscripts to extend
|
||||
any standard textarea on the web so that it accepts
|
||||
Markdown instead of HTML. With a little more hacking,
|
||||
the same can probably be done with many rich edit
|
||||
controls.
|
||||
|
||||
* Build new web apps from scratch
|
||||
|
||||
A Showdown front-end can send back text in Markdown,
|
||||
HTML or both, so you can trade bandwidth for server
|
||||
load to reduce your cost of operation. If your app
|
||||
requires JavaScript, you won't need to do any
|
||||
Markdown processing on the server at all. (For most
|
||||
uses, you'll still need to sanitize the HTML before
|
||||
showing it to other users -- but you'd need to do
|
||||
that anyway if you're allowing raw HTML in your
|
||||
Markdown.)
|
||||
|
||||
|
||||
## Browser Compatibility
|
||||
|
||||
Showdown has been tested successfully with:
|
||||
|
||||
* Firefox 1.5 and 2.0
|
||||
* Internet Explorer 6 and 7
|
||||
* Safari 2.0.4
|
||||
* Opera 8.54 and 9.10
|
||||
* Netscape 8.1.2
|
||||
* Konqueror 3.5.4
|
||||
|
||||
In theory, Showdown will work in any browser that supports ECMA 262 3rd Edition (JavaScript 1.5). The converter itself might even work in things that aren't web browsers, like Acrobat. No promises.
|
||||
|
||||
|
||||
## Extensions
|
||||
|
||||
Showdown allows additional functionality to be loaded via extensions.
|
||||
|
||||
### Client-side Extension Usage
|
||||
|
||||
```js
|
||||
<script src="src/showdown.js" />
|
||||
<script src="src/extensions/twitter.js" />
|
||||
|
||||
var converter = new Showdown.converter({ extensions: 'twitter' });
|
||||
```
|
||||
|
||||
### Server-side Extension Usage
|
||||
|
||||
```js
|
||||
// Using a bundled extension
|
||||
var Showdown = require('showdown');
|
||||
var converter = new Showdown.converter({ extensions: ['twitter'] });
|
||||
|
||||
// Using a custom extension
|
||||
var mine = require('./custom-extensions/mine');
|
||||
var converter = new Showdown.converter({ extensions: ['twitter', mine] });
|
||||
```
|
||||
|
||||
|
||||
## Known Differences in Output
|
||||
|
||||
In most cases, Showdown's output is identical to that of Perl Markdown v1.0.2b7. What follows is a list of all known deviations. Please file an issue if you find more.
|
||||
|
||||
* This release uses the HTML parser from Markdown 1.0.2b2,
|
||||
which means it fails `Inline HTML (Advanced).text` from
|
||||
the Markdown test suite:
|
||||
|
||||
<div>
|
||||
<div>
|
||||
unindented == broken
|
||||
</div>
|
||||
</div>
|
||||
|
||||
* Showdown doesn't support the markdown="1" attribute:
|
||||
|
||||
<div markdown="1">
|
||||
Markdown does *not* work in here.
|
||||
</div>
|
||||
|
||||
This is half laziness on my part and half stubbornness.
|
||||
Markdown is smart enough to process the contents of span-
|
||||
level tags without screwing things up; shouldn't it be
|
||||
able to do the same inside block elements? Let's find a
|
||||
way to make markdown="1" the default.
|
||||
|
||||
|
||||
* You can only nest square brackets in link titles to a
|
||||
depth of two levels:
|
||||
|
||||
[[fine]](http://www.attacklab.net/)
|
||||
[[[broken]]](http://www.attacklab.net/)
|
||||
|
||||
If you need more, you can escape them with backslashes.
|
||||
|
||||
|
||||
* When sublists have paragraphs, Showdown produces equivalent
|
||||
HTML with a slightly different arrangement of newlines:
|
||||
|
||||
+ item
|
||||
|
||||
- subitem
|
||||
|
||||
The HTML has a superfluous newline before this
|
||||
paragraph.
|
||||
|
||||
- subitem
|
||||
|
||||
The HTML here is unchanged.
|
||||
|
||||
- subitem
|
||||
|
||||
The HTML is missing a newline after this
|
||||
list subitem.
|
||||
|
||||
|
||||
|
||||
* Markdown.pl creates empty title attributes for
|
||||
inline-style images:
|
||||
|
||||
Here's an empty title on an inline-style
|
||||
![image](http://w3.org/Icons/valid-xhtml10).
|
||||
|
||||
I tried to replicate this to clean up my diffs during
|
||||
testing, but I went too far: now Showdown also makes
|
||||
empty titles for reference-style images:
|
||||
|
||||
Showdown makes an empty title for
|
||||
reference-style ![images][] too.
|
||||
|
||||
[images]: http://w3.org/Icons/valid-xhtml10
|
||||
|
||||
|
||||
* With crazy input, Markdown will mistakenly put
|
||||
`<strong>` or `<em>` tags in URLs:
|
||||
|
||||
<a href="<*Markdown adds em tags in here*>">
|
||||
improbable URL
|
||||
</a>
|
||||
|
||||
Showdown won't. But still, don't do that.
|
||||
|
||||
|
||||
## Tests
|
||||
|
||||
A suite of tests is available which require node.js. Once node is installed, run the following command from the project root to install the development dependencies:
|
||||
|
||||
npm install --dev
|
||||
|
||||
Once installed the tests can be run from the project root using:
|
||||
|
||||
npm test
|
||||
|
||||
New test cases can easily be added. Create a markdown file (ending in `.md`) which contains the markdown to test. Create a `.html` file of the exact same name. It will automatically be tested when the tests are executed with `mocha`.
|
||||
|
||||
|
||||
## Creating Markdown Extensions
|
||||
|
||||
A showdown extension is simply a function which returns an array of extensions. Each single extension can be one of two types:
|
||||
|
||||
* Language Extension -- Language extensions are ones that that add new markdown syntax to showdown. For example, say you wanted `^^youtube http://www.youtube.com/watch?v=oHg5SJYRHA0` to automatically render as an embedded YouTube video, that would be a language extension.
|
||||
* Output Modifiers -- After showdown has run, and generated HTML, an output modifier would change that HTML. For example, say you wanted to change `<div class="header">` to be `<header>`, that would be an output modifier.
|
||||
|
||||
Each extension can provide two combinations of interfaces for showdown.
|
||||
|
||||
### Regex/Replace
|
||||
|
||||
Regex/replace style extensions are very similar to javascripts `string.replace` function. Two properties are given, `regex` and `replace`. `regex` is a string and `replace` can be either a string or a function. If `replace` is a string, it can use the `$1` syntax for group substitution, exactly as if it were making use of `string.replace` (internally it does this actually); The value of `regex` is assumed to be a global replacement.
|
||||
|
||||
**Example:**
|
||||
|
||||
```js
|
||||
var demo = function(converter) {
|
||||
return [
|
||||
// Replace escaped @ symbols
|
||||
{ type: 'lang', regex: '\\@', replace: '@' }
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Filter
|
||||
|
||||
Alternately, if you'd just like to do everything yourself, you can specify a filter which is a callback with a single input parameter, text (the current source text within the showdown engine).
|
||||
|
||||
**Example:**
|
||||
|
||||
```js
|
||||
var demo = function(converter) {
|
||||
return [
|
||||
// Replace escaped @ symbols
|
||||
{ type: 'lang', function(text) {
|
||||
return text.replace(/\\@/g, '@');
|
||||
}}
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Implementation Concerns
|
||||
|
||||
One bit which should be taken into account is maintaining both client-side and server-side compatibility. This can be achieved with a few lines of boilerplate code. First, to prevent polluting the global scope for client-side code, the extension definition should be wrapped in a self-executing function.
|
||||
|
||||
```js
|
||||
(function(){
|
||||
// Your extension here
|
||||
}());
|
||||
```
|
||||
|
||||
Second, client-side extensions should add a property onto `Showdown.extensions` which matches the name of the file. As an example, a file named `demo.js` should then add `Showdown.extensions.demo`. Server-side extensions can simply export themselves.
|
||||
|
||||
```js
|
||||
(function(){
|
||||
var demo = function(converter) {
|
||||
// ... extension code here ...
|
||||
};
|
||||
|
||||
// Client-side export
|
||||
if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) { window.Showdown.extensions.demo = demo; }
|
||||
// Server-side export
|
||||
if (typeof module !== 'undefined') module.exports = demo;
|
||||
}());
|
||||
```
|
||||
|
||||
### Testing Extensions
|
||||
|
||||
The showdown test runner is setup to automatically test cases for extensions. To add test cases for an extension, create a new folder under `./test/extensions` which matches the name of the `.js` file in `./src/extensions`. Place any test cases into the filder using the md/html format and they will automatically be run when tests are run.
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
* Origins
|
||||
* [John Fraser](http://attacklab.net/):<br/>
|
||||
Author of Showdown
|
||||
* [John Gruber](http://daringfireball.net/projects/markdown/):<br/>
|
||||
Author of Markdown
|
||||
* Maintenance/Contributions (roughly chronologically)
|
||||
* [Corey Innis](http://github.com/coreyti):<br/>
|
||||
GitHub project maintainer
|
||||
* [Remy Sharp](https://github.com/remy/):<br/>
|
||||
CommonJS-compatibility and more
|
||||
* [Konstantin Käfer](https://github.com/kkaefer/):<br/>
|
||||
CommonJS packaging
|
||||
* [Roger Braun](https://github.com/rogerbraun):<br/>
|
||||
Github-style code blocks
|
||||
* [Dominic Tarr](https://github.com/dominictarr):<br/>
|
||||
Documentation
|
||||
* [Cat Chen](https://github.com/CatChen):<br/>
|
||||
Export fix
|
||||
* [Titus Stone](https://github.com/tstone):<br/>
|
||||
Mocha tests, extension mechanism, and bug fixes
|
||||
* [Rob Sutherland](https://github.com/roberocity):<br/>
|
||||
The idea that lead to extensions
|
||||
* [Pavel Lang](https://github.com/langpavel):<br/>
|
||||
Code cleanup
|
||||
* [Ben Combee](https://github.com/unwiredben):<br/>
|
||||
Regex optimization
|
||||
* [Adam Backstrom](https://github.com/abackstrom):<br/>
|
||||
WebKit bugfix
|
||||
* [Pascal Deschênes](https://github.com/pdeschen):<br/>
|
||||
Grunt support, extension fixes + additions, packaging improvements, documentation
|
||||
![Showdown][sd-logo]
|
||||
|
||||
[![Build Status](https://travis-ci.org/showdownjs/showdown.svg?branch=master)](https://travis-ci.org/showdownjs/showdown) [![npm version](https://badge.fury.io/js/showdown.svg)](http://badge.fury.io/js/showdown) [![Bower version](https://badge.fury.io/bo/showdown.svg)](http://badge.fury.io/bo/showdown) [![Join the chat at https://gitter.im/showdownjs/showdown](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/showdownjs/showdown?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
------
|
||||
|
||||
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).
|
||||
|
||||
## Live DEMO
|
||||
|
||||
Check a live Demo here http://showdownjs.github.io/demo/
|
||||
|
||||
|
||||
## Who uses Showdown (or a fork)
|
||||
|
||||
- [GoogleCloudPlatform](https://github.com/GoogleCloudPlatform)
|
||||
- [Ghost](https://ghost.org/)
|
||||
- [Meteor](https://www.meteor.com/)
|
||||
- [Stackexchange](http://stackexchange.com/) - forked as [PageDown](https://code.google.com/p/pagedown/)
|
||||
- [docular](https://github.com/Vertafore/docular)
|
||||
- [and some others...](https://www.npmjs.com/browse/depended/showdown)
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
### Download tarball
|
||||
|
||||
You can download the latest release tarball directly from [releases][releases]
|
||||
|
||||
### Bower
|
||||
|
||||
bower install showdown
|
||||
|
||||
### npm (server-side)
|
||||
|
||||
npm install showdown
|
||||
|
||||
### CDN
|
||||
|
||||
You can also use one of several CDNs available:
|
||||
|
||||
* github CDN
|
||||
|
||||
https://cdn.rawgit.com/showdownjs/showdown/<version tag>/dist/showdown.min.js
|
||||
|
||||
* cdnjs
|
||||
|
||||
https://cdnjs.cloudflare.com/ajax/libs/showdown/<version tag>/showdown.min.js
|
||||
|
||||
|
||||
## Browser Compatibility
|
||||
|
||||
Showdown has been tested successfully with:
|
||||
|
||||
* Firefox 1.5 and 2.0
|
||||
* Chrome 12.0
|
||||
* Internet Explorer 6 and 7
|
||||
* Safari 2.0.4
|
||||
* Opera 8.54 and 9.10
|
||||
* Netscape 8.1.2
|
||||
* Konqueror 3.5.4
|
||||
|
||||
In theory, Showdown will work in any browser that supports ECMA 262 3rd Edition (JavaScript 1.5). The converter itself might even work in things that aren't web browsers, like Acrobat. No promises.
|
||||
|
||||
|
||||
## Node compatibility
|
||||
|
||||
Showdown has been tested with node 0.8 and 0.10. However, it should work with previous versions, such as node 0.6.
|
||||
|
||||
|
||||
## Legacy version
|
||||
|
||||
If you're looking for showdown v<1.0.0, you can find it in the [**legacy branch**][legacy-branch].
|
||||
|
||||
## Changelog
|
||||
|
||||
You can check the full [changelog][changelog]
|
||||
|
||||
## Extended documentation
|
||||
Check our [wiki pages][wiki] for examples and a more in-depth documentation.
|
||||
|
||||
|
||||
## Quick Example
|
||||
|
||||
### Node
|
||||
|
||||
```js
|
||||
var showdown = require('showdown'),
|
||||
converter = new showdown.Converter(),
|
||||
text = '#hello, markdown!',
|
||||
html = converter.makeHtml(text);
|
||||
```
|
||||
|
||||
### Browser
|
||||
|
||||
```js
|
||||
var converter = new showdown.Converter(),
|
||||
text = '#hello, markdown!',
|
||||
html = converter.makeHtml(text);
|
||||
```
|
||||
|
||||
### Output
|
||||
|
||||
Both examples should output...
|
||||
|
||||
<h1 id="hellomarkdown">hello, markdown!</h1>
|
||||
|
||||
## Options
|
||||
|
||||
You can change some of showdown's default behavior through options.
|
||||
|
||||
### Setting options
|
||||
|
||||
Options can be set:
|
||||
|
||||
#### Globally
|
||||
|
||||
Setting a "global" option affects all instances of showdown
|
||||
|
||||
```js
|
||||
showdown.setOption('optionKey', 'value');
|
||||
```
|
||||
|
||||
#### Locally
|
||||
Setting a "local" option only affects the specified Converter object.
|
||||
Local options can be set:
|
||||
|
||||
* **through the constructor**
|
||||
```js
|
||||
var converter = new showdown.Converter({optionKey: 'value'});
|
||||
```
|
||||
|
||||
* **through the setOption() method**
|
||||
```js
|
||||
var converter = new showdown.Converter();
|
||||
converter.setOption('optionKey', 'value');
|
||||
```
|
||||
|
||||
### Getting an option
|
||||
|
||||
Showdown provides 2 methods (both local and global) to retrieve previous set options.
|
||||
|
||||
#### getOption()
|
||||
|
||||
```js
|
||||
// Global
|
||||
var myOption = showdown.getOption('optionKey');
|
||||
|
||||
//Local
|
||||
var myOption = converter.getOption('optionKey');
|
||||
```
|
||||
|
||||
#### getOptions()
|
||||
|
||||
```js
|
||||
// Global
|
||||
var showdownGlobalOptions = showdown.getOptions();
|
||||
|
||||
//Local
|
||||
var thisConverterSpecificOptions = converter.getOptions();
|
||||
```
|
||||
|
||||
### Retrieve the default options
|
||||
|
||||
You can get showdown's default options with:
|
||||
```js
|
||||
var defaultOptions = showdown.getDefaultOptions();
|
||||
```
|
||||
|
||||
### Valid Options
|
||||
|
||||
* **omitExtraWLInCodeBlocks**: (boolean) [default false] Omit the trailing newline in a code block. Ex:
|
||||
|
||||
This:
|
||||
```html
|
||||
<code><pre>var foo = 'bar';
|
||||
</pre></code>
|
||||
```
|
||||
Becomes this:
|
||||
```html
|
||||
<code><pre>var foo = 'bar';</pre></code>
|
||||
```
|
||||
|
||||
* **noHeaderId**: (boolean) [default false] Disable the automatic generation of header ids. Setting to true overrides **prefixHeaderId**
|
||||
|
||||
* **prefixHeaderId**: (string/boolean) [default false] Add a prefix to the generated header ids. Passing a string will prefix that string to the header id. Setting to `true` will add a generic 'section' prefix.
|
||||
|
||||
* **parseImgDimensions**: (boolean) [default false] Enable support for setting image dimensions from within markdown syntax.
|
||||
Examples:
|
||||
```
|
||||
![foo](foo.jpg =100x80) simple, assumes units are in px
|
||||
![bar](bar.jpg =100x*) sets the height to "auto"
|
||||
![baz](baz.jpg =80%x5em) Image with width of 80% and height of 5em
|
||||
```
|
||||
|
||||
* **headerLevelStart**: (integer) [default 1] Set the header starting level. For instance, setting this to 3 means that
|
||||
|
||||
```md
|
||||
# foo
|
||||
```
|
||||
will be parsed as
|
||||
|
||||
```html
|
||||
<h3>foo</h3>
|
||||
```
|
||||
|
||||
* **simplifiedAutoLink**: (boolean) [default false] Turning this on will enable GFM autolink style. This means that
|
||||
|
||||
```md
|
||||
some text www.google.com
|
||||
```
|
||||
will be parsed as
|
||||
````
|
||||
<p>some text <a href="www.google.com">www.google.com</a>
|
||||
```
|
||||
|
||||
* **literalMidWordUnderscores**: (boolean) [default false] Turning this on will stop showdown from interpreting underscores in the middle of words as `<em>` and `<strong>` and instead treat them as literal underscores.
|
||||
|
||||
Example:
|
||||
|
||||
```md
|
||||
some text with__underscores__in middle
|
||||
```
|
||||
will be parsed as
|
||||
```html
|
||||
<p>some text with__underscores__in middle</p>
|
||||
```
|
||||
|
||||
* **strikethrough**: (boolean) [default false] Enable support for strikethrough syntax.
|
||||
`~~strikethrough~~` as `<del>strikethrough</del>`
|
||||
|
||||
* **tables**: (boolean) [default false] Enable support for tables syntax. Example:
|
||||
|
||||
```md
|
||||
| h1 | h2 | h3 |
|
||||
|:------|:-------:|--------:|
|
||||
| 100 | [a][1] | ![b][2] |
|
||||
| *foo* | **bar** | ~~baz~~ |
|
||||
```
|
||||
|
||||
See the wiki for more info
|
||||
|
||||
* **tablesHeaderId**: (boolean) [default false] If enabled adds an id property to table headers tags.
|
||||
|
||||
* **ghCodeBlocks**: (boolean) [default true] Enable support for GFM code block style.
|
||||
|
||||
* **tasklists**:(boolean) [default false] Enable support for GFM takslists. Example:
|
||||
|
||||
```md
|
||||
- [x] This task is done
|
||||
- [ ] This is still pending
|
||||
```
|
||||
* **smoothLivePreview**: (boolean) [default false] Prevents weird effects in live previews due to incomplete input
|
||||
|
||||
## CLI Tool
|
||||
|
||||
Showdown also comes bundled with a Command Line Interface tool. You can check the [CLI wiki page][cli-wiki] for more info
|
||||
|
||||
## Integration with AngularJS
|
||||
|
||||
ShowdownJS project also provides seamlessly integration with AngularJS via a "plugin".
|
||||
Please visit https://github.com/showdownjs/ngShowdown for more information.
|
||||
|
||||
## Integration with TypeScript
|
||||
|
||||
If you're using TypeScript you maybe want to use the types from [DefinitelyTyped][definitely-typed]
|
||||
|
||||
## XSS vulnerability
|
||||
|
||||
Showdown doesn't sanitize the input. This is by design since markdown relies on it to allow certain features to be correctly parsed into HTML.
|
||||
This, however, means XSS injection is quite possible.
|
||||
|
||||
Please refer to the wiki article [Markdown's XSS Vulnerability (and how to mitigate it)][xss-wiki]
|
||||
for more information.
|
||||
|
||||
## Extensions
|
||||
|
||||
Showdown allows additional functionality to be loaded via extensions. (you can find a list of known showdown extensions [here][ext-wiki])
|
||||
|
||||
### Client-side Extension Usage
|
||||
|
||||
```js
|
||||
<script src="showdown.js" />
|
||||
<script src="twitter-extension.js" />
|
||||
|
||||
var converter = new showdown.Converter({ extensions: 'twitter' });
|
||||
```
|
||||
|
||||
### Server-side Extension Usage
|
||||
|
||||
```js
|
||||
var showdown = require('showdown'),
|
||||
myExtension = require('myExtension'),
|
||||
converter = new showdown.Converter({ extensions: ['myExtension'] });
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
A suite of tests is available which require node.js. Once node is installed, run the following command from the project root to install the dependencies:
|
||||
|
||||
npm install
|
||||
|
||||
Once installed the tests can be run from the project root using:
|
||||
|
||||
npm test
|
||||
|
||||
New test cases can easily be added. Create a markdown file (ending in `.md`) which contains the markdown to test. Create a `.html` file of the exact same name. It will automatically be tested when the tests are executed with `mocha`.
|
||||
|
||||
## Contributing
|
||||
|
||||
If you wish to contribute please read the following quick guide.
|
||||
|
||||
### Want a Feature?
|
||||
You can request a new feature by submitting an issue. If you would like to implement a new feature feel free to issue a
|
||||
Pull Request.
|
||||
|
||||
|
||||
### Pull requests (PRs)
|
||||
PRs are awesome. However, before you submit your pull request consider the following guidelines:
|
||||
|
||||
- Search GitHub for an open or closed Pull Request that relates to your submission. You don't want to duplicate effort.
|
||||
- When issuing PRs that change code, make your changes in a new git branch based on master:
|
||||
|
||||
```bash
|
||||
git checkout -b my-fix-branch master
|
||||
```
|
||||
|
||||
- Documentation (i.e: README.md) changes can be made directly against master.
|
||||
- Run the full test suite before submitting and make sure all tests pass (obviously =P).
|
||||
- Try to follow our [**coding style rules**][coding-rules].
|
||||
Breaking them prevents the PR to pass the tests.
|
||||
- Refrain from fixing multiple issues in the same pull request. It's preferable to open multiple small PRs instead of one
|
||||
hard to review big one.
|
||||
- If the PR introduces a new feature or fixes an issue, please add the appropriate test case.
|
||||
- We use commit notes to generate the changelog. It's extremely helpful if your commit messages adhere to the
|
||||
[**AngularJS Git Commit Guidelines**][ng-commit-guide].
|
||||
- If we suggest changes then:
|
||||
- Make the required updates.
|
||||
- Re-run the Angular test suite to ensure tests are still passing.
|
||||
- Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
|
||||
|
||||
```bash
|
||||
git rebase master -i
|
||||
git push origin my-fix-branch -f
|
||||
```
|
||||
- After your pull request is merged, you can safely delete your branch.
|
||||
|
||||
If you have time to contribute to this project, we feel obliged that you get credit for it.
|
||||
These rules enable us to review your PR faster and will give you appropriate credit in your GitHub profile.
|
||||
We thank you in advance for your contribution!
|
||||
|
||||
### Joining the team
|
||||
We're looking for members to help maintaining Showdown.
|
||||
Please see [this issue](https://github.com/showdownjs/showdown/issues/114) to express interest or comment on this note.
|
||||
|
||||
## Credits
|
||||
Full credit list at https://github.com/showdownjs/showdown/blob/master/CREDITS.md
|
||||
|
||||
Showdown is powered by:<br/>
|
||||
[![webstorm](https://www.jetbrains.com/webstorm/documentation/docs/logo_webstorm.png)](https://www.jetbrains.com/webstorm/)
|
||||
|
||||
|
||||
|
||||
[sd-logo]: https://raw.githubusercontent.com/showdownjs/logo/master/dist/logo.readme.png
|
||||
[legacy-branch]: https://github.com/showdownjs/showdown/tree/legacy
|
||||
[releases]: https://github.com/showdownjs/showdown/releases
|
||||
[changelog]: https://github.com/showdownjs/showdown/blob/master/CHANGELOG.md
|
||||
[wiki]: https://github.com/showdownjs/showdown/wiki
|
||||
[cli-wiki]: https://github.com/showdownjs/showdown/wiki/CLI-tool
|
||||
[definitely-typed]: https://github.com/borisyankov/DefinitelyTyped/tree/master/showdown
|
||||
[xss-wiki]: https://github.com/showdownjs/showdown/wiki/Markdown's-XSS-Vulnerability-(and-how-to-mitigate-it)
|
||||
[ext-wiki]: https://github.com/showdownjs/showdown/wiki/extensions
|
||||
[coding-rules]: https://github.com/showdownjs/code-style/blob/master/README.md
|
||||
[ng-commit-guide]: https://github.com/showdownjs/code-style/blob/master/README.md#commit-message-convention
|
||||
|
|
BIN
bin/showdown.js
Normal file
BIN
bin/showdown.js
Normal file
Binary file not shown.
33
bower.json
Normal file
33
bower.json
Normal file
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "showdown",
|
||||
"description": "A Markdown to HTML converter written in Javascript",
|
||||
"homepage": "https://github.com/showdownjs/showdown",
|
||||
"authors": [
|
||||
"Estevão Santos (https://github.com/tivie)",
|
||||
"Pascal Deschênes (https://github.com/pdeschen)"
|
||||
],
|
||||
"main": ["dist/showdown.js"],
|
||||
"ignore": [
|
||||
".editorconfig",
|
||||
".gitattributes",
|
||||
".gitignore",
|
||||
".jscs.json",
|
||||
".jshintignore",
|
||||
".jshintrc",
|
||||
".travis.yml",
|
||||
"Gruntfile.js",
|
||||
"package.json",
|
||||
"src/*",
|
||||
"test/*"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/showdownjs/showdown.git"
|
||||
},
|
||||
"keywords": [
|
||||
"markdown",
|
||||
"md",
|
||||
"mdown"
|
||||
],
|
||||
"license": "https://github.com/showdownjs/showdown/blob/master/license.txt"
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
//
|
||||
// Github Extension (WIP)
|
||||
// ~~strike-through~~ -> <del>strike-through</del>
|
||||
//
|
||||
(function(){var a=function(a){return[{type:"lang",regex:"(~T){2}([^~]+)(~T){2}",replace:function(a,b,c,d){return"<del>"+c+"</del>"}}]};typeof window!="undefined"&&window.Showdown&&window.Showdown.extensions&&(window.Showdown.extensions.github=a),typeof module!="undefined"&&(module.exports=a)})();
|
|
@ -1,6 +0,0 @@
|
|||
//
|
||||
// Google Prettify
|
||||
// A showdown extension to add Google Prettify (http://code.google.com/p/google-code-prettify/)
|
||||
// hints to showdown's HTML output.
|
||||
//
|
||||
(function(){var a=function(a){return[{type:"output",filter:function(a){return a.replace(/(<pre>)?<code>/gi,function(a,b){return b?'<pre class="prettyprint linenums" tabIndex="0"><code data-inner="1">':'<code class="prettyprint">'})}}]};typeof window!="undefined"&&window.Showdown&&window.Showdown.extensions&&(window.Showdown.extensions.prettify=a),typeof module!="undefined"&&(module.exports=a)})();
|
|
@ -1,13 +0,0 @@
|
|||
/*global module:true*//*
|
||||
* Basic table support with re-entrant parsing, where cell content
|
||||
* can also specify markdown.
|
||||
*
|
||||
* Tables
|
||||
* ======
|
||||
*
|
||||
* | Col 1 | Col 2 |
|
||||
* |======== |====================================================|
|
||||
* |**bold** | ![Valid XHTML] (http://w3.org/Icons/valid-xhtml10) |
|
||||
* | Plain | Value |
|
||||
*
|
||||
*/(function(){var a=function(a){var b={},c="text-align:left;",d;return b.th=function(a){if(a.trim()==="")return"";var b=a.trim().replace(/ /g,"_").toLowerCase();return'<th id="'+b+'" style="'+c+'">'+a+"</th>"},b.td=function(b){return'<td style="'+c+'">'+a.makeHtml(b)+"</td>"},b.ths=function(){var a="",c=0,d=[].slice.apply(arguments);for(c;c<d.length;c+=1)a+=b.th(d[c])+"\n";return a},b.tds=function(){var a="",c=0,d=[].slice.apply(arguments);for(c;c<d.length;c+=1)a+=b.td(d[c])+"\n";return a},b.thead=function(){var a,c=0,d=[].slice.apply(arguments);return a="<thead>\n",a+="<tr>\n",a+=b.ths.apply(this,d),a+="</tr>\n",a+="</thead>\n",a},b.tr=function(){var a,c=0,d=[].slice.apply(arguments);return a="<tr>\n",a+=b.tds.apply(this,d),a+="</tr>\n",a},d=function(a){var c=0,d=a.split("\n"),e=[],f,g,h,i=[];for(c;c<d.length;c+=1){f=d[c];if(f.trim().match(/^[|]{1}.*[|]{1}$/)){f=f.trim(),e.push("<table>"),g=f.substring(1,f.length-1).split("|"),e.push(b.thead.apply(this,g)),f=d[++c];if(!!f.trim().match(/^[|]{1}[-=| ]+[|]{1}$/)){f=d[++c],e.push("<tbody>");while(f.trim().match(/^[|]{1}.*[|]{1}$/))f=f.trim(),e.push(b.tr.apply(this,f.substring(1,f.length-1).split("|"))),f=d[++c];e.push("</tbody>"),e.push("</table>"),i.push(e.join("\n"));continue}f=d[--c]}i.push(f)}return i.join("\n")},[{type:"lang",filter:d}]};typeof window!="undefined"&&window.Showdown&&window.Showdown.extensions&&(window.Showdown.extensions.table=a),typeof module!="undefined"&&(module.exports=a)})();
|
|
@ -1,6 +0,0 @@
|
|||
//
|
||||
// Twitter Extension
|
||||
// @username -> <a href="http://twitter.com/username">@username</a>
|
||||
// #hashtag -> <a href="http://twitter.com/search/%23hashtag">#hashtag</a>
|
||||
//
|
||||
(function(){var a=function(a){return[{type:"lang",regex:"\\B(\\\\)?@([\\S]+)\\b",replace:function(a,b,c){return b==="\\"?a:'<a href="http://twitter.com/'+c+'">@'+c+"</a>"}},{type:"lang",regex:"\\B(\\\\)?#([\\S]+)\\b",replace:function(a,b,c){return b==="\\"?a:'<a href="http://twitter.com/search/%23'+c+'">#'+c+"</a>"}},{type:"lang",regex:"\\\\@",replace:"@"}]};typeof window!="undefined"&&window.Showdown&&window.Showdown.extensions&&(window.Showdown.extensions.twitter=a),typeof module!="undefined"&&(module.exports=a)})();
|
File diff suppressed because one or more lines are too long
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,720 +0,0 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
|
||||
<title>Showdown - Markdown in Javascript</title>
|
||||
<script type="text/javascript" src="showdown.js"></script>
|
||||
<script type="text/javascript" src="showdown-gui.js"></script>
|
||||
<style type="text/css">
|
||||
html,body {
|
||||
margin:0;
|
||||
padding:0;
|
||||
font-family: Helvetica, Arial, Verdana, sans-serif;
|
||||
font-size: 90%;
|
||||
background-color: #e0d8d8;
|
||||
}
|
||||
|
||||
html {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
textarea {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#pageHeader {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
margin-bottom: 0;
|
||||
margin-top: 0.4em;
|
||||
color: #766;
|
||||
}
|
||||
|
||||
#pageHeader h1 {
|
||||
font-size: 3em;
|
||||
}
|
||||
|
||||
#pageHeader * {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1em;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
#pageHeader a {
|
||||
color: #766;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
#pageHeader h1 a:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#pageHeader h4 a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#leftContainer, #rightContainer {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
width: 47.5%;
|
||||
margin-top: -1.4em;
|
||||
}
|
||||
|
||||
#leftContainer {
|
||||
float: left;
|
||||
left: 1.5%;
|
||||
}
|
||||
|
||||
#rightContainer {
|
||||
float: right;
|
||||
right: 1.5%;
|
||||
}
|
||||
|
||||
#rightContainer > * {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.paneHeader {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: block;
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
.paneHeader * {
|
||||
position: relative;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.paneHeader span {
|
||||
background-color: #ddd5d5;
|
||||
color: #444;
|
||||
padding: 0 0.75em;
|
||||
font-size: 110%;
|
||||
}
|
||||
|
||||
#paneSetting {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: 0.5em;
|
||||
font-size: 110%;
|
||||
font-weight: 900;
|
||||
font-family: Arial, Verdana, sans-serif;
|
||||
background-color: #dacccc;
|
||||
color: #444;
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
.pane {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding-left: 4px; /* pane padding */
|
||||
width: 100%;
|
||||
border: none;
|
||||
background-color: #eee;
|
||||
display: block;
|
||||
border: 1px solid #888;
|
||||
border-right: 1px solid #000;
|
||||
border-bottom: 1px solid #000;
|
||||
|
||||
/* note: the panes get their height set with
|
||||
javascript; see sizeTextAreas(). */
|
||||
|
||||
/* for now, set a height so things look nice
|
||||
if the user has javascript disabled */
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
#previewPane {
|
||||
background-color: #f3eeee;
|
||||
}
|
||||
|
||||
#outputPane {
|
||||
background-color: #6c6666;
|
||||
color: #fff;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#syntaxPane {
|
||||
background-color: #e6dede;
|
||||
background-color: #f7ecec;
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.pane {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#inputPane {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
#previewPane {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#previewPane > * {
|
||||
margin-left: 4px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
#previewPane > blockquote {
|
||||
margin-left: 3em;
|
||||
}
|
||||
|
||||
#previewPane > :first-child {
|
||||
margin-top: 4px; /* pane padding */
|
||||
}
|
||||
|
||||
#previewPane * {
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
#previewPane code {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
#footer {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 100%;
|
||||
height: 2.5em;
|
||||
margin-top: 0.5em;
|
||||
font-family: Helvetica, Arial, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
#footer a {
|
||||
text-decoration: none;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
#footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#byline {
|
||||
padding-left: 2em;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
#convertTextControls {
|
||||
position: absolute;
|
||||
right: 5em;
|
||||
}
|
||||
|
||||
#convertTextButton {
|
||||
line-height: 1em;
|
||||
background-color: #ccbfbf;
|
||||
color: #000;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#convertTextButton:hover {
|
||||
background-color: #fff;
|
||||
color: black;
|
||||
}
|
||||
|
||||
#convertTextSetting {
|
||||
background-color: #dacccc;
|
||||
color: #222;
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
#processingTime {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 4em;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
top: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="pageHeader">
|
||||
<h1><a href="http://www.attacklab.net/showdown-gui.html">Showdown</a></h1>
|
||||
<h4>a javascript port of <a href="http://daringfireball.net/projects/markdown/" title="The Markdown web site">Markdown</a></h4>
|
||||
</div>
|
||||
|
||||
<div id="leftContainer">
|
||||
<div class="paneHeader">
|
||||
<span>Input</span>
|
||||
</div>
|
||||
<textarea id="inputPane" cols="80" rows="20" class="pane">Using this tool
|
||||
---------------
|
||||
|
||||
This page lets you create HTML by entering text in a simple format that's easy to read and write.
|
||||
|
||||
- Type Markdown text in the left window
|
||||
- See the HTML in the right
|
||||
|
||||
Markdown is a lightweight markup language based on the formatting conventions that people naturally use in email. As [John Gruber] writes on the [Markdown site] [1]:
|
||||
|
||||
> The overriding design goal for Markdown's
|
||||
> formatting syntax is to make it as readable
|
||||
> as possible. The idea is that a
|
||||
> Markdown-formatted document should be
|
||||
> publishable as-is, as plain text, without
|
||||
> looking like it's been marked up with tags
|
||||
> or formatting instructions.
|
||||
|
||||
This document is written in Markdown; you can see the plain-text version on the left. To get a feel for Markdown's syntax, type some text into the left window and watch the results in the right. You can see a Markdown syntax guide by switching the right-hand window from *Preview* to *Syntax Guide*.
|
||||
|
||||
Showdown is a Javascript port of Markdown. You can get the full [source code] by clicking on the version number at the bottom of the page.
|
||||
|
||||
**Start with a [blank page] or edit this document in the left window.**
|
||||
|
||||
[john gruber]: http://daringfireball.net/
|
||||
[1]: http://daringfireball.net/projects/markdown/
|
||||
[source code]: http://www.attacklab.net/showdown-v0.9.zip
|
||||
[blank page]: ?blank=1 "Clear all text"
|
||||
|
||||
</textarea>
|
||||
</div>
|
||||
|
||||
<div id="rightContainer">
|
||||
<div class="paneHeader">
|
||||
<select id="paneSetting">
|
||||
<option value="previewPane">Preview</option>
|
||||
<option value="outputPane">HTML Output</option>
|
||||
<option value="syntaxPane">Syntax Guide</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<textarea id="outputPane" class="pane" cols="80" rows="20" readonly="readonly"></textarea>
|
||||
|
||||
<div id="previewPane" class="pane"><noscript><h2>You'll need to enable Javascript to use this tool.</h2></noscript></div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<textarea id="syntaxPane" class="pane" cols="80" rows="20" readonly="readonly">
|
||||
|
||||
Markdown Syntax Guide
|
||||
=====================
|
||||
|
||||
This is an overview of Markdown's syntax. For more information, visit the [Markdown web site].
|
||||
|
||||
[Markdown web site]:
|
||||
http://daringfireball.net/projects/markdown/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Italics and Bold
|
||||
================
|
||||
|
||||
|
||||
*This is italicized*, and so is _this_.
|
||||
|
||||
**This is bold**, and so is __this__.
|
||||
|
||||
You can use ***italics and bold together*** if you ___have to___.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Links
|
||||
=====
|
||||
|
||||
|
||||
Simple links
|
||||
------------
|
||||
|
||||
There are three ways to write links. Each is easier to read than the last:
|
||||
|
||||
Here's an inline link to [Google](http://www.google.com/).
|
||||
Here's a reference-style link to [Google] [1].
|
||||
Here's a very readable link to [Yahoo!].
|
||||
|
||||
[1]: http://www.google.com/
|
||||
[yahoo!]: http://www.yahoo.com/
|
||||
|
||||
The link definitions can appear anywhere in the document -- before or after the place where you use them. The link definition names (`1` and `Yahoo!`) can be any unique string, and are case-insensitive; `[Yahoo!]` is the same as `[YAHOO!]`.
|
||||
|
||||
|
||||
Advanced links: Title attributes
|
||||
--------------------------------
|
||||
|
||||
You can also add a `title` attribute to a link, which will show up when the user holds the mouse pointer it. Title attributes are helpful if your link text is not descriptive enough to tell users where they're going. (In reference links, you can use optionally parentheses for the link title instead of quotation marks.)
|
||||
|
||||
Here's a [poorly-named link](http://www.google.com/ "Google").
|
||||
Never write "[click here][^2]".
|
||||
Trust [me].
|
||||
|
||||
[^2]: http://www.w3.org/QA/Tips/noClickHere
|
||||
(Advice against the phrase "click here")
|
||||
[me]: http://www.attacklab.net/ "Attacklab"
|
||||
|
||||
|
||||
Advanced links: Bare URLs
|
||||
-------------------------
|
||||
|
||||
You can write bare URLs by enclosing them in angle brackets:
|
||||
|
||||
My web site is at <http://www.attacklab.net>.
|
||||
|
||||
If you use this format for email addresses, Showdown will encode the address to make it harder for spammers to harvest. Try it and look in the *HTML Output* pane to see the results:
|
||||
|
||||
Humans can read this, but most spam harvesting robots can't: <me@privacy.net>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Headers
|
||||
=======
|
||||
|
||||
|
||||
There are two ways to do headers in Markdown. (In these examples, Header 1 is the biggest, and Header 6 is the smallest.)
|
||||
|
||||
You can underline text to make the two top-level headers:
|
||||
|
||||
Header 1
|
||||
========
|
||||
|
||||
Header 2
|
||||
--------
|
||||
|
||||
The number of `=` or `-` signs doesn't matter; you can get away with just one. But using enough to underline the text makes your titles look better in plain text.
|
||||
|
||||
You can also use hash marks for all six levels of HTML headers:
|
||||
|
||||
# Header 1 #
|
||||
## Header 2 ##
|
||||
### Header 3 ###
|
||||
#### Header 4 ####
|
||||
##### Header 5 #####
|
||||
###### Header 6 ######
|
||||
|
||||
The closing `#` characters are optional.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Horizontal Rules
|
||||
================
|
||||
|
||||
|
||||
You can insert a horizontal rule by putting three or more hyphens, asterisks, or underscores on a line by themselves:
|
||||
|
||||
---
|
||||
|
||||
*******
|
||||
___
|
||||
|
||||
You can also use spaces between the characters:
|
||||
|
||||
- - - -
|
||||
|
||||
All of these examples produce the same output.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Lists
|
||||
=====
|
||||
|
||||
|
||||
Simple lists
|
||||
------------
|
||||
|
||||
A bulleted list:
|
||||
|
||||
- You can use a minus sign for a bullet
|
||||
+ Or plus sign
|
||||
* Or an asterisk
|
||||
|
||||
A numbered list:
|
||||
|
||||
1. Numbered lists are easy
|
||||
2. Markdown keeps track of the numbers for you
|
||||
7. So this will be item 3.
|
||||
|
||||
A double-spaced list:
|
||||
|
||||
- This list gets wrapped in `<p>` tags
|
||||
|
||||
- So there will be extra space between items
|
||||
|
||||
|
||||
Advanced lists: Nesting
|
||||
-----------------------
|
||||
|
||||
You can put other Markdown blocks in a list; just indent four spaces for each nesting level. So:
|
||||
|
||||
1. Lists in a list item:
|
||||
- Indented four spaces.
|
||||
* indented eight spaces.
|
||||
- Four spaces again.
|
||||
|
||||
2. Multiple paragraphs in a list items:
|
||||
|
||||
It's best to indent the paragraphs four spaces
|
||||
You can get away with three, but it can get
|
||||
confusing when you nest other things.
|
||||
Stick to four.
|
||||
|
||||
We indented the first line an extra space to align
|
||||
it with these paragraphs. In real use, we might do
|
||||
that to the entire list so that all items line up.
|
||||
|
||||
This paragraph is still part of the list item, but it looks messy to humans. So it's a good idea to wrap your nested paragraphs manually, as we did with the first two.
|
||||
|
||||
3. Blockquotes in a list item:
|
||||
|
||||
> Skip a line and
|
||||
> indent the >'s four spaces.
|
||||
|
||||
4. Preformatted text in a list item:
|
||||
|
||||
Skip a line and indent eight spaces.
|
||||
That's four spaces for the list
|
||||
and four to trigger the code block.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Blockquotes
|
||||
===========
|
||||
|
||||
|
||||
Simple blockquotes
|
||||
------------------
|
||||
|
||||
Blockquotes are indented:
|
||||
|
||||
> The syntax is based on the way email programs
|
||||
> usually do quotations. You don't need to hard-wrap
|
||||
> the paragraphs in your blockquotes, but it looks much nicer if you do. Depends how lazy you feel.
|
||||
|
||||
|
||||
Advanced blockquotes: Nesting
|
||||
-----------------------------
|
||||
|
||||
You can put other Markdown blocks in a blockquote; just add a `>` followed by a space:
|
||||
|
||||
Parragraph breaks in a blockquote:
|
||||
|
||||
> The > on the blank lines is optional.
|
||||
> Include it or don't; Markdown doesn't care.
|
||||
>
|
||||
> But your plain text looks better to
|
||||
> humans if you include the extra `>`
|
||||
> between paragraphs.
|
||||
|
||||
|
||||
Blockquotes within a blockquote:
|
||||
|
||||
> A standard blockquote is indented
|
||||
> > A nested blockquote is indented more
|
||||
> > > > You can nest to any depth.
|
||||
|
||||
|
||||
Lists in a blockquote:
|
||||
|
||||
> - A list in a blockquote
|
||||
> - With a > and space in front of it
|
||||
> * A sublist
|
||||
|
||||
Preformatted text in a blockquote:
|
||||
|
||||
> Indent five spaces total. The first
|
||||
> one is part of the blockquote designator.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Images
|
||||
======
|
||||
|
||||
|
||||
Images are exactly like links, but they have an exclamation point in front of them:
|
||||
|
||||
![Valid XHTML] (http://w3.org/Icons/valid-xhtml10).
|
||||
|
||||
The word in square brackets is the alt text, which gets displayed if the browser can't show the image. Be sure to include meaningful alt text for blind users' screen-reader software.
|
||||
|
||||
Just like links, images work with reference syntax and titles:
|
||||
|
||||
This page is ![valid XHTML][checkmark].
|
||||
|
||||
[checkmark]: http://w3.org/Icons/valid-xhtml10
|
||||
"What are you smiling at?"
|
||||
|
||||
|
||||
**Note:**
|
||||
|
||||
Markdown does not currently support the shortest reference syntax for images:
|
||||
|
||||
Here's a broken ![checkmark].
|
||||
|
||||
But you can use a slightly more verbose version of implicit reference names:
|
||||
|
||||
This ![checkmark][] works.
|
||||
|
||||
The reference name (`valid icon`) is also used as the alt text.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Inline HTML
|
||||
===========
|
||||
|
||||
|
||||
If you need to do something that Markdown can't handle, you can always just use HTML:
|
||||
|
||||
Strikethrough humor is <strike>funny</strike>.
|
||||
|
||||
Markdown is smart enough not to mangle your span-level HTML:
|
||||
|
||||
<u>Markdown works *fine* in here.</u>
|
||||
|
||||
Block-level HTML elments have a few restrictions:
|
||||
|
||||
1. They must be separated from surrounding text by blank
|
||||
lines.
|
||||
2. The begin and end tags of the outermost block element
|
||||
must not be indented.
|
||||
3. You can't use Markdown within HTML blocks.
|
||||
|
||||
So:
|
||||
|
||||
<div style="background-color: lightgray">
|
||||
You can <em>not</em> use Markdown in here.
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Preformatted Text
|
||||
=================
|
||||
|
||||
|
||||
You can include preformatted text in a Markdown document.
|
||||
|
||||
To make a code block, indent four spaces:
|
||||
|
||||
printf("goodbye world!"); /* his suicide note
|
||||
was in C */
|
||||
|
||||
The text will be wrapped in `<pre>` and `<code>` tags, and the browser will display it in a monospaced typeface. The first four spaces will be stripped off, but all other whitespace will be preserved.
|
||||
|
||||
You cannot use Markdown or HTML within a code block, which makes them a convenient way to show samples of Markdown or HTML syntax:
|
||||
|
||||
<blink>
|
||||
You would hate this if it weren't
|
||||
wrapped in a code block.
|
||||
</blink>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Code Spans
|
||||
==========
|
||||
|
||||
|
||||
You can make inline `<code>` tags by using code spans. Use backticks to make a code span:
|
||||
|
||||
Press the `<Tab>` key, then type a `$`.
|
||||
|
||||
(The backtick key is in the upper left corner of most keyboards.)
|
||||
|
||||
Like code blocks, code spans will be displayed in a monospaced typeface. Markdown and HTML will not work within them:
|
||||
|
||||
Markdown italicizes things like this: `I *love* it.`
|
||||
|
||||
Don't use the `<font>` tag; use CSS instead.
|
||||
|
||||
</textarea>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div id="footer">
|
||||
<span id="byline">
|
||||
<b><a href="http://www.attacklab.net/showdown-v0.9.zip">Download v0.9</a></b> copyright © 2007
|
||||
|
||||
<script type="text/javascript">
|
||||
/* <![CDATA[ */
|
||||
function hivelogic_enkoder(){var kode=
|
||||
"kode=\"oked\\\"=rnhg%@uqkj(Cxtnm+Ftxmn+e{F\\\\p00o1yq0\\\\00_z33:3~u\\\\q0"+
|
||||
"0.0m4tHq,I~.rmhxy{u0\\\\001\\\\77{Fpt3_333_33L{\\\\z00m0\\\\m00w0mo:xqnhz0"+
|
||||
"\\\\00.,\\\\u00x00\\\\00qIh.Qymux,\\\\t00,0\\\\q00M1\\\\t00~0.{.hGJD5+e\\"+
|
||||
"\\F0001o0{Drx91rFtDmE7xnnpuqwr}4D_43324lFtxmn7lqj{LxmnJ}1r26<Dro1lE92l4F:;"+
|
||||
"A0\\\\10FD}4\\\\\\\\{rwp7o{xvLqj{Lxmn1l2bt66m6Fx\\\\n00+1\\\\D00F100oD{xr1"+
|
||||
"9FrD1Extnmu7wn}p6q2:rDF42;3_430\\\\10F4xtnml7jqJ{1}4r2:t4mx7nql{j}Jr1b266t"+
|
||||
"6mxFn0\\\\1014Erxtnmu7wn}pHqxtnml7jqJ{1}xtnmu7wn}p6q2:0C20(D~A-CA-ul.xCoA6"+
|
||||
"Bouqkjr4tkzmAn1o/10\\\\10Ciuqkji4gnIxjuGk.z/o93oA.lBi/61i7C>8~AC1zYoxmtl4u"+
|
||||
"xIsgnIxju.k/i3_33uqkj~C>%@{**i>url+3@l>n?gr1hhojqkwl>..~,@frnhgf1dkFugrDh+"+
|
||||
"w,l60l>+i?f,3.f4@;5{>@.wVlujqi1ruFpdkFugr+h,f0\\\\00rnhg{@;\\\"=x''f;roi(0"+
|
||||
"=i;k<do.eelgnhti;++{)=cokedc.ahCrdoAe(t)i3-i;(f<c)0+c1=82x;=+tSirgnf.orCma"+
|
||||
"hCrdo(e)ck}do=ex\";x='';for(i=0;i<(kode.length-1);i+=2){x+=kode.charAt(i+1"+
|
||||
")+kode.charAt(i)}kode=x+(i<kode.length?kode.charAt(kode.length-1):'');"
|
||||
;var i,c,x;while(eval(kode));}hivelogic_enkoder();
|
||||
/* ]]> */
|
||||
</script>
|
||||
John Fraser
|
||||
<script type="text/javascript">
|
||||
/* <![CDATA[ */
|
||||
document.write("</a>");
|
||||
/* ]]> */
|
||||
</script>
|
||||
</span>
|
||||
|
||||
<span id="convertTextControls">
|
||||
<button id="convertTextButton" type="button" title="Convert text now">
|
||||
Convert text
|
||||
</button>
|
||||
|
||||
<select id="convertTextSetting">
|
||||
<option value="delayed">in the background</option>
|
||||
<option value="continuous">every keystroke</option>
|
||||
<option value="manual">manually</option>
|
||||
</select>
|
||||
</span>
|
||||
<div id="processingTime" title="Last processing time">0 ms</div>
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,349 +0,0 @@
|
|||
//
|
||||
// showdown-gui.js
|
||||
//
|
||||
// A sample application for Showdown, a javascript port
|
||||
// of Markdown.
|
||||
//
|
||||
// Copyright (c) 2007 John Fraser.
|
||||
//
|
||||
// Redistributable under a BSD-style open source license.
|
||||
// See license.txt for more information.
|
||||
//
|
||||
// The full source distribution is at:
|
||||
//
|
||||
// A A L
|
||||
// T C A
|
||||
// T K B
|
||||
//
|
||||
// <http://www.attacklab.net/>
|
||||
//
|
||||
|
||||
//
|
||||
// The Showdown converter itself is in showdown.js, which must be
|
||||
// included by the HTML before this file is.
|
||||
//
|
||||
// showdown-gui.js assumes the id and class definitions in
|
||||
// showdown.html. It isn't dependent on the CSS, but it does
|
||||
// manually hide, display, and resize the individual panes --
|
||||
// overriding the stylesheets.
|
||||
//
|
||||
// This sample application only interacts with showdown.js in
|
||||
// two places:
|
||||
//
|
||||
// In startGui():
|
||||
//
|
||||
// converter = new Showdown.converter();
|
||||
//
|
||||
// In convertText():
|
||||
//
|
||||
// text = converter.makeHtml(text);
|
||||
//
|
||||
// The rest of this file is user interface stuff.
|
||||
//
|
||||
|
||||
|
||||
//
|
||||
// Register for onload
|
||||
//
|
||||
window.onload = startGui;
|
||||
|
||||
|
||||
//
|
||||
// Globals
|
||||
//
|
||||
|
||||
var converter;
|
||||
var convertTextTimer,processingTime;
|
||||
var lastText,lastOutput,lastRoomLeft;
|
||||
var convertTextSetting, convertTextButton, paneSetting;
|
||||
var inputPane,previewPane,outputPane,syntaxPane;
|
||||
var maxDelay = 3000; // longest update pause (in ms)
|
||||
|
||||
|
||||
//
|
||||
// Initialization
|
||||
//
|
||||
|
||||
function startGui() {
|
||||
// find elements
|
||||
convertTextSetting = document.getElementById("convertTextSetting");
|
||||
convertTextButton = document.getElementById("convertTextButton");
|
||||
paneSetting = document.getElementById("paneSetting");
|
||||
|
||||
inputPane = document.getElementById("inputPane");
|
||||
previewPane = document.getElementById("previewPane");
|
||||
outputPane = document.getElementById("outputPane");
|
||||
syntaxPane = document.getElementById("syntaxPane");
|
||||
|
||||
// set event handlers
|
||||
convertTextSetting.onchange = onConvertTextSettingChanged;
|
||||
convertTextButton.onclick = onConvertTextButtonClicked;
|
||||
paneSetting.onchange = onPaneSettingChanged;
|
||||
window.onresize = setPaneHeights;
|
||||
|
||||
// First, try registering for keyup events
|
||||
// (There's no harm in calling onInput() repeatedly)
|
||||
window.onkeyup = inputPane.onkeyup = onInput;
|
||||
|
||||
// In case we can't capture paste events, poll for them
|
||||
var pollingFallback = window.setInterval(function(){
|
||||
if(inputPane.value != lastText)
|
||||
onInput();
|
||||
},1000);
|
||||
|
||||
// Try registering for paste events
|
||||
inputPane.onpaste = function() {
|
||||
// It worked! Cancel paste polling.
|
||||
if (pollingFallback!=undefined) {
|
||||
window.clearInterval(pollingFallback);
|
||||
pollingFallback = undefined;
|
||||
}
|
||||
onInput();
|
||||
}
|
||||
|
||||
// Try registering for input events (the best solution)
|
||||
if (inputPane.addEventListener) {
|
||||
// Let's assume input also fires on paste.
|
||||
// No need to cancel our keyup handlers;
|
||||
// they're basically free.
|
||||
inputPane.addEventListener("input",inputPane.onpaste,false);
|
||||
}
|
||||
|
||||
// poll for changes in font size
|
||||
// this is cheap; do it often
|
||||
window.setInterval(setPaneHeights,250);
|
||||
|
||||
// start with blank page?
|
||||
if (top.document.location.href.match(/\?blank=1$/))
|
||||
inputPane.value = "";
|
||||
|
||||
// refresh panes to avoid a hiccup
|
||||
onPaneSettingChanged();
|
||||
|
||||
// build the converter
|
||||
converter = new Showdown.converter();
|
||||
|
||||
// do an initial conversion to avoid a hiccup
|
||||
convertText();
|
||||
|
||||
// give the input pane focus
|
||||
inputPane.focus();
|
||||
|
||||
// start the other panes at the top
|
||||
// (our smart scrolling moved them to the bottom)
|
||||
previewPane.scrollTop = 0;
|
||||
outputPane.scrollTop = 0;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Conversion
|
||||
//
|
||||
|
||||
function convertText() {
|
||||
// get input text
|
||||
var text = inputPane.value;
|
||||
|
||||
// if there's no change to input, cancel conversion
|
||||
if (text && text == lastText) {
|
||||
return;
|
||||
} else {
|
||||
lastText = text;
|
||||
}
|
||||
|
||||
var startTime = new Date().getTime();
|
||||
|
||||
// Do the conversion
|
||||
text = converter.makeHtml(text);
|
||||
|
||||
// display processing time
|
||||
var endTime = new Date().getTime();
|
||||
processingTime = endTime - startTime;
|
||||
document.getElementById("processingTime").innerHTML = processingTime+" ms";
|
||||
|
||||
// save proportional scroll positions
|
||||
saveScrollPositions();
|
||||
|
||||
// update right pane
|
||||
if (paneSetting.value == "outputPane") {
|
||||
// the output pane is selected
|
||||
outputPane.value = text;
|
||||
} else if (paneSetting.value == "previewPane") {
|
||||
// the preview pane is selected
|
||||
previewPane.innerHTML = text;
|
||||
}
|
||||
|
||||
lastOutput = text;
|
||||
|
||||
// restore proportional scroll positions
|
||||
restoreScrollPositions();
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Event handlers
|
||||
//
|
||||
|
||||
function onConvertTextSettingChanged() {
|
||||
// If the user just enabled automatic
|
||||
// updates, we'll do one now.
|
||||
onInput();
|
||||
}
|
||||
|
||||
function onConvertTextButtonClicked() {
|
||||
// hack: force the converter to run
|
||||
lastText = "";
|
||||
|
||||
convertText();
|
||||
inputPane.focus();
|
||||
}
|
||||
|
||||
function onPaneSettingChanged() {
|
||||
previewPane.style.display = "none";
|
||||
outputPane.style.display = "none";
|
||||
syntaxPane.style.display = "none";
|
||||
|
||||
// now make the selected one visible
|
||||
top[paneSetting.value].style.display = "block";
|
||||
|
||||
lastRoomLeft = 0; // hack: force resize of new pane
|
||||
setPaneHeights();
|
||||
|
||||
if (paneSetting.value == "outputPane") {
|
||||
// Update output pane
|
||||
outputPane.value = lastOutput;
|
||||
} else if (paneSetting.value == "previewPane") {
|
||||
// Update preview pane
|
||||
previewPane.innerHTML = lastOutput;
|
||||
}
|
||||
}
|
||||
|
||||
function onInput() {
|
||||
// In "delayed" mode, we do the conversion at pauses in input.
|
||||
// The pause is equal to the last runtime, so that slow
|
||||
// updates happen less frequently.
|
||||
//
|
||||
// Use a timer to schedule updates. Each keystroke
|
||||
// resets the timer.
|
||||
|
||||
// if we already have convertText scheduled, cancel it
|
||||
if (convertTextTimer) {
|
||||
window.clearTimeout(convertTextTimer);
|
||||
convertTextTimer = undefined;
|
||||
}
|
||||
|
||||
if (convertTextSetting.value != "manual") {
|
||||
var timeUntilConvertText = 0;
|
||||
if (convertTextSetting.value == "delayed") {
|
||||
// make timer adaptive
|
||||
timeUntilConvertText = processingTime;
|
||||
}
|
||||
|
||||
if (timeUntilConvertText > maxDelay)
|
||||
timeUntilConvertText = maxDelay;
|
||||
|
||||
// Schedule convertText().
|
||||
// Even if we're updating every keystroke, use a timer at 0.
|
||||
// This gives the browser time to handle other events.
|
||||
convertTextTimer = window.setTimeout(convertText,timeUntilConvertText);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Smart scrollbar adjustment
|
||||
//
|
||||
// We need to make sure the user can't type off the bottom
|
||||
// of the preview and output pages. We'll do this by saving
|
||||
// the proportional scroll positions before the update, and
|
||||
// restoring them afterwards.
|
||||
//
|
||||
|
||||
var previewScrollPos;
|
||||
var outputScrollPos;
|
||||
|
||||
function getScrollPos(element) {
|
||||
// favor the bottom when the text first overflows the window
|
||||
if (element.scrollHeight <= element.clientHeight)
|
||||
return 1.0;
|
||||
return element.scrollTop/(element.scrollHeight-element.clientHeight);
|
||||
}
|
||||
|
||||
function setScrollPos(element,pos) {
|
||||
element.scrollTop = (element.scrollHeight - element.clientHeight) * pos;
|
||||
}
|
||||
|
||||
function saveScrollPositions() {
|
||||
previewScrollPos = getScrollPos(previewPane);
|
||||
outputScrollPos = getScrollPos(outputPane);
|
||||
}
|
||||
|
||||
function restoreScrollPositions() {
|
||||
// hack for IE: setting scrollTop ensures scrollHeight
|
||||
// has been updated after a change in contents
|
||||
previewPane.scrollTop = previewPane.scrollTop;
|
||||
|
||||
setScrollPos(previewPane,previewScrollPos);
|
||||
setScrollPos(outputPane,outputScrollPos);
|
||||
}
|
||||
|
||||
//
|
||||
// Textarea resizing
|
||||
//
|
||||
// Some browsers (i.e. IE) refuse to set textarea
|
||||
// percentage heights in standards mode. (But other units?
|
||||
// No problem. Percentage widths? No problem.)
|
||||
//
|
||||
// So we'll do it in javascript. If IE's behavior ever
|
||||
// changes, we should remove this crap and do 100% textarea
|
||||
// heights in CSS, because it makes resizing much smoother
|
||||
// on other browsers.
|
||||
//
|
||||
|
||||
function getTop(element) {
|
||||
var sum = element.offsetTop;
|
||||
while(element = element.offsetParent)
|
||||
sum += element.offsetTop;
|
||||
return sum;
|
||||
}
|
||||
|
||||
function getElementHeight(element) {
|
||||
var height = element.clientHeight;
|
||||
if (!height) height = element.scrollHeight;
|
||||
return height;
|
||||
}
|
||||
|
||||
function getWindowHeight(element) {
|
||||
if (window.innerHeight)
|
||||
return window.innerHeight;
|
||||
else if (document.documentElement && document.documentElement.clientHeight)
|
||||
return document.documentElement.clientHeight;
|
||||
else if (document.body)
|
||||
return document.body.clientHeight;
|
||||
}
|
||||
|
||||
function setPaneHeights() {
|
||||
var textarea = inputPane;
|
||||
var footer = document.getElementById("footer");
|
||||
|
||||
var windowHeight = getWindowHeight();
|
||||
var footerHeight = getElementHeight(footer);
|
||||
var textareaTop = getTop(textarea);
|
||||
|
||||
// figure out how much room the panes should fill
|
||||
var roomLeft = windowHeight - footerHeight - textareaTop;
|
||||
|
||||
if (roomLeft < 0) roomLeft = 0;
|
||||
|
||||
// if it hasn't changed, return
|
||||
if (roomLeft == lastRoomLeft) {
|
||||
return;
|
||||
}
|
||||
lastRoomLeft = roomLeft;
|
||||
|
||||
// resize all panes
|
||||
inputPane.style.height = roomLeft + "px";
|
||||
previewPane.style.height = roomLeft + "px";
|
||||
outputPane.style.height = roomLeft + "px";
|
||||
syntaxPane.style.height = roomLeft + "px";
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
../src/showdown.js
|
30
grunt.js
30
grunt.js
|
@ -1,30 +0,0 @@
|
|||
|
||||
module.exports = function(grunt) {
|
||||
|
||||
// Project configuration.
|
||||
grunt.initConfig({
|
||||
lint: {
|
||||
all: ['src/**/*.js', 'test/**/*.js']
|
||||
},
|
||||
jshint: {
|
||||
options: {
|
||||
browser: true
|
||||
}
|
||||
},
|
||||
simplemocha: {
|
||||
all: {
|
||||
src: 'test/run.js',
|
||||
options: {
|
||||
globals: ['should'],
|
||||
timeout: 3000,
|
||||
ignoreLeaks: false,
|
||||
ui: 'bdd'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
grunt.loadNpmTasks('grunt-simple-mocha');
|
||||
|
||||
grunt.registerTask('default', ['simplemocha', 'lint']);
|
||||
};
|
|
@ -1,9 +1,9 @@
|
|||
Copyright (c) 2007, John Fraser
|
||||
<http://www.attacklab.net/>
|
||||
Showdown Copyright (c) 2007, John Fraser
|
||||
<http://www.attacklab.net/>
|
||||
All rights reserved.
|
||||
|
||||
Original Markdown copyright (c) 2004, John Gruber
|
||||
<http://daringfireball.net/>
|
||||
Original Markdown copyright (c) 2004, John Gruber
|
||||
<http://daringfireball.net/>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
98
package.json
98
package.json
|
@ -1,41 +1,61 @@
|
|||
{
|
||||
"name": "showdown",
|
||||
"version": "0.3.1",
|
||||
"author": "John Fraser",
|
||||
"scripts": {
|
||||
"test": "mocha ./test/run.js"
|
||||
},
|
||||
"contributors": [
|
||||
"John Gruber",
|
||||
"John Fraser",
|
||||
"Corey Innis",
|
||||
"Remy Sharp",
|
||||
"Konstantin Käfer",
|
||||
"Roger Braun",
|
||||
"Dominic Tarr",
|
||||
"Cat Chen",
|
||||
"Titus Stone",
|
||||
"Rob Sutherland",
|
||||
"Pavel Lang",
|
||||
"Ben Combee",
|
||||
"Adam Backstrom",
|
||||
"Pascal Deschênes"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/coreyti/showdown.git",
|
||||
"web": "https://github.com/coreyti/showdown"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "1.3.0",
|
||||
"grunt": "0.3.17",
|
||||
"grunt-simple-mocha": "*",
|
||||
"grunt-mocha": "*",
|
||||
"should": "1.2.0"
|
||||
},
|
||||
"licenses": [{
|
||||
"type": "BSD",
|
||||
"url": "https://github.com/coreyti/showdown/raw/master/license.txt"
|
||||
}],
|
||||
"main": "./src/showdown"
|
||||
"name": "showdown",
|
||||
"version": "1.2.2",
|
||||
"description": "A Markdown to HTML converter written in Javascript",
|
||||
"author": "Estevão Santos",
|
||||
"homepage": "http://showdownjs.github.io/showdown/",
|
||||
"keywords": [
|
||||
"markdown",
|
||||
"converter"
|
||||
],
|
||||
"contributors": [
|
||||
"John Gruber",
|
||||
"John Fraser",
|
||||
"Corey Innis",
|
||||
"Remy Sharp",
|
||||
"Konstantin Käfer",
|
||||
"Roger Braun",
|
||||
"Dominic Tarr",
|
||||
"Cat Chen",
|
||||
"Titus Stone",
|
||||
"Rob Sutherland",
|
||||
"Pavel Lang",
|
||||
"Ben Combee",
|
||||
"Adam Backstrom",
|
||||
"Pascal Deschênes",
|
||||
"Estevão Santos"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/showdownjs/showdown.git",
|
||||
"web": "https://github.com/showdownjs/showdown"
|
||||
},
|
||||
"license": "BSD-2-Clause",
|
||||
"main": "./dist/showdown.js",
|
||||
"scripts": {
|
||||
"test": "grunt test"
|
||||
},
|
||||
"bin": {
|
||||
"showdown": "bin/showdown.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^1.10.0",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-contrib-clean": "^0.6.0",
|
||||
"grunt-contrib-concat": "^0.5.0",
|
||||
"grunt-contrib-jshint": "^0.10.0",
|
||||
"grunt-contrib-uglify": "^0.6.0",
|
||||
"grunt-conventional-changelog": "^1.1.0",
|
||||
"grunt-jscs": "^1.2.0",
|
||||
"grunt-simple-mocha": "^0.4.0",
|
||||
"js-beautify": "^1.5.6",
|
||||
"load-grunt-tasks": "^3.2.0",
|
||||
"quiet-grunt": "^0.2.3",
|
||||
"semver": "^5.0.0",
|
||||
"sinon": "^1.14.1",
|
||||
"source-map-support": "^0.2.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"yargs": "^3.15.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
Copyright (c) 2004, John Gruber
|
||||
<http://daringfireball.net/>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name "Markdown" nor the names of its contributors may
|
||||
be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors "as
|
||||
is" and any express or implied warranties, including, but not limited
|
||||
to, the implied warranties of merchantability and fitness for a
|
||||
particular purpose are disclaimed. In no event shall the copyright owner
|
||||
or contributors be liable for any direct, indirect, incidental, special,
|
||||
exemplary, or consequential damages (including, but not limited to,
|
||||
procurement of substitute goods or services; loss of use, data, or
|
||||
profits; or business interruption) however caused and on any theory of
|
||||
liability, whether in contract, strict liability, or tort (including
|
||||
negligence or otherwise) arising in any way out of the use of this
|
||||
software, even if advised of the possibility of such damage.
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,13 +0,0 @@
|
|||
Reference Implementation
|
||||
------------------------
|
||||
|
||||
This directory contains John Gruber's original Perl implementation of Markdown. Smart diff programs like Araxis Merge will be able to match up this file with markdown.pl.
|
||||
|
||||
A little tweaking helps. In markdown.pl:
|
||||
|
||||
- replace `#` with `//`
|
||||
- replace `$text` with `text`
|
||||
|
||||
Be sure to ignore whitespace and line endings.
|
||||
|
||||
Note: This release of Showdown is based on `markdown1.0.2b7.pl`, but uses the HTML parser from `markdown1.0.2b2.pl`.
|
30
src/cli/cli.js
Normal file
30
src/cli/cli.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
'use strict';
|
||||
|
||||
var version = require('../../package.json').version,
|
||||
yargs = require('yargs');
|
||||
|
||||
yargs
|
||||
.version(version, 'v')
|
||||
.alias('v', 'version')
|
||||
.option('h', {
|
||||
alias: 'help',
|
||||
description: 'Show help'
|
||||
})
|
||||
.usage('Usage: showdown <command> [options]')
|
||||
.demand(1, 'You must provide a valid command')
|
||||
.command('makehtml', 'Converts markdown into html')
|
||||
.example('showdown makehtml -i foo.md -o bar.html', 'Converts \'foo.md\' to \'bar.html\'')
|
||||
.wrap(yargs.terminalWidth());
|
||||
|
||||
var argv = yargs.argv,
|
||||
command = argv._[0];
|
||||
if (command === 'makehtml') {
|
||||
require('./makehtml.cmd.js').run();
|
||||
} else {
|
||||
yargs.showHelp();
|
||||
}
|
||||
|
||||
if (argv.help) {
|
||||
yargs.showHelp();
|
||||
}
|
||||
process.exit(0);
|
6
src/cli/errorexit.js
Normal file
6
src/cli/errorexit.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
module.exports = exports = function errorExit(e) {
|
||||
'use strict';
|
||||
console.error('ERROR: ' + e.message);
|
||||
console.error('Run \'showdown <command> -h\' for help');
|
||||
process.exit(1);
|
||||
};
|
123
src/cli/makehtml.cmd.js
Normal file
123
src/cli/makehtml.cmd.js
Normal file
|
@ -0,0 +1,123 @@
|
|||
var yargs = require('yargs'),
|
||||
fs = require('fs'),
|
||||
errorExit = require('./errorexit.js'),
|
||||
showdown = require('../../dist/showdown');
|
||||
|
||||
yargs.reset()
|
||||
.usage('Usage: showdown makehtml [options]')
|
||||
.example('showdown makehtml -i', 'Reads from stdin and outputs to stdout')
|
||||
.example('showdown makehtml -i foo.md -o bar.html', 'Reads \'foo.md\' and writes to \'bar.html\'')
|
||||
//.demand(['i'])
|
||||
.option('i', {
|
||||
alias : 'input',
|
||||
describe: 'Input source. Usually a md file. If omitted or empty, reads from stdin',
|
||||
type: 'string'
|
||||
})
|
||||
.option('o', {
|
||||
alias : 'output',
|
||||
describe: 'Output target. Usually a html file. If omitted or empty, writes to stdout',
|
||||
type: 'string',
|
||||
default: false
|
||||
})
|
||||
.option('u', {
|
||||
alias : 'encoding',
|
||||
describe: 'Input encoding',
|
||||
type: 'string'
|
||||
})
|
||||
.option('a', {
|
||||
alias : 'append',
|
||||
describe: 'Append data to output instead of overwriting',
|
||||
type: 'string'
|
||||
})
|
||||
.option('e', {
|
||||
alias : 'extensions',
|
||||
describe: 'Load the specified extensions. Should be valid paths to node compatible extensions',
|
||||
type: 'array'
|
||||
})
|
||||
.config('c')
|
||||
.alias('c', 'config')
|
||||
.help('h')
|
||||
.alias('h', 'help');
|
||||
|
||||
yargs.options(showdown.getDefaultOptions(false));
|
||||
argv = yargs.argv;
|
||||
|
||||
function run() {
|
||||
'use strict';
|
||||
var input = '',
|
||||
enc = 'utf8',
|
||||
output;
|
||||
|
||||
if (argv.encoding) {
|
||||
enc = argv.encoding;
|
||||
}
|
||||
|
||||
// to avoid passing extensions to converter
|
||||
delete argv.extensions;
|
||||
var converter = new showdown.Converter(argv);
|
||||
|
||||
// Load extensions
|
||||
if (argv.e) {
|
||||
for (var i = 0; i < argv.e.length; ++i) {
|
||||
loadExtension(argv.e[i], converter);
|
||||
}
|
||||
}
|
||||
|
||||
if (!argv.i || argv.i === '') {
|
||||
// 'i' is undefined or empty, read from stdin
|
||||
try {
|
||||
var size = fs.fstatSync(process.stdin.fd).size;
|
||||
input = size > 0 ? fs.readSync(process.stdin.fd, size)[0] : '';
|
||||
} catch (e) {
|
||||
var err = new Error('Could not read from stdin, reason: ' + e.message);
|
||||
errorExit(err);
|
||||
}
|
||||
} else {
|
||||
// 'i' has a value, read from file
|
||||
try {
|
||||
input = fs.readFileSync(argv.i, enc);
|
||||
} catch (err) {
|
||||
errorExit(err);
|
||||
}
|
||||
}
|
||||
|
||||
// parse and convert file
|
||||
output = converter.makeHtml(input);
|
||||
|
||||
// Write output
|
||||
if (!argv.o || argv.o === '') {
|
||||
// o is undefined or empty, write to stdout
|
||||
process.stdout.write(output);
|
||||
// we won't print anything since it would conspurcate stdout and,
|
||||
// consequently, the outputted file
|
||||
} else {
|
||||
// o is has a value, presumably a file, write to it.
|
||||
|
||||
// If a flag is passed, it means we should append instead of overwriting.
|
||||
// Only works with files, obviously
|
||||
var write = (argv.a) ? fs.appendFileSync : fs.writeFileSync;
|
||||
|
||||
try {
|
||||
write(argv.o, output);
|
||||
} catch (err) {
|
||||
errorExit(err);
|
||||
}
|
||||
console.error('DONE!');
|
||||
}
|
||||
}
|
||||
|
||||
function loadExtension(path, converter) {
|
||||
'use strict';
|
||||
var ext;
|
||||
try {
|
||||
ext = require(path);
|
||||
converter.addExtension(ext, path);
|
||||
} catch (e) {
|
||||
console.error('Could not load extension ' + path + '. Reason:');
|
||||
console.error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = exports = {
|
||||
run: run
|
||||
};
|
344
src/converter.js
Normal file
344
src/converter.js
Normal file
|
@ -0,0 +1,344 @@
|
|||
/**
|
||||
* Created by Estevao on 31-05-2015.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Showdown Converter class
|
||||
* @class
|
||||
* @param {object} [converterOptions]
|
||||
* @returns {
|
||||
* {makeHtml: Function},
|
||||
* {setOption: Function},
|
||||
* {getOption: Function},
|
||||
* {getOptions: Function}
|
||||
* }
|
||||
*/
|
||||
showdown.Converter = function (converterOptions) {
|
||||
'use strict';
|
||||
|
||||
var
|
||||
/**
|
||||
* Options used by this converter
|
||||
* @private
|
||||
* @type {{}}
|
||||
*/
|
||||
options = {},
|
||||
|
||||
/**
|
||||
* Language extensions used by this converter
|
||||
* @private
|
||||
* @type {Array}
|
||||
*/
|
||||
langExtensions = [],
|
||||
|
||||
/**
|
||||
* Output modifiers extensions used by this converter
|
||||
* @private
|
||||
* @type {Array}
|
||||
*/
|
||||
outputModifiers = [],
|
||||
|
||||
/**
|
||||
* The parser Order
|
||||
* @private
|
||||
* @type {string[]}
|
||||
*/
|
||||
parserOrder = [
|
||||
'githubCodeBlocks',
|
||||
'hashHTMLBlocks',
|
||||
'stripLinkDefinitions',
|
||||
'blockGamut',
|
||||
'unescapeSpecialChars'
|
||||
];
|
||||
|
||||
_constructor();
|
||||
|
||||
/**
|
||||
* Converter constructor
|
||||
* @private
|
||||
*/
|
||||
function _constructor() {
|
||||
converterOptions = converterOptions || {};
|
||||
|
||||
for (var gOpt in globalOptions) {
|
||||
if (globalOptions.hasOwnProperty(gOpt)) {
|
||||
options[gOpt] = globalOptions[gOpt];
|
||||
}
|
||||
}
|
||||
|
||||
// Merge options
|
||||
if (typeof converterOptions === 'object') {
|
||||
for (var opt in converterOptions) {
|
||||
if (converterOptions.hasOwnProperty(opt)) {
|
||||
options[opt] = converterOptions[opt];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw Error('Converter expects the passed parameter to be an object, but ' + typeof converterOptions +
|
||||
' was passed instead.');
|
||||
}
|
||||
|
||||
if (options.extensions) {
|
||||
showdown.helper.forEach(options.extensions, _parseExtension);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse extension
|
||||
* @param {*} ext
|
||||
* @param {string} [name='']
|
||||
* @private
|
||||
*/
|
||||
function _parseExtension(ext, name) {
|
||||
|
||||
name = name || null;
|
||||
// If it's a string, the extension was previously loaded
|
||||
if (showdown.helper.isString(ext)) {
|
||||
ext = showdown.helper.stdExtName(ext);
|
||||
name = ext;
|
||||
|
||||
// LEGACY_SUPPORT CODE
|
||||
if (showdown.extensions[ext]) {
|
||||
console.warn('DEPRECATION WARNING: ' + ext + ' is an old extension that uses a deprecated loading method.' +
|
||||
'Please inform the developer that the extension should be updated!');
|
||||
legacyExtensionLoading(showdown.extensions[ext], ext);
|
||||
return;
|
||||
// END LEGACY SUPPORT CODE
|
||||
|
||||
} else if (!showdown.helper.isUndefined(extensions[ext])) {
|
||||
ext = extensions[ext];
|
||||
|
||||
} else {
|
||||
throw Error('Extension "' + ext + '" could not be loaded. It was either not found or is not a valid extension.');
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof ext === 'function') {
|
||||
ext = ext();
|
||||
}
|
||||
|
||||
if (!showdown.helper.isArray(ext)) {
|
||||
ext = [ext];
|
||||
}
|
||||
|
||||
var validExt = validate(ext, name);
|
||||
if (!validExt.valid) {
|
||||
throw Error(validExt.error);
|
||||
}
|
||||
|
||||
for (var i = 0; i < ext.length; ++i) {
|
||||
switch (ext[i].type) {
|
||||
case 'lang':
|
||||
langExtensions.push(ext[i]);
|
||||
break;
|
||||
|
||||
case 'output':
|
||||
outputModifiers.push(ext[i]);
|
||||
break;
|
||||
|
||||
default:
|
||||
// should never reach here
|
||||
throw Error('Extension loader error: Type unrecognized!!!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LEGACY_SUPPORT
|
||||
* @param {*} ext
|
||||
* @param {string} name
|
||||
*/
|
||||
function legacyExtensionLoading(ext, name) {
|
||||
if (typeof ext === 'function') {
|
||||
ext = ext(new showdown.Converter());
|
||||
}
|
||||
if (!showdown.helper.isArray(ext)) {
|
||||
ext = [ext];
|
||||
}
|
||||
var valid = validate(ext, name);
|
||||
|
||||
if (!valid.valid) {
|
||||
throw Error(valid.error);
|
||||
}
|
||||
|
||||
for (var i = 0; i < ext.length; ++i) {
|
||||
switch (ext[i].type) {
|
||||
case 'lang':
|
||||
langExtensions.push(ext[i]);
|
||||
break;
|
||||
case 'output':
|
||||
outputModifiers.push(ext[i]);
|
||||
break;
|
||||
default:// should never reach here
|
||||
throw Error('Extension loader error: Type unrecognized!!!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a markdown string into HTML
|
||||
* @param {string} text
|
||||
* @returns {*}
|
||||
*/
|
||||
this.makeHtml = function (text) {
|
||||
//check if text is not falsy
|
||||
if (!text) {
|
||||
return text;
|
||||
}
|
||||
|
||||
var globals = {
|
||||
gHtmlBlocks: [],
|
||||
gUrls: {},
|
||||
gTitles: {},
|
||||
gDimensions: {},
|
||||
gListLevel: 0,
|
||||
hashLinkCounts: {},
|
||||
langExtensions: langExtensions,
|
||||
outputModifiers: outputModifiers,
|
||||
converter: this
|
||||
};
|
||||
|
||||
// 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 = showdown.subParser('detab')(text, options, globals);
|
||||
|
||||
// stripBlankLines
|
||||
text = showdown.subParser('stripBlankLines')(text, options, globals);
|
||||
|
||||
//run languageExtensions
|
||||
showdown.helper.forEach(langExtensions, function (ext) {
|
||||
text = showdown.subParser('runExtension')(ext, 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(outputModifiers, function (ext) {
|
||||
text = showdown.subParser('runExtension')(ext, text, options, globals);
|
||||
});
|
||||
|
||||
return text;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set an option of this Converter instance
|
||||
* @param {string} key
|
||||
* @param {*} value
|
||||
*/
|
||||
this.setOption = function (key, value) {
|
||||
options[key] = value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the option of this Converter instance
|
||||
* @param {string} key
|
||||
* @returns {*}
|
||||
*/
|
||||
this.getOption = function (key) {
|
||||
return options[key];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the options of this Converter instance
|
||||
* @returns {{}}
|
||||
*/
|
||||
this.getOptions = function () {
|
||||
return options;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add extension to THIS converter
|
||||
* @param {{}} extension
|
||||
* @param {string} [name=null]
|
||||
*/
|
||||
this.addExtension = function (extension, name) {
|
||||
name = name || null;
|
||||
_parseExtension(extension, name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Use a global registered extension with THIS converter
|
||||
* @param {string} extensionName Name of the previously registered extension
|
||||
*/
|
||||
this.useExtension = function (extensionName) {
|
||||
_parseExtension(extensionName);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the flavor THIS converter should use
|
||||
* @param {string} name
|
||||
*/
|
||||
this.setFlavor = function (name) {
|
||||
if (flavor.hasOwnProperty(name)) {
|
||||
var preset = flavor[name];
|
||||
for (var option in preset) {
|
||||
if (preset.hasOwnProperty(option)) {
|
||||
options[option] = preset[option];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove an extension from THIS converter.
|
||||
* Note: This is a costly operation. It's better to initialize a new converter
|
||||
* and specify the extensions you wish to use
|
||||
* @param {Array} extension
|
||||
*/
|
||||
this.removeExtension = function (extension) {
|
||||
if (!showdown.helper.isArray(extension)) {
|
||||
extension = [extension];
|
||||
}
|
||||
for (var a = 0; a < extension.length; ++a) {
|
||||
var ext = extension[a];
|
||||
for (var i = 0; i < langExtensions.length; ++i) {
|
||||
if (langExtensions[i] === ext) {
|
||||
langExtensions[i].splice(i, 1);
|
||||
}
|
||||
}
|
||||
for (var ii = 0; ii < outputModifiers.length; ++i) {
|
||||
if (outputModifiers[ii] === ext) {
|
||||
outputModifiers[ii].splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all extension of THIS converter
|
||||
* @returns {{language: Array, output: Array}}
|
||||
*/
|
||||
this.getAllExtensions = function () {
|
||||
return {
|
||||
language: langExtensions,
|
||||
output: outputModifiers
|
||||
};
|
||||
};
|
||||
};
|
|
@ -1,25 +0,0 @@
|
|||
//
|
||||
// Github Extension (WIP)
|
||||
// ~~strike-through~~ -> <del>strike-through</del>
|
||||
//
|
||||
|
||||
(function(){
|
||||
var github = function(converter) {
|
||||
return [
|
||||
{
|
||||
// strike-through
|
||||
// NOTE: showdown already replaced "~" with "~T", so we need to adjust accordingly.
|
||||
type : 'lang',
|
||||
regex : '(~T){2}([^~]+)(~T){2}',
|
||||
replace : function(match, prefix, content, suffix) {
|
||||
return '<del>' + content + '</del>';
|
||||
}
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
// Client-side export
|
||||
if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) { window.Showdown.extensions.github = github; }
|
||||
// Server-side export
|
||||
if (typeof module !== 'undefined') module.exports = github;
|
||||
}());
|
|
@ -1,29 +0,0 @@
|
|||
//
|
||||
// Google Prettify
|
||||
// A showdown extension to add Google Prettify (http://code.google.com/p/google-code-prettify/)
|
||||
// hints to showdown's HTML output.
|
||||
//
|
||||
|
||||
(function(){
|
||||
|
||||
var prettify = function(converter) {
|
||||
return [
|
||||
{ type: 'output', filter: function(source){
|
||||
|
||||
return source.replace(/(<pre>)?<code>/gi, function(match, pre) {
|
||||
if (pre) {
|
||||
return '<pre class="prettyprint linenums" tabIndex="0"><code data-inner="1">';
|
||||
} else {
|
||||
return '<code class="prettyprint">';
|
||||
}
|
||||
});
|
||||
}}
|
||||
];
|
||||
};
|
||||
|
||||
// Client-side export
|
||||
if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) { window.Showdown.extensions.prettify = prettify; }
|
||||
// Server-side export
|
||||
if (typeof module !== 'undefined') module.exports = prettify;
|
||||
|
||||
}());
|
|
@ -1,105 +0,0 @@
|
|||
/*global module:true*/
|
||||
/*
|
||||
* Basic table support with re-entrant parsing, where cell content
|
||||
* can also specify markdown.
|
||||
*
|
||||
* Tables
|
||||
* ======
|
||||
*
|
||||
* | Col 1 | Col 2 |
|
||||
* |======== |====================================================|
|
||||
* |**bold** | ![Valid XHTML] (http://w3.org/Icons/valid-xhtml10) |
|
||||
* | Plain | Value |
|
||||
*
|
||||
*/
|
||||
|
||||
(function(){
|
||||
var table = function(converter) {
|
||||
var tables = {}, style = 'text-align:left;', filter;
|
||||
tables.th = function(header){
|
||||
if (header.trim() === "") { return "";}
|
||||
var id = header.trim().replace(/ /g, '_').toLowerCase();
|
||||
return '<th id="' + id + '" style="'+style+'">' + header + '</th>';
|
||||
};
|
||||
tables.td = function(cell) {
|
||||
return '<td style="'+style+'">' + converter.makeHtml(cell) + '</td>';
|
||||
};
|
||||
tables.ths = function(){
|
||||
var out = "", i = 0, hs = [].slice.apply(arguments);
|
||||
for (i;i<hs.length;i+=1) {
|
||||
out += tables.th(hs[i]) + '\n';
|
||||
}
|
||||
return out;
|
||||
};
|
||||
tables.tds = function(){
|
||||
var out = "", i = 0, ds = [].slice.apply(arguments);
|
||||
for (i;i<ds.length;i+=1) {
|
||||
out += tables.td(ds[i]) + '\n';
|
||||
}
|
||||
return out;
|
||||
};
|
||||
tables.thead = function() {
|
||||
var out, i = 0, hs = [].slice.apply(arguments);
|
||||
out = "<thead>\n";
|
||||
out += "<tr>\n";
|
||||
out += tables.ths.apply(this, hs);
|
||||
out += "</tr>\n";
|
||||
out += "</thead>\n";
|
||||
return out;
|
||||
};
|
||||
tables.tr = function() {
|
||||
var out, i = 0, cs = [].slice.apply(arguments);
|
||||
out = "<tr>\n";
|
||||
out += tables.tds.apply(this, cs);
|
||||
out += "</tr>\n";
|
||||
return out;
|
||||
};
|
||||
filter = function(text) {
|
||||
var i=0, lines = text.split('\n'), tbl = [], line, hs, rows, out = [];
|
||||
for (i; i<lines.length;i+=1) {
|
||||
line = lines[i];
|
||||
// looks like a table heading
|
||||
if (line.trim().match(/^[|]{1}.*[|]{1}$/)) {
|
||||
line = line.trim();
|
||||
tbl = ['<table>'];
|
||||
hs = line.substring(1, line.length -1).split('|');
|
||||
tbl.push(tables.thead.apply(this, hs));
|
||||
line = lines[++i];
|
||||
if (!line.trim().match(/^[|]{1}[-=| ]+[|]{1}$/)) {
|
||||
// not a table rolling back
|
||||
line = lines[--i];
|
||||
}
|
||||
else {
|
||||
line = lines[++i];
|
||||
tbl.push('<tbody>');
|
||||
while (line.trim().match(/^[|]{1}.*[|]{1}$/)) {
|
||||
line = line.trim();
|
||||
tbl.push(tables.tr.apply(this, line.substring(1, line.length -1).split('|')));
|
||||
line = lines[++i];
|
||||
}
|
||||
tbl.push('</tbody>');
|
||||
tbl.push('</table>');
|
||||
// we are done with this table and we move along
|
||||
out.push(tbl.join('\n'));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
out.push(line);
|
||||
}
|
||||
return out.join('\n');
|
||||
};
|
||||
return [
|
||||
{
|
||||
type: 'lang',
|
||||
filter: filter
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
// Client-side export
|
||||
if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) { window.Showdown.extensions.table = table; }
|
||||
// Server-side export
|
||||
if (typeof module !== 'undefined') {
|
||||
module.exports = table;
|
||||
}
|
||||
}());
|
|
@ -1,42 +0,0 @@
|
|||
//
|
||||
// Twitter Extension
|
||||
// @username -> <a href="http://twitter.com/username">@username</a>
|
||||
// #hashtag -> <a href="http://twitter.com/search/%23hashtag">#hashtag</a>
|
||||
//
|
||||
|
||||
(function(){
|
||||
|
||||
var twitter = function(converter) {
|
||||
return [
|
||||
|
||||
// @username syntax
|
||||
{ type: 'lang', regex: '\\B(\\\\)?@([\\S]+)\\b', replace: function(match, leadingSlash, username) {
|
||||
// Check if we matched the leading \ and return nothing changed if so
|
||||
if (leadingSlash === '\\') {
|
||||
return match;
|
||||
} else {
|
||||
return '<a href="http://twitter.com/' + username + '">@' + username + '</a>';
|
||||
}
|
||||
}},
|
||||
|
||||
// #hashtag syntax
|
||||
{ type: 'lang', regex: '\\B(\\\\)?#([\\S]+)\\b', replace: function(match, leadingSlash, tag) {
|
||||
// Check if we matched the leading \ and return nothing changed if so
|
||||
if (leadingSlash === '\\') {
|
||||
return match;
|
||||
} else {
|
||||
return '<a href="http://twitter.com/search/%23' + tag + '">#' + tag + '</a>';
|
||||
}
|
||||
}},
|
||||
|
||||
// Escaped @'s
|
||||
{ type: 'lang', regex: '\\\\@', replace: '@' }
|
||||
];
|
||||
};
|
||||
|
||||
// Client-side export
|
||||
if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) { window.Showdown.extensions.twitter = twitter; }
|
||||
// Server-side export
|
||||
if (typeof module !== 'undefined') module.exports = twitter;
|
||||
|
||||
}());
|
123
src/helpers.js
Normal file
123
src/helpers.js
Normal file
|
@ -0,0 +1,123 @@
|
|||
/**
|
||||
* 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* POLYFILLS
|
||||
*/
|
||||
if (showdown.helper.isUndefined(console)) {
|
||||
console = {
|
||||
warn: function (msg) {
|
||||
'use strict';
|
||||
alert(msg);
|
||||
},
|
||||
log: function (msg) {
|
||||
'use strict';
|
||||
alert(msg);
|
||||
}
|
||||
};
|
||||
}
|
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;
|
||||
}
|
85
src/options.js
Normal file
85
src/options.js
Normal file
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* Created by Tivie on 13-07-2015.
|
||||
*/
|
||||
|
||||
function getDefaultOpts(simple) {
|
||||
'use strict';
|
||||
|
||||
var defaultOptions = {
|
||||
omitExtraWLInCodeBlocks: {
|
||||
default: false,
|
||||
describe: 'Omit the default extra whiteline added to code blocks',
|
||||
type: 'boolean'
|
||||
},
|
||||
noHeaderId: {
|
||||
default: false,
|
||||
describe: 'Turn on/off generated header id',
|
||||
type: 'boolean'
|
||||
},
|
||||
prefixHeaderId: {
|
||||
default: false,
|
||||
describe: 'Specify a prefix to generated header ids',
|
||||
type: 'string'
|
||||
},
|
||||
headerLevelStart: {
|
||||
default: false,
|
||||
describe: 'The header blocks level start',
|
||||
type: 'integer'
|
||||
},
|
||||
parseImgDimensions: {
|
||||
default: false,
|
||||
describe: 'Turn on/off image dimension parsing',
|
||||
type: 'boolean'
|
||||
},
|
||||
simplifiedAutoLink: {
|
||||
default: false,
|
||||
describe: 'Turn on/off GFM autolink style',
|
||||
type: 'boolean'
|
||||
},
|
||||
literalMidWordUnderscores: {
|
||||
default: false,
|
||||
describe: 'Parse midword underscores as literal underscores',
|
||||
type: 'boolean'
|
||||
},
|
||||
strikethrough: {
|
||||
default: false,
|
||||
describe: 'Turn on/off strikethrough support',
|
||||
type: 'boolean'
|
||||
},
|
||||
tables: {
|
||||
default: false,
|
||||
describe: 'Turn on/off tables support',
|
||||
type: 'boolean'
|
||||
},
|
||||
tablesHeaderId: {
|
||||
default: false,
|
||||
describe: 'Add an id to table headers',
|
||||
type: 'boolean'
|
||||
},
|
||||
ghCodeBlocks: {
|
||||
default: true,
|
||||
describe: 'Turn on/off GFM fenced code blocks support',
|
||||
type: 'boolean'
|
||||
},
|
||||
tasklists: {
|
||||
default: false,
|
||||
describe: 'Turn on/off GFM tasklist support',
|
||||
type: 'boolean'
|
||||
},
|
||||
smoothLivePreview: {
|
||||
default: false,
|
||||
describe: 'Prevents weird effects in live previews due to incomplete input',
|
||||
type: 'boolean'
|
||||
}
|
||||
};
|
||||
if (simple === false) {
|
||||
return JSON.parse(JSON.stringify(defaultOptions));
|
||||
}
|
||||
var ret = {};
|
||||
for (var opt in defaultOptions) {
|
||||
if (defaultOptions.hasOwnProperty(opt)) {
|
||||
ret[opt] = defaultOptions[opt].default;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
1708
src/showdown.js
1708
src/showdown.js
File diff suppressed because it is too large
Load Diff
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;
|
||||
|
||||
});
|
27
src/subParsers/autoLinks.js
Normal file
27
src/subParsers/autoLinks.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
showdown.subParser('autoLinks', function (text, options) {
|
||||
'use strict';
|
||||
|
||||
//simpleURLRegex = /\b(((https?|ftp|dict):\/\/|www\.)[-.+~:?#@!$&'()*,;=[\]\w]+)\b/gi,
|
||||
|
||||
var simpleURLRegex = /\b(((https?|ftp|dict):\/\/|www\.)[^'">\s]+\.[^'">\s]+)(?=\s|$)(?!["<>])/gi,
|
||||
delimUrlRegex = /<(((https?|ftp|dict):\/\/|www\.)[^'">\s]+)>/gi,
|
||||
simpleMailRegex = /\b(?:mailto:)?([-.\w]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)\b/gi,
|
||||
delimMailRegex = /<(?:mailto:)?([-.\w]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi;
|
||||
|
||||
text = text.replace(delimUrlRegex, '<a href=\"$1\">$1</a>');
|
||||
text = text.replace(delimMailRegex, replaceMail);
|
||||
//simpleURLRegex = /\b(((https?|ftp|dict):\/\/|www\.)[-.+~:?#@!$&'()*,;=[\]\w]+)\b/gi,
|
||||
// Email addresses: <address@domain.foo>
|
||||
|
||||
if (options.simplifiedAutoLink) {
|
||||
text = text.replace(simpleURLRegex, '<a href=\"$1\">$1</a>');
|
||||
text = text.replace(simpleMailRegex, replaceMail);
|
||||
}
|
||||
|
||||
function replaceMail(wholeMatch, m1) {
|
||||
var unescapedStr = showdown.subParser('unescapeSpecialChars')(m1);
|
||||
return showdown.subParser('encodeEmailAddress')(unescapedStr);
|
||||
}
|
||||
|
||||
return text;
|
||||
});
|
30
src/subParsers/blockGamut.js
Normal file
30
src/subParsers/blockGamut.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* 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('tables')(text, options, globals);
|
||||
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;
|
||||
});
|
48
src/subParsers/codeBlocks.js
Normal file
48
src/subParsers/codeBlocks.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* 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,
|
||||
end = '\n';
|
||||
|
||||
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 newlines
|
||||
|
||||
if (options.omitExtraWLInCodeBlocks) {
|
||||
end = '';
|
||||
}
|
||||
|
||||
codeblock = '<pre><code>' + codeblock + end + '</code></pre>';
|
||||
|
||||
return showdown.subParser('hashBlock')(codeblock, options, globals) + nextChar;
|
||||
});
|
||||
|
||||
// attacklab: strip sentinel
|
||||
text = text.replace(/~0/, '');
|
||||
|
||||
return text;
|
||||
});
|
60
src/subParsers/codeSpans.js
Normal file
60
src/subParsers/codeSpans.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
*
|
||||
* * 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';
|
||||
|
||||
//special case -> literal html code tag
|
||||
text = text.replace(/(<code[^><]*?>)([^]*?)<\/code>/g, function (wholeMatch, tag, c) {
|
||||
c = c.replace(/^([ \t]*)/g, ''); // leading whitespace
|
||||
c = c.replace(/[ \t]*$/g, ''); // trailing whitespace
|
||||
c = showdown.subParser('encodeCode')(c);
|
||||
return tag + c + '</code>';
|
||||
});
|
||||
|
||||
/*
|
||||
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;
|
||||
});
|
39
src/subParsers/githubCodeBlocks.js
Normal file
39
src/subParsers/githubCodeBlocks.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
// early exit if option is not enabled
|
||||
if (!options.ghCodeBlocks) {
|
||||
return text;
|
||||
}
|
||||
|
||||
text += '~0';
|
||||
|
||||
text = text.replace(/(?:^|\n)```(.*)\n([\s\S]*?)\n```/g, function (wholeMatch, language, codeblock) {
|
||||
var end = (options.omitExtraWLInCodeBlocks) ? '' : '\n';
|
||||
|
||||
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 + ' language-' + 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;
|
||||
|
||||
});
|
72
src/subParsers/headers.js
Normal file
72
src/subParsers/headers.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
showdown.subParser('headers', function (text, options, globals) {
|
||||
'use strict';
|
||||
|
||||
var prefixHeader = options.prefixHeaderId,
|
||||
headerLevelStart = (isNaN(parseInt(options.headerLevelStart))) ? 1 : parseInt(options.headerLevelStart),
|
||||
|
||||
// Set text-style headers:
|
||||
// Header 1
|
||||
// ========
|
||||
//
|
||||
// Header 2
|
||||
// --------
|
||||
//
|
||||
setextRegexH1 = (options.smoothLivePreview) ? /^(.+)[ \t]*\n={2,}[ \t]*\n+/gm : /^(.+)[ \t]*\n=+[ \t]*\n+/gm,
|
||||
setextRegexH2 = (options.smoothLivePreview) ? /^(.+)[ \t]*\n-{2,}[ \t]*\n+/gm : /^(.+)[ \t]*\n-+[ \t]*\n+/gm;
|
||||
|
||||
text = text.replace(setextRegexH1, function (wholeMatch, m1) {
|
||||
|
||||
var spanGamut = showdown.subParser('spanGamut')(m1, options, globals),
|
||||
hID = (options.noHeaderId) ? '' : ' id="' + headerId(m1) + '"',
|
||||
hLevel = headerLevelStart,
|
||||
hashBlock = '<h' + hLevel + hID + '>' + spanGamut + '</h' + hLevel + '>';
|
||||
return showdown.subParser('hashBlock')(hashBlock, options, globals);
|
||||
});
|
||||
|
||||
text = text.replace(setextRegexH2, function (matchFound, m1) {
|
||||
var spanGamut = showdown.subParser('spanGamut')(m1, options, globals),
|
||||
hID = (options.noHeaderId) ? '' : ' id="' + headerId(m1) + '"',
|
||||
hLevel = headerLevelStart + 1,
|
||||
hashBlock = '<h' + hLevel + hID + '>' + spanGamut + '</h' + hLevel + '>';
|
||||
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})[ \t]*(.+?)[ \t]*#*\n+/gm, function (wholeMatch, m1, m2) {
|
||||
var span = showdown.subParser('spanGamut')(m2, options, globals),
|
||||
hID = (options.noHeaderId) ? '' : ' id="' + headerId(m2) + '"',
|
||||
hLevel = headerLevelStart - 1 + m1.length,
|
||||
header = '<h' + hLevel + hID + '>' + span + '</h' + hLevel + '>';
|
||||
|
||||
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;
|
||||
});
|
74
src/subParsers/images.js
Normal file
74
src/subParsers/images.js
Normal file
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* Turn Markdown image shortcuts into <img> tags.
|
||||
*/
|
||||
showdown.subParser('images', function (text, options, globals) {
|
||||
'use strict';
|
||||
|
||||
var inlineRegExp = /!\[(.*?)]\s?\([ \t]*()<?(\S+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(['"])(.*?)\6[ \t]*)?\)/g,
|
||||
referenceRegExp = /!\[(.*?)][ ]?(?:\n[ ]*)?\[(.*?)]()()()()()/g;
|
||||
|
||||
function writeImageTag (wholeMatch, altText, linkId, url, width, height, m5, title) {
|
||||
|
||||
var gUrls = globals.gUrls,
|
||||
gTitles = globals.gTitles,
|
||||
gDims = globals.gDimensions;
|
||||
|
||||
linkId = linkId.toLowerCase();
|
||||
|
||||
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 (!showdown.helper.isUndefined(gUrls[linkId])) {
|
||||
url = gUrls[linkId];
|
||||
if (!showdown.helper.isUndefined(gTitles[linkId])) {
|
||||
title = gTitles[linkId];
|
||||
}
|
||||
if (!showdown.helper.isUndefined(gDims[linkId])) {
|
||||
width = gDims[linkId].width;
|
||||
height = gDims[linkId].height;
|
||||
}
|
||||
} else {
|
||||
return wholeMatch;
|
||||
}
|
||||
}
|
||||
|
||||
altText = altText.replace(/"/g, '"');
|
||||
altText = showdown.helper.escapeCharacters(altText, '*_', false);
|
||||
url = showdown.helper.escapeCharacters(url, '*_', false);
|
||||
var result = '<img src="' + url + '" alt="' + altText + '"';
|
||||
|
||||
if (title) {
|
||||
title = title.replace(/"/g, '"');
|
||||
title = showdown.helper.escapeCharacters(title, '*_', false);
|
||||
result += ' title="' + title + '"';
|
||||
}
|
||||
|
||||
if (width && height) {
|
||||
width = (width === '*') ? 'auto' : width;
|
||||
height = (height === '*') ? 'auto' : height;
|
||||
|
||||
result += ' width="' + width + '"';
|
||||
result += ' height="' + height + '"';
|
||||
}
|
||||
|
||||
result += ' />';
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// First, handle reference-style labeled images: ![alt text][id]
|
||||
text = text.replace(referenceRegExp, writeImageTag);
|
||||
|
||||
// Next, handle inline images: ![alt text](url =<width>x<height> "optional title")
|
||||
text = text.replace(inlineRegExp, writeImageTag);
|
||||
|
||||
return text;
|
||||
});
|
19
src/subParsers/italicsAndBold.js
Normal file
19
src/subParsers/italicsAndBold.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
showdown.subParser('italicsAndBold', function (text, options) {
|
||||
'use strict';
|
||||
|
||||
if (options.literalMidWordUnderscores) {
|
||||
//underscores
|
||||
// Since we are consuming a \s character, we need to add it
|
||||
text = text.replace(/(^|\s|>|\b)__(?=\S)([^]+?)__(?=\b|<|\s|$)/gm, '$1<strong>$2</strong>');
|
||||
text = text.replace(/(^|\s|>|\b)_(?=\S)([^]+?)_(?=\b|<|\s|$)/gm, '$1<em>$2</em>');
|
||||
//asterisks
|
||||
text = text.replace(/\*\*(?=\S)([^]+?)\*\*/g, '<strong>$1</strong>');
|
||||
text = text.replace(/\*(?=\S)([^]+?)\*/g, '<em>$1</em>');
|
||||
|
||||
} else {
|
||||
// <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;
|
||||
});
|
160
src/subParsers/lists.js
Normal file
160
src/subParsers/lists.js
Normal file
|
@ -0,0 +1,160 @@
|
|||
/**
|
||||
* 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}
|
||||
*/
|
||||
function processListItems (listStr, trimTrailing) {
|
||||
// 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';
|
||||
|
||||
var rgx = /(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+((\[(x| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,
|
||||
isParagraphed = (/\n[ \t]*\n(?!~0)/.test(listStr));
|
||||
|
||||
listStr = listStr.replace(rgx, function (wholeMatch, m1, m2, m3, m4, taskbtn, checked) {
|
||||
checked = (checked && checked.trim() !== '');
|
||||
var item = showdown.subParser('outdent')(m4, options, globals),
|
||||
bulletStyle = '';
|
||||
|
||||
// Support for github tasklists
|
||||
if (taskbtn && options.tasklists) {
|
||||
bulletStyle = ' class="task-list-item" style="list-style-type: none;"';
|
||||
item = item.replace(/^[ \t]*\[(x| )?]/m, function () {
|
||||
var otp = '<input type="checkbox" disabled style="margin: 0px 0.35em 0.25em -1.6em; vertical-align: middle;"';
|
||||
if (checked) {
|
||||
otp += ' checked';
|
||||
}
|
||||
otp += '>';
|
||||
return otp;
|
||||
});
|
||||
}
|
||||
// m1 - Leading line or
|
||||
// Has a double return (multi paragraph) or
|
||||
// Has sublist
|
||||
if (m1 || (item.search(/\n{2,}/) > -1)) {
|
||||
item = showdown.subParser('githubCodeBlocks')(item, options, globals);
|
||||
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)
|
||||
if (isParagraphed) {
|
||||
item = showdown.subParser('paragraphs')(item, options, globals);
|
||||
} else {
|
||||
item = showdown.subParser('spanGamut')(item, options, globals);
|
||||
}
|
||||
}
|
||||
item = '\n<li' + bulletStyle + '>' + item + '</li>\n';
|
||||
return item;
|
||||
});
|
||||
|
||||
// attacklab: strip sentinel
|
||||
listStr = listStr.replace(/~0/g, '');
|
||||
|
||||
globals.gListLevel--;
|
||||
|
||||
if (trimTrailing) {
|
||||
listStr = listStr.replace(/\s+$/, '');
|
||||
}
|
||||
|
||||
return listStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and parse consecutive lists (better fix for issue #142)
|
||||
* @param {string} list
|
||||
* @param {string} listType
|
||||
* @returns {string}
|
||||
*/
|
||||
function parseConsecutiveLists(list, listType, trimTrailing) {
|
||||
// check if we caught 2 or more consecutive lists by mistake
|
||||
// we use the counterRgx, meaning if listType is UL we look for UL and vice versa
|
||||
var counterRxg = (listType === 'ul') ? /^ {0,2}\d+\.[ \t]/gm : /^ {0,2}[*+-][ \t]/gm,
|
||||
subLists = [],
|
||||
result = '';
|
||||
|
||||
if (list.search(counterRxg) !== -1) {
|
||||
(function parseCL(txt) {
|
||||
var pos = txt.search(counterRxg);
|
||||
if (pos !== -1) {
|
||||
// slice
|
||||
result += '\n\n<' + listType + '>' + processListItems(txt.slice(0, pos), !!trimTrailing) + '</' + listType + '>\n\n';
|
||||
|
||||
// invert counterType and listType
|
||||
listType = (listType === 'ul') ? 'ol' : 'ul';
|
||||
counterRxg = (listType === 'ul') ? /^ {0,2}\d+\.[ \t]/gm : /^ {0,2}[*+-][ \t]/gm;
|
||||
|
||||
//recurse
|
||||
parseCL(txt.slice(pos));
|
||||
} else {
|
||||
result += '\n\n<' + listType + '>' + processListItems(txt, !!trimTrailing) + '</' + listType + '>\n\n';
|
||||
}
|
||||
})(list);
|
||||
for (var i = 0; i < subLists.length; ++i) {
|
||||
|
||||
}
|
||||
} else {
|
||||
result = '\n\n<' + listType + '>' + processListItems(list, !!trimTrailing) + '</' + listType + '>\n\n';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 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 entire ul or ol list:
|
||||
var wholeList = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
|
||||
|
||||
if (globals.gListLevel) {
|
||||
text = text.replace(wholeList, function (wholeMatch, list, m2) {
|
||||
var listType = (m2.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
|
||||
return parseConsecutiveLists(list, listType, true);
|
||||
});
|
||||
} else {
|
||||
wholeList = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
|
||||
//wholeList = /(\n\n|^\n?)( {0,3}([*+-]|\d+\.)[ \t]+[\s\S]+?)(?=(~0)|(\n\n(?!\t| {2,}| {0,3}([*+-]|\d+\.)[ \t])))/g;
|
||||
text = text.replace(wholeList, function (wholeMatch, m1, list, m3) {
|
||||
|
||||
var listType = (m3.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
|
||||
return parseConsecutiveLists(list, listType);
|
||||
});
|
||||
}
|
||||
|
||||
// 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;
|
||||
});
|
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');
|
||||
});
|
20
src/subParsers/runExtension.js
Normal file
20
src/subParsers/runExtension.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* Run extension
|
||||
*/
|
||||
showdown.subParser('runExtension', function (ext, text, options, globals) {
|
||||
'use strict';
|
||||
|
||||
if (ext.filter) {
|
||||
text = ext.filter(text, globals.converter, options);
|
||||
|
||||
} else if (ext.regex) {
|
||||
// TODO remove this when old extension loading mechanism is deprecated
|
||||
var re = ext.regex;
|
||||
if (!re instanceof RegExp) {
|
||||
re = new RegExp(re, 'g');
|
||||
}
|
||||
text = text.replace(re, ext.replace);
|
||||
}
|
||||
|
||||
return text;
|
||||
});
|
30
src/subParsers/spanGamut.js
Normal file
30
src/subParsers/spanGamut.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* 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);
|
||||
text = showdown.subParser('strikethrough')(text, options, globals);
|
||||
|
||||
// Do hard breaks:
|
||||
text = text.replace(/ +\n/g, ' <br />\n');
|
||||
|
||||
return text;
|
||||
|
||||
});
|
9
src/subParsers/strikethrough.js
Normal file
9
src/subParsers/strikethrough.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
showdown.subParser('strikethrough', function (text, options) {
|
||||
'use strict';
|
||||
|
||||
if (options.strikethrough) {
|
||||
text = text.replace(/(?:~T){2}([^~]+)(?:~T){2}/g, '<del>$1</del>');
|
||||
}
|
||||
|
||||
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, '');
|
||||
});
|
62
src/subParsers/stripLinkDefinitions.js
Normal file
62
src/subParsers/stripLinkDefinitions.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* 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+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \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, linkId, url, width, height, blankLines, title) {
|
||||
linkId = linkId.toLowerCase();
|
||||
globals.gUrls[linkId] = showdown.subParser('encodeAmpsAndAngles')(url); // Link IDs are case-insensitive
|
||||
|
||||
if (blankLines) {
|
||||
// Oops, found blank lines, so it's not a title.
|
||||
// Put back the parenthetical statement we stole.
|
||||
return blankLines + title;
|
||||
|
||||
} else {
|
||||
if (title) {
|
||||
globals.gTitles[linkId] = title.replace(/"|'/g, '"');
|
||||
}
|
||||
if (options.parseImgDimensions && width && height) {
|
||||
globals.gDimensions[linkId] = {
|
||||
width: width,
|
||||
height: height
|
||||
};
|
||||
}
|
||||
}
|
||||
// Completely remove the definition from the text
|
||||
return '';
|
||||
});
|
||||
|
||||
// attacklab: strip sentinel
|
||||
text = text.replace(/~0/, '');
|
||||
|
||||
return text;
|
||||
});
|
162
src/subParsers/tables.js
Normal file
162
src/subParsers/tables.js
Normal file
|
@ -0,0 +1,162 @@
|
|||
showdown.subParser('tables', function (text, options, globals) {
|
||||
'use strict';
|
||||
|
||||
var table = function () {
|
||||
|
||||
var tables = {},
|
||||
filter;
|
||||
|
||||
tables.th = function (header, style) {
|
||||
var id = '';
|
||||
header = header.trim();
|
||||
if (header === '') {
|
||||
return '';
|
||||
}
|
||||
if (options.tableHeaderId) {
|
||||
id = ' id="' + header.replace(/ /g, '_').toLowerCase() + '"';
|
||||
}
|
||||
header = showdown.subParser('spanGamut')(header, options, globals);
|
||||
if (!style || style.trim() === '') {
|
||||
style = '';
|
||||
} else {
|
||||
style = ' style="' + style + '"';
|
||||
}
|
||||
return '<th' + id + style + '>' + header + '</th>';
|
||||
};
|
||||
|
||||
tables.td = function (cell, style) {
|
||||
var subText = showdown.subParser('spanGamut')(cell.trim(), options, globals);
|
||||
if (!style || style.trim() === '') {
|
||||
style = '';
|
||||
} else {
|
||||
style = ' style="' + style + '"';
|
||||
}
|
||||
return '<td' + style + '>' + subText + '</td>';
|
||||
};
|
||||
|
||||
tables.ths = function () {
|
||||
var out = '',
|
||||
i = 0,
|
||||
hs = [].slice.apply(arguments[0]),
|
||||
style = [].slice.apply(arguments[1]);
|
||||
|
||||
for (i; i < hs.length; i += 1) {
|
||||
out += tables.th(hs[i], style[i]) + '\n';
|
||||
}
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
tables.tds = function () {
|
||||
var out = '',
|
||||
i = 0,
|
||||
ds = [].slice.apply(arguments[0]),
|
||||
style = [].slice.apply(arguments[1]);
|
||||
|
||||
for (i; i < ds.length; i += 1) {
|
||||
out += tables.td(ds[i], style[i]) + '\n';
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
tables.thead = function () {
|
||||
var out,
|
||||
hs = [].slice.apply(arguments[0]),
|
||||
style = [].slice.apply(arguments[1]);
|
||||
|
||||
out = '<thead>\n';
|
||||
out += '<tr>\n';
|
||||
out += tables.ths.apply(this, [hs, style]);
|
||||
out += '</tr>\n';
|
||||
out += '</thead>\n';
|
||||
return out;
|
||||
};
|
||||
|
||||
tables.tr = function () {
|
||||
var out,
|
||||
cs = [].slice.apply(arguments[0]),
|
||||
style = [].slice.apply(arguments[1]);
|
||||
|
||||
out = '<tr>\n';
|
||||
out += tables.tds.apply(this, [cs, style]);
|
||||
out += '</tr>\n';
|
||||
return out;
|
||||
};
|
||||
|
||||
filter = function (text) {
|
||||
var i = 0,
|
||||
lines = text.split('\n'),
|
||||
line,
|
||||
hs,
|
||||
out = [];
|
||||
|
||||
for (i; i < lines.length; i += 1) {
|
||||
line = lines[i];
|
||||
// looks like a table heading
|
||||
if (line.trim().match(/^[|].*[|]$/)) {
|
||||
line = line.trim();
|
||||
|
||||
var tbl = [],
|
||||
align = lines[i + 1].trim(),
|
||||
styles = [],
|
||||
j = 0;
|
||||
|
||||
if (align.match(/^[|][-=|: ]+[|]$/)) {
|
||||
styles = align.substring(1, align.length - 1).split('|');
|
||||
for (j = 0; j < styles.length; ++j) {
|
||||
styles[j] = styles[j].trim();
|
||||
if (styles[j].match(/^[:][-=| ]+[:]$/)) {
|
||||
styles[j] = 'text-align:center;';
|
||||
|
||||
} else if (styles[j].match(/^[-=| ]+[:]$/)) {
|
||||
styles[j] = 'text-align:right;';
|
||||
|
||||
} else if (styles[j].match(/^[:][-=| ]+$/)) {
|
||||
styles[j] = 'text-align:left;';
|
||||
} else {
|
||||
styles[j] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
tbl.push('<table>');
|
||||
hs = line.substring(1, line.length - 1).split('|');
|
||||
|
||||
if (styles.length === 0) {
|
||||
for (j = 0; j < hs.length; ++j) {
|
||||
styles.push('text-align:left');
|
||||
}
|
||||
}
|
||||
tbl.push(tables.thead.apply(this, [hs, styles]));
|
||||
line = lines[++i];
|
||||
if (!line.trim().match(/^[|][-=|: ]+[|]$/)) {
|
||||
// not a table rolling back
|
||||
line = lines[--i];
|
||||
} else {
|
||||
line = lines[++i];
|
||||
tbl.push('<tbody>');
|
||||
while (line.trim().match(/^[|].*[|]$/)) {
|
||||
line = line.trim();
|
||||
tbl.push(tables.tr.apply(this, [line.substring(1, line.length - 1).split('|'), styles]));
|
||||
line = lines[++i];
|
||||
}
|
||||
tbl.push('</tbody>');
|
||||
tbl.push('</table>');
|
||||
// we are done with this table and we move along
|
||||
out.push(tbl.join('\n'));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
out.push(line);
|
||||
}
|
||||
return out.join('\n');
|
||||
};
|
||||
return {parse: filter};
|
||||
};
|
||||
|
||||
if (options.tables) {
|
||||
var tableParser = table();
|
||||
return tableParser.parse(text);
|
||||
} else {
|
||||
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;
|
||||
});
|
98
test/bootstrap.js
vendored
Normal file
98
test/bootstrap.js
vendored
Normal file
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* Created by Estevao on 08-06-2015.
|
||||
*/
|
||||
|
||||
//jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
require('source-map-support').install();
|
||||
require('chai').should();
|
||||
var fs = require('fs'),
|
||||
os = require('os'),
|
||||
/*jshint -W106 */
|
||||
beautify = require('js-beautify').html_beautify,
|
||||
beauOptions = {
|
||||
eol: os.EOL,
|
||||
indent_size: 2,
|
||||
preserve_newlines: false
|
||||
};
|
||||
/*jshint +W106 */
|
||||
|
||||
function getTestSuite(dir) {
|
||||
return fs.readdirSync(dir)
|
||||
.filter(filter())
|
||||
.map(map(dir));
|
||||
}
|
||||
|
||||
function filter() {
|
||||
return function (file) {
|
||||
var ext = file.slice(-3);
|
||||
return (ext === '.md');
|
||||
};
|
||||
}
|
||||
|
||||
function map(dir) {
|
||||
return function (file) {
|
||||
var name = file.replace('.md', ''),
|
||||
htmlPath = dir + name + '.html',
|
||||
html = fs.readFileSync(htmlPath, 'utf8'),
|
||||
mdPath = dir + name + '.md',
|
||||
md = fs.readFileSync(mdPath, 'utf8');
|
||||
|
||||
return {
|
||||
name: name,
|
||||
input: md,
|
||||
expected: html
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function assertion(testCase, converter) {
|
||||
return function () {
|
||||
testCase.actual = converter.makeHtml(testCase.input);
|
||||
testCase = normalize(testCase);
|
||||
|
||||
// Compare
|
||||
testCase.actual.should.equal(testCase.expected);
|
||||
};
|
||||
}
|
||||
|
||||
//Normalize input/output
|
||||
function normalize(testCase) {
|
||||
|
||||
// Normalize line returns
|
||||
testCase.expected = testCase.expected.replace(/(\r\n)|\n|\r/g, '\n');
|
||||
testCase.actual = testCase.actual.replace(/(\r\n)|\n|\r/g, '\n');
|
||||
|
||||
// Ignore all leading/trailing whitespace
|
||||
testCase.expected = testCase.expected.split('\n').map(function (x) {
|
||||
return x.trim();
|
||||
}).join('\n');
|
||||
testCase.actual = testCase.actual.split('\n').map(function (x) {
|
||||
return x.trim();
|
||||
}).join('\n');
|
||||
|
||||
// Remove extra lines
|
||||
testCase.expected = testCase.expected.trim();
|
||||
testCase.actual = testCase.actual.trim();
|
||||
|
||||
//Beautify
|
||||
testCase.expected = beautify(testCase.expected, beauOptions);
|
||||
testCase.actual = beautify(testCase.actual, beauOptions);
|
||||
|
||||
// Normalize line returns
|
||||
testCase.expected = testCase.expected.replace(/(\r\n)|\n|\r/g, '\n');
|
||||
testCase.actual = testCase.actual.replace(/(\r\n)|\n|\r/g, '\n');
|
||||
|
||||
return testCase;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getTestSuite: getTestSuite,
|
||||
assertion: assertion,
|
||||
normalize: normalize,
|
||||
showdown: require('../.build/showdown.js')
|
||||
};
|
||||
})();
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
|
||||
<p>This is <a href="http://example.com/" title="Optional Title Here">an example</a> reference-style link.
|
||||
This is <a href="http://example.com/" title="Optional Title Here">another</a> reference-style link.
|
||||
This is <a href="http://example.com/" title="Optional Title Here">a third</a> reference-style link.
|
||||
This is <a href="http://example.com/" title="Optional Title Here">a fourth</a> reference-style link.</p>
|
||||
This is <a href="http://example.com/" title="Optional Title Here">another</a> reference-style link.
|
||||
This is <a href="http://example.com/" title="Optional Title Here">a third</a> reference-style link.
|
||||
This is <a href="http://example.com/" title="Optional Title Here">a fourth</a> reference-style link.</p>
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
|
||||
<p><a href="http://example.com/">http://example.com/</a></p>
|
||||
<p><a href="http://example.com/">http://example.com/</a></p>
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<blockquote>
|
||||
<h2 id="thisisaheader">This is a header.</h2>
|
||||
<h2 id="thisisaheader">This is a header.</h2>
|
||||
|
||||
<ol>
|
||||
<li>This is the first list item.</li>
|
||||
<li>This is the second list item.</li>
|
||||
</ol>
|
||||
<ol>
|
||||
<li>This is the first list item.</li>
|
||||
<li>This is the second list item.</li>
|
||||
</ol>
|
||||
|
||||
<p>Here's some example code:</p>
|
||||
<p>Here's some example code:</p>
|
||||
|
||||
<pre><code>return shell_exec("echo $input | $markdown_script");
|
||||
</code></pre>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<blockquote>
|
||||
<p>This is a multi line blockquote test</p>
|
||||
<p>This is a multi line blockquote test</p>
|
||||
|
||||
<p>With more than one line.</p>
|
||||
</blockquote>
|
||||
<p>With more than one line.</p>
|
||||
</blockquote>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
|
||||
<p>This is some HTML:</p>
|
||||
|
||||
<pre><code><h1>Heading</h1>
|
||||
</code></pre>
|
||||
</code></pre>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
|
||||
<p>This is a normal paragraph:</p>
|
||||
|
||||
<pre><code>This is a code block.
|
||||
</code></pre>
|
||||
</code></pre>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
|
||||
<ul>
|
||||
<li><p>Bird</p></li>
|
||||
<li><p>Magic</p></li>
|
||||
</ul>
|
||||
<li><p>Bird</p></li>
|
||||
<li><p>Magic</p></li>
|
||||
</ul>
|
||||
|
|
|
@ -1,8 +1,66 @@
|
|||
<p><em>single asterisks</em></p>
|
||||
|
||||
<p><em>important</em></p>
|
||||
<p><em>single underscores</em></p>
|
||||
|
||||
<p><em>important</em></p>
|
||||
<p><strong>double asterisks</strong></p>
|
||||
|
||||
<p>this mid<em>important</em>sentence</p>
|
||||
<p><strong>double underscores</strong></p>
|
||||
|
||||
<p>*not important*</p>
|
||||
<p>text <em>with italic sentence</em> in middle</p>
|
||||
|
||||
<p>text <strong>with bold sentence</strong> in middle</p>
|
||||
|
||||
<p>text with <strong>bold text that
|
||||
spans across multiple</strong> lines</p>
|
||||
|
||||
<p>underscored_word</p>
|
||||
|
||||
<p>doubleunderscore__word</p>
|
||||
|
||||
<p>asterix*word</p>
|
||||
|
||||
<p>doubleasterix**word</p>
|
||||
|
||||
<p>line with_underscored word</p>
|
||||
|
||||
<p>line with__doubleunderscored word</p>
|
||||
|
||||
<p>line with*asterixed word</p>
|
||||
|
||||
<p>line with**doubleasterixed word</p>
|
||||
|
||||
<p>some line<em>with</em>inner underscores</p>
|
||||
|
||||
<p>some line<strong>with</strong>inner double underscores</p>
|
||||
|
||||
<p>some line<em>with</em>inner asterixs</p>
|
||||
|
||||
<p>some line<strong>with</strong>inner double asterixs</p>
|
||||
|
||||
<p>another line with just _one underscore</p>
|
||||
|
||||
<p>another line with just __one double underscore</p>
|
||||
|
||||
<p>another line with just *one asterix</p>
|
||||
|
||||
<p>another line with just **one double asterix</p>
|
||||
|
||||
<p>a sentence with<em>underscore and another</em>underscore</p>
|
||||
|
||||
<p>a sentence with<strong>doubleunderscore and another</strong>doubleunderscore</p>
|
||||
|
||||
<p>a sentence with<em>asterix and another</em>asterix</p>
|
||||
|
||||
<p>a sentence with<strong>doubleasterix and another</strong>doubleasterix</p>
|
||||
|
||||
<p>escaped word_with_underscores</p>
|
||||
|
||||
<p>escaped word__with__double underscores</p>
|
||||
|
||||
<p>escaped word<em>_with_</em>single italic underscore</p>
|
||||
|
||||
<p>escaped word*with*asterixs</p>
|
||||
|
||||
<p>escaped word**with**asterixs</p>
|
||||
|
||||
<p>escaped word<strong>*with*</strong>bold asterixs</p>
|
||||
|
|
|
@ -1,8 +1,66 @@
|
|||
*single asterisks*
|
||||
|
||||
*important*
|
||||
_single underscores_
|
||||
|
||||
_important_
|
||||
**double asterisks**
|
||||
|
||||
this mid*important*sentence
|
||||
__double underscores__
|
||||
|
||||
\*not important\*
|
||||
text *with italic sentence* in middle
|
||||
|
||||
text __with bold sentence__ in middle
|
||||
|
||||
text with __bold text that
|
||||
spans across multiple__ lines
|
||||
|
||||
underscored_word
|
||||
|
||||
doubleunderscore__word
|
||||
|
||||
asterix*word
|
||||
|
||||
doubleasterix**word
|
||||
|
||||
line with_underscored word
|
||||
|
||||
line with__doubleunderscored word
|
||||
|
||||
line with*asterixed word
|
||||
|
||||
line with**doubleasterixed word
|
||||
|
||||
some line_with_inner underscores
|
||||
|
||||
some line__with__inner double underscores
|
||||
|
||||
some line*with*inner asterixs
|
||||
|
||||
some line**with**inner double asterixs
|
||||
|
||||
another line with just _one underscore
|
||||
|
||||
another line with just __one double underscore
|
||||
|
||||
another line with just *one asterix
|
||||
|
||||
another line with just **one double asterix
|
||||
|
||||
a sentence with_underscore and another_underscore
|
||||
|
||||
a sentence with__doubleunderscore and another__doubleunderscore
|
||||
|
||||
a sentence with*asterix and another*asterix
|
||||
|
||||
a sentence with**doubleasterix and another**doubleasterix
|
||||
|
||||
escaped word\_with\_underscores
|
||||
|
||||
escaped word\_\_with\_\_double underscores
|
||||
|
||||
escaped word_\_with\__single italic underscore
|
||||
|
||||
escaped word\*with*asterixs
|
||||
|
||||
escaped word\*\*with\*\*asterixs
|
||||
|
||||
escaped word**\*with\***bold asterixs
|
||||
|
|
|
@ -1 +1 @@
|
|||
<p>It happened in 1986. What a great season.</p>
|
||||
<p>It happened in 1986. What a great season.</p>
|
||||
|
|
|
@ -1 +1 @@
|
|||
It happened in 1986\. What a great season.
|
||||
It happened in 1986\. What a great season.
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
<p>These should all be escaped:</p>
|
||||
|
||||
<p>\</p>
|
||||
|
@ -29,4 +28,4 @@
|
|||
|
||||
<p>.</p>
|
||||
|
||||
<p>!</p>
|
||||
<p>!</p>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
<pre><code>function MyFunc(a) {
|
||||
// ...
|
||||
}
|
||||
// ...
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<p>That is some code!</p>
|
||||
<p>That is some code!</p>
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
|
||||
|
||||
<p>Define a function in javascript:</p>
|
||||
|
||||
<pre><code>function MyFunc(a) {
|
||||
var s = '`';
|
||||
}
|
||||
var s = '`';
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<p>And some HTML</p>
|
||||
|
||||
<pre><code class="html"><div>HTML!</div>
|
||||
</code></pre>
|
||||
<pre><code class="html language-html"><div>HTML!</div>
|
||||
</code></pre>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
<pre><code>code can go here
|
||||
this is rendered on a second line
|
||||
</code></pre>
|
||||
this is rendered on a second line
|
||||
</code></pre>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<h1 id="thisisanh1">This is an H1</h1>
|
||||
<h1 id="thisisanh1">This is an H1</h1>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<h1 id="thisisanh1">This is an H1</h1>
|
||||
<h1 id="thisisanh1">This is an H1</h1>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<h1 id="thisisanh1">This is an H1</h1>
|
||||
<h1 id="thisisanh1">This is an H1</h1>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<h2 id="thisisanh2">This is an H2</h2>
|
||||
<h2 id="thisisanh2">This is an H2</h2>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<h2 id="thisisanh2">This is an H2</h2>
|
||||
<h2 id="thisisanh2">This is an H2</h2>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<h2 id="thisisanh2">This is an H2</h2>
|
||||
<h2 id="thisisanh2">This is an H2</h2>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<h3 id="thisisanh3">This is an H3</h3>
|
||||
<h3 id="thisisanh3">This is an H3</h3>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<h3 id="thisisanh3">This is an H3</h3>
|
||||
<h3 id="thisisanh3">This is an H3</h3>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<h4 id="thisisanh4">This is an H4</h4>
|
||||
<h4 id="thisisanh4">This is an H4</h4>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<h5 id="thisisanh5">This is an H5</h5>
|
||||
<h5 id="thisisanh5">This is an H5</h5>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user