├── .github └── workflows │ ├── browserstack.yml │ ├── codeql-analysis.yml │ ├── docs.yml │ ├── node.linux.yml │ └── node.win.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── CREDITS.md ├── DONATIONS.md ├── LICENSE ├── README.md ├── SECURITY.md ├── TASKS.TODO.md ├── bin └── showdown.js ├── bower.json ├── dist ├── showdown.js ├── showdown.js.map ├── showdown.min.js └── showdown.min.js.map ├── docs ├── assets │ ├── extra.css │ └── markdown-editor.png ├── available-options.md ├── cli.md ├── compatibility.md ├── configuration.md ├── create-extension.md ├── credits.md ├── donations.md ├── event_system.md ├── extensions-list.md ├── extensions.md ├── flavors.md ├── index.md ├── integrations.md ├── markdown-syntax.md ├── quickstart.md ├── tutorials │ ├── add-default-class-to-html.md │ ├── index.md │ ├── markdown-editor-with-showdown.md │ └── use-both-extension-types-together.md └── xss.md ├── karma.browserstack.js ├── karma.conf.js ├── mkdocs.yml ├── package-lock.json ├── package.json └── src ├── cli └── cli.js ├── converter.js ├── helpers.js ├── loader.js ├── options.js ├── showdown.js └── subParsers ├── makehtml ├── blockGamut.js ├── blockQuotes.js ├── codeBlocks.js ├── codeSpans.js ├── completeHTMLDocument.js ├── detab.js ├── ellipsis.js ├── emoji.js ├── encodeAmpsAndAngles.js ├── encodeBackslashEscapes.js ├── encodeCode.js ├── escapeSpecialCharsWithinTagAttributes.js ├── githubCodeBlocks.js ├── hashBlock.js ├── hashCodeTags.js ├── hashElement.js ├── hashHTMLBlocks.js ├── hashHTMLSpans.js ├── hashPreCodeTags.js ├── headers.js ├── horizontalRule.js ├── images.js ├── italicsAndBold.js ├── links.js ├── lists.js ├── metadata.js ├── outdent.js ├── paragraphs.js ├── runExtension.js ├── spanGamut.js ├── strikethrough.js ├── stripLinkDefinitions.js ├── tables.js ├── underline.js └── unescapeSpecialChars.js └── makemarkdown ├── blockquote.js ├── break.js ├── codeBlock.js ├── codeSpan.js ├── emphasis.js ├── header.js ├── hr.js ├── image.js ├── input.js ├── links.js ├── list.js ├── listItem.js ├── node.js ├── paragraph.js ├── pre.js ├── strikethrough.js ├── strong.js ├── table.js ├── tableCell.js └── txt.js /.github/workflows/browserstack.yml: -------------------------------------------------------------------------------- 1 | name: 'BrowserStack Test' 2 | 3 | on: 4 | push: 5 | branches: [ master, develop ] 6 | pull_request: 7 | branches: [ master, develop ] 8 | 9 | jobs: 10 | ubuntu-job: 11 | name: 'BrowserStack Test on Ubuntu' 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: set up env vars 15 | # Only the first line of commit msg 16 | run: echo "COMMIT_MSG=$(printf "%s" "${{ github.event.head_commit.message }}" | head -n 1)" >> $GITHUB_ENV 17 | 18 | - name: '📦 Checkout the repository' 19 | uses: actions/checkout@v2 20 | 21 | - name: '🚚 Upgrade NPM' 22 | run: npm install -g npm 23 | 24 | - name: '⚙ Setup Node.js v17.x' 25 | uses: actions/setup-node@v2 26 | with: 27 | node-version: 17.x 28 | cache: 'npm' 29 | 30 | - name: '📖 Get current package version' 31 | id: package-version 32 | uses: martinbeentjes/npm-get-version-action@v1.2.3 33 | 34 | - name: '📝 Print build version and commit msg' 35 | run: 'printf "version: %s\n build:%s\n message:%s\n" "${{ steps.package-version.outputs.current-version}}" "${{ github.run_id }}" "$COMMIT_MSG"' 36 | 37 | - name: '📱 BrowserStack Env Setup' # Invokes the setup-env action 38 | uses: browserstack/github-actions/setup-env@master 39 | with: 40 | username: ${{ secrets.BROWSERSTACK_USERNAME }} 41 | access-key: ${{ secrets.BROWSERSTACK_ACCESSKEY }} 42 | project-name: 'showdown' 43 | build-name: ${{ steps.package-version.outputs.current-version}}-${{ github.run_id }} 44 | 45 | - name: '🚇 BrowserStack Local Tunnel Setup' # Invokes the setup-local action 46 | uses: browserstack/github-actions/setup-local@master 47 | with: 48 | local-testing: start 49 | local-identifier: random 50 | 51 | - name: '🚚 Install dependencies for CI' 52 | run: npm ci 53 | 54 | - name: '🏗 Building src files for testing' 55 | run: npx grunt concat:test 56 | 57 | - name: '✅ Running test on BrowserStack with Karma' 58 | run: npx karma start karma.browserstack.js 59 | 60 | - name: '🛑 BrowserStackLocal Stop' # Terminating the BrowserStackLocal tunnel connection 61 | uses: browserstack/github-actions/setup-local@master 62 | with: 63 | local-testing: stop 64 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master, develop ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master, develop ] 20 | schedule: 21 | - cron: '39 3 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v1 71 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - 'mkdocs.yml' 9 | - 'docs/**' 10 | - '.github/workflows/docs.yml' 11 | 12 | jobs: 13 | build_docs: 14 | name: Build documentation 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Trigger external build 20 | env: 21 | TOKEN: ${{ secrets.DOCS_DEPLOY_KEY }} 22 | COMMIT: ${{ github.event.head_commit.id }} 23 | COMMITTER: ${{ github.event.head_commit.author.username }} 24 | OWNER: showdownjs 25 | REPO: showdownjs.github.io 26 | run: | 27 | curl -X POST \ 28 | -H "Authorization: token ${TOKEN}" \ 29 | -H "Accept: application/vnd.github.v3+json" \ 30 | https://api.github.com/repos/${OWNER}/${REPO}/dispatches \ 31 | -d '{ "event_type": "e: \"'"${COMMIT}"'\" by '"${COMMITTER}"'", "client_payload": { "source": "showdown" } }' 32 | -------------------------------------------------------------------------------- /.github/workflows/node.linux.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node Linux CI 5 | 6 | on: 7 | push: 8 | branches: [ master, develop ] 9 | pull_request: 10 | branches: [ master, develop ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [12.x, 14.x, 16.x, 17.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | 25 | - name: 🚚 Upgrade NPM 26 | run: npm install -g npm 27 | 28 | - name: Use Node.js ${{ matrix.node-version }} 29 | uses: actions/setup-node@v2 30 | with: 31 | node-version: ${{ matrix.node-version }} 32 | cache: 'npm' 33 | - run: npm ci 34 | - run: npm test 35 | -------------------------------------------------------------------------------- /.github/workflows/node.win.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node Windows CI 5 | 6 | on: 7 | push: 8 | branches: [ master, develop ] 9 | pull_request: 10 | branches: [ master, develop ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: windows-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [12.x, 14.x, 16.x, 17.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v2 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'npm' 29 | - run: npm ci 30 | - run: npm test 31 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | If you wish to contribute please read the following quick guide. 5 | 6 | # Issues (bug reporting) 7 | Generally, bug reports should be in the following format: 8 | 9 | 1. Description (Brief description of the problem) 10 | 2. Input (input that is causing problems) 11 | 3. Expected Output (Output that is expected) 12 | 4. Actual Output (Actual showdown output) 13 | 14 | ex: 15 | 16 | **Description:** 17 | Double asterisks do not parse as bold 18 | 19 | **Input:** 20 | 21 | hello **world**! 22 | 23 | **Expected output:** 24 | 25 |
hello world! 26 | 27 | **Actual Output:** 28 | 29 |
hello **world**!
30 | 31 | 32 | 33 | # Want a Feature? 34 | You can request a new feature by submitting an issue. If you would like to implement a new feature feel free to issue a 35 | Pull Request. 36 | 37 | 38 | # Pull requests (PRs) 39 | PRs are awesome. However, before you submit your pull request consider the following guidelines: 40 | 41 | - Search GitHub for an open or closed Pull Request that relates to your submission. You don't want to duplicate effort. 42 | - When issuing PRs that change code, make your changes in a new git branch based on develop: 43 | 44 | ```bash 45 | git checkout -b my-fix-branch develop 46 | ``` 47 | 48 | - Run the full test suite before submitting and make sure all tests pass (obviously =P). 49 | - Try to follow our [**coding style rules**](https://github.com/showdownjs/code-style/blob/master/README.md). 50 | Breaking them prevents the PR to pass the tests. 51 | - Refrain from fixing multiple issues in the same pull request. It's preferable to open multiple small PRs instead of one 52 | hard to review big one. Also, don't reuse old forks (or PRs) to fix new issues. 53 | - If the PR introduces a new feature or fixes an issue, please add the appropriate test case. 54 | - We use commit notes to generate the changelog. It's extremely helpful if your commit messages adhere to the 55 | [**AngularJS Git Commit Guidelines**](https://github.com/showdownjs/code-style/blob/master/README.md#commit-message-convention). 56 | - If we suggest changes then: 57 | - Make the required updates. 58 | - Re-run the Angular test suite to ensure tests are still passing. 59 | - Rebase your branch and force push to your GitHub repository (this will update your Pull Request): 60 | 61 | ```bash 62 | git rebase develop -i 63 | git push origin my-fix-branch -f 64 | ``` 65 | - After your pull request is merged, you can safely delete your branch. 66 | 67 | If you have time to contribute to this project, we feel obliged that you get credit for it. 68 | These rules enable us to review your PR faster and will give you appropriate credit in your GitHub profile. 69 | We thank you in advance for your contribution! 70 | 71 | 72 | # Joining the team 73 | We're looking for members to help maintaining Showdown. 74 | Please see [this issue](https://github.com/showdownjs/showdown/issues/114) to express interest or comment on this note. 75 | -------------------------------------------------------------------------------- /CREDITS.md: -------------------------------------------------------------------------------- 1 | Credits 2 | ======= 3 | 4 | - Showdown v2 5 | * [Estevão Santos](https://github.com/tivie) 6 | * [SyntaxRules](https://github.com/SyntaxRules) 7 | 8 | - Showdown v1 9 | * [Estevão Santos](https://github.com/tivie) 10 | * [Pascal Deschênes](https://github.com/pdeschen) 11 | 12 | - Showdown v0 13 | * [Corey Innis](http://github.com/coreyti):`*
116 | - [ ] Rethink the global variable
117 |
118 | ## NEW Features
119 |
120 | ### Event system
121 | - [X] Listener system revamp
122 | - [ ] Standardize events for all event types
123 | - [ ] Unit testing
124 | - [ ] Functional testing
125 |
126 | This should address:
127 | - **#567**: Allow not making header ids lowercase
128 | - **#540**: Add complete class to the tasklist list element
129 |
130 | ### MD to HTML conversion
131 | - [X] Basic support
132 | - [X] Basic functional testcase
133 | - [ ] Advanced support for all showdown MD features
134 | - [ ] Advanced functional testcase
135 | - [ ] Unit testing
136 |
137 | ## Documentation (for v2.0)
138 | - [ ] Options
139 | - [ ] Extensions (and the new event system)
140 | - [ ] Cookbook (with stuff for backwards compatibility, specially regarding removed options)
141 |
142 | ## Browser Testing
143 |
144 | - [X] Implement unit tests in Karma
145 | - [ ] Implement functional tests in Karma
146 | - [ ] Integrate with browserstack
147 |
--------------------------------------------------------------------------------
/bin/showdown.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | var showdown,version,fs=require("fs"),path=require("path"),Command=require("commander").Command,program=new Command,path1=path.resolve(__dirname+"/../dist/showdown.js"),path2=path.resolve(__dirname+"/../../.build/showdown.js");function Messenger(o,t,e){"use strict";t=!!t||!!e,e=!!e,this._print="stdout"===(o=o||"stderr")?console.log:console.error,this.errorExit=function(o){e||(console.error("ERROR: "+o.message),console.error("Run 'showdown -h' for help")),process.exit(1)},this.okExit=function(){e||(this._print("\n"),this._print("DONE!")),process.exit(0)},this.printMsg=function(o){t||e||!o||this._print(o)},this.printError=function(o){e||console.error(o)}}function showShowdownOptions(){"use strict";var o,t=showdown.getDefaultOptions(!1);for(o in console.log("\nshowdown makehtml config options:"),t)t.hasOwnProperty(o)&&console.log(" "+o+":","[default="+t[o].defaultValue+"]",t[o].describe);console.log('\n\nExample: showdown makehtml -c openLinksInNewWindow ghMentions ghMentionsLink="https://google.com"')}function parseShowdownOptions(o,t){"use strict";var e=t;if(o)for(var n=0;n [options]").option("-q, --quiet","Quiet mode. Only print errors").option("-m, --mute","Mute mode. Does not print anything"),program.command("makehtml").description("Converts markdown into html").addHelpText("after","\n\nExamples:").addHelpText("after"," showdown makehtml -i Reads from stdin and outputs to stdout").addHelpText("after"," showdown makehtml -i foo.md -o bar.html Reads 'foo.md' and writes to 'bar.html'").addHelpText("after",' showdown makehtml -i --flavor="github" Parses stdin using GFM style').addHelpText("after","\nNote for windows users:").addHelpText("after","When reading from stdin, use option -u to set the proper encoding or run `chcp 65001` prior to calling showdown cli to set the command line to utf-8").option("-i, --input [file]","Input source. Usually a md file. If omitted or empty, reads from stdin. Windows users see note below.",!0).option("-o, --output [file]","Output target. Usually a html file. If omitted or empty, writes to stdout",!0).option("-u, --encoding ","Sets the input encoding","utf8").option("-y, --output-encoding ","Sets the output encoding","utf8").option("-a, --append","Append data to output instead of overwriting. Ignored if writing to stdout",!1).option("-e, --extensions ","Load the specified extensions. Should be valid paths to node compatible extensions").option("-p, --flavor ","Run with a predetermined flavor of options. Default is vanilla","vanilla").option("-c, --config ","Enables showdown makehtml parser config options. Overrides flavor").option("--config-help","Shows configuration options for showdown parser").action(makehtmlCommand),program.parse();
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "showdown",
3 | "description": "A Markdown to HTML converter written in Javascript",
4 | "homepage": "https://github.com/showdownjs/showdown",
5 | "authors": [
6 | "Estevão Santos (https://github.com/tivie)",
7 | "Pascal Deschênes (https://github.com/pdeschen)"
8 | ],
9 | "main": ["dist/showdown.js"],
10 | "ignore": [
11 | ".editorconfig",
12 | ".gitattributes",
13 | ".gitignore",
14 | ".jscs.json",
15 | ".jshintignore",
16 | ".jshintrc",
17 | ".travis.yml",
18 | "Gruntfile.js",
19 | "package.json",
20 | "test/*"
21 | ],
22 | "repository": {
23 | "type": "git",
24 | "url": "https://github.com/showdownjs/showdown.git"
25 | },
26 | "keywords": [
27 | "markdown",
28 | "md",
29 | "mdown"
30 | ],
31 | "license": "https://github.com/showdownjs/showdown/blob/master/license.txt"
32 | }
33 |
--------------------------------------------------------------------------------
/docs/assets/extra.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --md-primary-fg-color: rgb(196, 54, 39);
3 | --md-accent-fg-color: rgb(62, 139, 138);
4 | }
--------------------------------------------------------------------------------
/docs/assets/markdown-editor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/showdownjs/showdown/95255984ad80acf745ed74605bd3ad8357dc9b33/docs/assets/markdown-editor.png
--------------------------------------------------------------------------------
/docs/cli.md:
--------------------------------------------------------------------------------
1 | Showdown comes bundled with a Command-line interface (CLI) tool that allows you to run Showdown converter from the command line.
2 |
3 | ## Requirements
4 |
5 | * [Node.js](https://nodejs.org/en/)
6 |
7 | ## Quick start guide
8 |
9 | 1. Check that Showdown CLI is accessible.
10 |
11 | * If you installed Showdown globally via `npm install showdown -g`, you can access the CLI tool help by typing `showdown -h` in the command line:
12 |
13 | === "input"
14 |
15 | ```sh
16 | showdown -h
17 | ```
18 |
19 | === "output"
20 |
21 | ```
22 | Usage: showdown [options]
23 |
24 | CLI to Showdownjs markdown parser v3.0.0-alpha
25 |
26 | Options:
27 | -V, --version output the version number
28 | -q, --quiet Quiet mode. Only print errors
29 | -m, --mute Mute mode. Does not print anything
30 | -h, --help display help for command
31 |
32 | Commands:
33 | makehtml [options] Converts markdown into html
34 | help [command] display help for command
35 | ```
36 |
37 | * If you installed Showdown locally via `npm install showdown`, open the folder where Showdown is installed, and type `node ./bin/showdown.js -h` in the command line:
38 |
39 | === "input"
40 |
41 | ```sh
42 | node ./bin/showdown.js -h
43 | ```
44 |
45 | === "output"
46 |
47 | ```
48 | Usage: showdown [options]
49 |
50 | CLI to Showdownjs markdown parser v3.0.0-alpha
51 |
52 | Options:
53 | -V, --version output the version number
54 | -q, --quiet Quiet mode. Only print errors
55 | -m, --mute Mute mode. Does not print anything
56 | -h, --help display help for command
57 |
58 | Commands:
59 | makehtml [options] Converts markdown into html
60 | help [command] display help for command
61 | ```
62 |
63 | 1. Use `makehtml` command to convert your document to HTML. For example:
64 |
65 | !!! example "Convert `foo.md` into `bar.html`"
66 |
67 | ```sh
68 | showdown makehtml -i foo.md -o bar.html
69 | ```
70 |
71 | ## Commands
72 |
73 | ### `makehtml`
74 |
75 | Convert a Markdown input into HTML.
76 |
77 | **Usage**
78 |
79 | ```sh
80 | showdown makehtml [options]
81 | ```
82 |
83 | #### Options
84 |
85 | ###### `-i / --input`
86 |
87 | * Short format: `-i`
88 | * Alias: `--input`
89 | * Description: Input source. Usually a `.md` file. If omitted or empty, reads from `stdin`.
90 | * Examples:
91 |
92 | !!! example ""
93 |
94 | ```sh
95 | // Read from stdin and output to stdout
96 | showdown makehtml -i
97 |
98 | // Read from the foo.md file and output to stdout
99 | showdown makehtml --input foo.md
100 | ```
101 |
102 | ###### `-o/--output`
103 |
104 | * Short format: `-o`
105 | * Alias: `--output`
106 | * Description: Output target. Usually a `.html` file. If omitted or empty, writes to `stdout`.
107 | * Example:
108 |
109 | !!! example ""
110 |
111 | ```sh
112 | // Read from the foo.md file and output to bar.html
113 | showdown makehtml -i foo.md -o bar.html
114 | ```
115 |
116 | ###### `-a/--append`
117 |
118 | * Short format: `-a`
119 | * Alias: `--append`
120 | * Description: Append data to output instead of overwriting.
121 | * Example:
122 |
123 | !!! example ""
124 |
125 | ```sh
126 | showdown makehtml -a
127 | ```
128 |
129 | ###### `-u/--encoding`
130 |
131 | * Short format: `-u`
132 | * Alias: `--encoding`
133 | * Description: Specify the input encoding.
134 | * Example:
135 |
136 | !!! example ""
137 |
138 | ```sh
139 | showdown makehtml -u UTF8
140 | ```
141 |
142 | ###### `-e/--extensions`
143 |
144 | * Short format: `-e`
145 | * Alias: `--extension`
146 | * Description: Load the specified extension(s). Should be valid path(s) to Node-compatible extensions.
147 | * Example:
148 |
149 | !!! example ""
150 |
151 | ```sh
152 | showdown makehtml -e ~/twitter.js -e ~/youtube.js
153 | ```
154 |
155 | ###### `-c/--config`
156 |
157 | * Short format: `-c`
158 | * Alias: `--config`
159 | * Description: Enable or disable parser options.
160 | * Introduced in: `2.0.1` (Breaking change. See the [`Extra options`](#extra-options) section below)
161 | * Example:
162 |
163 | !!! example ""
164 |
165 | ```sh
166 | showdown makehtml -i foo.md -o bar.html -c strikethrough
167 | showdown makehtml -i foo.md -o bar.html -c strikethrough -c emoji
168 | ```
169 |
170 | ## Extra options
171 |
172 | Starting from the version `2.0.1`, CLI the format of passing extra options has changed. Please make the necessary changes to your code, if required.
173 |
174 | === "since `v2.0.1`"
175 |
176 | ```sh
177 | showdown makehtml -i foo.md -o bar.html -c strikethrough -c emoji
178 | ```
179 |
180 | === "before `v2.0.1`"
181 |
182 | ```sh
183 | showdown makehtml -i foo.md -o bar.html --strikethrough --emoji
184 | ```
185 |
186 |
187 | You can specify any of the [supported options](available-options.md), and they will be passed to the converter.
188 |
189 | The above commands are equivalent of doing:
190 |
191 | ```js
192 | var conv = new showdown.Converter({strikethrough: true, emoji: true});
193 | ```
194 |
195 | !!! warning ""
196 | In the CLI tool, all the extra options are **disabled** by default. This is the opposite of what is defined for node and browser, where some options, like `ghCodeBlocks` are enabled (for backward compatibility and historical reasons).
197 |
--------------------------------------------------------------------------------
/docs/compatibility.md:
--------------------------------------------------------------------------------
1 | ## Browsers
2 |
3 | Showdown has been tested successfully with:
4 |
5 | *
Firefox 1.5 and 2.0
6 | *
Chrome 12.0
7 | *
Internet Explorer 6 and 7
8 | *
Safari 2.0.4
9 | *
Opera 8.54 and 9.10
10 | *
Netscape 8.1.2
11 | *
Konqueror 3.5.4
12 |
13 | Generally, Showdown should work in any browser that supports ECMA 262 3rd Edition (JavaScript 1.5).
14 |
15 | The converter might even work in things that aren't web browsers, like Acrobat. However, no promises.
16 |
17 | ## Node.js
18 |
19 | Showdown is intended to work on any supported Node.js version (see the [Node.js releases schedule](https://nodejs.org/en/about/releases/).
20 |
21 | Previous versions may also be supported, but no accomodations are made to ensure this.
--------------------------------------------------------------------------------
/docs/configuration.md:
--------------------------------------------------------------------------------
1 | You can change Showdown's default behavior via options.
2 |
3 | ## Set option
4 |
5 | ### Globally
6 |
7 | Setting an option globally affects all Showdown instances.
8 |
9 | ```js
10 | showdown.setOption('optionKey', 'value');
11 | ```
12 |
13 | ### Locally
14 |
15 | Setting an option locally affects the specified Converter object only. You can set local options via:
16 |
17 | === "Constructor"
18 |
19 | ```js
20 | var converter = new showdown.Converter({optionKey: 'value'});
21 | ```
22 |
23 | === "setOption() method"
24 |
25 | ```js
26 | var converter = new showdown.Converter();
27 | converter.setOption('optionKey', 'value');
28 | ```
29 |
30 | ## Get option
31 |
32 | Showdown provides both local and global methods to retrieve previously set options:
33 |
34 | === "getOption()"
35 |
36 | ```js
37 | // Global
38 | var myOption = showdown.getOption('optionKey');
39 |
40 | //Local
41 | var myOption = converter.getOption('optionKey');
42 | ```
43 |
44 | === "getOptions()"
45 |
46 | ```js
47 | // Global
48 | var showdownGlobalOptions = showdown.getOptions();
49 |
50 | //Local
51 | var thisConverterSpecificOptions = converter.getOptions();
52 | ```
53 |
54 | ### Get default options
55 |
56 | You can get Showdown's default options with:
57 |
58 | ```js
59 | var defaultOptions = showdown.getDefaultOptions();
60 | ```
--------------------------------------------------------------------------------
/docs/create-extension.md:
--------------------------------------------------------------------------------
1 | A Showdown extension is a function that returns an array of language or outputs extensions (henceforth called "sub-extensions").
2 |
3 | ```js
4 | var myext = function () {
5 | var myext1 = {
6 | type: 'lang',
7 | regex: /markdown/g,
8 | replace: 'showdown'
9 | };
10 | var myext2 = {
11 | /* extension code */
12 | };
13 | return [myext1, myext2];
14 | }
15 | ```
16 |
17 | Each sub-extension (`myext1` and `myext2` in the example above) should be an object that defines the behavior of the corresponding sub-extension.
18 |
19 | ## Sub-extension object properties
20 |
21 | A sub-extension object should have a [`type` property](#type) that defines the type of the sub-extension, and either [`regex` and `replace` properties](#regex-and-replace) or a [`filter` property](#filter).
22 |
23 | ### Type
24 |
25 | **Type** is a **required** property that defines the nature of the corresponding sub-extensions. It takes one of the two values:
26 |
27 | * **`lang`**: language extension to add new Markdown syntax to Showdown.
28 |
29 | `lang` extensions have the **highest priority** in the subparser order, so they are called after [escaping and normalizing](#escape-and-normalization) the input text and before calling any other subparser (or extension).
30 |
31 | !!! example "When to use `lang` type"
32 |
33 | For example, if you want the `^^youtube http://www.youtube.com/watch?v=oHg5SJYRHA0` syntax to automatically be rendered as an embedded YouTube video.
34 |
35 | * **`output`**: output extension (or modifier) to alter the HTML output generated by Showdown.
36 |
37 | `output` extensions have the **lowest priority** in the subparser order, so they are called right before the cleanup step and after calling all other subparsers.
38 |
39 | !!! example "When to use `output` type"
40 |
41 | For example, if you want the `` to become ``.
42 |
43 | ### Regex and replace
44 |
45 | `regex`/`replace` properties are similar to the Javascript's `string.replace` function and work the same way:
46 |
47 | * `regex`: a `string` or a `RegExp` object.
48 |
49 | If `regex` is a `string`, it will automatically be assigned a `g` (global) modifier, that is, all matches of that string will be replaced.
50 |
51 | * `replace` a `string` or a `function`.
52 |
53 | If `replace` is a `string`, you can use the `$1` syntax for group substitution, exactly as if it were making use of `string.replace`.
54 |
55 | !!! example "Regex and replace example"
56 |
57 | In this example, all the occurrences of `markdown` will be replaced with `showndown`.
58 |
59 | ```js
60 | var myext = {
61 | type: 'lang',
62 | regex: /markdown/g,
63 | replace: 'showdown'
64 | };
65 | ```
66 |
67 | ### Filter
68 |
69 | Alternately, if you'd like to have more control over the modification process, you can use `filter` property.
70 |
71 | This property should be used as a function that acts as a callback. The callback should receive the following parameters:
72 |
73 | 1. `text`: the source text within the Showdown's engine.
74 | 1. `converter`: the full instance of the current Showdown's converter object.
75 | 1. `options`: the options used to initialize the converter
76 |
77 | !!! warning ""
78 | The filter function **should return the transformed text**. If it doesn't, it will fail **silently** and return an empty output.
79 |
80 | !!! example "Filter example"
81 |
82 | ```js
83 | var myext = {
84 | type: 'lang',
85 | filter: function (text, converter, options) {
86 | // ... do stuff to text ...
87 | return text;
88 | }
89 | };
90 | ```
91 |
92 | !!! warning "Use `filter` with care"
93 |
94 | Although Filter extensions are more powerful, they have a few pitfalls that you should keep in mind before using them, especially regarding the `converter` parameter.
95 |
96 | Since the `converter` parameter passed to the filter function is the fully initialized instance, any change made to it will be propagated outside the scope of the filter function and will remain there until a new converter instance is created. So, **it is not recommended to make ANY change to the converter object**.
97 |
98 | Another aspect is that if you call the `converter` recursively, it will call your extension itself at some point. It may lead to infinite recursion in some circumstances, and it's up to you to prevent this. A simple solution is to place a kind of safeguard to disable your extension if it's called more than x times:
99 |
100 | ```js
101 | var x = 0;
102 | var myext = {
103 | type: 'lang',
104 | filter: function (text, converter) {
105 | if (x < 3) {
106 | ++x;
107 | someSubText = converter.makeHtml(someSubText);
108 | }
109 | }
110 | };
111 | ```
112 |
113 | ## Register an extension
114 |
115 |
116 | To let Showdown know what extensions are available, you need to register them in the Showdown global object.
117 |
118 | To register an extension, call the `showdown.extension` function with two parameters: the first one is the extension name; the second one is the actual extension.
119 |
120 | ```js
121 | showdown.extension('myext', myext);
122 | ```
123 |
124 | ## Test an extension
125 |
126 | The Showdown test runner is configured to automatically test cases for extensions.
127 |
128 | To add test cases for an extension:
129 |
130 | 1. Create a new folder under `./test/extensions` that matches with the name of the `.js` file in `./src/extensions`.
131 | 1. Place any test cases into the filter using the `md/html` format. These cases will automatically be executed when running tests.
132 |
133 | ## Additional information
134 |
135 | ### Escape and normalization
136 |
137 | Showdown performs the following escape/normalization:
138 |
139 | * Replaces `¨` (trema) with `¨T`
140 | * Replaces `$` (dollar sign) with `¨D`
141 | * Normalizes line endings (`\r`, `\r\n` are converted into `\n`)
142 | * Uses `\r` as a char placeholder
143 |
144 | !!! note ""
145 | This only applies to **language extensions** since these chars are unescaped before output extensions are run.
146 |
147 | !!! warning ""
148 |
149 | Keep in mind that these modifications happen **before language extensions** are run, so if your extension relies on any of those chars, you have to make the appropriate adjustments.
150 |
151 |
152 | ### Implementation concerns
153 |
154 | One of the concerns is maintaining both client-side and server-side compatibility. You can do this with a few lines of boilerplate code.:
155 |
156 | ```js
157 | (function (extension) {
158 | if (typeof showdown !== 'undefined') {
159 | // global (browser or node.js global)
160 | extension(showdown);
161 | } else if (typeof define === 'function' && define.amd) {
162 | // AMD
163 | define(['showdown'], extension);
164 | } else if (typeof exports === 'object') {
165 | // Node, CommonJS-like
166 | module.exports = extension(require('showdown'));
167 | } else {
168 | // showdown was not found so an error is thrown
169 | throw Error('Could not find showdown library');
170 | }
171 | }(function (showdown) {
172 | // loading extension into showdown
173 | showdown.extension('myext', function () {
174 | var myext = { /* ... actual extension code ... */ };
175 | return [myext];
176 | });
177 | }));
178 | ```
179 |
180 | In the code above, the extension definition is wrapped in a self-executing function to prevent pollution of the global scope. It has another benefit of creating several scope layers that can be useful for interaction between sub-extensions global-wise or local-wise.
181 |
182 | It is also loaded conditionally to make it compatible with different loading mechanisms (such as browser, CommonJS, or AMD).
183 |
--------------------------------------------------------------------------------
/docs/credits.md:
--------------------------------------------------------------------------------
1 | === "v.2"
2 |
3 | * [Estevão Santos](https://github.com/tivie)
4 | * [SyntaxRules](https://github.com/SyntaxRules)
5 |
6 | === "v.1"
7 |
8 | * [Estevão Santos](https://github.com/tivie)
9 | * [Pascal Deschênes](https://github.com/pdeschen)
10 |
11 | === "v.0"
12 |
13 | * [Corey Innis](http://github.com/coreyti) - Original GitHub project maintainer
14 | * [Remy Sharp](https://github.com/remy/) - CommonJS-compatibility and more
15 | * [Konstantin Käfer](https://github.com/kkaefer/) - CommonJS packaging
16 | * [Roger Braun](https://github.com/rogerbraun) - GitHub-style code blocks
17 | * [Dominic Tarr](https://github.com/dominictarr) - Documentation
18 | * [Cat Chen](https://github.com/CatChen) - Export fix
19 | * [Titus Stone](https://github.com/tstone) - Mocha tests, extension mechanism, bug fixes
20 | * [Rob Sutherland](https://github.com/roberocity) - The idea that lead to extensions
21 | * [Pavel Lang](https://github.com/langpavel) - Code cleanup
22 | * [Ben Combee](https://github.com/unwiredben) - Regex optimization
23 | * [Adam Backstrom](https://github.com/abackstrom) - WebKit bug fixes
24 | * [Pascal Deschênes](https://github.com/pdeschen) - Grunt support, extension fixes + additions, packaging improvements, documentation
25 | * [Estevão Santos](https://github.com/tivie) - Bug fixes and late maintainer
26 | * [Hannah Wolfe](https://github.com/ErisDS) - Bug fixes
27 | * [Alexandre Courtiol](https://github.com/acourtiol) - Bug fixes and build optimization
28 | * [Karthik Balakrishnan](https://github.com/torcellite) - Support for table alignment
29 | * [rheber](https://github.com/rheber) - CLI
30 |
31 |
32 | === "Original Project"
33 |
34 | * [John Gruber](http://daringfireball.net/projects/markdown/) - Author of Markdown
35 | * [John Fraser](http://attacklab.net/) - Author of Showdown
36 |
--------------------------------------------------------------------------------
/docs/donations.md:
--------------------------------------------------------------------------------
1 | ShowdownJS is a **free** library and it will remain **free forever**.
2 |
3 | However, maintaining and improving the library costs time and money.
4 |
5 | If you like our work and find it useful, please donate through [PayPal](https://www.paypal.me/tiviesantos).
6 |
7 | :heart: :pray: Your contributions are greatly appreciated and will help us with the development of this awesome library.
--------------------------------------------------------------------------------
/docs/event_system.md:
--------------------------------------------------------------------------------
1 | # Event System
2 |
3 | ## Introduction
4 |
5 |
6 | ## The Event Object
7 |
8 |
9 | ## Events
10 |
11 | Events are raised when a subparser is run (or about to be run).
12 | Within a subparser, the events always follow a certain order (sequence). For instance, **.before** events always run before **.captureStart**.
13 | Each subparser raises several events sequentially:
14 |
15 | 1. **.start**: **always runs** except it subparser is disabled
16 |
17 | Raised when the **subparser has started**, but no capturing or any modification to the text was done.
18 |
19 | **Always runs** (except if the subparser is deactivated through options).
20 |
21 | ***Properties***:
22 |
23 | | property | type | access | description |
24 | |----------|-----------|------------|--------------------------------------------------------------------|
25 | | input | string | read | The full text that was passed to the subparser |
26 | | output | string | write | The full text with modification that will be passed along the chain|
27 | | regexp | null | | |
28 | | matches | null | | |
29 |
30 | Usually you would want to use this event if you wish to change the input to the subparser
31 |
32 | 2. **.captureStart**: *might not be run*;
33 |
34 | Raised when a regex match is found and a capture was successful. Some normalization and modification
35 | of the regex captured groups might be performed.
36 |
37 | Might not be run if no regex match is found.
38 |
39 | ***Properties***:
40 |
41 | | property | type | access | description |
42 | |----------|-----------|------------|--------------------------------------------------------------------|
43 | | input | string | read | The captured text |
44 | | output | string | write | The text that will be passed to the subparser/other listeners |
45 | | regexp | RegExp | readonly | Regular Expression used to capture groups |
46 | | matches | object | read/write | Matches groups. Changes to this object are reflected in the output |
47 |
48 | Usually you would want to use this event if you wish to modify a certain subparser behavior.
49 | Exs: remove all title attributes from links; change indentation of code blocks; etc...
50 |
51 | 3. **.captureEnd**: *might not be run*;
52 |
53 | Raised after the modifications to the captured text are done but before the replacement is introduced in the document.
54 |
55 | Might not be run if no regex match is found.
56 |
57 | ***Properties***:
58 |
59 | | property | type | access | description |
60 | |------------|-----------|------------|--------------------------------------------------------------------------------|
61 | | input | string | read | The captured text |
62 | | output | string | write | The text that will be passed to the subparser/other listeners |
63 | | regexp | RegExp | readonly | Regular Expression used to capture groups |
64 | | matches | object | read/write | Keypairs of matches groups. Changes to this object are reflected in the output |
65 | | attributes | object | read/write | Attributes to add to the HTML output |
66 |
67 | 4. **.beforeHash**: *might not be run*;
68 |
69 | Raised before the output is hashed.
70 |
71 | Always run (except if the subparser was deactivated through options), even if no hashing is performed.
72 |
73 | ***Properties***:
74 |
75 | | property | type | access | description |
76 | |----------|------------|------------|--------------------------------------------------------------------|
77 | | input | string | read | The captured text |
78 | | output | string | write | The text that will be passed to the subparser/other listeners |
79 | | regexp | null | | |
80 | | matches | null | | |
81 |
82 | Usually you would want to use this event if you wish change the subparser output before it is hashed
83 |
84 | 5. **.end**: *always runs*;
85 |
86 | Raised when the subparser has finished its work and is about to exit.
87 |
88 | Always runs (except if the subparser is deactivated through options).
89 |
90 | ***Properties***:
91 |
92 | | property | type | access | description |
93 | |----------|-----------|------------|--------------------------------------------------------------------|
94 | | input | string | read | The partial/full text with the subparser modifications |
95 | | output | string | write | The text that will be passed to other subparsers |
96 | | regexp | null | | |
97 | | matches | null | | |
98 |
99 | Usually you would want to use this event if you wish change the subparser hashed output
100 |
101 |
102 | ### Special Events
103 |
104 | There are some special events that are useful for *"positioning"* a listener extension in the main chain of events.
105 | Usually these extensions introduce new syntax that, due to precedence
106 | These events are always guaranteed to be called, regardless of options or circumstances.
107 |
108 | 1. **.before_{subparserName}**: *always runs*
109 |
110 | Raised just before the **{subparserName} is about to be entered**.
111 |
112 | ***Properties***:
113 |
114 | | property | type | access | description |
115 | |----------|-----------|------------|--------------------------------------------------------------------|
116 | | input | string | read | The full text that was passed to the subparser |
117 | | output | string | write | The full text with modification that will be passed along the chain|
118 | | regexp | null | | |
119 | | matches | null | | |
120 |
121 | 2. **.after**.{subparserName}: *always runs*;
122 |
123 | Raised when the **{subparserName} has exited** and before the next one is called.
124 |
125 | ***Properties***:
126 |
127 | | property | type | access | description |
128 | |----------|-----------|------------|--------------------------------------------------------------------|
129 | | input | string | read | The partial/full text with the subparser modifications |
130 | | output | string | write | The text that will be passed to other subparsers |
131 | | regexp | null | | |
132 | | matches | null | | |
133 |
134 |
135 | ### Notes
136 |
137 | - There are 2 main differences between **before.{subparserName}** and **{subparserName}.start**.
138 |
139 | 1. **before.{subparserName}** is always guaranteed to be called, even if the subparser is disabled,
140 | while **{subparserName}.start** doesn't.
141 |
142 | ex: `makehtml.before.strikethrough` is always called even if the option `strikethrough` is false
143 |
144 | 2. **before.{subparserName}** is only raised once in a span context while **{subparserName}.start** is raised
145 | everytime **{subparserName}** is called.
146 |
147 | As a rule of thumb,
148 |
149 | ## Events List
150 |
--------------------------------------------------------------------------------
/docs/extensions-list.md:
--------------------------------------------------------------------------------
1 | ## Official
2 |
3 | * [twitter-extension][1] - Adds support of Twitter usernames and hastags
4 | * [prettify-extension][2] - Adds [Google Prettify][3] hints to HTML output
5 |
6 | ## Community
7 |
8 | * [showdown-icon][4] - Adds support of Glyphicon and font-awesome into Markdown
9 | * [showdown-xss-filter][5] - Filters XSS, using leizongmin/js-xss
10 | * [showdown-toc][6] - Adds Table of Contents
11 | * [showdown-footnotes][7] - Adds simple footnotes
12 | * [katex-latex][8] - Displays math using KaTeX and LaTeX or AsciiMath
13 |
14 | !!! note ""
15 | If you have a Showdown extension you would like to add here, you can [raise an issue](https://github.com/showdownjs/showdown/issues).
16 |
17 | [1]: https://github.com/showdownjs/twitter-extension
18 | [2]: https://github.com/showdownjs/prettify-extension
19 | [3]: https://github.com/googlearchive/code-prettify
20 | [4]: https://github.com/dbtek/showdown-icon
21 | [5]: https://github.com/VisionistInc/showdown-xss-filter
22 | [6]: https://github.com/ravisorg/showdown-toc
23 | [7]: https://github.com/Kriegslustig/showdown-footnotes
24 | [8]: https://obedm503.github.io/showdown-katex
25 |
--------------------------------------------------------------------------------
/docs/extensions.md:
--------------------------------------------------------------------------------
1 | Showdown allows you to load additional functionality via extensions. You can find a list of known Showdown extensions [here][ext-wiki].
2 |
3 | You can also check the [boilerplate repo][boilerplate-repo], to create your own extension(s).
4 |
5 | ## Usage
6 |
7 | === "Server-side"
8 |
9 | ```js
10 | // Using a bundled extension
11 | var showdown = require('showdown');
12 | var converter = new showdown.Converter({ extensions: ['twitter'] });
13 |
14 | // Using a custom extension
15 | var mine = require('./custom-extensions/mine');
16 | var converter = new showdown.Converter({ extensions: ['twitter', mine] });
17 | ```
18 |
19 | === "Client-side"
20 |
21 | ```js
22 |
23 |
24 |
25 | ```
26 |
27 | === "CLI"
28 |
29 | In the CLI tool, use the [`-e` flag](/cli/#-e-extensions) to load an extension.
30 |
31 | ```sh
32 | showdown -e twitter -i foo.md -o bar.html
33 | ```
34 |
35 | [ext-wiki]: https://github.com/showdownjs/showdown/wiki/extensions
36 | [boilerplate-repo]: https://github.com/showdownjs/extension-boilerplate
--------------------------------------------------------------------------------
/docs/flavors.md:
--------------------------------------------------------------------------------
1 | ## Overview
2 |
3 | You can use _flavors_ (or presets) to set the preferred options automatically. In this way, Showdown behaves like popular Markdown flavors.
4 |
5 | Currently, the following flavors are available:
6 |
7 | * `original`: Original Markdown flavor as in [John Gruber's spec](https://daringfireball.net/projects/markdown/)
8 | * `vanilla`: Showdown base flavor (v1.3.1 onwards)
9 | * `github`: [GitHub Flavored Markdown, or GFM](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax)
10 |
11 | ## Set flavor
12 |
13 | === "Globally"
14 |
15 | ```js
16 | showdown.setFlavor('github');
17 | ```
18 |
19 | === "Locally"
20 |
21 | ```js
22 | converter.setFlavor('github');
23 | ```
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # Showdown documentation
2 |
3 | ![Showdown][sd-logo]
4 |
5 | 
6 | 
7 | [](http://badge.fury.io/js/showdown)
8 | [](http://badge.fury.io/bo/showdown)
9 | [](https://gitter.im/showdownjs/showdown?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
10 | [](https://www.paypal.me/tiviesantos)
11 |
12 | Showdown is a JavaScript Markdown to HTML converter, based on the original works by John Gruber.
13 | Showdown can be used on the client-side (in the browser) or server-side (with Node.js).
14 |
15 | ----
16 |
17 | ## Live demo
18 |
19 |
20 |
21 | ## Who uses Showdown (or a fork)
22 |
23 | * [Antmarky](https://github.com/bandantonio/antmarky)
24 | * [GoogleCloudPlatform](https://github.com/GoogleCloudPlatform)
25 | * [Meteor](https://www.meteor.com/)
26 | * [StackExchange](http://stackexchange.com/) - forked as [PageDown](https://code.google.com/p/pagedown/)
27 | * [docular](https://github.com/Vertafore/docular)
28 | * [md-page](https://github.com/oscarmorrison/md-page)
29 | * [QCObjects](https://qcobjects.dev)
30 | * [and some others](https://www.npmjs.com/browse/depended/showdown)
31 |
32 | ## Installation
33 |
34 | To install Showdown, follow the instructions from the [Quickstart guide](quickstart.md).
35 |
36 |
37 | ## License
38 |
39 | ShowdownJS v 2.0 is release under the MIT version.
40 |
41 | Previous versions are release under BSD.
42 |
43 | [sd-logo]: https://raw.githubusercontent.com/showdownjs/logo/master/dist/logo.readme.png
44 | [wiki]: https://github.com/showdownjs/showdown/wiki
45 | [cli-wiki]: https://github.com/showdownjs/showdown/wiki/CLI-tool
46 | [definitely-typed]: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/showdown
47 | [xss-wiki]: https://github.com/showdownjs/showdown/wiki/Markdown's-XSS-Vulnerability-(and-how-to-mitigate-it)
48 | [ext-wiki]: https://github.com/showdownjs/showdown/wiki/extensions
49 | [coding-rules]: https://github.com/showdownjs/code-style/blob/master/README.md
50 | [ng-commit-guide]: https://github.com/showdownjs/code-style/blob/master/README.md#commit-message-convention
51 | [boilerplate-repo]: https://github.com/showdownjs/extension-boilerplate
--------------------------------------------------------------------------------
/docs/integrations.md:
--------------------------------------------------------------------------------
1 | ## AngularJS
2 |
3 | ShowdownJS project provides seamless integration with AngularJS via a plugin.
4 |
5 | Check [`ng-showdown`](https://github.com/showdownjs/ngShowdown) repository for more information.
6 |
7 | ## TypeScript
8 |
9 | If you're using TypeScript, you may want to use the types from the [DefinitelyTyped][definitely-typed] repository.
10 |
11 | ## SystemJS/JSPM
12 |
13 | To integrate ShowdownJS with SystemJS, you can use a third-party [system-md plugin](https://github.com/guybedford/system-md).
14 |
15 | ## Vue.js
16 |
17 | To use ShowdownJS as a Vue component, you can check [vue-showdown](https://vue-showdown.js.org/).
18 |
19 |
20 | [definitely-typed]: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/showdown
--------------------------------------------------------------------------------
/docs/quickstart.md:
--------------------------------------------------------------------------------
1 | To quickstart with Showdown, install it as a package (for server-side) or include it to your browser (client-side) via CDN:
2 |
3 | ## Installation
4 |
5 | ### Server-side
6 |
7 | === "npm"
8 |
9 | ```
10 | npm install showdown
11 | ```
12 |
13 | === "bower"
14 |
15 | ```
16 | bower install showdown
17 | ```
18 |
19 | === "NuGet"
20 |
21 | ```
22 | PM> Install-Package showdownjs
23 | ```
24 |
25 | More information about the package you can find on the [NuGet website](https://www.nuget.org/packages/showdownjs/).
26 |
27 | ### Client-side
28 |
29 | === "jsDelivr"
30 |
31 | ```
32 | https://cdn.jsdelivr.net/npm/showdown@/dist/showdown.min.js
33 | ```
34 |
35 | [Showndown page on jsDelivr](https://www.jsdelivr.com/package/npm/showdown)
36 |
37 | === "cdnjs"
38 |
39 | ```
40 | https://cdnjs.cloudflare.com/ajax/libs/showdown//showdown.min.js
41 | ```
42 |
43 | [Showndown page on cdnjs](https://cdnjs.com/libraries/showdown)
44 |
45 | === "unpkg"
46 |
47 | ```
48 | https://unpkg.com/showdown/dist/showdown.min.js
49 | ```
50 |
51 | [Showndown page on unpkg](https://unpkg.com/browse/showdown@latest/)
52 |
53 | !!! note ""
54 | Replace `` with an actual full length version you're interested in. For example, `2.0.3`.
55 |
56 | ## Usage
57 |
58 | Once installed, you can use Showndown according to the chosen method:
59 |
60 | ### Server-side
61 |
62 | !!! example "Node.js"
63 |
64 | === "code"
65 |
66 | ```js
67 | var showdown = require('showdown'),
68 | converter = new showdown.Converter(),
69 | text = '# hello, markdown!',
70 | html = converter.makeHtml(text);
71 | ```
72 |
73 | === "output"
74 |
75 | ```html
76 | hello, markdown!
77 | ```
78 |
79 | ### Client-side
80 |
81 | !!! example "Browser"
82 |
83 | === "code"
84 |
85 | ```js
86 | var converter = new showdown.Converter(),
87 | text = '# hello, markdown!',
88 | html = converter.makeHtml(text);
89 | ```
90 |
91 | === "output"
92 |
93 | ```html
94 | hello, markdown!
95 | ```
96 |
97 | !!! warning "Potential XSS vulnerabilities"
98 | Showdown doesn't sanitize the input since Markdown relies on it to parse certain features correctly into HTML. As a result, this may lead to potential XSS injection vulnerabilities.
99 |
100 | Please refer to the [Markdown's XSS vulnerability](xss.md) page for more information.
101 |
102 | ## Other installation methods
103 |
104 | ### Tarball
105 |
106 | You can download the latest tarball directly from [releases][releases].
107 |
108 | ## Previous versions
109 |
110 | If you're looking for Showdown prior to version 1.0.0, you can find them in the [legacy branch][legacy-branch].
111 |
112 | ## Changelog
113 |
114 | The full changelog is available [here][changelog].
115 |
116 | [legacy-branch]: https://github.com/showdownjs/showdown/tree/legacy
117 | [releases]: https://github.com/showdownjs/showdown/releases
118 | [changelog]: https://github.com/showdownjs/showdown/blob/master/CHANGELOG.md
--------------------------------------------------------------------------------
/docs/tutorials/add-default-class-to-html.md:
--------------------------------------------------------------------------------
1 | # Add default class for each HTML element
2 |
3 | Many people use CSS kits like Bootstrap, Semantic UI, or others that require default name classes for HTML elements:
4 |
5 | ```html
6 | 1st Heading
7 | 2nd Heading
8 |
9 | - first item
10 | - second item
11 |
12 | ```
13 |
14 | Showdown does not support this out-of-the-box. But you can create an extension for this:
15 |
16 | ```js
17 | const showdown = require('showdown');
18 |
19 | const classMap = {
20 | h1: 'ui large header',
21 | h2: 'ui medium header',
22 | ul: 'ui list',
23 | li: 'ui item'
24 | }
25 |
26 | const bindings = Object.keys(classMap)
27 | .map(key => ({
28 | type: 'output',
29 | regex: new RegExp(`<${key}(.*)>`, 'g'),
30 | replace: `<${key} class="${classMap[key]}" $1>`
31 | }));
32 |
33 | const conv = new showdown.Converter({
34 | extensions: [...bindings]
35 | });
36 |
37 | const text = `
38 | # 1st Heading
39 | ## 2nd Heading
40 |
41 | - first item
42 | - second item
43 | `;
44 | ```
45 |
46 | With this extension, the output will be as follows:
47 |
48 | ```html
49 | 1st Heading
50 | 2nd Heading
51 |
52 | - first item
53 | - second item
54 |
55 | ```
56 |
57 | ## Credits
58 |
59 | * Initial creator: [@zusamann](https://github.com/zusamann), [(original issue)](https://github.com/showdownjs/showdown/issues/376).
60 | * Updated by [@Kameelridder](https://github.com/Kameelridder), [(original issue)](https://github.com/showdownjs/showdown/issues/509).
61 |
--------------------------------------------------------------------------------
/docs/tutorials/index.md:
--------------------------------------------------------------------------------
1 | # Tutorials
2 |
3 | * [Add default class for each HTML element](add-default-class-to-html.md)
4 | * [Markdown editor with Showdown](markdown-editor-with-showdown.md)
5 | * [Use language and output extensions on the same block](use-both-extension-types-together.md)
--------------------------------------------------------------------------------
/docs/tutorials/markdown-editor-with-showdown.md:
--------------------------------------------------------------------------------
1 | # Markdown editor with Showdown
2 |
3 | ## Introduction
4 |
5 | In this tutorial, you will create a simple in-browser Markdown editor using Showdown and some of its extensions. The purpose is to show how easy it is to include and configure Showdown in your project.
6 |
7 | The fully working example you can see in [Fiddle][1].
8 |
9 | ## Step 1: Prepare project
10 |
11 | 1. Install [node.js](https://nodejs.org/en/).
12 | 1. Install project package management tool
13 |
14 | !!! info ""
15 | Showdown core library doesn't have any dependencies so the setup is pretty straightforward. However, you are strongly encouraged to use a package manager such as [**npm**](http://npmjs.com) or [**yarn**](https://yarnpkg.com) to manage project dependencies.
16 |
17 | To install package management tool:
18 |
19 | 1. Create a directory called `showdown-editor` and recreate the following structure:
20 |
21 | ```
22 | showdown-editor
23 | ├── css
24 | │ └── style.css
25 | ├── js
26 | │ └── script.js
27 | └── index.html
28 | ```
29 |
30 | 1. Initialize `package.json` file by running the following interactive console command:
31 |
32 | ```
33 | npm init -y
34 | ```
35 |
36 | This command creates `package.json` file in the root of the project folder, and populates the default content that you can change later if you wish.
37 |
38 | ## Step 2: Install Showdown
39 |
40 | Inside the `showdown-editor` directory, run the following command:
41 |
42 | ```
43 | npm install showdown --save
44 | ```
45 |
46 | This command will install `showdown` inside the `node_modules` directory and save `showdown` as a dependency in the `package.json` file.
47 |
48 | ## Step 3: Update project files
49 |
50 | Add the following content to the corresponding project files:
51 |
52 | === "index.html"
53 |
54 | ```html
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | ```
98 |
99 | !!! warning ""
100 | Please note how Showdown and the script file are included to the `index.html` via the `script` tag at the bottom of the file.
101 |
102 | === "style.css"
103 |
104 | ```css
105 | #sourceTA {
106 | display: block;
107 | }
108 | #targetDiv {
109 | border: 1px dashed #333333;
110 | width: 600px;
111 | height: 400px;
112 | }
113 | ```
114 |
115 | === "script.js"
116 |
117 | ```js
118 | function run() {
119 | var text = document.getElementById('sourceTA').value,
120 | target = document.getElementById('targetDiv'),
121 | converter = new showdown.Converter(),
122 | html = converter.makeHtml(text);
123 |
124 | target.innerHTML = html;
125 | }
126 | ```
127 |
128 | The `script.js` file is simple: when the `runBtn` button is clicked, the script gets the text of the textarea, passes it through Showdown to convert the markdown text into HTML. The resulting HTML is then put inside the `targetDiv`, replacing the previous content.
129 |
130 | ## Step 4: Check the result
131 |
132 | 1. Open your `index.html` file. You should see your editor with prefilled markdown text in the text area.
133 | 1. Click `Convert` button. You show see the text to be converted to HTML:
134 |
135 | 
136 |
137 | The fully working example you can see in [Fiddle][1].
138 |
139 | ## Conclusion
140 |
141 | Congratulations! :tada: You have successfully created a simple Markdown editor!
142 |
143 | [1]: http://jsfiddle.net/tivie/6bnpptkb/
--------------------------------------------------------------------------------
/docs/tutorials/use-both-extension-types-together.md:
--------------------------------------------------------------------------------
1 | # Use language and output extensions on the same block
2 |
3 | ## Overview
4 |
5 | Showdown allows you to define and use any number of extensions that act on the same block. These extensions can be executed sequentially or at different moments.
6 |
7 | This enables you to pre-parse/mark a block of text but defer any modifications for the last by using a combination of language and output extensions.
8 |
9 | This is useful if you, for example, don't want Showdown to parse the contents of your new language construct.
10 |
11 | ## Example
12 |
13 | Let's say you create an extension that captures everything between `%start%` and `%end%`. However, that content should not be modified by Showdown. Obviously, you can use `` tags but that is beside the point.
14 |
15 | Although Showdown doesn't have any flag to prevent parsing the content of an extension, the same effect can be easily achieved by using lang and output extensions together.
16 |
17 | !!! example ""
18 | The fully working example you can see in [Fiddle][1].
19 |
20 | ### Code
21 |
22 | [Create your extensions](../create-extension.md) with the following content:
23 |
24 | ```js
25 | showdown.extension('myExt', function() {
26 | var matches = [];
27 | return [
28 | {
29 | type: 'lang',
30 | regex: /%start%([^]+?)%end%/gi,
31 | replace: function(s, match) {
32 | matches.push(match);
33 | var n = matches.length - 1;
34 | return '%PLACEHOLDER' + n + '%';
35 | }
36 | },
37 | {
38 | type: 'output',
39 | filter: function (text) {
40 | for (var i=0; i< matches.length; ++i) {
41 | var pat = '%PLACEHOLDER' + i + '% *<\/p>';
42 | text = text.replace(new RegExp(pat, 'gi'), matches[i]);
43 | }
44 | //reset array
45 | matches = [];
46 | return text;
47 | }
48 | }
49 | ]
50 | });
51 | ```
52 |
53 | In this example, you created a [`lang` extension](../create-extension.md#type) that:
54 |
55 | 1. Checks for the pseudo tags `%start%` and `%end%`.
56 | 1. Extracts everything in between the tags.
57 | 1. Saves the content between the tags in a variable.
58 | 1. Replaces the saved content with a placeholder to identify the exact position of the extracted text.
59 |
60 | and an [`output` extension](../create-extension.md#type) that replaces the placeholder with the saved content, once Showdown is finished parsing.
61 |
62 | [1]: http://jsfiddle.net/tivie/1rqr7xy8/
--------------------------------------------------------------------------------
/docs/xss.md:
--------------------------------------------------------------------------------
1 | # Markdown's XSS vulnerability
2 |
3 | ## Introduction
4 |
5 | Cross-Site Scripting (XSS) is a well-known technique to gain access to the private information of users on a website. The attacker injects spurious HTML content (a script) on the web page. This script can read the user’s cookies and do other malicious actions (like steal credentials). As a countermeasure, you should always filter user input for suspicious content. Showdown doesn’t include an XSS filter, so you must provide your own. But be careful in how you do it.
6 |
7 | ## Markdown is inherently unsafe
8 |
9 | Markdown syntax allows the inclusion of arbitrary HTML. For example, below is a perfectly valid Markdown:
10 |
11 | ```md
12 | This is a regular paragraph.
13 |
14 |
15 | Foo
16 |
17 |
18 | This is another regular paragraph.
19 | ```
20 |
21 | This means that an attacker could do something like this:
22 |
23 | ```md
24 | This is a regular paragraph.
25 |
26 |
27 |
28 | This is another regular paragraph.
29 | ```
30 |
31 | While `alert('xss');` is hardly problematic (maybe just annoying) a real-world scenario might be a lot worse. Obviously, you can easily prevent this kind of this straightforward attack. For example, you can define a whitelist for Showdown that will contain a limited set of allowed HTML tags. However, an attacker can easily circumvent this "defense".
32 |
33 | ## Whitelist / blacklist can't prevent XSS
34 |
35 | Consider the following Markdown content:
36 |
37 | ```md
38 | hello *you*
39 | ```
40 |
41 | As you can see, it's a link, nothing malicious about this. And `` tags are pretty innocuous, right? Showdown should definitely allow them. But what if the content is slightly altered, like this:
42 |
43 | ```md
44 | hello *you*
45 | ```
46 |
47 | Now this is a lot more problematic. Once again, it's not that hard to filter Showdown's input to expunge problematic attributes (such as `href` in `` tags) of scripting attacks. In fact, a regular HTML XSS prevention library should catch this kind of straightforward attack.
48 |
49 | At this point you're probably thinking that the best way is to follow Stackoverflow's cue and disallow embedded HTML in Markdown. Unfortunately it's still not enough.
50 |
51 | ## Strip HTML tags is not enough
52 |
53 | Consider the following Markdown input:
54 |
55 | ```md
56 | [some text](javascript:alert('xss'))
57 | ```
58 |
59 | Showdown will correctly parse this piece of Markdown input as:
60 |
61 | ```html
62 | some text
63 | ```
64 |
65 | In this case, it was Markdown's syntax itself to create the dangerous link. HTML XSS filter cannot catch this. And unless you start striping dangerous words like *javascript* (which would make this article extremely hard to write), there's nothing you can really do to filter XSS attacks from your input. Things get even harder when you tightly mix HTML with Markdown.
66 |
67 | ## Mixed HTML/Markdown XSS attack
68 |
69 | Consider the following piece of Markdown:
70 |
71 | ```md
72 | > hello href="javascript:alert('xss')">*you*
74 | ```
75 |
76 | If you apply an XSS filter to filter bad HTML in this Markdown input, the XSS filter, expecting HTML, will likely think the `` tag ends with the first character on the second line and will leave the text snippet untouched. It will probably fail to see that the `href="javascript:…"` is part of the `` element and leave it alone. But when Markdown converts this to HTML, you get this:
77 |
78 | ```html
79 |
80 | hello you
82 |
83 | ```
84 |
85 | After parsing with Markdown, the first `>` on the second line disappears because it was the blockquote marker in the Markdown blockquote syntax. As a result, you’ve got a link containing an XSS attack!
86 |
87 | Did Markdown generate the HTML? No, the HTML was already in plain sight in the input. The XSS filter couldn’t catch it because the input doesn’t follow HTML rules: it’s a mix of Markdown and HTML, and the filter doesn’t know a dime about Markdown.
88 |
89 | ## Mitigate XSS
90 |
91 | So, is it all lost? Not really. The answer is not to filter the *input* but rather the *output*. After the *input* text is converted into full-fledged HTML, you can reliably apply the correct XSS filters to remove any dangerous or malicious content.
92 |
93 | Also, client-side validations are not reliable. It should be a given, but in case you're wondering, you should (almost) never trust data sent by the client. If there's some critical operation you must perform on the data (such as XSS filtering), you should do it *SERVER-SIDE* not client-side.
94 |
95 | HTML XSS filtering libraries are useful here since they prevent most of the attacks. However, you should not use them blindly: a library can't predict all the contexts and situations your application may face.
96 |
97 | ## Conclusion
98 |
99 | Showdown tries to convert the input text as closely as possible, without any concerns for XSS attacks or malicious intent. So, the basic rules are:
100 |
101 | * **removing HTML entities from Markdown does not prevent XSS**. Markdown syntax can generate XSS attacks.
102 | * **XSS filtering should be done after Showdown has processed input, not before or during**. If you filter before, it will break some of Markdown’s features and will leave security holes.
103 | * **perform the necessary filtering server-side, not client-side**. XSS filtering libraries are useful but should not be used blindly.
104 |
105 | ## Disclaimer
106 |
107 | This page is based on the excellent article: ["Markdown and XSS"][1] by [Michel Fortin][2]
108 |
109 | [1]: https://michelf.ca/blog/2010/markdown-and-xss/
110 | [2]: https://github.com/michelf
111 |
--------------------------------------------------------------------------------
/karma.browserstack.js:
--------------------------------------------------------------------------------
1 | module.exports = function (config) {
2 | config.set({
3 | // global config of your BrowserStack account
4 | browserStack: {
5 | username: process.env.BROWSERSTACK_USERNAME,
6 | accessKey: process.env.BROWSERSTACK_ACCESSKEY,
7 | project: process.env.BROWSERSTACK_PROJECT_NAME || 'showdown',
8 | build: process.env.BROWSERSTACK_BUILD_NAME || require('./package.json').version,
9 | name: process.env.COMMIT_MSG || 'Unit Testing'
10 | },
11 |
12 | // define browsers
13 | customLaunchers: {
14 | bstack_chrome_windows: {
15 | base: 'BrowserStack',
16 | browser: 'chrome',
17 | browser_version: '49',
18 | os: 'Windows',
19 | os_version: '10'
20 | },
21 | bstack_firefox_windows: {
22 | base: 'BrowserStack',
23 | browser: 'firefox',
24 | browser_version: '44',
25 | os: 'Windows',
26 | os_version: '10'
27 | },
28 | bstack_edge_windows: {
29 | base: 'BrowserStack',
30 | browser: 'edge',
31 | browser_version: '15',
32 | os: 'Windows',
33 | os_version: '10'
34 | },
35 | bstack_ie11_windows: {
36 | base: 'BrowserStack',
37 | browser: 'ie',
38 | browser_version: '11',
39 | os: 'Windows',
40 | os_version: '10'
41 | },
42 | bstack_macos_safari: {
43 | base: 'BrowserStack',
44 | browser: 'safari',
45 | browser_version: '10.1',
46 | os: 'OS X',
47 | os_version: 'Sierra'
48 | },
49 | bstack_iphoneX: {
50 | base: 'BrowserStack',
51 | browser: 'safari',
52 | os: 'ios',
53 | os_version: '11.0',
54 | device: 'iPhone X',
55 | real_mobile: true
56 | },
57 | bstack_android: {
58 | base: 'BrowserStack',
59 | browser: 'chrome',
60 | os: 'android',
61 | os_version:'4.4',
62 | device: 'Samsung Galaxy Tab 4',
63 | realMobile: true
64 | }
65 | },
66 |
67 | browsers: ['bstack_chrome_windows', 'bstack_firefox_windows', 'bstack_ie11_windows', 'bstack_edge_windows', 'bstack_iphoneX', 'bstack_macos_safari', 'bstack_android'],
68 | frameworks: ['mocha', 'chai'],
69 | reporters: ['dots', 'BrowserStack'],
70 | files: [
71 | { pattern: '.build/showdown.js'},
72 | { pattern: 'src/options.js'},
73 | // tests
74 | { pattern: 'test/unit/showdown*.js' }
75 | //{ pattern: 'test/functional/showdown*.js' },
76 | ],
77 | singleRun: true,
78 | concurrency: Infinity
79 | });
80 | };
81 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = function (config) {
2 | config.set({
3 | client: {
4 | captureConsole: true
5 | },
6 | browserConsoleLogOptions: {
7 | level: 'log',
8 | format: '%b %T: %m',
9 | terminal: true
10 | },
11 | logLevel: config.LOG_LOG,
12 | frameworks: ['mocha', 'chai'],
13 | files: [
14 | { pattern: '.build/showdown.js'},
15 | { pattern: 'src/options.js'},
16 | // tests
17 | { pattern: 'test/unit/showdown*.js' },
18 | { pattern: 'test/functional/showdown*.js' },
19 | ],
20 | reporters: ['progress'],
21 | port: 9876, // karma web server port
22 | colors: true,
23 | browsers: ['ChromeHeadless', 'FirefoxHeadless', 'jsdom'],
24 | autoWatch: false,
25 | singleRun: true, // Karma captures browsers, runs the tests and exits
26 | //concurrency: Infinity,
27 | customLaunchers: {
28 | 'FirefoxHeadless': {
29 | base: 'Firefox',
30 | flags: [
31 | '-headless',
32 | ]
33 | }
34 | },
35 | });
36 | };
37 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: Showdown documentation
2 | site_description: Showdown is a JavaScript Markdown to HTML converter
3 |
4 | theme:
5 | name: material
6 | logo: http://showdownjs.com/apple-touch-icon.png
7 | favicon: http://showdownjs.com/apple-touch-icon.png
8 | icon:
9 | repo: fontawesome/brands/github
10 | features:
11 | - navigation.tabs
12 |
13 | markdown_extensions:
14 | - admonition
15 | - pymdownx.superfences
16 | - pymdownx.tabbed:
17 | alternate_style: true
18 | - pymdownx.tasklist
19 | - pymdownx.emoji:
20 | emoji_index: !!python/name:materialx.emoji.twemoji
21 | emoji_generator: !!python/name:materialx.emoji.to_svg
22 |
23 | extra_css:
24 | - assets/extra.css
25 |
26 | repo_url: https://github.com/showdownjs/showdown
27 | repo_name: showdownjs/showdown
28 | site_dir: public
29 |
30 | nav:
31 | - Home:
32 | - Introduction: index.md
33 | - Donations: donations.md
34 | - Credits: credits.md
35 | - Quickstart:
36 | - Quickstart: quickstart.md
37 | - Showdown's Markdown syntax: markdown-syntax.md
38 | - Compatibility: compatibility.md
39 | - Configuration:
40 | - Showdown options: configuration.md
41 | - Available options: available-options.md
42 | - Flavors: flavors.md
43 | - CLI: cli.md
44 | - Integrations: integrations.md
45 | - Extensions:
46 | - Overview: extensions.md
47 | - Create an extension: create-extension.md
48 | - List of known extensions: extensions-list.md
49 | - Tutorials: tutorials/index.md
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "showdown",
3 | "version": "3.0.0-alpha",
4 | "description": "A Markdown to HTML converter written in Javascript",
5 | "author": "Estevão Santos",
6 | "homepage": "http://showdownjs.com/",
7 | "keywords": [
8 | "markdown",
9 | "converter"
10 | ],
11 | "contributors": [
12 | "John Gruber",
13 | "John Fraser",
14 | "Corey Innis",
15 | "Remy Sharp",
16 | "Konstantin Käfer",
17 | "Roger Braun",
18 | "Dominic Tarr",
19 | "Cat Chen",
20 | "Titus Stone",
21 | "Rob Sutherland",
22 | "Pavel Lang",
23 | "Ben Combee",
24 | "Adam Backstrom",
25 | "Pascal Deschênes",
26 | "Estevão Santos"
27 | ],
28 | "funding": {
29 | "type": "individual",
30 | "url": "https://www.paypal.me/tiviesantos"
31 | },
32 | "repository": {
33 | "type": "git",
34 | "url": "https://github.com/showdownjs/showdown.git",
35 | "web": "https://github.com/showdownjs/showdown"
36 | },
37 | "license": "MIT",
38 | "main": "./dist/showdown.js",
39 | "scripts": {
40 | "test": "grunt test"
41 | },
42 | "bin": {
43 | "showdown": "bin/showdown.js"
44 | },
45 | "files": [
46 | "bin",
47 | "dist"
48 | ],
49 | "devDependencies": {
50 | "chai": "*",
51 | "chai-match": "*",
52 | "grunt": "^1.4.1",
53 | "grunt-contrib-clean": "^2.0.0",
54 | "grunt-contrib-concat": "^2.0.0",
55 | "grunt-contrib-jshint": "^3.1.0",
56 | "grunt-contrib-uglify": "^5.0.1",
57 | "grunt-conventional-changelog": "^6.1.0",
58 | "grunt-conventional-github-releaser": "^1.0.0",
59 | "grunt-endline": "^0.7.0",
60 | "grunt-eslint": "^24.0.0",
61 | "grunt-mocha-test": "^0.13.3",
62 | "grunt-simple-mocha": "^0.4.0",
63 | "karma": "^6.3.17",
64 | "karma-browserstack-launcher": "^1.6.0",
65 | "karma-chai": "^0.1.0",
66 | "karma-chrome-launcher": "^3.1.1",
67 | "karma-firefox-launcher": "^2.1.2",
68 | "karma-jsdom-launcher": "^12.0.0",
69 | "karma-mocha": "^2.0.1",
70 | "load-grunt-tasks": "^5.1.0",
71 | "performance-now": "^2.1.0",
72 | "quiet-grunt": "^0.2.0",
73 | "semver-sort": "^1.0.0",
74 | "sinon": "*",
75 | "source-map-support": "^0.5.21"
76 | },
77 | "dependencies": {
78 | "commander": "^9.0.0",
79 | "jsdom": "^19.0.0"
80 | },
81 | "overrides": {
82 | "minimist": "^1.2.6"
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/cli/cli.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by tivie
3 | */
4 | var fs = require('fs'),
5 | path = require('path'),
6 | Command = require('commander').Command,
7 | program = new Command(),
8 | path1 = path.resolve(__dirname + '/../dist/showdown.js'),
9 | path2 = path.resolve(__dirname + '/../../.build/showdown.js'),
10 | showdown,
11 | version;
12 |
13 | // require shodown. We use conditional loading for each use case
14 | if (fs.existsSync(path1)) {
15 | // production. File lives in bin directory
16 | showdown = require(path1);
17 | version = require(path.resolve(__dirname + '/../package.json')).version;
18 | } else if (fs.existsSync(path2)) {
19 | // testing envo, uses the concatenated stuff for testing
20 | showdown = require(path2);
21 | version = require(path.resolve(__dirname + '/../../package.json')).version;
22 | } else {
23 | // cold testing (manual) of cli.js in the src file. We load the dist file
24 | showdown = require('../../dist/showdown');
25 | version = require('../../package.json');
26 | }
27 |
28 |
29 | program
30 | .name('showdown')
31 | .description('CLI to Showdownjs markdown parser v' + version)
32 | .version(version)
33 | .usage(' [options]')
34 | .option('-q, --quiet', 'Quiet mode. Only print errors')
35 | .option('-m, --mute', 'Mute mode. Does not print anything');
36 |
37 | program.command('makehtml')
38 | .description('Converts markdown into html')
39 |
40 | .addHelpText('after', '\n\nExamples:')
41 | .addHelpText('after', ' showdown makehtml -i Reads from stdin and outputs to stdout')
42 | .addHelpText('after', ' showdown makehtml -i foo.md -o bar.html Reads \'foo.md\' and writes to \'bar.html\'')
43 | .addHelpText('after', ' showdown makehtml -i --flavor="github" Parses stdin using GFM style')
44 |
45 | .addHelpText('after', '\nNote for windows users:')
46 | .addHelpText('after', 'When reading from stdin, use option -u to set the proper encoding or run `chcp 65001` prior to calling showdown cli to set the command line to utf-8')
47 |
48 | .option('-i, --input [file]', 'Input source. Usually a md file. If omitted or empty, reads from stdin. Windows users see note below.', true)
49 | .option('-o, --output [file]', 'Output target. Usually a html file. If omitted or empty, writes to stdout', true)
50 | .option('-u, --encoding ', 'Sets the input encoding', 'utf8')
51 | .option('-y, --output-encoding ', 'Sets the output encoding', 'utf8')
52 | .option('-a, --append', 'Append data to output instead of overwriting. Ignored if writing to stdout', false)
53 | .option('-e, --extensions ', 'Load the specified extensions. Should be valid paths to node compatible extensions')
54 | .option('-p, --flavor ', 'Run with a predetermined flavor of options. Default is vanilla', 'vanilla')
55 | .option('-c, --config ', 'Enables showdown makehtml parser config options. Overrides flavor')
56 | .option('--config-help', 'Shows configuration options for showdown parser')
57 | .action(makehtmlCommand);
58 |
59 | program.parse();
60 |
61 |
62 | //
63 | // HELPER FUCNTIONS
64 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
65 |
66 | /**
67 | * Messenger helper object to the CLI
68 | * @param {string} writeMode
69 | * @param {boolean} supress
70 | * @param {boolean} mute
71 | * @constructor
72 | */
73 | function Messenger (writeMode, supress, mute) {
74 | 'use strict';
75 | writeMode = writeMode || 'stderr';
76 | supress = (!!supress || !!mute);
77 | mute = !!mute;
78 | this._print = (writeMode === 'stdout') ? console.log : console.error;
79 |
80 | this.errorExit = function (e) {
81 | if (!mute) {
82 | console.error('ERROR: ' + e.message);
83 | console.error('Run \'showdown -h\' for help');
84 | }
85 | process.exit(1);
86 | };
87 |
88 | this.okExit = function () {
89 | if (!mute) {
90 | this._print('\n');
91 | this._print('DONE!');
92 | }
93 | process.exit(0);
94 | };
95 |
96 | this.printMsg = function (msg) {
97 | if (supress || mute || !msg) {
98 | return;
99 | }
100 | this._print(msg);
101 | };
102 |
103 | this.printError = function (msg) {
104 | if (mute) {
105 | return;
106 | }
107 | console.error(msg);
108 | };
109 |
110 | }
111 |
112 | /**
113 | * Helper function to show Showdown Options
114 | */
115 | function showShowdownOptions () {
116 | 'use strict';
117 | var showdownOptions = showdown.getDefaultOptions(false);
118 | console.log('\nshowdown makehtml config options:');
119 | // show showdown options
120 | for (var sopt in showdownOptions) {
121 | if (showdownOptions.hasOwnProperty(sopt)) {
122 | console.log(' ' + sopt + ':', '[default=' + showdownOptions[sopt].defaultValue + ']',showdownOptions[sopt].describe);
123 | }
124 | }
125 | console.log('\n\nExample: showdown makehtml -c openLinksInNewWindow ghMentions ghMentionsLink="https://google.com"');
126 | }
127 |
128 | /**
129 | * Helper function to parse showdown options
130 | * @param {{}} configOptions
131 | * @param {{}} defaultOptions
132 | * @returns {{}}
133 | */
134 | function parseShowdownOptions (configOptions, defaultOptions) {
135 | 'use strict';
136 | var shOpt = defaultOptions;
137 |
138 | // first prepare passed options
139 | if (configOptions) {
140 | for (var i = 0; i < configOptions.length; ++i) {
141 | var opt = configOptions[i],
142 | key = configOptions[i],
143 | val = true;
144 | if (/=/.test(opt)) {
145 | key = opt.split('=')[0];
146 | val = opt.split('=')[1];
147 | }
148 | shOpt[key] = val;
149 | }
150 | }
151 | return shOpt;
152 | }
153 |
154 | /**
155 | * Reads stdin
156 | * @returns {string}
157 | */
158 | function readFromStdIn (encoding) {
159 | 'use strict';
160 | /*
161 | // aparently checking the size of stdin is unreliable so we just won't test
162 | var size = fs.fstatSync(process.stdin.fd).size;
163 | if (size <= 0) {
164 | throw new Error('Could not read from stdin, reason: stdin is empty');
165 | }
166 | */
167 | encoding = encoding || 'utf8';
168 | try {
169 | return fs.readFileSync(process.stdin.fd, encoding).toString();
170 | } catch (e) {
171 | throw new Error('Could not read from stdin, reason: ' + e.message);
172 | }
173 | }
174 |
175 | /**
176 | * Reads from a file
177 | * @param {string} file Filepath to dile
178 | * @param {string} encoding Encoding of the file
179 | * @returns {Buffer}
180 | */
181 | function readFromFile (file, encoding) {
182 | 'use strict';
183 | try {
184 | return fs.readFileSync(file, encoding);
185 | } catch (err) {
186 | throw new Error('Could not read from file ' + file + ', reason: ' + err.message);
187 | }
188 | }
189 |
190 | /**
191 | * Writes to stdout
192 | * @param {string} html
193 | * @returns {boolean}
194 | */
195 | function writeToStdOut (html) {
196 | 'use strict';
197 | if (!process.stdout.write(html)) {
198 | throw new Error('Could not write to StdOut');
199 | }
200 | }
201 |
202 | /**
203 | * Writes to file
204 | * @param {string} html HTML to write
205 | * @param {string} file Filepath
206 | * @param {boolean} append If the result should be appended
207 | */
208 | function writeToFile (html, file, append) {
209 | 'use strict';
210 | // If a flag is passed, it means we should append instead of overwriting.
211 | // Only works with files, obviously
212 | var write = (append) ? fs.appendFileSync : fs.writeFileSync;
213 | try {
214 | write(file, html);
215 | } catch (err) {
216 | throw new Error('Could not write to file ' + file + ', readon: ' + err.message);
217 | }
218 | }
219 |
220 | /**
221 | * makehtml command
222 | * @param {{}} options
223 | * @param {Command} cmd
224 | */
225 | function makehtmlCommand (options, cmd) {
226 | 'use strict';
227 |
228 | // show configuration options for showdown helper if configHelp was passed
229 | if (options.configHelp) {
230 | showShowdownOptions();
231 | return;
232 | }
233 |
234 | var quiet = !!(cmd.parent._optionValues.quiet),
235 | mute = !!(cmd.parent._optionValues.mute),
236 | readMode = (!options.input || options.input === '' || options.input === true) ? 'stdin' : 'file',
237 | writeMode = (!options.output || options.output === '' || options.output === true) ? 'stdout' : 'file',
238 | msgMode = (writeMode === 'file') ? 'stdout' : 'stderr',
239 | // initiate Messenger helper, can maybe be replaced with commanderjs internal stuff
240 | messenger = new Messenger(msgMode, quiet, mute),
241 | defaultOptions = showdown.getDefaultOptions(true),
242 | md, html;
243 |
244 | // deal with flavor first since config flag overrides flavor individual options
245 | if (options.flavor) {
246 | messenger.printMsg('Enabling flavor ' + options.flavor + '...');
247 | defaultOptions = showdown.getFlavorOptions(options.flavor);
248 | if (!defaultOptions) {
249 | messenger.errorExit(new Error('Flavor ' + options.flavor + ' is not recognised'));
250 | return;
251 | }
252 | messenger.printMsg('OK!');
253 | }
254 | // store config options in the options.config as an object
255 | options.config = parseShowdownOptions(options.config, defaultOptions);
256 |
257 | // print enabled options
258 | for (var o in options.config) {
259 | if (options.config.hasOwnProperty(o) && options.config[o] === true) {
260 | messenger.printMsg('Enabling option ' + o);
261 | }
262 | }
263 |
264 | // initialize the converter
265 | messenger.printMsg('\nInitializing converter...');
266 | var converter;
267 | try {
268 | converter = new showdown.Converter(options.config);
269 | } catch (e) {
270 | messenger.errorExit(e);
271 | return;
272 | }
273 | messenger.printMsg('OK!');
274 |
275 | // load extensions
276 | if (options.extensions) {
277 | messenger.printMsg('\nLoading extensions...');
278 | for (var i = 0; i < options.extensions.length; ++i) {
279 | try {
280 | messenger.printMsg(options.extensions[i]);
281 | var ext = require(options.extensions[i]);
282 | converter.addExtension(ext, options.extensions[i]);
283 | messenger.printMsg(options.extensions[i] + ' loaded...');
284 | } catch (e) {
285 | messenger.printError('ERROR: Could not load extension ' + options.extensions[i] + '. Reason:');
286 | messenger.errorExit(e);
287 | }
288 | }
289 | }
290 |
291 | messenger.printMsg('...');
292 | // read the input
293 | messenger.printMsg('Reading data from ' + readMode + '...');
294 |
295 | if (readMode === 'stdin') {
296 | try {
297 | md = readFromStdIn(options.encoding);
298 | } catch (err) {
299 | messenger.errorExit(err);
300 | return;
301 | }
302 | } else {
303 | try {
304 | md = readFromFile(options.input, options.encoding);
305 | } catch (err) {
306 | messenger.errorExit(err);
307 | return;
308 | }
309 | }
310 |
311 | // process the input
312 | messenger.printMsg('Parsing markdown...');
313 | html = converter.makeHtml(md);
314 |
315 | // write the output
316 | messenger.printMsg('Writing data to ' + writeMode + '...');
317 | if (writeMode === 'stdout') {
318 | try {
319 | writeToStdOut(html);
320 | } catch (err) {
321 | messenger.errorExit(err);
322 | return;
323 | }
324 | } else {
325 | try {
326 | writeToFile(html, options.output, options.append);
327 | } catch (err) {
328 | messenger.errorExit(err);
329 | return;
330 | }
331 | }
332 | messenger.okExit();
333 | }
334 |
--------------------------------------------------------------------------------
/src/loader.js:
--------------------------------------------------------------------------------
1 | var root = this;
2 |
3 | // AMD Loader
4 | if (typeof define === 'function' && define.amd) {
5 | define(function () {
6 | 'use strict';
7 | return showdown;
8 | });
9 |
10 | // CommonJS/nodeJS Loader
11 | } else if (typeof module !== 'undefined' && module.exports) {
12 | module.exports = showdown;
13 |
14 | // Regular Browser loader
15 | } else {
16 | root.showdown = showdown;
17 | }
18 |
--------------------------------------------------------------------------------
/src/options.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Tivie on 13-07-2015.
3 | */
4 |
5 | function getDefaultOpts (simple) {
6 | 'use strict';
7 |
8 | var defaultOptions = {
9 | omitExtraWLInCodeBlocks: {
10 | defaultValue: false,
11 | describe: 'Omit the default extra whiteline added to code blocks',
12 | type: 'boolean'
13 | },
14 | noHeaderId: {
15 | defaultValue: false,
16 | describe: 'Turn on/off generated header id',
17 | type: 'boolean'
18 | },
19 | prefixHeaderId: {
20 | defaultValue: false,
21 | describe: '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',
22 | type: 'string'
23 | },
24 | rawPrefixHeaderId: {
25 | defaultValue: false,
26 | describe: 'Setting this option to true will prevent showdown from modifying the prefix. This might result in malformed IDs (if, for instance, the " char is used in the prefix)',
27 | type: 'boolean'
28 | },
29 | ghCompatibleHeaderId: {
30 | defaultValue: false,
31 | describe: 'Generate header ids compatible with github style (spaces are replaced with dashes, a bunch of non alphanumeric chars are removed)',
32 | type: 'boolean'
33 | },
34 | rawHeaderId: {
35 | defaultValue: false,
36 | describe: 'Remove only spaces, \' and " from generated header ids (including prefixes), replacing them with dashes (-). WARNING: This might result in malformed ids',
37 | type: 'boolean'
38 | },
39 | headerLevelStart: {
40 | defaultValue: false,
41 | describe: 'The header blocks level start',
42 | type: 'integer'
43 | },
44 | parseImgDimensions: {
45 | defaultValue: false,
46 | describe: 'Turn on/off image dimension parsing',
47 | type: 'boolean'
48 | },
49 | simplifiedAutoLink: {
50 | defaultValue: false,
51 | describe: 'Turn on/off GFM autolink style',
52 | type: 'boolean'
53 | },
54 | literalMidWordUnderscores: {
55 | defaultValue: false,
56 | describe: 'Parse midword underscores as literal underscores',
57 | type: 'boolean'
58 | },
59 | literalMidWordAsterisks: {
60 | defaultValue: false,
61 | describe: 'Parse midword asterisks as literal asterisks',
62 | type: 'boolean'
63 | },
64 | strikethrough: {
65 | defaultValue: false,
66 | describe: 'Turn on/off strikethrough support',
67 | type: 'boolean'
68 | },
69 | tables: {
70 | defaultValue: false,
71 | describe: 'Turn on/off tables support',
72 | type: 'boolean'
73 | },
74 | tablesHeaderId: {
75 | defaultValue: false,
76 | describe: 'Add an id to table headers',
77 | type: 'boolean'
78 | },
79 | ghCodeBlocks: {
80 | defaultValue: true,
81 | describe: 'Turn on/off GFM fenced code blocks support',
82 | type: 'boolean'
83 | },
84 | tasklists: {
85 | defaultValue: false,
86 | describe: 'Turn on/off GFM tasklist support',
87 | type: 'boolean'
88 | },
89 | smoothLivePreview: {
90 | defaultValue: false,
91 | describe: 'Prevents weird effects in live previews due to incomplete input',
92 | type: 'boolean'
93 | },
94 | smartIndentationFix: {
95 | defaultValue: false,
96 | describe: 'Tries to smartly fix indentation in es6 strings',
97 | type: 'boolean'
98 | },
99 | disableForced4SpacesIndentedSublists: {
100 | defaultValue: false,
101 | describe: 'Disables the requirement of indenting nested sublists by 4 spaces',
102 | type: 'boolean'
103 | },
104 | simpleLineBreaks: {
105 | defaultValue: false,
106 | describe: 'Parses simple line breaks as
(GFM Style)',
107 | type: 'boolean'
108 | },
109 | requireSpaceBeforeHeadingText: {
110 | defaultValue: false,
111 | describe: 'Makes adding a space between `#` and the header text mandatory (GFM Style)',
112 | type: 'boolean'
113 | },
114 | ghMentions: {
115 | defaultValue: false,
116 | describe: 'Enables github @mentions',
117 | type: 'boolean'
118 | },
119 | ghMentionsLink: {
120 | defaultValue: 'https://github.com/{u}',
121 | describe: 'Changes the link generated by @mentions. Only applies if ghMentions option is enabled.',
122 | type: 'string'
123 | },
124 | encodeEmails: {
125 | defaultValue: true,
126 | describe: 'Encode e-mail addresses through the use of Character Entities, transforming ASCII e-mail addresses into its equivalent decimal entities',
127 | type: 'boolean'
128 | },
129 | openLinksInNewWindow: {
130 | defaultValue: false,
131 | describe: 'Open all links in new windows',
132 | type: 'boolean'
133 | },
134 | backslashEscapesHTMLTags: {
135 | defaultValue: false,
136 | describe: 'Support for HTML Tag escaping. ex: \foo\',
137 | type: 'boolean'
138 | },
139 | emoji: {
140 | defaultValue: false,
141 | describe: 'Enable emoji support. Ex: `this is a :smile: emoji`',
142 | type: 'boolean'
143 | },
144 | underline: {
145 | defaultValue: false,
146 | describe: 'Enable support for underline. Syntax is double or triple underscores: `__underline word__`. With this option enabled, underscores no longer parses into `` and ``',
147 | type: 'boolean'
148 | },
149 | ellipsis: {
150 | defaultValue: true,
151 | describe: 'Replaces three dots with the ellipsis unicode character',
152 | type: 'boolean'
153 | },
154 | completeHTMLDocument: {
155 | defaultValue: false,
156 | describe: 'Outputs a complete html document, including ``, `` and `` tags',
157 | type: 'boolean'
158 | },
159 | metadata: {
160 | defaultValue: false,
161 | describe: 'Enable support for document metadata (defined at the top of the document between `«««` and `»»»` or between `---` and `---`).',
162 | type: 'boolean'
163 | },
164 | splitAdjacentBlockquotes: {
165 | defaultValue: false,
166 | describe: 'Split adjacent blockquote blocks',
167 | type: 'boolean'
168 | },
169 | moreStyling: {
170 | defaultValue: false,
171 | describe: 'Adds some useful styling css classes in the generated html',
172 | type: 'boolean'
173 | },
174 | relativePathBaseUrl: {
175 | defaultValue: false,
176 | describe: 'Prepends a base URL to relative paths',
177 | type: 'string'
178 | },
179 | };
180 | if (simple === false) {
181 | return JSON.parse(JSON.stringify(defaultOptions));
182 | }
183 | var ret = {};
184 | for (var opt in defaultOptions) {
185 | if (defaultOptions.hasOwnProperty(opt)) {
186 | ret[opt] = defaultOptions[opt].defaultValue;
187 | }
188 | }
189 | return ret;
190 | }
191 |
192 | function allOptionsOn () {
193 | 'use strict';
194 | var options = getDefaultOpts(true),
195 | ret = {};
196 | for (var opt in options) {
197 | if (options.hasOwnProperty(opt)) {
198 | ret[opt] = true;
199 | }
200 | }
201 | return ret;
202 | }
203 |
--------------------------------------------------------------------------------
/src/showdown.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Tivie on 06-01-2015.
3 | */
4 | // Private properties
5 | var showdown = {},
6 | parsers = {},
7 | extensions = {},
8 | globalOptions = getDefaultOpts(true),
9 | setFlavor = 'vanilla',
10 | flavor = {
11 | github: {
12 | omitExtraWLInCodeBlocks: true,
13 | simplifiedAutoLink: true,
14 | literalMidWordUnderscores: true,
15 | strikethrough: true,
16 | tables: true,
17 | tablesHeaderId: true,
18 | ghCodeBlocks: true,
19 | tasklists: true,
20 | disableForced4SpacesIndentedSublists: true,
21 | simpleLineBreaks: true,
22 | requireSpaceBeforeHeadingText: true,
23 | ghCompatibleHeaderId: true,
24 | ghMentions: true,
25 | backslashEscapesHTMLTags: true,
26 | emoji: true,
27 | splitAdjacentBlockquotes: true
28 | },
29 | original: {
30 | noHeaderId: true,
31 | ghCodeBlocks: false
32 | },
33 | ghost: {
34 | omitExtraWLInCodeBlocks: true,
35 | parseImgDimensions: true,
36 | simplifiedAutoLink: true,
37 | literalMidWordUnderscores: true,
38 | strikethrough: true,
39 | tables: true,
40 | tablesHeaderId: true,
41 | ghCodeBlocks: true,
42 | tasklists: true,
43 | smoothLivePreview: true,
44 | simpleLineBreaks: true,
45 | requireSpaceBeforeHeadingText: true,
46 | ghMentions: false,
47 | encodeEmails: true
48 | },
49 | vanilla: getDefaultOpts(true),
50 | allOn: allOptionsOn()
51 | };
52 |
53 | /**
54 | * helper namespace
55 | * @type {{}}
56 | */
57 | showdown.helper = {};
58 |
59 | /**
60 | * TODO LEGACY SUPPORT CODE
61 | * @type {{}}
62 | */
63 | showdown.extensions = {};
64 |
65 | /**
66 | * Set a global option
67 | * @static
68 | * @param {string} key
69 | * @param {*} value
70 | * @returns {showdown}
71 | */
72 | showdown.setOption = function (key, value) {
73 | 'use strict';
74 | globalOptions[key] = value;
75 | return this;
76 | };
77 |
78 | /**
79 | * Get a global option
80 | * @static
81 | * @param {string} key
82 | * @returns {*}
83 | */
84 | showdown.getOption = function (key) {
85 | 'use strict';
86 | return globalOptions[key];
87 | };
88 |
89 | /**
90 | * Get the global options
91 | * @static
92 | * @returns {{}}
93 | */
94 | showdown.getOptions = function () {
95 | 'use strict';
96 | return globalOptions;
97 | };
98 |
99 | /**
100 | * Reset global options to the default values
101 | * @static
102 | */
103 | showdown.resetOptions = function () {
104 | 'use strict';
105 | globalOptions = getDefaultOpts(true);
106 | };
107 |
108 | /**
109 | * Set the flavor showdown should use as default
110 | * @param {string} name
111 | */
112 | showdown.setFlavor = function (name) {
113 | 'use strict';
114 | if (!flavor.hasOwnProperty(name)) {
115 | throw Error(name + ' flavor was not found');
116 | }
117 | showdown.resetOptions();
118 | var preset = flavor[name];
119 | setFlavor = name;
120 | for (var option in preset) {
121 | if (preset.hasOwnProperty(option)) {
122 | globalOptions[option] = preset[option];
123 | }
124 | }
125 | };
126 |
127 | /**
128 | * Get the currently set flavor
129 | * @returns {string}
130 | */
131 | showdown.getFlavor = function () {
132 | 'use strict';
133 | return setFlavor;
134 | };
135 |
136 | /**
137 | * Get the options of a specified flavor. Returns undefined if the flavor was not found
138 | * @param {string} name Name of the flavor
139 | * @returns {{}|undefined}
140 | */
141 | showdown.getFlavorOptions = function (name) {
142 | 'use strict';
143 | if (flavor.hasOwnProperty(name)) {
144 | return flavor[name];
145 | }
146 | };
147 |
148 | /**
149 | * Get the default options
150 | * @static
151 | * @param {boolean} [simple=true]
152 | * @returns {{}}
153 | */
154 | showdown.getDefaultOptions = function (simple) {
155 | 'use strict';
156 | return getDefaultOpts(simple);
157 | };
158 |
159 | /**
160 | * Get or set a subParser
161 | *
162 | * subParser(name) - Get a registered subParser
163 | * subParser(name, func) - Register a subParser
164 | * @static
165 | * @param {string} name
166 | * @param {function} [func]
167 | * @returns {*}
168 | */
169 | showdown.subParser = function (name, func) {
170 | 'use strict';
171 | if (showdown.helper.isString(name)) {
172 | if (typeof func !== 'undefined') {
173 | parsers[name] = func;
174 | } else {
175 | if (parsers.hasOwnProperty(name)) {
176 | return parsers[name];
177 | } else {
178 | throw Error('SubParser named ' + name + ' not registered!');
179 | }
180 | }
181 | } else {
182 | throw Error('showdown.subParser function first argument must be a string (the name of the subparser)');
183 | }
184 | };
185 |
186 | /**
187 | * Gets or registers an extension
188 | * @static
189 | * @param {string} name
190 | * @param {object|object[]|function=} ext
191 | * @returns {*}
192 | */
193 | showdown.extension = function (name, ext) {
194 | 'use strict';
195 |
196 | if (!showdown.helper.isString(name)) {
197 | throw Error('Extension \'name\' must be a string');
198 | }
199 |
200 | name = showdown.helper.stdExtName(name);
201 |
202 | // Getter
203 | if (showdown.helper.isUndefined(ext)) {
204 | if (!extensions.hasOwnProperty(name)) {
205 | throw Error('Extension named ' + name + ' is not registered!');
206 | }
207 | return extensions[name];
208 |
209 | // Setter
210 | } else {
211 | // Expand extension if it's wrapped in a function
212 | if (typeof ext === 'function') {
213 | ext = ext();
214 | }
215 |
216 | // Ensure extension is an array
217 | if (!showdown.helper.isArray(ext)) {
218 | ext = [ext];
219 | }
220 |
221 | var validExtension = validate(ext, name);
222 |
223 | if (validExtension.valid) {
224 | extensions[name] = ext;
225 | } else {
226 | throw Error(validExtension.error);
227 | }
228 | }
229 | };
230 |
231 | /**
232 | * Gets all extensions registered
233 | * @returns {{}}
234 | */
235 | showdown.getAllExtensions = function () {
236 | 'use strict';
237 | return extensions;
238 | };
239 |
240 | /**
241 | * Remove an extension
242 | * @param {string} name
243 | */
244 | showdown.removeExtension = function (name) {
245 | 'use strict';
246 | delete extensions[name];
247 | };
248 |
249 | /**
250 | * Removes all extensions
251 | */
252 | showdown.resetExtensions = function () {
253 | 'use strict';
254 | extensions = {};
255 | };
256 |
257 | /**
258 | * Validate extension
259 | * @param {array} extension
260 | * @param {string} name
261 | * @returns {{valid: boolean, error: string}}
262 | */
263 | function validate (extension, name) {
264 | 'use strict';
265 |
266 | var errMsg = (name) ? 'Error in ' + name + ' extension->' : 'Error in unnamed extension',
267 | ret = {
268 | valid: true,
269 | error: ''
270 | };
271 |
272 | if (!showdown.helper.isArray(extension)) {
273 | extension = [extension];
274 | }
275 |
276 | for (var i = 0; i < extension.length; ++i) {
277 | var baseMsg = errMsg + ' sub-extension ' + i + ': ',
278 | ext = extension[i];
279 | if (typeof ext !== 'object') {
280 | ret.valid = false;
281 | ret.error = baseMsg + 'must be an object, but ' + typeof ext + ' given';
282 | return ret;
283 | }
284 |
285 | if (!showdown.helper.isString(ext.type)) {
286 | ret.valid = false;
287 | ret.error = baseMsg + 'property "type" must be a string, but ' + typeof ext.type + ' given';
288 | return ret;
289 | }
290 |
291 | var type = ext.type = ext.type.toLowerCase();
292 |
293 | // normalize extension type
294 | if (type === 'language') {
295 | type = ext.type = 'lang';
296 | }
297 |
298 | if (type === 'html') {
299 | type = ext.type = 'output';
300 | }
301 |
302 | if (type !== 'lang' && type !== 'output' && type !== 'listener') {
303 | ret.valid = false;
304 | ret.error = baseMsg + 'type ' + type + ' is not recognized. Valid values: "lang/language", "output/html" or "listener"';
305 | return ret;
306 | }
307 |
308 | if (type === 'listener') {
309 | if (showdown.helper.isUndefined(ext.listeners)) {
310 | ret.valid = false;
311 | ret.error = baseMsg + '. Extensions of type "listener" must have a property called "listeners"';
312 | return ret;
313 | }
314 | } else {
315 | if (showdown.helper.isUndefined(ext.filter) && showdown.helper.isUndefined(ext.regex)) {
316 | ret.valid = false;
317 | ret.error = baseMsg + type + ' extensions must define either a "regex" property or a "filter" method';
318 | return ret;
319 | }
320 | }
321 |
322 | if (ext.listeners) {
323 | if (typeof ext.listeners !== 'object') {
324 | ret.valid = false;
325 | ret.error = baseMsg + '"listeners" property must be an object but ' + typeof ext.listeners + ' given';
326 | return ret;
327 | }
328 | for (var ln in ext.listeners) {
329 | if (ext.listeners.hasOwnProperty(ln)) {
330 | if (typeof ext.listeners[ln] !== 'function') {
331 | ret.valid = false;
332 | ret.error = baseMsg + '"listeners" property must be an hash of [event name]: [callback]. listeners.' + ln +
333 | ' must be a function but ' + typeof ext.listeners[ln] + ' given';
334 | return ret;
335 | }
336 | }
337 | }
338 | }
339 |
340 | if (ext.filter) {
341 | if (typeof ext.filter !== 'function') {
342 | ret.valid = false;
343 | ret.error = baseMsg + '"filter" must be a function, but ' + typeof ext.filter + ' given';
344 | return ret;
345 | }
346 | } else if (ext.regex) {
347 | if (showdown.helper.isString(ext.regex)) {
348 | ext.regex = new RegExp(ext.regex, 'g');
349 | }
350 | if (!(ext.regex instanceof RegExp)) {
351 | ret.valid = false;
352 | ret.error = baseMsg + '"regex" property must either be a string or a RegExp object, but ' + typeof ext.regex + ' given';
353 | return ret;
354 | }
355 | if (showdown.helper.isUndefined(ext.replace)) {
356 | ret.valid = false;
357 | ret.error = baseMsg + '"regex" extensions must implement a replace string or function';
358 | return ret;
359 | }
360 | }
361 | }
362 | return ret;
363 | }
364 |
365 | /**
366 | * Validate extension
367 | * @param {object} ext
368 | * @returns {boolean}
369 | */
370 | showdown.validateExtension = function (ext) {
371 | 'use strict';
372 |
373 | var validateExtension = validate(ext, null);
374 | if (!validateExtension.valid) {
375 | console.warn(validateExtension.error);
376 | return false;
377 | }
378 | return true;
379 | };
380 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/blockGamut.js:
--------------------------------------------------------------------------------
1 | /**
2 | * These are all the transformations that form block-level
3 | * tags like paragraphs, headers, and list items.
4 | */
5 | showdown.subParser('makehtml.blockGamut', function (text, options, globals) {
6 | 'use strict';
7 |
8 | text = globals.converter._dispatch('makehtml.blockGamut.before', text, options, globals).getText();
9 |
10 | // we parse blockquotes first so that we can have headings and hrs
11 | // inside blockquotes
12 | text = showdown.subParser('makehtml.blockQuotes')(text, options, globals);
13 | text = showdown.subParser('makehtml.headers')(text, options, globals);
14 |
15 | // Do Horizontal Rules:
16 | text = showdown.subParser('makehtml.horizontalRule')(text, options, globals);
17 |
18 | text = showdown.subParser('makehtml.lists')(text, options, globals);
19 | text = showdown.subParser('makehtml.codeBlocks')(text, options, globals);
20 | text = showdown.subParser('makehtml.tables')(text, options, globals);
21 |
22 | // We already ran _HashHTMLBlocks() before, in Markdown(), but that
23 | // was to escape raw HTML in the original Markdown source. This time,
24 | // we're escaping the markup we've just created, so that we don't wrap
25 | // tags around block-level tags.
26 | text = showdown.subParser('makehtml.hashHTMLBlocks')(text, options, globals);
27 | text = showdown.subParser('makehtml.paragraphs')(text, options, globals);
28 |
29 | text = globals.converter._dispatch('makehtml.blockGamut.after', text, options, globals).getText();
30 |
31 | return text;
32 | });
33 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/blockQuotes.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makehtml.blockQuotes', function (text, options, globals) {
2 | 'use strict';
3 |
4 | text = globals.converter._dispatch('makehtml.blockQuotes.before', text, options, globals).getText();
5 |
6 | // add a couple extra lines after the text and endtext mark
7 | text = text + '\n\n';
8 |
9 | var rgx = /(^ {0,3}>[ \t]?.+\n(.+\n)*\n*)+/gm;
10 |
11 | if (options.splitAdjacentBlockquotes) {
12 | rgx = /^ {0,3}>[\s\S]*?(?:\n\n)/gm;
13 | }
14 |
15 | text = text.replace(rgx, function (bq) {
16 | // attacklab: hack around Konqueror 3.5.4 bug:
17 | // "----------bug".replace(/^-/g,"") == "bug"
18 | bq = bq.replace(/^[ \t]*>[ \t]?/gm, ''); // trim one level of quoting
19 |
20 | // attacklab: clean up hack
21 | bq = bq.replace(/¨0/g, '');
22 |
23 | bq = bq.replace(/^[ \t]+$/gm, ''); // trim whitespace-only lines
24 | bq = showdown.subParser('makehtml.githubCodeBlocks')(bq, options, globals);
25 | bq = showdown.subParser('makehtml.blockGamut')(bq, options, globals); // recurse
26 |
27 | bq = bq.replace(/(^|\n)/g, '$1 ');
28 | // These leading spaces screw with
content, so we need to fix that:
29 | bq = bq.replace(/(\s*[^\r]+?<\/pre>)/gm, function (wholeMatch, m1) {
30 | var pre = m1;
31 | // attacklab: hack around Konqueror 3.5.4 bug:
32 | pre = pre.replace(/^ /mg, '¨0');
33 | pre = pre.replace(/¨0/g, '');
34 | return pre;
35 | });
36 |
37 | return showdown.subParser('makehtml.hashBlock')('\n' + bq + '\n
', options, globals);
38 | });
39 |
40 | text = globals.converter._dispatch('makehtml.blockQuotes.after', text, options, globals).getText();
41 | return text;
42 | });
43 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/codeBlocks.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Process Markdown `` blocks.
3 | */
4 | showdown.subParser('makehtml.codeBlocks', function (text, options, globals) {
5 | 'use strict';
6 |
7 | text = globals.converter._dispatch('makehtml.codeBlocks.before', text, options, globals).getText();
8 |
9 | // sentinel workarounds for lack of \A and \Z, safari\khtml bug
10 | text += '¨0';
11 |
12 | var pattern = /(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=¨0))/g;
13 | text = text.replace(pattern, function (wholeMatch, m1, m2) {
14 | var codeblock = m1,
15 | nextChar = m2,
16 | end = '\n';
17 |
18 | codeblock = showdown.subParser('makehtml.outdent')(codeblock, options, globals);
19 | codeblock = showdown.subParser('makehtml.encodeCode')(codeblock, options, globals);
20 | codeblock = showdown.subParser('makehtml.detab')(codeblock, options, globals);
21 | codeblock = codeblock.replace(/^\n+/g, ''); // trim leading newlines
22 | codeblock = codeblock.replace(/\n+$/g, ''); // trim trailing newlines
23 |
24 | if (options.omitExtraWLInCodeBlocks) {
25 | end = '';
26 | }
27 |
28 | codeblock = '' + codeblock + end + '
';
29 |
30 | return showdown.subParser('makehtml.hashBlock')(codeblock, options, globals) + nextChar;
31 | });
32 |
33 | // strip sentinel
34 | text = text.replace(/¨0/, '');
35 |
36 | text = globals.converter._dispatch('makehtml.codeBlocks.after', text, options, globals).getText();
37 | return text;
38 | });
39 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/codeSpans.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * * Backtick quotes are used for
spans.
4 | *
5 | * * You can use multiple backticks as the delimiters if you want to
6 | * include literal backticks in the code span. So, this input:
7 | *
8 | * Just type ``foo `bar` baz`` at the prompt.
9 | *
10 | * Will translate to:
11 | *
12 | * Just type foo `bar` baz
at the prompt.
13 | *
14 | * There's no arbitrary limit to the number of backticks you
15 | * can use as delimters. If you need three consecutive backticks
16 | * in your code, use four for delimiters, etc.
17 | *
18 | * * You can use spaces to get literal backticks at the edges:
19 | *
20 | * ... type `` `bar` `` ...
21 | *
22 | * Turns to:
23 | *
24 | * ... type `bar`
...
25 | */
26 | showdown.subParser('makehtml.codeSpans', function (text, options, globals) {
27 | 'use strict';
28 |
29 | text = globals.converter._dispatch('makehtml.codeSpans.before', text, options, globals).getText();
30 |
31 | if (typeof (text) === 'undefined') {
32 | text = '';
33 | }
34 | text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
35 | function (wholeMatch, m1, m2, m3) {
36 | var c = m3;
37 | c = c.replace(/^([ \t]*)/g, ''); // leading whitespace
38 | c = c.replace(/[ \t]*$/g, ''); // trailing whitespace
39 | c = showdown.subParser('makehtml.encodeCode')(c, options, globals);
40 | c = m1 + '' + c + '
';
41 | c = showdown.subParser('makehtml.hashHTMLSpans')(c, options, globals);
42 | return c;
43 | }
44 | );
45 |
46 | text = globals.converter._dispatch('makehtml.codeSpans.after', text, options, globals).getText();
47 | return text;
48 | });
49 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/completeHTMLDocument.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Create a full HTML document from the processed markdown
3 | */
4 | showdown.subParser('makehtml.completeHTMLDocument', function (text, options, globals) {
5 | 'use strict';
6 |
7 | if (!options.completeHTMLDocument) {
8 | return text;
9 | }
10 |
11 | text = globals.converter._dispatch('makehtml.completeHTMLDocument.before', text, options, globals).getText();
12 |
13 | var doctype = 'html',
14 | doctypeParsed = '\n',
15 | title = '',
16 | charset = '\n',
17 | lang = '',
18 | metadata = '';
19 |
20 | if (typeof globals.metadata.parsed.doctype !== 'undefined') {
21 | doctypeParsed = '\n';
22 | doctype = globals.metadata.parsed.doctype.toString().toLowerCase();
23 | if (doctype === 'html' || doctype === 'html5') {
24 | charset = '';
25 | }
26 | }
27 |
28 | for (var meta in globals.metadata.parsed) {
29 | if (globals.metadata.parsed.hasOwnProperty(meta)) {
30 | switch (meta.toLowerCase()) {
31 | case 'doctype':
32 | break;
33 |
34 | case 'title':
35 | title = '' + globals.metadata.parsed.title + ' \n';
36 | break;
37 |
38 | case 'charset':
39 | if (doctype === 'html' || doctype === 'html5') {
40 | charset = '\n';
41 | } else {
42 | charset = '\n';
43 | }
44 | break;
45 |
46 | case 'language':
47 | case 'lang':
48 | lang = ' lang="' + globals.metadata.parsed[meta] + '"';
49 | metadata += '\n';
50 | break;
51 |
52 | default:
53 | metadata += '\n';
54 | }
55 | }
56 | }
57 |
58 | text = doctypeParsed + '\n\n' + title + charset + metadata + '\n\n' + text.trim() + '\n\n';
59 |
60 | text = globals.converter._dispatch('makehtml.completeHTMLDocument.after', text, options, globals).getText();
61 | return text;
62 | });
63 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/detab.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Convert all tabs to spaces
3 | */
4 | showdown.subParser('makehtml.detab', function (text, options, globals) {
5 | 'use strict';
6 | text = globals.converter._dispatch('makehtml.detab.before', text, options, globals).getText();
7 |
8 | // expand first n-1 tabs
9 | text = text.replace(/\t(?=\t)/g, ' '); // g_tab_width
10 |
11 | // replace the nth with two sentinels
12 | text = text.replace(/\t/g, '¨A¨B');
13 |
14 | // use the sentinel to anchor our regex so it doesn't explode
15 | text = text.replace(/¨B(.+?)¨A/g, function (wholeMatch, m1) {
16 | var leadingText = m1,
17 | numSpaces = 4 - leadingText.length % 4; // g_tab_width
18 |
19 | // there *must* be a better way to do this:
20 | for (var i = 0; i < numSpaces; i++) {
21 | leadingText += ' ';
22 | }
23 |
24 | return leadingText;
25 | });
26 |
27 | // clean up sentinels
28 | text = text.replace(/¨A/g, ' '); // g_tab_width
29 | text = text.replace(/¨B/g, '');
30 |
31 | text = globals.converter._dispatch('makehtml.detab.after', text, options, globals).getText();
32 | return text;
33 | });
34 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/ellipsis.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makehtml.ellipsis', function (text, options, globals) {
2 | 'use strict';
3 |
4 | if (!options.ellipsis) {
5 | return text;
6 | }
7 |
8 | text = globals.converter._dispatch('makehtml.ellipsis.before', text, options, globals).getText();
9 |
10 | text = text.replace(/\.\.\./g, '…');
11 |
12 | text = globals.converter._dispatch('makehtml.ellipsis.after', text, options, globals).getText();
13 |
14 | return text;
15 | });
16 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/emoji.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Turn emoji codes into emojis
3 | *
4 | * List of supported emojis: https://github.com/showdownjs/showdown/wiki/Emojis
5 | */
6 | showdown.subParser('makehtml.emoji', function (text, options, globals) {
7 | 'use strict';
8 |
9 | if (!options.emoji) {
10 | return text;
11 | }
12 |
13 | text = globals.converter._dispatch('makehtml.emoji.before', text, options, globals).getText();
14 |
15 | var emojiRgx = /:([\S]+?):/g;
16 |
17 | text = text.replace(emojiRgx, function (wm, emojiCode) {
18 | if (showdown.helper.emojis.hasOwnProperty(emojiCode)) {
19 | return showdown.helper.emojis[emojiCode];
20 | }
21 | return wm;
22 | });
23 |
24 | text = globals.converter._dispatch('makehtml.emoji.after', text, options, globals).getText();
25 |
26 | return text;
27 | });
28 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/encodeAmpsAndAngles.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Smart processing for ampersands and angle brackets that need to be encoded.
3 | */
4 | showdown.subParser('makehtml.encodeAmpsAndAngles', function (text, options, globals) {
5 | 'use strict';
6 | text = globals.converter._dispatch('makehtml.encodeAmpsAndAngles.before', text, options, globals).getText();
7 |
8 | // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
9 | // http://bumppo.net/projects/amputator/
10 | text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, '&');
11 |
12 | // Encode naked <'s
13 | text = text.replace(/<(?![a-z\/?$!])/gi, '<');
14 |
15 | // Encode <
16 | text = text.replace(/
19 | text = text.replace(/>/g, '>');
20 |
21 | text = globals.converter._dispatch('makehtml.encodeAmpsAndAngles.after', text, options, globals).getText();
22 | return text;
23 | });
24 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/encodeBackslashEscapes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns the string, with after processing the following backslash escape sequences.
3 | *
4 | * attacklab: The polite way to do this is with the new escapeCharacters() function:
5 | *
6 | * text = escapeCharacters(text,"\\",true);
7 | * text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
8 | *
9 | * ...but we're sidestepping its use of the (slow) RegExp constructor
10 | * as an optimization for Firefox. This function gets called a LOT.
11 | */
12 | showdown.subParser('makehtml.encodeBackslashEscapes', function (text, options, globals) {
13 | 'use strict';
14 | text = globals.converter._dispatch('makehtml.encodeBackslashEscapes.before', text, options, globals).getText();
15 |
16 | text = text.replace(/\\(\\)/g, showdown.helper.escapeCharactersCallback);
17 | text = text.replace(/\\([`*_{}\[\]()>#+.!~=|:-])/g, showdown.helper.escapeCharactersCallback);
18 |
19 | text = globals.converter._dispatch('makehtml.encodeBackslashEscapes.after', text, options, globals).getText();
20 | return text;
21 | });
22 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/encodeCode.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Encode/escape certain characters inside Markdown code runs.
3 | * The point is that in code, these characters are literals,
4 | * and lose their special Markdown meanings.
5 | */
6 | showdown.subParser('makehtml.encodeCode', function (text, options, globals) {
7 | 'use strict';
8 |
9 | text = globals.converter._dispatch('makehtml.encodeCode.before', text, options, globals).getText();
10 |
11 | // Encode all ampersands; HTML entities are not
12 | // entities within a Markdown code span.
13 | text = text
14 | .replace(/&/g, '&')
15 | // Do the angle bracket song and dance:
16 | .replace(//g, '>')
18 | // Now, escape characters that are magic in Markdown:
19 | .replace(/([*_{}\[\]\\=~-])/g, showdown.helper.escapeCharactersCallback);
20 |
21 | text = globals.converter._dispatch('makehtml.encodeCode.after', text, options, globals).getText();
22 | return text;
23 | });
24 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/escapeSpecialCharsWithinTagAttributes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Within tags -- meaning between < and > -- encode [\ ` * _ ~ =] so they
3 | * don't conflict with their use in Markdown for code, italics and strong.
4 | */
5 | showdown.subParser('makehtml.escapeSpecialCharsWithinTagAttributes', function (text, options, globals) {
6 | 'use strict';
7 | text = globals.converter._dispatch('makehtml.escapeSpecialCharsWithinTagAttributes.before', text, options, globals).getText();
8 |
9 | // Build a regex to find HTML tags.
10 | var tags = /<\/?[a-z\d_:-]+(?:[\s]+[\s\S]+?)?>/gi,
11 | comments = /-]|-[^>])(?:[^-]|-[^-])*)--)>/gi;
12 |
13 | text = text.replace(tags, function (wholeMatch) {
14 | return wholeMatch
15 | .replace(/(.)<\/?code>(?=.)/g, '$1`')
16 | .replace(/([\\`*_~=|])/g, showdown.helper.escapeCharactersCallback);
17 | });
18 |
19 | text = text.replace(comments, function (wholeMatch) {
20 | return wholeMatch
21 | .replace(/([\\`*_~=|])/g, showdown.helper.escapeCharactersCallback);
22 | });
23 |
24 | text = globals.converter._dispatch('makehtml.escapeSpecialCharsWithinTagAttributes.after', text, options, globals).getText();
25 | return text;
26 | });
27 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/githubCodeBlocks.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Handle github codeblocks prior to running HashHTML so that
3 | * HTML contained within the codeblock gets escaped properly
4 | * Example:
5 | * ```ruby
6 | * def hello_world(x)
7 | * puts "Hello, #{x}"
8 | * end
9 | * ```
10 | */
11 | showdown.subParser('makehtml.githubCodeBlocks', function (text, options, globals) {
12 | 'use strict';
13 |
14 | // early exit if option is not enabled
15 | if (!options.ghCodeBlocks) {
16 | return text;
17 | }
18 |
19 | text = globals.converter._dispatch('makehtml.githubCodeBlocks.before', text, options, globals).getText();
20 |
21 | text += '¨0';
22 |
23 | text = text.replace(/(?:^|\n) {0,3}(```+|~~~+) *([^\n\t`~]*)\n([\s\S]*?)\n {0,3}\1/g, function (wholeMatch, delim, language, codeblock) {
24 | var end = (options.omitExtraWLInCodeBlocks) ? '' : '\n';
25 |
26 | // if the language has spaces followed by some other chars, according to the spec we should just ignore everything
27 | // after the first space
28 | language = language.trim().split(' ')[0];
29 |
30 | // First parse the github code block
31 | codeblock = showdown.subParser('makehtml.encodeCode')(codeblock, options, globals);
32 | codeblock = showdown.subParser('makehtml.detab')(codeblock, options, globals);
33 | codeblock = codeblock.replace(/^\n+/g, ''); // trim leading newlines
34 | codeblock = codeblock.replace(/\n+$/g, ''); // trim trailing whitespace
35 |
36 | codeblock = '' + codeblock + end + '
';
37 |
38 | codeblock = showdown.subParser('makehtml.hashBlock')(codeblock, options, globals);
39 |
40 | // Since GHCodeblocks can be false positives, we need to
41 | // store the primitive text and the parsed text in a global var,
42 | // and then return a token
43 | return '\n\n¨G' + (globals.ghCodeBlocks.push({text: wholeMatch, codeblock: codeblock}) - 1) + 'G\n\n';
44 | });
45 |
46 | // attacklab: strip sentinel
47 | text = text.replace(/¨0/, '');
48 |
49 | return globals.converter._dispatch('makehtml.githubCodeBlocks.after', text, options, globals).getText();
50 | });
51 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/hashBlock.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makehtml.hashBlock', function (text, options, globals) {
2 | 'use strict';
3 | text = globals.converter._dispatch('makehtml.hashBlock.before', text, options, globals).getText();
4 | text = text.replace(/(^\n+|\n+$)/g, '');
5 | text = '\n\n¨K' + (globals.gHtmlBlocks.push(text) - 1) + 'K\n\n';
6 | text = globals.converter._dispatch('makehtml.hashBlock.after', text, options, globals).getText();
7 | return text;
8 | });
9 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/hashCodeTags.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Hash and escape elements that should not be parsed as markdown
3 | */
4 | showdown.subParser('makehtml.hashCodeTags', function (text, options, globals) {
5 | 'use strict';
6 | text = globals.converter._dispatch('makehtml.hashCodeTags.before', text, options, globals).getText();
7 |
8 | var repFunc = function (wholeMatch, match, left, right) {
9 | var codeblock = left + showdown.subParser('makehtml.encodeCode')(match, options, globals) + right;
10 | return '¨C' + (globals.gHtmlSpans.push(codeblock) - 1) + 'C';
11 | };
12 |
13 | // Hash naked
14 | text = showdown.helper.replaceRecursiveRegExp(text, repFunc, ']*>', '
', 'gim');
15 |
16 | text = globals.converter._dispatch('makehtml.hashCodeTags.after', text, options, globals).getText();
17 | return text;
18 | });
19 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/hashElement.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makehtml.hashElement', function (text, options, globals) {
2 | 'use strict';
3 |
4 | return function (wholeMatch, m1) {
5 | var blockText = m1;
6 |
7 | // Undo double lines
8 | blockText = blockText.replace(/\n\n/g, '\n');
9 | blockText = blockText.replace(/^\n/, '');
10 |
11 | // strip trailing blank lines
12 | blockText = blockText.replace(/\n+$/g, '');
13 |
14 | // Replace the element text with a marker ("¨KxK" where x is its key)
15 | blockText = '\n\n¨K' + (globals.gHtmlBlocks.push(blockText) - 1) + 'K\n\n';
16 |
17 | return blockText;
18 | };
19 | });
20 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/hashHTMLBlocks.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makehtml.hashHTMLBlocks', function (text, options, globals) {
2 | 'use strict';
3 | text = globals.converter._dispatch('makehtml.hashHTMLBlocks.before', text, options, globals).getText();
4 |
5 | var blockTags = [
6 | 'pre',
7 | 'div',
8 | 'h1',
9 | 'h2',
10 | 'h3',
11 | 'h4',
12 | 'h5',
13 | 'h6',
14 | 'blockquote',
15 | 'table',
16 | 'dl',
17 | 'ol',
18 | 'ul',
19 | 'script',
20 | 'noscript',
21 | 'form',
22 | 'fieldset',
23 | 'iframe',
24 | 'math',
25 | 'style',
26 | 'section',
27 | 'header',
28 | 'footer',
29 | 'nav',
30 | 'article',
31 | 'aside',
32 | 'address',
33 | 'audio',
34 | 'canvas',
35 | 'figure',
36 | 'hgroup',
37 | 'output',
38 | 'video',
39 | 'details',
40 | 'p'
41 | ],
42 | repFunc = function (wholeMatch, match, left, right) {
43 | var txt = wholeMatch;
44 | // check if this html element is marked as markdown
45 | // if so, it's contents should be parsed as markdown
46 | if (left.search(/\bmarkdown\b/) !== -1) {
47 | txt = left + globals.converter.makeHtml(match) + right;
48 | }
49 | return '\n\n¨K' + (globals.gHtmlBlocks.push(txt) - 1) + 'K\n\n';
50 | };
51 |
52 | if (options.backslashEscapesHTMLTags) {
53 | // encode backslash escaped HTML tags
54 | text = text.replace(/\\<(\/?[^>]+?)>/g, function (wm, inside) {
55 | return '<' + inside + '>';
56 | });
57 | }
58 |
59 | // hash HTML Blocks
60 | for (var i = 0; i < blockTags.length; ++i) {
61 |
62 | var opTagPos,
63 | rgx1 = new RegExp('^ {0,3}(<' + blockTags[i] + '\\b[^>]*>)', 'im'),
64 | patLeft = '<' + blockTags[i] + '\\b[^>]*>',
65 | patRight = '' + blockTags[i] + '>';
66 | // 1. Look for the first position of the first opening HTML tag in the text
67 | while ((opTagPos = showdown.helper.regexIndexOf(text, rgx1)) !== -1) {
68 |
69 | // if the HTML tag is \ escaped, we need to escape it and break
70 |
71 |
72 | //2. Split the text in that position
73 | var subTexts = showdown.helper.splitAtIndex(text, opTagPos),
74 | //3. Match recursively
75 | newSubText1 = showdown.helper.replaceRecursiveRegExp(subTexts[1], repFunc, patLeft, patRight, 'im');
76 |
77 | // prevent an infinite loop
78 | if (newSubText1 === subTexts[1]) {
79 | break;
80 | }
81 | text = subTexts[0].concat(newSubText1);
82 | }
83 | }
84 | // HR SPECIAL CASE
85 | text = text.replace(/(\n {0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,
86 | showdown.subParser('makehtml.hashElement')(text, options, globals));
87 |
88 | // Special case for standalone HTML comments
89 | text = showdown.helper.replaceRecursiveRegExp(text, function (txt) {
90 | return '\n\n¨K' + (globals.gHtmlBlocks.push(txt) - 1) + 'K\n\n';
91 | }, '^ {0,3}', 'gm');
92 |
93 | // PHP and ASP-style processor instructions (...?> and <%...%>)
94 | text = text.replace(/\n\n( {0,3}<([?%])[^\r]*?\2>[ \t]*(?=\n{2,}))/g,
95 | showdown.subParser('makehtml.hashElement')(text, options, globals));
96 |
97 | text = globals.converter._dispatch('makehtml.hashHTMLBlocks.after', text, options, globals).getText();
98 | return text;
99 | });
100 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/hashHTMLSpans.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Hash span elements that should not be parsed as markdown
3 | */
4 | showdown.subParser('makehtml.hashHTMLSpans', function (text, options, globals) {
5 | 'use strict';
6 | text = globals.converter._dispatch('makehtml.hashHTMLSpans.before', text, options, globals).getText();
7 |
8 | // Hash Self Closing tags
9 | text = text.replace(/<[^>]+?\/>/gi, function (wm) {
10 | return showdown.helper._hashHTMLSpan(wm, globals);
11 | });
12 |
13 | // Hash tags without properties
14 | text = text.replace(/<([^>]+?)>[\s\S]*?<\/\1>/g, function (wm) {
15 | return showdown.helper._hashHTMLSpan(wm, globals);
16 | });
17 |
18 | // Hash tags with properties
19 | text = text.replace(/<([^>]+?)\s[^>]+?>[\s\S]*?<\/\1>/g, function (wm) {
20 | return showdown.helper._hashHTMLSpan(wm, globals);
21 | });
22 |
23 | // Hash self closing tags without />
24 | text = text.replace(/<[^>]+?>/gi, function (wm) {
25 | return showdown.helper._hashHTMLSpan(wm, globals);
26 | });
27 |
28 | text = globals.converter._dispatch('makehtml.hashHTMLSpans.after', text, options, globals).getText();
29 | return text;
30 | });
31 |
32 | /**
33 | * Unhash HTML spans
34 | */
35 | showdown.subParser('makehtml.unhashHTMLSpans', function (text, options, globals) {
36 | 'use strict';
37 | text = globals.converter._dispatch('makehtml.unhashHTMLSpans.before', text, options, globals).getText();
38 |
39 | for (var i = 0; i < globals.gHtmlSpans.length; ++i) {
40 | var repText = globals.gHtmlSpans[i],
41 | // limiter to prevent infinite loop (assume 10 as limit for recurse)
42 | limit = 0;
43 |
44 | while (/¨C(\d+)C/.test(repText)) {
45 | var num = RegExp.$1;
46 | repText = repText.replace('¨C' + num + 'C', globals.gHtmlSpans[num]);
47 | if (limit === 10) {
48 | console.error('maximum nesting of 10 spans reached!!!');
49 | break;
50 | }
51 | ++limit;
52 | }
53 | text = text.replace('¨C' + i + 'C', repText);
54 | }
55 |
56 | text = globals.converter._dispatch('makehtml.unhashHTMLSpans.after', text, options, globals).getText();
57 | return text;
58 | });
59 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/hashPreCodeTags.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Hash and escape elements that should not be parsed as markdown
3 | */
4 | showdown.subParser('makehtml.hashPreCodeTags', function (text, options, globals) {
5 | 'use strict';
6 | text = globals.converter._dispatch('makehtml.hashPreCodeTags.before', text, options, globals).getText();
7 |
8 | var repFunc = function (wholeMatch, match, left, right) {
9 | // encode html entities
10 | var codeblock = left + showdown.subParser('makehtml.encodeCode')(match, options, globals) + right;
11 | return '\n\n¨G' + (globals.ghCodeBlocks.push({text: wholeMatch, codeblock: codeblock}) - 1) + 'G\n\n';
12 | };
13 |
14 | // Hash
15 | text = showdown.helper.replaceRecursiveRegExp(text, repFunc, '^ {0,3}]*>\\s*]*>', '^ {0,3}
\\s*
', 'gim');
16 |
17 | text = globals.converter._dispatch('makehtml.hashPreCodeTags.after', text, options, globals).getText();
18 | return text;
19 | });
20 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/headers.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makehtml.headers', function (text, options, globals) {
2 | 'use strict';
3 |
4 | text = globals.converter._dispatch('makehtml.headers.before', text, options, globals).getText();
5 |
6 | var headerLevelStart = (isNaN(parseInt(options.headerLevelStart))) ? 1 : parseInt(options.headerLevelStart),
7 |
8 | // Set text-style headers:
9 | // Header 1
10 | // ========
11 | //
12 | // Header 2
13 | // --------
14 | //
15 | setextRegexH1 = (options.smoothLivePreview) ? /^(.+)[ \t]*\n={2,}[ \t]*\n+/gm : /^(.+)[ \t]*\n=+[ \t]*\n+/gm,
16 | setextRegexH2 = (options.smoothLivePreview) ? /^(.+)[ \t]*\n-{2,}[ \t]*\n+/gm : /^(.+)[ \t]*\n-+[ \t]*\n+/gm;
17 |
18 | text = text.replace(setextRegexH1, function (wholeMatch, m1) {
19 |
20 | var spanGamut = showdown.subParser('makehtml.spanGamut')(m1, options, globals),
21 | hID = (options.noHeaderId) ? '' : ' id="' + headerId(m1) + '"',
22 | hLevel = headerLevelStart,
23 | hashBlock = '' + spanGamut + ' ';
24 | return showdown.subParser('makehtml.hashBlock')(hashBlock, options, globals);
25 | });
26 |
27 | text = text.replace(setextRegexH2, function (matchFound, m1) {
28 | var spanGamut = showdown.subParser('makehtml.spanGamut')(m1, options, globals),
29 | hID = (options.noHeaderId) ? '' : ' id="' + headerId(m1) + '"',
30 | hLevel = headerLevelStart + 1,
31 | hashBlock = '' + spanGamut + ' ';
32 | return showdown.subParser('makehtml.hashBlock')(hashBlock, options, globals);
33 | });
34 |
35 | // atx-style headers:
36 | // # Header 1
37 | // ## Header 2
38 | // ## Header 2 with closing hashes ##
39 | // ...
40 | // ###### Header 6
41 | //
42 | var atxStyle = (options.requireSpaceBeforeHeadingText) ? /^(#{1,6})[ \t]+(.+?)[ \t]*#*\n+/gm : /^(#{1,6})[ \t]*(.+?)[ \t]*#*\n+/gm;
43 |
44 | text = text.replace(atxStyle, function (wholeMatch, m1, m2) {
45 | var hText = m2;
46 | if (options.customizedHeaderId) {
47 | hText = m2.replace(/\s?{([^{]+?)}\s*$/, '');
48 | }
49 |
50 | var span = showdown.subParser('makehtml.spanGamut')(hText, options, globals),
51 | hID = (options.noHeaderId) ? '' : ' id="' + headerId(m2) + '"',
52 | hLevel = headerLevelStart - 1 + m1.length,
53 | header = '' + span + ' ';
54 |
55 | return showdown.subParser('makehtml.hashBlock')(header, options, globals);
56 | });
57 |
58 | function headerId (m) {
59 | var title,
60 | prefix;
61 |
62 | // It is separate from other options to allow combining prefix and customized
63 | if (options.customizedHeaderId) {
64 | var match = m.match(/{([^{]+?)}\s*$/);
65 | if (match && match[1]) {
66 | m = match[1];
67 | }
68 | }
69 |
70 | title = m;
71 |
72 | // Prefix id to prevent causing inadvertent pre-existing style matches.
73 | if (showdown.helper.isString(options.prefixHeaderId)) {
74 | prefix = options.prefixHeaderId;
75 | } else if (options.prefixHeaderId === true) {
76 | prefix = 'section-';
77 | } else {
78 | prefix = '';
79 | }
80 |
81 | if (!options.rawPrefixHeaderId) {
82 | title = prefix + title;
83 | }
84 |
85 | if (options.ghCompatibleHeaderId) {
86 | title = title
87 | .replace(/ /g, '-')
88 | // replace previously escaped chars (&, ¨ and $)
89 | .replace(/&/g, '')
90 | .replace(/¨T/g, '')
91 | .replace(/¨D/g, '')
92 | // replace rest of the chars (&~$ are repeated as they might have been escaped)
93 | // borrowed from github's redcarpet (some they should produce similar results)
94 | .replace(/[&+$,\/:;=?@"#{}|^¨~\[\]`\\*)(%.!'<>]/g, '')
95 | .toLowerCase();
96 | } else if (options.rawHeaderId) {
97 | title = title
98 | .replace(/ /g, '-')
99 | // replace previously escaped chars (&, ¨ and $)
100 | .replace(/&/g, '&')
101 | .replace(/¨T/g, '¨')
102 | .replace(/¨D/g, '$')
103 | // replace " and '
104 | .replace(/["']/g, '-')
105 | .toLowerCase();
106 | } else {
107 | title = title
108 | .replace(/[^\w]/g, '')
109 | .toLowerCase();
110 | }
111 |
112 | if (options.rawPrefixHeaderId) {
113 | title = prefix + title;
114 | }
115 |
116 | if (globals.hashLinkCounts[title]) {
117 | title = title + '-' + (globals.hashLinkCounts[title]++);
118 | } else {
119 | globals.hashLinkCounts[title] = 1;
120 | }
121 | return title;
122 | }
123 |
124 | text = globals.converter._dispatch('makehtml.headers.after', text, options, globals).getText();
125 | return text;
126 | });
127 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/horizontalRule.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Turn Markdown horizontal rule shortcuts into
tags.
3 | *
4 | * Any 3 or more unindented consecutive hyphens, asterisks or underscores with or without a space beetween them
5 | * in a single line is considered a horizontal rule
6 | */
7 | showdown.subParser('makehtml.horizontalRule', function (text, options, globals) {
8 | 'use strict';
9 | text = globals.converter._dispatch('makehtml.horizontalRule.before', text, options, globals).getText();
10 |
11 | var key = showdown.subParser('makehtml.hashBlock')('
', options, globals);
12 | text = text.replace(/^ {0,2}( ?-){3,}[ \t]*$/gm, key);
13 | text = text.replace(/^ {0,2}( ?\*){3,}[ \t]*$/gm, key);
14 | text = text.replace(/^ {0,2}( ?_){3,}[ \t]*$/gm, key);
15 |
16 | text = globals.converter._dispatch('makehtml.horizontalRule.after', text, options, globals).getText();
17 | return text;
18 | });
19 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/images.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Turn Markdown image shortcuts into
tags.
3 | */
4 | showdown.subParser('makehtml.images', function (text, options, globals) {
5 | 'use strict';
6 |
7 | text = globals.converter._dispatch('makehtml.images.before', text, options, globals).getText();
8 |
9 | var inlineRegExp = /!\[([^\]]*?)][ \t]*()\([ \t]?([\S]+?(?:\([\S]*?\)[\S]*?)?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\6)?[ \t]?\)/g,
10 | crazyRegExp = /!\[([^\]]*?)][ \t]*()\([ \t]?<([^>]*)>(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(?:(["'])([^"]*?)\6))?[ \t]?\)/g,
11 | base64RegExp = /!\[([^\]]*?)][ \t]*()\([ \t]?(data:.+?\/.+?;base64,[A-Za-z0-9+/=\n]+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\6)?[ \t]?\)/g,
12 | referenceRegExp = /!\[([^\]]*?)] ?(?:\n *)?\[([\s\S]*?)]()()()()()/g,
13 | refShortcutRegExp = /!\[([^\[\]]+)]()()()()()/g;
14 |
15 | function writeImageTagBase64 (wholeMatch, altText, linkId, url, width, height, m5, title) {
16 | url = url.replace(/\s/g, '');
17 | return writeImageTag (wholeMatch, altText, linkId, url, width, height, m5, title);
18 | }
19 |
20 | function writeImageTagBaseUrl (wholeMatch, altText, linkId, url, width, height, m5, title) {
21 | url = showdown.helper.applyBaseUrl(options.relativePathBaseUrl, url);
22 |
23 | return writeImageTag (wholeMatch, altText, linkId, url, width, height, m5, title);
24 | }
25 |
26 | function writeImageTag (wholeMatch, altText, linkId, url, width, height, m5, title) {
27 |
28 | var gUrls = globals.gUrls,
29 | gTitles = globals.gTitles,
30 | gDims = globals.gDimensions;
31 |
32 | linkId = linkId.toLowerCase();
33 |
34 | if (!title) {
35 | title = '';
36 | }
37 | // Special case for explicit empty url
38 | if (wholeMatch.search(/\(\s*>? ?(['"].*['"])?\)$/m) > -1) {
39 | url = '';
40 |
41 | } else if (url === '' || url === null) {
42 | if (linkId === '' || linkId === null) {
43 | // lower-case and turn embedded newlines into spaces
44 | linkId = altText.toLowerCase().replace(/ ?\n/g, ' ');
45 | }
46 | url = '#' + linkId;
47 |
48 | if (!showdown.helper.isUndefined(gUrls[linkId])) {
49 | url = gUrls[linkId];
50 | if (!showdown.helper.isUndefined(gTitles[linkId])) {
51 | title = gTitles[linkId];
52 | }
53 | if (!showdown.helper.isUndefined(gDims[linkId])) {
54 | width = gDims[linkId].width;
55 | height = gDims[linkId].height;
56 | }
57 | } else {
58 | return wholeMatch;
59 | }
60 | }
61 |
62 | altText = altText
63 | .replace(/"/g, '"')
64 | //altText = showdown.helper.escapeCharacters(altText, '*_', false);
65 | .replace(showdown.helper.regexes.asteriskDashTildeAndColon, showdown.helper.escapeCharactersCallback);
66 | //url = showdown.helper.escapeCharacters(url, '*_', false);
67 | url = url.replace(showdown.helper.regexes.asteriskDashTildeAndColon, showdown.helper.escapeCharactersCallback);
68 | var result = '
';
87 |
88 | return result;
89 | }
90 |
91 | // First, handle reference-style labeled images: ![alt text][id]
92 | text = text.replace(referenceRegExp, writeImageTag);
93 |
94 | // Next, handle inline images: 
95 |
96 | // base64 encoded images
97 | text = text.replace(base64RegExp, writeImageTagBase64);
98 |
99 | // cases with crazy urls like ./image/cat1).png
100 | text = text.replace(crazyRegExp, writeImageTagBaseUrl);
101 |
102 | // normal cases
103 | text = text.replace(inlineRegExp, writeImageTagBaseUrl);
104 |
105 | // handle reference-style shortcuts: ![img text]
106 | text = text.replace(refShortcutRegExp, writeImageTag);
107 |
108 | text = globals.converter._dispatch('makehtml.images.after', text, options, globals).getText();
109 | return text;
110 | });
111 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/italicsAndBold.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makehtml.italicsAndBold', function (text, options, globals) {
2 | 'use strict';
3 |
4 | text = globals.converter._dispatch('makehtml.italicsAndBold.before', text, options, globals).getText();
5 |
6 | // it's faster to have 3 separate regexes for each case than have just one
7 | // because of backtracing, in some cases, it could lead to an exponential effect
8 | // called "catastrophic backtrace". Ominous!
9 |
10 | function parseInside (txt, left, right) {
11 | return left + txt + right;
12 | }
13 |
14 | // Parse underscores
15 | if (options.literalMidWordUnderscores) {
16 | text = text.replace(/\b___(\S[\s\S]*?)___\b/g, function (wm, txt) {
17 | return parseInside (txt, '', '');
18 | });
19 | text = text.replace(/\b__(\S[\s\S]*?)__\b/g, function (wm, txt) {
20 | return parseInside (txt, '', '');
21 | });
22 | text = text.replace(/\b_(\S[\s\S]*?)_\b/g, function (wm, txt) {
23 | return parseInside (txt, '', '');
24 | });
25 | } else {
26 | text = text.replace(/___(\S[\s\S]*?)___/g, function (wm, m) {
27 | return (/\S$/.test(m)) ? parseInside (m, '', '') : wm;
28 | });
29 | text = text.replace(/__(\S[\s\S]*?)__/g, function (wm, m) {
30 | return (/\S$/.test(m)) ? parseInside (m, '', '') : wm;
31 | });
32 | text = text.replace(/_([^\s_][\s\S]*?)_/g, function (wm, m) {
33 | // !/^_[^_]/.test(m) - test if it doesn't start with __ (since it seems redundant, we removed it)
34 | return (/\S$/.test(m)) ? parseInside (m, '', '') : wm;
35 | });
36 | }
37 |
38 | // Now parse asterisks
39 | /*
40 | if (options.literalMidWordAsterisks) {
41 | text = text.replace(/([^*]|^)\B\*\*\*(\S[\s\S]+?)\*\*\*\B(?!\*)/g, function (wm, lead, txt) {
42 | return parseInside (txt, lead + '', '');
43 | });
44 | text = text.replace(/([^*]|^)\B\*\*(\S[\s\S]+?)\*\*\B(?!\*)/g, function (wm, lead, txt) {
45 | return parseInside (txt, lead + '', '');
46 | });
47 | text = text.replace(/([^*]|^)\B\*(\S[\s\S]+?)\*\B(?!\*)/g, function (wm, lead, txt) {
48 | return parseInside (txt, lead + '', '');
49 | });
50 | } else {
51 | */
52 | text = text.replace(/\*\*\*(\S[\s\S]*?)\*\*\*/g, function (wm, m) {
53 | return (/\S$/.test(m)) ? parseInside (m, '', '') : wm;
54 | });
55 | text = text.replace(/\*\*(\S[\s\S]*?)\*\*/g, function (wm, m) {
56 | return (/\S$/.test(m)) ? parseInside (m, '', '') : wm;
57 | });
58 | text = text.replace(/\*([^\s*][\s\S]*?)\*/g, function (wm, m) {
59 | // !/^\*[^*]/.test(m) - test if it doesn't start with ** (since it seems redundant, we removed it)
60 | return (/\S$/.test(m)) ? parseInside (m, '', '') : wm;
61 | });
62 | //}
63 |
64 | text = globals.converter._dispatch('makehtml.italicsAndBold.after', text, options, globals).getText();
65 | return text;
66 | });
67 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/lists.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Form HTML ordered (numbered) and unordered (bulleted) lists.
3 | */
4 | showdown.subParser('makehtml.lists', function (text, options, globals) {
5 | 'use strict';
6 |
7 | /**
8 | * Process the contents of a single ordered or unordered list, splitting it
9 | * into individual list items.
10 | * @param {string} listStr
11 | * @param {boolean} trimTrailing
12 | * @returns {string}
13 | */
14 | function processListItems (listStr, trimTrailing) {
15 | // The $g_list_level global keeps track of when we're inside a list.
16 | // Each time we enter a list, we increment it; when we leave a list,
17 | // we decrement. If it's zero, we're not in a list anymore.
18 | //
19 | // We do this because when we're not inside a list, we want to treat
20 | // something like this:
21 | //
22 | // I recommend upgrading to version
23 | // 8. Oops, now this line is treated
24 | // as a sub-list.
25 | //
26 | // As a single paragraph, despite the fact that the second line starts
27 | // with a digit-period-space sequence.
28 | //
29 | // Whereas when we're inside a list (or sub-list), that line will be
30 | // treated as the start of a sub-list. What a kludge, huh? This is
31 | // an aspect of Markdown's syntax that's hard to parse perfectly
32 | // without resorting to mind-reading. Perhaps the solution is to
33 | // change the syntax rules such that sub-lists must start with a
34 | // starting cardinal number; e.g. "1." or "a.".
35 | globals.gListLevel++;
36 |
37 | // trim trailing blank lines:
38 | listStr = listStr.replace(/\n{2,}$/, '\n');
39 |
40 | // attacklab: add sentinel to emulate \z
41 | listStr += '¨0';
42 |
43 | var rgx = /(\n)?(^ {0,3})([*+-]|\d+[.])[ \t]+((\[([xX ])])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(¨0| {0,3}([*+-]|\d+[.])[ \t]+))/gm,
44 | isParagraphed = (/\n[ \t]*\n(?!¨0)/.test(listStr));
45 |
46 | // Since version 1.5, nesting sublists requires 4 spaces (or 1 tab) indentation,
47 | // which is a syntax breaking change
48 | // activating this option reverts to old behavior
49 | // This will be removed in version 2.0
50 | if (options.disableForced4SpacesIndentedSublists) {
51 | rgx = /(\n)?(^ {0,3})([*+-]|\d+[.])[ \t]+((\[([xX ])])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(¨0|\2([*+-]|\d+[.])[ \t]+))/gm;
52 | }
53 |
54 | listStr = listStr.replace(rgx, function (wholeMatch, m1, m2, m3, m4, taskbtn, checked) {
55 | checked = (checked && checked.trim() !== '');
56 |
57 | var item = showdown.subParser('makehtml.outdent')(m4, options, globals),
58 | bulletStyle = '';
59 |
60 | // Support for github tasklists
61 | if (taskbtn && options.tasklists) {
62 |
63 | // Style used for tasklist bullets
64 | bulletStyle = ' class="task-list-item';
65 | if (options.moreStyling) {bulletStyle += checked ? ' task-list-item-complete' : '';}
66 | bulletStyle += '" style="list-style-type: none;"';
67 |
68 | item = item.replace(/^[ \t]*\[([xX ])?]/m, function () {
69 | var otp = '';
74 | return otp;
75 | });
76 | }
77 |
78 | // ISSUE #312
79 | // This input: - - - a
80 | // causes trouble to the parser, since it interprets it as:
81 | // - a
82 | // instead of:
83 | // - - - a
84 | // So, to prevent it, we will put a marker (¨A)in the beginning of the line
85 | // Kind of hackish/monkey patching, but seems more effective than overcomplicating the list parser
86 | item = item.replace(/^([-*+]|\d\.)[ \t]+[\S\n ]*/g, function (wm2) {
87 | return '¨A' + wm2;
88 | });
89 |
90 | // SPECIAL CASE: a heading followed by a paragraph of text that is not separated by a double newline
91 | // or/nor indented. ex:
92 | //
93 | // - # foo
94 | // bar is great
95 | //
96 | // While this does now follow the spec per se, not allowing for this might cause confusion since
97 | // header blocks don't need double-newlines after
98 | if (/^#+.+\n.+/.test(item)) {
99 | item = item.replace(/^(#+.+)$/m, '$1\n');
100 | }
101 |
102 | // m1 - Leading line or
103 | // Has a double return (multi paragraph)
104 | if (m1 || (item.search(/\n{2,}/) > -1)) {
105 | item = showdown.subParser('makehtml.githubCodeBlocks')(item, options, globals);
106 | item = showdown.subParser('makehtml.blockQuotes')(item, options, globals);
107 | item = showdown.subParser('makehtml.headers')(item, options, globals);
108 | item = showdown.subParser('makehtml.lists')(item, options, globals);
109 | item = showdown.subParser('makehtml.codeBlocks')(item, options, globals);
110 | item = showdown.subParser('makehtml.tables')(item, options, globals);
111 | item = showdown.subParser('makehtml.hashHTMLBlocks')(item, options, globals);
112 | //item = showdown.subParser('makehtml.paragraphs')(item, options, globals);
113 |
114 | // TODO: This is a copy of the paragraph parser
115 | // This is a provisory fix for issue #494
116 | // For a permanente fix we need to rewrite the paragraph parser, passing the unhashify logic outside
117 | // so that we can call the paragraph parser without accidently unashifying previously parsed blocks
118 |
119 | // Strip leading and trailing lines:
120 | item = item.replace(/^\n+/g, '');
121 | item = item.replace(/\n+$/g, '');
122 |
123 | var grafs = item.split(/\n{2,}/g),
124 | grafsOut = [],
125 | end = grafs.length; // Wrap tags
126 |
127 | for (var i = 0; i < end; i++) {
128 | var str = grafs[i];
129 | // if this is an HTML marker, copy it
130 | if (str.search(/¨([KG])(\d+)\1/g) >= 0) {
131 | grafsOut.push(str);
132 |
133 | // test for presence of characters to prevent empty lines being parsed
134 | // as paragraphs (resulting in undesired extra empty paragraphs)
135 | } else if (str.search(/\S/) >= 0) {
136 | str = showdown.subParser('makehtml.spanGamut')(str, options, globals);
137 | str = str.replace(/^([ \t]*)/g, '
');
138 | str += '
';
139 | grafsOut.push(str);
140 | }
141 | }
142 | item = grafsOut.join('\n');
143 | // Strip leading and trailing lines:
144 | item = item.replace(/^\n+/g, '');
145 | item = item.replace(/\n+$/g, '');
146 |
147 | } else {
148 |
149 | // Recursion for sub-lists:
150 | item = showdown.subParser('makehtml.lists')(item, options, globals);
151 | item = item.replace(/\n$/, ''); // chomp(item)
152 | item = showdown.subParser('makehtml.hashHTMLBlocks')(item, options, globals);
153 |
154 | // Colapse double linebreaks
155 | item = item.replace(/\n\n+/g, '\n\n');
156 |
157 | if (isParagraphed) {
158 | item = showdown.subParser('makehtml.paragraphs')(item, options, globals);
159 | } else {
160 | item = showdown.subParser('makehtml.spanGamut')(item, options, globals);
161 | }
162 | }
163 |
164 | // now we need to remove the marker (¨A)
165 | item = item.replace('¨A', '');
166 | // we can finally wrap the line in list item tags
167 | item = '' + item + ' \n';
168 |
169 | return item;
170 | });
171 |
172 | // attacklab: strip sentinel
173 | listStr = listStr.replace(/¨0/g, '');
174 |
175 | globals.gListLevel--;
176 |
177 | if (trimTrailing) {
178 | listStr = listStr.replace(/\s+$/, '');
179 | }
180 |
181 | return listStr;
182 | }
183 |
184 | function styleStartNumber (list, listType) {
185 | // check if ol and starts by a number different than 1
186 | if (listType === 'ol') {
187 | var res = list.match(/^ *(\d+)\./);
188 | if (res && res[1] !== '1') {
189 | return ' start="' + res[1] + '"';
190 | }
191 | }
192 | return '';
193 | }
194 |
195 | /**
196 | * Check and parse consecutive lists (better fix for issue #142)
197 | * @param {string} list
198 | * @param {string} listType
199 | * @param {boolean} trimTrailing
200 | * @returns {string}
201 | */
202 | function parseConsecutiveLists (list, listType, trimTrailing) {
203 | // check if we caught 2 or more consecutive lists by mistake
204 | // we use the counterRgx, meaning if listType is UL we look for OL and vice versa
205 | var olRgx = (options.disableForced4SpacesIndentedSublists) ? /^ ?\d+\.[ \t]/gm : /^ {0,3}\d+\.[ \t]/gm,
206 | ulRgx = (options.disableForced4SpacesIndentedSublists) ? /^ ?[*+-][ \t]/gm : /^ {0,3}[*+-][ \t]/gm,
207 | counterRxg = (listType === 'ul') ? olRgx : ulRgx,
208 | result = '';
209 |
210 | if (list.search(counterRxg) !== -1) {
211 | (function parseCL (txt) {
212 | var pos = txt.search(counterRxg),
213 | style = styleStartNumber(list, listType);
214 | if (pos !== -1) {
215 | // slice
216 | result += '\n\n<' + listType + style + '>\n' + processListItems(txt.slice(0, pos), !!trimTrailing) + '' + listType + '>\n';
217 |
218 | // invert counterType and listType
219 | listType = (listType === 'ul') ? 'ol' : 'ul';
220 | counterRxg = (listType === 'ul') ? olRgx : ulRgx;
221 |
222 | //recurse
223 | parseCL(txt.slice(pos));
224 | } else {
225 | result += '\n\n<' + listType + style + '>\n' + processListItems(txt, !!trimTrailing) + '' + listType + '>\n';
226 | }
227 | })(list);
228 | } else {
229 | var style = styleStartNumber(list, listType);
230 | result = '\n\n<' + listType + style + '>\n' + processListItems(list, !!trimTrailing) + '' + listType + '>\n';
231 | }
232 |
233 | return result;
234 | }
235 |
236 | // Start of list parsing
237 | var subListRgx = /^(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(¨0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
238 | var mainListRgx = /(\n\n|^\n?)(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(¨0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
239 |
240 | text = globals.converter._dispatch('lists.before', text, options, globals).getText();
241 | // add sentinel to hack around khtml/safari bug:
242 | // http://bugs.webkit.org/show_bug.cgi?id=11231
243 | text += '¨0';
244 |
245 | if (globals.gListLevel) {
246 | text = text.replace(subListRgx, function (wholeMatch, list, m2) {
247 | var listType = (m2.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
248 | return parseConsecutiveLists(list, listType, true);
249 | });
250 | } else {
251 | text = text.replace(mainListRgx, function (wholeMatch, m1, list, m3) {
252 | var listType = (m3.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
253 | return parseConsecutiveLists(list, listType, false);
254 | });
255 | }
256 |
257 | // strip sentinel
258 | text = text.replace(/¨0/, '');
259 | text = globals.converter._dispatch('makehtml.lists.after', text, options, globals).getText();
260 | return text;
261 | });
262 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/metadata.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Parse metadata at the top of the document
3 | */
4 | showdown.subParser('makehtml.metadata', function (text, options, globals) {
5 | 'use strict';
6 |
7 | if (!options.metadata) {
8 | return text;
9 | }
10 |
11 | text = globals.converter._dispatch('makehtml.metadata.before', text, options, globals).getText();
12 |
13 | function parseMetadataContents (content) {
14 | // raw is raw so it's not changed in any way
15 | globals.metadata.raw = content;
16 |
17 | // escape chars forbidden in html attributes
18 | // double quotes
19 | content = content
20 | // ampersand first
21 | .replace(/&/g, '&')
22 | // double quotes
23 | .replace(/"/g, '"');
24 |
25 | // Restore dollar signs and tremas
26 | content = content
27 | .replace(/¨D/g, '$$')
28 | .replace(/¨T/g, '¨');
29 |
30 | content = content.replace(/\n {4}/g, ' ');
31 | content.replace(/^([\S ]+): +([\s\S]+?)$/gm, function (wm, key, value) {
32 | globals.metadata.parsed[key] = value;
33 | return '';
34 | });
35 | }
36 |
37 | text = text.replace(/^\s*«««+\s*(\S*?)\n([\s\S]+?)\n»»»+\s*\n/, function (wholematch, format, content) {
38 | parseMetadataContents(content);
39 | return '¨M';
40 | });
41 |
42 | text = text.replace(/^\s*---+\s*(\S*?)\n([\s\S]+?)\n---+\s*\n/, function (wholematch, format, content) {
43 | if (format) {
44 | globals.metadata.format = format;
45 | }
46 | parseMetadataContents(content);
47 | return '¨M';
48 | });
49 |
50 | text = text.replace(/¨M/g, '');
51 |
52 | text = globals.converter._dispatch('makehtml.metadata.after', text, options, globals).getText();
53 | return text;
54 | });
55 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/outdent.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Remove one level of line-leading tabs or spaces
3 | */
4 | showdown.subParser('makehtml.outdent', function (text, options, globals) {
5 | 'use strict';
6 | text = globals.converter._dispatch('makehtml.outdent.before', text, options, globals).getText();
7 |
8 | // attacklab: hack around Konqueror 3.5.4 bug:
9 | // "----------bug".replace(/^-/g,"") == "bug"
10 | text = text.replace(/^(\t|[ ]{1,4})/gm, '¨0'); // attacklab: g_tab_width
11 |
12 | // attacklab: clean up hack
13 | text = text.replace(/¨0/g, '');
14 |
15 | text = globals.converter._dispatch('makehtml.outdent.after', text, options, globals).getText();
16 | return text;
17 | });
18 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/paragraphs.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | showdown.subParser('makehtml.paragraphs', function (text, options, globals) {
5 | 'use strict';
6 |
7 | text = globals.converter._dispatch('makehtml.paragraphs.before', text, options, globals).getText();
8 | // Strip leading and trailing lines:
9 | text = text.replace(/^\n+/g, '');
10 | text = text.replace(/\n+$/g, '');
11 |
12 | var grafs = text.split(/\n{2,}/g),
13 | grafsOut = [],
14 | end = grafs.length; // Wrap tags
15 |
16 | for (var i = 0; i < end; i++) {
17 | var str = grafs[i];
18 | // if this is an HTML marker, copy it
19 | if (str.search(/¨(K|G)(\d+)\1/g) >= 0) {
20 | grafsOut.push(str);
21 |
22 | // test for presence of characters to prevent empty lines being parsed
23 | // as paragraphs (resulting in undesired extra empty paragraphs)
24 | } else if (str.search(/\S/) >= 0) {
25 | str = showdown.subParser('makehtml.spanGamut')(str, options, globals);
26 | str = str.replace(/^([ \t]*)/g, '
');
27 | str += '
';
28 | grafsOut.push(str);
29 | }
30 | }
31 |
32 | /** Unhashify HTML blocks */
33 | end = grafsOut.length;
34 | for (i = 0; i < end; i++) {
35 | var blockText = '',
36 | grafsOutIt = grafsOut[i],
37 | codeFlag = false;
38 | // if this is a marker for an html block...
39 | // use RegExp.test instead of string.search because of QML bug
40 | while (/¨(K|G)(\d+)\1/.test(grafsOutIt)) {
41 | var delim = RegExp.$1,
42 | num = RegExp.$2;
43 |
44 | if (delim === 'K') {
45 | blockText = globals.gHtmlBlocks[num];
46 | } else {
47 | // we need to check if ghBlock is a false positive
48 | if (codeFlag) {
49 | // use encoded version of all text
50 | blockText = showdown.subParser('makehtml.encodeCode')(globals.ghCodeBlocks[num].text, options, globals);
51 | } else {
52 | blockText = globals.ghCodeBlocks[num].codeblock;
53 | }
54 | }
55 | blockText = blockText.replace(/\$/g, '$$$$'); // Escape any dollar signs
56 |
57 | grafsOutIt = grafsOutIt.replace(/(\n\n)?¨(K|G)\d+\2(\n\n)?/, blockText);
58 | // Check if grafsOutIt is a pre->code
59 | if (/^]*>\s*]*>/.test(grafsOutIt)) {
60 | codeFlag = true;
61 | }
62 | }
63 | grafsOut[i] = grafsOutIt;
64 | }
65 | text = grafsOut.join('\n');
66 | // Strip leading and trailing lines:
67 | text = text.replace(/^\n+/g, '');
68 | text = text.replace(/\n+$/g, '');
69 | return globals.converter._dispatch('makehtml.paragraphs.after', text, options, globals).getText();
70 | });
71 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/runExtension.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Run extension
3 | */
4 | showdown.subParser('makehtml.runExtension', function (ext, text, options, globals) {
5 | 'use strict';
6 |
7 | if (ext.filter) {
8 | text = ext.filter(text, globals.converter, options);
9 |
10 | } else if (ext.regex) {
11 | // TODO remove this when old extension loading mechanism is deprecated
12 | var re = ext.regex;
13 | if (!(re instanceof RegExp)) {
14 | re = new RegExp(re, 'g');
15 | }
16 | text = text.replace(re, ext.replace);
17 | }
18 |
19 | return text;
20 | });
21 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/spanGamut.js:
--------------------------------------------------------------------------------
1 | /**
2 | * These are all the transformations that occur *within* block-level
3 | * tags like paragraphs, headers, and list items.
4 | */
5 | showdown.subParser('makehtml.spanGamut', function (text, options, globals) {
6 | 'use strict';
7 |
8 | text = globals.converter._dispatch('makehtml.span.before', text, options, globals).getText();
9 |
10 | text = showdown.subParser('makehtml.codeSpans')(text, options, globals);
11 | text = showdown.subParser('makehtml.escapeSpecialCharsWithinTagAttributes')(text, options, globals);
12 | text = showdown.subParser('makehtml.encodeBackslashEscapes')(text, options, globals);
13 |
14 | // Process link and image tags. Images must come first,
15 | // because ![foo][f] looks like a link.
16 | text = showdown.subParser('makehtml.images')(text, options, globals);
17 |
18 | text = globals.converter._dispatch('smakehtml.links.before', text, options, globals).getText();
19 | text = showdown.subParser('makehtml.links')(text, options, globals);
20 | text = globals.converter._dispatch('smakehtml.links.after', text, options, globals).getText();
21 |
22 | //text = showdown.subParser('makehtml.autoLinks')(text, options, globals);
23 | //text = showdown.subParser('makehtml.simplifiedAutoLinks')(text, options, globals);
24 | text = showdown.subParser('makehtml.emoji')(text, options, globals);
25 | text = showdown.subParser('makehtml.underline')(text, options, globals);
26 | text = showdown.subParser('makehtml.italicsAndBold')(text, options, globals);
27 | text = showdown.subParser('makehtml.strikethrough')(text, options, globals);
28 | text = showdown.subParser('makehtml.ellipsis')(text, options, globals);
29 |
30 | // we need to hash HTML tags inside spans
31 | text = showdown.subParser('makehtml.hashHTMLSpans')(text, options, globals);
32 |
33 | // now we encode amps and angles
34 | text = showdown.subParser('makehtml.encodeAmpsAndAngles')(text, options, globals);
35 |
36 | // Do hard breaks
37 | if (options.simpleLineBreaks) {
38 | // GFM style hard breaks
39 | // only add line breaks if the text does not contain a block (special case for lists)
40 | if (!/\n\n¨K/.test(text)) {
41 | text = text.replace(/\n+/g, '
\n');
42 | }
43 | } else {
44 | // Vanilla hard breaks
45 | text = text.replace(/ +\n/g, '
\n');
46 | }
47 |
48 | text = globals.converter._dispatch('makehtml.spanGamut.after', text, options, globals).getText();
49 | return text;
50 | });
51 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/strikethrough.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makehtml.strikethrough', function (text, options, globals) {
2 | 'use strict';
3 |
4 | if (options.strikethrough) {
5 | text = globals.converter._dispatch('makehtml.strikethrough.before', text, options, globals).getText();
6 | text = text.replace(/(?:~){2}([\s\S]+?)(?:~){2}/g, function (wm, txt) { return '' + txt + ''; });
7 | text = globals.converter._dispatch('makehtml.strikethrough.after', text, options, globals).getText();
8 | }
9 |
10 | return text;
11 | });
12 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/stripLinkDefinitions.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Strips link definitions from text, stores the URLs and titles in
3 | * hash references.
4 | * Link defs are in the form: ^[id]: url "optional title"
5 | */
6 | showdown.subParser('makehtml.stripLinkDefinitions', function (text, options, globals) {
7 | 'use strict';
8 |
9 | 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,
10 | base64Regex = /^ {0,3}\[([^\]]+)]:[ \t]*\n?[ \t]*(data:.+?\/.+?;base64,[A-Za-z0-9+/=\n]+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*\n?[ \t]*(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n\n|(?=¨0)|(?=\n\[))/gm;
11 |
12 | // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
13 | text += '¨0';
14 |
15 | var replaceFunc = function (wholeMatch, linkId, url, width, height, blankLines, title) {
16 |
17 | // if there aren't two instances of linkId it must not be a reference link so back out
18 | linkId = linkId.toLowerCase();
19 | if (text.toLowerCase().split(linkId).length - 1 < 2) {
20 | return wholeMatch;
21 | }
22 | if (url.match(/^data:.+?\/.+?;base64,/)) {
23 | // remove newlines
24 | globals.gUrls[linkId] = url.replace(/\s/g, '');
25 | } else {
26 | url = showdown.helper.applyBaseUrl(options.relativePathBaseUrl, url);
27 |
28 | globals.gUrls[linkId] = showdown.subParser('makehtml.encodeAmpsAndAngles')(url, options, globals); // Link IDs are case-insensitive
29 | }
30 |
31 | if (blankLines) {
32 | // Oops, found blank lines, so it's not a title.
33 | // Put back the parenthetical statement we stole.
34 | return blankLines + title;
35 |
36 | } else {
37 | if (title) {
38 | globals.gTitles[linkId] = title.replace(/"|'/g, '"');
39 | }
40 | if (options.parseImgDimensions && width && height) {
41 | globals.gDimensions[linkId] = {
42 | width: width,
43 | height: height
44 | };
45 | }
46 | }
47 | // Completely remove the definition from the text
48 | return '';
49 | };
50 |
51 | // first we try to find base64 link references
52 | text = text.replace(base64Regex, replaceFunc);
53 |
54 | text = text.replace(regex, replaceFunc);
55 |
56 | // attacklab: strip sentinel
57 | text = text.replace(/¨0/, '');
58 |
59 | return text;
60 | });
61 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/tables.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makehtml.tables', function (text, options, globals) {
2 | 'use strict';
3 |
4 | if (!options.tables) {
5 | return text;
6 | }
7 |
8 | var tableRgx = /^ {0,3}\|?.+\|.+\n {0,3}\|?[ \t]*:?[ \t]*[-=]{2,}[ \t]*:?[ \t]*\|[ \t]*:?[ \t]*[-=]{2,}[\s\S]+?(?:\n\n|¨0)/gm,
9 | //singeColTblRgx = /^ {0,3}\|.+\|\n {0,3}\|[ \t]*:?[ \t]*(?:[-=]){2,}[ \t]*:?[ \t]*\|[ \t]*\n(?: {0,3}\|.+\|\n)+(?:\n\n|¨0)/gm;
10 | singeColTblRgx = /^ {0,3}\|.+\|[ \t]*\n {0,3}\|[ \t]*:?[ \t]*[-=]{2,}[ \t]*:?[ \t]*\|[ \t]*\n( {0,3}\|.+\|[ \t]*\n)*(?:\n|¨0)/gm;
11 |
12 | function parseStyles (sLine) {
13 | if (/^:[ \t]*--*$/.test(sLine)) {
14 | return ' style="text-align:left;"';
15 | } else if (/^--*[ \t]*:[ \t]*$/.test(sLine)) {
16 | return ' style="text-align:right;"';
17 | } else if (/^:[ \t]*--*[ \t]*:$/.test(sLine)) {
18 | return ' style="text-align:center;"';
19 | } else {
20 | return '';
21 | }
22 | }
23 |
24 | function parseHeaders (header, style) {
25 | var id = '';
26 | header = header.trim();
27 | // support both tablesHeaderId and tableHeaderId due to error in documentation so we don't break backwards compatibility
28 | if (options.tablesHeaderId || options.tableHeaderId) {
29 | id = ' id="' + header.replace(/ /g, '_').toLowerCase() + '"';
30 | }
31 | header = showdown.subParser('makehtml.spanGamut')(header, options, globals);
32 |
33 | return '' + header + ' \n';
34 | }
35 |
36 | function parseCells (cell, style) {
37 | var subText = showdown.subParser('makehtml.spanGamut')(cell, options, globals);
38 | return '' + subText + ' \n';
39 | }
40 |
41 | function buildTable (headers, cells) {
42 | var tb = '\n\n\n',
43 | tblLgn = headers.length;
44 |
45 | for (var i = 0; i < tblLgn; ++i) {
46 | tb += headers[i];
47 | }
48 | tb += ' \n\n\n';
49 |
50 | for (i = 0; i < cells.length; ++i) {
51 | tb += '\n';
52 | for (var ii = 0; ii < tblLgn; ++ii) {
53 | tb += cells[i][ii];
54 | }
55 | tb += ' \n';
56 | }
57 | tb += '\n
\n';
58 | return tb;
59 | }
60 |
61 | function parseTable (rawTable) {
62 | var i, tableLines = rawTable.split('\n');
63 |
64 | for (i = 0; i < tableLines.length; ++i) {
65 | // strip wrong first and last column if wrapped tables are used
66 | if (/^ {0,3}\|/.test(tableLines[i])) {
67 | tableLines[i] = tableLines[i].replace(/^ {0,3}\|/, '');
68 | }
69 | if (/\|[ \t]*$/.test(tableLines[i])) {
70 | tableLines[i] = tableLines[i].replace(/\|[ \t]*$/, '');
71 | }
72 | // parse code spans first, but we only support one line code spans
73 |
74 | tableLines[i] = showdown.subParser('makehtml.codeSpans')(tableLines[i], options, globals);
75 | }
76 |
77 | var rawHeaders = tableLines[0].split('|').map(function (s) { return s.trim();}),
78 | rawStyles = tableLines[1].split('|').map(function (s) { return s.trim();}),
79 | rawCells = [],
80 | headers = [],
81 | styles = [],
82 | cells = [];
83 |
84 | tableLines.shift();
85 | tableLines.shift();
86 |
87 | for (i = 0; i < tableLines.length; ++i) {
88 | if (tableLines[i].trim() === '') {
89 | continue;
90 | }
91 | rawCells.push(
92 | tableLines[i]
93 | .split('|')
94 | .map(function (s) {
95 | return s.trim();
96 | })
97 | );
98 | }
99 |
100 | if (rawHeaders.length < rawStyles.length) {
101 | return rawTable;
102 | }
103 |
104 | for (i = 0; i < rawStyles.length; ++i) {
105 | styles.push(parseStyles(rawStyles[i]));
106 | }
107 |
108 | for (i = 0; i < rawHeaders.length; ++i) {
109 | if (showdown.helper.isUndefined(styles[i])) {
110 | styles[i] = '';
111 | }
112 | headers.push(parseHeaders(rawHeaders[i], styles[i]));
113 | }
114 |
115 | for (i = 0; i < rawCells.length; ++i) {
116 | var row = [];
117 | for (var ii = 0; ii < headers.length; ++ii) {
118 | if (showdown.helper.isUndefined(rawCells[i][ii])) {
119 |
120 | }
121 | row.push(parseCells(rawCells[i][ii], styles[ii]));
122 | }
123 | cells.push(row);
124 | }
125 |
126 | return buildTable(headers, cells);
127 | }
128 |
129 | text = globals.converter._dispatch('makehtml.tables.before', text, options, globals).getText();
130 |
131 | // find escaped pipe characters
132 | text = text.replace(/\\(\|)/g, showdown.helper.escapeCharactersCallback);
133 |
134 | // parse multi column tables
135 | text = text.replace(tableRgx, parseTable);
136 |
137 | // parse one column tables
138 | text = text.replace(singeColTblRgx, parseTable);
139 |
140 | text = globals.converter._dispatch('makehtml.tables.after', text, options, globals).getText();
141 |
142 | return text;
143 | });
144 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/underline.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makehtml.underline', function (text, options, globals) {
2 | 'use strict';
3 |
4 | if (!options.underline) {
5 | return text;
6 | }
7 |
8 | text = globals.converter._dispatch('makehtml.underline.before', text, options, globals).getText();
9 |
10 | if (options.literalMidWordUnderscores) {
11 | text = text.replace(/\b___(\S[\s\S]*?)___\b/g, function (wm, txt) {
12 | return '' + txt + '';
13 | });
14 | text = text.replace(/\b__(\S[\s\S]*?)__\b/g, function (wm, txt) {
15 | return '' + txt + '';
16 | });
17 | } else {
18 | text = text.replace(/___(\S[\s\S]*?)___/g, function (wm, m) {
19 | return (/\S$/.test(m)) ? '' + m + '' : wm;
20 | });
21 | text = text.replace(/__(\S[\s\S]*?)__/g, function (wm, m) {
22 | return (/\S$/.test(m)) ? '' + m + '' : wm;
23 | });
24 | }
25 |
26 | // escape remaining underscores to prevent them being parsed by italic and bold
27 | text = text.replace(/(_)/g, showdown.helper.escapeCharactersCallback);
28 |
29 | text = globals.converter._dispatch('makehtml.underline.after', text, options, globals).getText();
30 |
31 | return text;
32 | });
33 |
--------------------------------------------------------------------------------
/src/subParsers/makehtml/unescapeSpecialChars.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Swap back in all the special characters we've hidden.
3 | */
4 | showdown.subParser('makehtml.unescapeSpecialChars', function (text, options, globals) {
5 | 'use strict';
6 | text = globals.converter._dispatch('makehtml.unescapeSpecialChars.before', text, options, globals).getText();
7 |
8 | text = text.replace(/¨E(\d+)E/g, function (wholeMatch, m1) {
9 | var charCodeToReplace = parseInt(m1);
10 | return String.fromCharCode(charCodeToReplace);
11 | });
12 |
13 | text = globals.converter._dispatch('makehtml.unescapeSpecialChars.after', text, options, globals).getText();
14 | return text;
15 | });
16 |
--------------------------------------------------------------------------------
/src/subParsers/makemarkdown/blockquote.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makeMarkdown.blockquote', function (node, options, globals) {
2 | 'use strict';
3 |
4 | var txt = '';
5 | if (node.hasChildNodes()) {
6 | var children = node.childNodes,
7 | childrenLength = children.length;
8 |
9 | for (var i = 0; i < childrenLength; ++i) {
10 | var innerTxt = showdown.subParser('makeMarkdown.node')(children[i], options, globals);
11 |
12 | if (innerTxt === '') {
13 | continue;
14 | }
15 | txt += innerTxt;
16 | }
17 | }
18 | // cleanup
19 | txt = txt.trim();
20 | txt = '> ' + txt.split('\n').join('\n> ');
21 | return txt;
22 | });
23 |
--------------------------------------------------------------------------------
/src/subParsers/makemarkdown/break.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makeMarkdown.break', function () {
2 | 'use strict';
3 |
4 | return ' \n';
5 | });
6 |
--------------------------------------------------------------------------------
/src/subParsers/makemarkdown/codeBlock.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makeMarkdown.codeBlock', function (node, options, globals) {
2 | 'use strict';
3 |
4 | var lang = node.getAttribute('language'),
5 | num = node.getAttribute('precodenum');
6 | return '```' + lang + '\n' + globals.preList[num] + '\n```';
7 | });
8 |
--------------------------------------------------------------------------------
/src/subParsers/makemarkdown/codeSpan.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makeMarkdown.codeSpan', function (node) {
2 | 'use strict';
3 |
4 | return '`' + node.innerHTML + '`';
5 | });
6 |
--------------------------------------------------------------------------------
/src/subParsers/makemarkdown/emphasis.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makeMarkdown.emphasis', function (node, options, globals) {
2 | 'use strict';
3 |
4 | var txt = '';
5 | if (node.hasChildNodes()) {
6 | txt += '*';
7 | var children = node.childNodes,
8 | childrenLength = children.length;
9 | for (var i = 0; i < childrenLength; ++i) {
10 | txt += showdown.subParser('makeMarkdown.node')(children[i], options, globals);
11 | }
12 | txt += '*';
13 | }
14 | return txt;
15 | });
16 |
--------------------------------------------------------------------------------
/src/subParsers/makemarkdown/header.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makeMarkdown.header', function (node, options, globals, headerLevel) {
2 | 'use strict';
3 |
4 | var headerMark = new Array(headerLevel + 1).join('#'),
5 | txt = '';
6 |
7 | if (node.hasChildNodes()) {
8 | txt = headerMark + ' ';
9 | var children = node.childNodes,
10 | childrenLength = children.length;
11 |
12 | for (var i = 0; i < childrenLength; ++i) {
13 | txt += showdown.subParser('makeMarkdown.node')(children[i], options, globals);
14 | }
15 | }
16 | return txt;
17 | });
18 |
--------------------------------------------------------------------------------
/src/subParsers/makemarkdown/hr.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makeMarkdown.hr', function () {
2 | 'use strict';
3 |
4 | return '---';
5 | });
6 |
--------------------------------------------------------------------------------
/src/subParsers/makemarkdown/image.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makeMarkdown.image', function (node) {
2 | 'use strict';
3 |
4 | var txt = '';
5 | if (node.hasAttribute('src') && node.getAttribute('src') !== '') {
6 | txt += ' + '>';
8 | if (node.hasAttribute('width') && node.hasAttribute('height')) {
9 | var width = node.getAttribute('width');
10 | var height = node.getAttribute('height');
11 | txt += ' =' + (width === 'auto' ? '*' : width) + 'x' + (height === 'auto' ? '*' : height);
12 | }
13 |
14 | if (node.hasAttribute('title')) {
15 | txt += ' "' + node.getAttribute('title') + '"';
16 | }
17 | txt += ')';
18 | }
19 | return txt;
20 | });
21 |
--------------------------------------------------------------------------------
/src/subParsers/makemarkdown/input.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makeMarkdown.input', function (node, options, globals) {
2 | 'use strict';
3 |
4 | var txt = '';
5 | if (node.getAttribute('checked') !== null) {
6 | txt += '[x]';
7 | } else {
8 | txt += '[ ]';
9 | }
10 | var children = node.childNodes,
11 | childrenLength = children.length;
12 | for (var i = 0; i < childrenLength; ++i) {
13 | txt += showdown.subParser('makeMarkdown.node')(children[i], options, globals);
14 | }
15 | return txt;
16 | });
17 |
--------------------------------------------------------------------------------
/src/subParsers/makemarkdown/links.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makeMarkdown.links', function (node, options, globals) {
2 | 'use strict';
3 |
4 | var txt = '';
5 | if (node.hasChildNodes() && node.hasAttribute('href')) {
6 | var children = node.childNodes,
7 | childrenLength = children.length;
8 |
9 | // special case for mentions
10 | // to simplify (and not make stuff really complicated) mentions will only work in this circumstance:
11 | // @user
12 | // that is, if there's a "user-mention" class and option ghMentions is true
13 | // otherwise is ignored
14 | var classes = node.getAttribute('class');
15 | if (options.ghMentions && /(?:^| )user-mention\b/.test(classes)) {
16 | for (var ii = 0; ii < childrenLength; ++ii) {
17 | txt += showdown.subParser('makeMarkdown.node')(children[ii], options, globals);
18 | }
19 |
20 | } else {
21 | txt = '[';
22 | for (var i = 0; i < childrenLength; ++i) {
23 | txt += showdown.subParser('makeMarkdown.node')(children[i], options, globals);
24 | }
25 | txt += '](';
26 | txt += '<' + node.getAttribute('href') + '>';
27 | if (node.hasAttribute('title')) {
28 | txt += ' "' + node.getAttribute('title') + '"';
29 | }
30 | txt += ')';
31 | }
32 |
33 |
34 | }
35 | return txt;
36 | });
37 |
--------------------------------------------------------------------------------
/src/subParsers/makemarkdown/list.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makeMarkdown.list', function (node, options, globals, type) {
2 | 'use strict';
3 |
4 | var txt = '';
5 | if (!node.hasChildNodes()) {
6 | return '';
7 | }
8 | var listItems = node.childNodes,
9 | listItemsLenght = listItems.length,
10 | listNum = node.getAttribute('start') || 1;
11 |
12 | for (var i = 0; i < listItemsLenght; ++i) {
13 | if (typeof listItems[i].tagName === 'undefined' || listItems[i].tagName.toLowerCase() !== 'li') {
14 | continue;
15 | }
16 |
17 | // define the bullet to use in list
18 | var bullet = '';
19 | if (type === 'ol') {
20 | bullet = listNum.toString() + '. ';
21 | } else {
22 | bullet = '- ';
23 | }
24 |
25 | // parse list item
26 | txt += bullet + showdown.subParser('makeMarkdown.listItem')(listItems[i], options, globals);
27 | ++listNum;
28 | }
29 |
30 | return txt.trim();
31 | });
32 |
--------------------------------------------------------------------------------
/src/subParsers/makemarkdown/listItem.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makeMarkdown.listItem', function (node, options, globals) {
2 | 'use strict';
3 |
4 | var listItemTxt = '';
5 |
6 | var children = node.childNodes,
7 | childrenLenght = children.length;
8 |
9 | for (var i = 0; i < childrenLenght; ++i) {
10 | listItemTxt += showdown.subParser('makeMarkdown.node')(children[i], options, globals);
11 | }
12 | // if it's only one liner, we need to add a newline at the end
13 | if (!/\n$/.test(listItemTxt)) {
14 | listItemTxt += '\n';
15 | } else {
16 | // it's multiparagraph, so we need to indent
17 | listItemTxt = listItemTxt
18 | .split('\n')
19 | .join('\n ')
20 | .replace(/^ {4}$/gm, '')
21 | .replace(/\n\n+/g, '\n\n');
22 | }
23 |
24 | return listItemTxt;
25 | });
26 |
--------------------------------------------------------------------------------
/src/subParsers/makemarkdown/node.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | showdown.subParser('makeMarkdown.node', function (node, options, globals, spansOnly) {
4 | 'use strict';
5 |
6 | spansOnly = spansOnly || false;
7 |
8 | var txt = '';
9 |
10 | // edge case of text without wrapper paragraph
11 | if (node.nodeType === 3) {
12 | return showdown.subParser('makeMarkdown.txt')(node, options, globals);
13 | }
14 |
15 | // HTML comment
16 | if (node.nodeType === 8) {
17 | return '\n\n';
18 | }
19 |
20 | // process only node elements
21 | if (node.nodeType !== 1) {
22 | return '';
23 | }
24 |
25 | var tagName = node.tagName.toLowerCase();
26 |
27 | switch (tagName) {
28 |
29 | //
30 | // BLOCKS
31 | //
32 | case 'h1':
33 | if (!spansOnly) { txt = showdown.subParser('makeMarkdown.header')(node, options, globals, 1) + '\n\n'; }
34 | break;
35 | case 'h2':
36 | if (!spansOnly) { txt = showdown.subParser('makeMarkdown.header')(node, options, globals, 2) + '\n\n'; }
37 | break;
38 | case 'h3':
39 | if (!spansOnly) { txt = showdown.subParser('makeMarkdown.header')(node, options, globals, 3) + '\n\n'; }
40 | break;
41 | case 'h4':
42 | if (!spansOnly) { txt = showdown.subParser('makeMarkdown.header')(node, options, globals, 4) + '\n\n'; }
43 | break;
44 | case 'h5':
45 | if (!spansOnly) { txt = showdown.subParser('makeMarkdown.header')(node, options, globals, 5) + '\n\n'; }
46 | break;
47 | case 'h6':
48 | if (!spansOnly) { txt = showdown.subParser('makeMarkdown.header')(node, options, globals, 6) + '\n\n'; }
49 | break;
50 |
51 | case 'p':
52 | if (!spansOnly) { txt = showdown.subParser('makeMarkdown.paragraph')(node, options, globals) + '\n\n'; }
53 | break;
54 |
55 | case 'blockquote':
56 | if (!spansOnly) { txt = showdown.subParser('makeMarkdown.blockquote')(node, options, globals) + '\n\n'; }
57 | break;
58 |
59 | case 'hr':
60 | if (!spansOnly) { txt = showdown.subParser('makeMarkdown.hr')(node, options, globals) + '\n\n'; }
61 | break;
62 |
63 | case 'ol':
64 | if (!spansOnly) { txt = showdown.subParser('makeMarkdown.list')(node, options, globals, 'ol') + '\n\n'; }
65 | break;
66 |
67 | case 'ul':
68 | if (!spansOnly) { txt = showdown.subParser('makeMarkdown.list')(node, options, globals, 'ul') + '\n\n'; }
69 | break;
70 |
71 | case 'precode':
72 | if (!spansOnly) { txt = showdown.subParser('makeMarkdown.codeBlock')(node, options, globals) + '\n\n'; }
73 | break;
74 |
75 | case 'pre':
76 | if (!spansOnly) { txt = showdown.subParser('makeMarkdown.pre')(node, options, globals) + '\n\n'; }
77 | break;
78 |
79 | case 'table':
80 | if (!spansOnly) { txt = showdown.subParser('makeMarkdown.table')(node, options, globals) + '\n\n'; }
81 | break;
82 |
83 | //
84 | // SPANS
85 | //
86 | case 'code':
87 | txt = showdown.subParser('makeMarkdown.codeSpan')(node, options, globals);
88 | break;
89 |
90 | case 'em':
91 | case 'i':
92 | txt = showdown.subParser('makeMarkdown.emphasis')(node, options, globals);
93 | break;
94 |
95 | case 'strong':
96 | case 'b':
97 | txt = showdown.subParser('makeMarkdown.strong')(node, options, globals);
98 | break;
99 |
100 | case 'del':
101 | txt = showdown.subParser('makeMarkdown.strikethrough')(node, options, globals);
102 | break;
103 |
104 | case 'a':
105 | txt = showdown.subParser('makeMarkdown.links')(node, options, globals);
106 | break;
107 |
108 | case 'img':
109 | txt = showdown.subParser('makeMarkdown.image')(node, options, globals);
110 | break;
111 |
112 | case 'br':
113 | txt = showdown.subParser('makeMarkdown.break')(node, options, globals);
114 | break;
115 |
116 | case 'input':
117 | txt = showdown.subParser('makeMarkdown.input')(node, options, globals);
118 | break;
119 |
120 | default:
121 | txt = node.outerHTML + '\n\n';
122 | }
123 |
124 | // common normalization
125 | // TODO eventually
126 |
127 | return txt;
128 | });
129 |
--------------------------------------------------------------------------------
/src/subParsers/makemarkdown/paragraph.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makeMarkdown.paragraph', function (node, options, globals) {
2 | 'use strict';
3 |
4 | var txt = '';
5 | if (node.hasChildNodes()) {
6 | var children = node.childNodes,
7 | childrenLength = children.length;
8 | for (var i = 0; i < childrenLength; ++i) {
9 | txt += showdown.subParser('makeMarkdown.node')(children[i], options, globals);
10 | }
11 | }
12 |
13 | // some text normalization
14 | txt = txt.trim();
15 |
16 | return txt;
17 | });
18 |
--------------------------------------------------------------------------------
/src/subParsers/makemarkdown/pre.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makeMarkdown.pre', function (node, options, globals) {
2 | 'use strict';
3 |
4 | var num = node.getAttribute('prenum');
5 | return '' + globals.preList[num] + '
';
6 | });
7 |
--------------------------------------------------------------------------------
/src/subParsers/makemarkdown/strikethrough.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makeMarkdown.strikethrough', function (node, options, globals) {
2 | 'use strict';
3 |
4 | var txt = '';
5 | if (node.hasChildNodes()) {
6 | txt += '~~';
7 | var children = node.childNodes,
8 | childrenLength = children.length;
9 | for (var i = 0; i < childrenLength; ++i) {
10 | txt += showdown.subParser('makeMarkdown.node')(children[i], options, globals);
11 | }
12 | txt += '~~';
13 | }
14 | return txt;
15 | });
16 |
--------------------------------------------------------------------------------
/src/subParsers/makemarkdown/strong.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makeMarkdown.strong', function (node, options, globals) {
2 | 'use strict';
3 |
4 | var txt = '';
5 | if (node.hasChildNodes()) {
6 | txt += '**';
7 | var children = node.childNodes,
8 | childrenLength = children.length;
9 | for (var i = 0; i < childrenLength; ++i) {
10 | txt += showdown.subParser('makeMarkdown.node')(children[i], options, globals);
11 | }
12 | txt += '**';
13 | }
14 | return txt;
15 | });
16 |
--------------------------------------------------------------------------------
/src/subParsers/makemarkdown/table.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makeMarkdown.table',
2 | /**
3 | *
4 | * @param {DocumentFragment} node
5 | * @param {{}} options
6 | * @param {{}} globals
7 | * @returns {string}
8 | */
9 | function (node, options, globals) {
10 | 'use strict';
11 |
12 | var txt = '',
13 | tableArray = [[], []],
14 | headings,
15 | rows = [],
16 | colCount,
17 | i,
18 | ii;
19 |
20 | /**
21 | * @param {Element} tr
22 | */
23 | function iterateRow (tr) {
24 | var children = tr.childNodes,
25 | cols = [];
26 | // we need to iterate by order, since td and th can be used interchangeably and in any order
27 | // we will ignore malformed stuff, comments and floating text.
28 | for (var i = 0; i < children.length; ++i) {
29 | var childName = children[i].nodeName.toUpperCase();
30 | if (childName === 'TD' || childName === 'TH') {
31 | cols.push(children[i]);
32 | }
33 | }
34 | return cols;
35 | }
36 |
37 |
38 | // first lets look for
39 | // we will ignore thead without children
40 | // also, since markdown doesn't support tables with multiple heading rows, only the first one will be transformed
41 | // the rest will count as regular rows
42 | if (node.querySelectorAll(':scope>thead').length !== 0 && node.querySelectorAll(':scope>thead>tr').length !== 0) {
43 | var thead = node.querySelectorAll(':scope>thead>tr');
44 |
45 | // thead>tr can have td and th children
46 | for (i = 0; i < thead.length; ++i) {
47 | rows.push(iterateRow(thead[i]));
48 | }
49 | }
50 |
51 | // now let's look for tbody
52 | // we will ignore tbody without children
53 | if (node.querySelectorAll(':scope>tbody').length !== 0 && node.querySelectorAll(':scope>tbody>tr').length !== 0) {
54 | var tbody = node.querySelectorAll(':scope>tbody>tr');
55 | // tbody>tr can have td and th children, although th are not very screen reader friendly
56 | for (i = 0; i < tbody.length; ++i) {
57 | rows.push(iterateRow(tbody[i]));
58 | }
59 | }
60 |
61 | // now look for tfoot
62 | if (node.querySelectorAll(':scope>tfoot').length !== 0 && node.querySelectorAll(':scope>tfoot>tr').length !== 0) {
63 | var tfoot = node.querySelectorAll(':scope>tfoot>tr');
64 | // tfoot>tr can have td and th children, although th are not very screen reader friendly
65 | for (i = 0; i < tfoot.length; ++i) {
66 | rows.push(iterateRow(tfoot[i]));
67 | }
68 | }
69 |
70 | // lastly look for naked tr
71 | if (node.querySelectorAll(':scope>tr').length !== 0) {
72 |
73 | var tr = node.querySelectorAll(':scope>tr');
74 | // tfoot>tr can have td and th children, although th are not very screen reader friendly
75 | for (i = 0; i < tr.length; ++i) {
76 | rows.push(iterateRow(tr[i]));
77 | }
78 | }
79 |
80 | // TODO: implement in tables https://developer.mozilla.org/pt-BR/docs/Web/HTML/Element/caption
81 | // note: is ignored, since they are basically styling
82 |
83 | // we need now to account for cases of completely empty tables, like
or equivalent
84 | if (rows.length === 0) {
85 | // table is empty, return empty text
86 | return txt;
87 | }
88 |
89 | // count the first row. We need it to trim the table (if table rows have inconsistent number of columns)
90 | colCount = rows[0].length;
91 |
92 | // let's shift the first row as a heading
93 | headings = rows.shift();
94 |
95 | for (i = 0; i < headings.length; ++i) {
96 | var headContent = showdown.subParser('makeMarkdown.tableCell')(headings[i], globals),
97 | align = '---';
98 |
99 | if (headings[i].hasAttribute('style')) {
100 | var style = headings[i].getAttribute('style').toLowerCase().replace(/\s/g, '');
101 | switch (style) {
102 | case 'text-align:left;':
103 | align = ':---';
104 | break;
105 | case 'text-align:right;':
106 | align = '---:';
107 | break;
108 | case 'text-align:center;':
109 | align = ':---:';
110 | break;
111 | }
112 | }
113 | tableArray[0][i] = headContent.trim();
114 | tableArray[1][i] = align;
115 | }
116 |
117 | // now iterate through the rows and create the pseudo output (not pretty yet)
118 | for (i = 0; i < rows.length; ++i) {
119 | var r = tableArray.push([]) - 1;
120 |
121 | for (ii = 0; ii < colCount; ++ii) {
122 | var cellContent = ' ';
123 | if (typeof rows[i][ii] !== 'undefined') {
124 | // Note: if rows[i][ii] is undefined, it means the row has fewer elements than the header,
125 | // and empty content will be added
126 | cellContent = showdown.subParser('makeMarkdown.tableCell')(rows[i][ii], globals);
127 | }
128 | tableArray[r].push(cellContent);
129 | }
130 | }
131 |
132 | // now tidy up the output, aligning cells and stuff
133 | var cellSpacesCount = 3;
134 | for (i = 0; i < tableArray.length; ++i) {
135 | for (ii = 0; ii < tableArray[i].length; ++ii) {
136 | var strLen = tableArray[i][ii].length;
137 | if (strLen > cellSpacesCount) {
138 | cellSpacesCount = strLen;
139 | }
140 | }
141 | }
142 |
143 | for (i = 0; i < tableArray.length; ++i) {
144 | for (ii = 0; ii < tableArray[i].length; ++ii) {
145 | if (i === 1) {
146 | if (tableArray[i][ii].slice(-1) === ':') {
147 | tableArray[i][ii] = showdown.helper.padEnd(tableArray[i][ii].slice(0, -1), cellSpacesCount - 1, '-') + ':';
148 | } else {
149 | tableArray[i][ii] = showdown.helper.padEnd(tableArray[i][ii], cellSpacesCount, '-');
150 | }
151 |
152 | } else {
153 | tableArray[i][ii] = showdown.helper.padEnd(tableArray[i][ii], cellSpacesCount);
154 | }
155 | }
156 | txt += '| ' + tableArray[i].join(' | ') + ' |\n';
157 | }
158 |
159 | return txt.trim();
160 | }
161 | );
162 |
--------------------------------------------------------------------------------
/src/subParsers/makemarkdown/tableCell.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makeMarkdown.tableCell', function (node, options, globals) {
2 | 'use strict';
3 |
4 | var txt = '';
5 | if (!node.hasChildNodes()) {
6 | return '';
7 | }
8 | var children = node.childNodes,
9 | childrenLength = children.length;
10 |
11 | for (var i = 0; i < childrenLength; ++i) {
12 | txt += showdown.subParser('makeMarkdown.node')(children[i], options, globals, true);
13 | }
14 | return txt.trim();
15 | });
16 |
--------------------------------------------------------------------------------
/src/subParsers/makemarkdown/txt.js:
--------------------------------------------------------------------------------
1 | showdown.subParser('makeMarkdown.txt', function (node) {
2 | 'use strict';
3 |
4 | var txt = node.nodeValue;
5 |
6 | // multiple spaces are collapsed
7 | txt = txt.replace(/ +/g, ' ');
8 |
9 | // replace the custom ¨NBSP; with a space
10 | txt = txt.replace(/¨NBSP;/g, ' ');
11 |
12 | // ", <, > and & should replace escaped html entities
13 | txt = showdown.helper.unescapeHTMLEntities(txt);
14 |
15 | // escape markdown magic characters
16 | // emphasis, strong and strikethrough - can appear everywhere
17 | // we also escape pipe (|) because of tables
18 | // and escape ` because of code blocks and spans
19 | txt = txt.replace(/([*_~|`])/g, '\\$1');
20 |
21 | // escape > because of blockquotes
22 | txt = txt.replace(/^(\s*)>/g, '\\$1>');
23 |
24 | // hash character, only troublesome at the beginning of a line because of headers
25 | txt = txt.replace(/^#/gm, '\\#');
26 |
27 | // horizontal rules
28 | txt = txt.replace(/^(\s*)([-=]{3,})(\s*)$/, '$1\\$2$3');
29 |
30 | // dot, because of ordered lists, only troublesome at the beginning of a line when preceded by an integer
31 | txt = txt.replace(/^( {0,3}\d+)\./gm, '$1\\.');
32 |
33 | // +, * and -, at the beginning of a line becomes a list, so we need to escape them also (asterisk was already escaped)
34 | txt = txt.replace(/^( {0,3})([+-])/gm, '$1\\$2');
35 |
36 | // images and links, ] followed by ( is problematic, so we escape it
37 | txt = txt.replace(/]([\s]*)\(/g, '\\]$1\\(');
38 |
39 | // reference URIs must also be escaped
40 | txt = txt.replace(/^ {0,3}\[([\S \t]*?)]:/gm, '\\[$1]:');
41 |
42 | return txt;
43 | });
44 |
--------------------------------------------------------------------------------