├── .gitignore ├── .spmignore ├── CONTRIBUTING.md ├── Gruntfile.js ├── Makefile ├── README.md ├── component.json ├── docs ├── index.html ├── markdown.html ├── markdown.md ├── marked.js └── yue.css ├── editor.css ├── package.json ├── paper.css ├── src ├── editor.js └── intro.js └── vendor ├── codemirror.js ├── continuelist.js ├── icomoon ├── fonts │ ├── icomoon.dev.svg │ ├── icomoon.eot │ ├── icomoon.svg │ ├── icomoon.ttf │ └── icomoon.woff ├── index.html ├── license.txt ├── lte-ie7.js └── style.css ├── markdown.js └── xml.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | npm-debug.log 5 | tmp 6 | build 7 | dist 8 | -------------------------------------------------------------------------------- /.spmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | npm-debug.log 4 | tmp 5 | build 6 | dist 7 | src 8 | icomoon 9 | codemirror 10 | docs 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | There are more than one way to contribute, and I will appreciate any way you choose. 4 | 5 | - tell your friends about lepture/editor, let lepture/editor to be known 6 | - discuss editor, and submit bugs with github issues 7 | - send patch with github pull request 8 | 9 | English and Chinsese issuses are acceptable, but English is prefered. 10 | 11 | Pull request and git commit message only accept English, if your commit message is in other language, it will be rejected. 12 | 13 | 14 | ## Codebase 15 | 16 | The codebase of editor is highly linted, as a way to keep all code written in a particular style for readability. You should follow the code style. 17 | 18 | A little hint to make things simple: 19 | 20 | - when you cloned this repo, run ``npm install`` 21 | - check the code style with ``grunt jshint`` 22 | 23 | ## Grunt 24 | 25 | If you haven't installed `grunt` yet, grab the command line: 26 | 27 | ``` 28 | $ npm install grunt-cli -g 29 | ``` 30 | 31 | Create a livereload server for your development: 32 | 33 | ``` 34 | $ grunt server 35 | ``` 36 | 37 | And open your browser at `http://localhost:8000`. 38 | 39 | The source code is in `src`. 40 | 41 | ## Git Help 42 | 43 | Something you should know about git. 44 | 45 | - don't add any code on the master branch, create a new one 46 | - don't add too many code in one pull request, you can't add too many features in one pull request 47 | 48 | Hint of git: 49 | 50 | ``` 51 | $ git branch [featurename] 52 | $ git checkout [featurename] 53 | ``` 54 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = function(grunt) { 4 | var pkg = require('./package.json'); 5 | 6 | grunt.initConfig({ 7 | pkg: pkg, 8 | connect: { 9 | livereload: { 10 | options: { 11 | port: 8000, 12 | middleware: function(connect) { 13 | return [ 14 | require('connect-livereload')(), 15 | connect.static(path.resolve('build')), 16 | connect.directory(path.resolve('build')) 17 | ]; 18 | } 19 | } 20 | }, 21 | }, 22 | watch: { 23 | editor: { 24 | files: ['*.css', 'src/*'], 25 | tasks: ['build'], 26 | options: { 27 | livereload: true 28 | } 29 | } 30 | }, 31 | transport: { 32 | component: { 33 | options: { 34 | dest: 'index.js', 35 | header: '', 36 | footer: 'module.exports = Editor' 37 | } 38 | }, 39 | window: {} 40 | } 41 | }); 42 | 43 | grunt.registerTask('concat', function() { 44 | var data = grunt.file.read('vendor/codemirror.js'); 45 | data = data.replace('window.CodeMirror', 'var CodeMirror'); 46 | ['continuelist', 'xml', 'markdown'].forEach(function(name) { 47 | data += '\n' + grunt.file.read('vendor/' + name + '.js'); 48 | }); 49 | data += '\n' + grunt.file.read('src/intro.js'); 50 | data += '\n' + grunt.file.read('src/editor.js'); 51 | grunt.file.write('tmp/editor.js', data); 52 | }); 53 | 54 | grunt.registerMultiTask('transport', function() { 55 | var options = this.options({ 56 | src: 'tmp/editor.js', 57 | dest: 'build/editor.js', 58 | header: '(function(global) {', 59 | footer: 'global.Editor = Editor;\n})(this);' 60 | }); 61 | var data = grunt.file.read(options.src); 62 | data = [options.header, data, options.footer].join('\n'); 63 | grunt.file.write(options.dest, data); 64 | }); 65 | 66 | grunt.registerTask('copy', function() { 67 | var dir = 'vendor/icomoon/fonts'; 68 | grunt.file.recurse(dir, function(fpath) { 69 | var fname = path.relative(dir, fpath); 70 | grunt.file.copy(fpath, path.join('build', 'fonts', fname)); 71 | }); 72 | var data = grunt.file.read('vendor/icomoon/style.css'); 73 | data += grunt.file.read('paper.css'); 74 | data += grunt.file.read('editor.css'); 75 | grunt.file.write('build/editor.css', data); 76 | grunt.file.copy('docs/index.html', 'build/index.html'); 77 | grunt.file.copy('docs/markdown.md', 'build/markdown.md'); 78 | grunt.file.copy('docs/markdown.html', 'build/markdown.html'); 79 | grunt.file.copy('docs/yue.css', 'build/yue.css'); 80 | grunt.file.copy('docs/marked.js', 'build/marked.js'); 81 | }); 82 | 83 | grunt.loadNpmTasks('grunt-contrib-connect'); 84 | grunt.loadNpmTasks('grunt-contrib-watch'); 85 | 86 | grunt.registerTask('build', ['concat', 'transport:window', 'copy']); 87 | grunt.registerTask('server', ['build', 'connect', 'watch']); 88 | grunt.registerTask('default', ['server']); 89 | }; 90 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | build: components index.js editor.css 3 | @component build --dev 4 | 5 | components: component.json 6 | @component install --dev 7 | 8 | clean: 9 | rm -fr build components template.js 10 | 11 | publish: 12 | @ghp-import build -p 13 | 14 | .PHONY: clean 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Editor 2 | 3 | > A markdown editor you really want. 4 | 5 | ## Sponsors 6 | 7 | Editor is sponsored by [Typlog](https://typlog.com/). 8 | 9 | ## Overview 10 | 11 | Editor is not a WYSIWYG editor, it is a plain text markdown editor. Thanks for the great project [codemirror](http://codemirror.net/), without which editor can never be created. 12 | 13 | ## Basic Usage 14 | 15 | The easiest way to use Editor is to simply load the script and stylesheet: 16 | 17 | ```html 18 | 19 | 20 | 21 | ``` 22 | 23 | You can also use [jsdelivr CDN](http://www.jsdelivr.com/#!editor): 24 | 25 | ```html 26 | 27 | 28 | 29 | ``` 30 | 31 | Having done this, an editor instance can be created: 32 | 33 | ```js 34 | var editor = new Editor(); 35 | editor.render(); 36 | ``` 37 | 38 | The editor will take the position of the first ` 41 | 42 | 43 | 44 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /docs/markdown.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Markdown 7 | 8 | 23 | 38 | 39 | 40 |
41 |

Markdown

42 | 43 |
44 | 45 |

Markdown is a text formatting syntax inspired on plain text email. In the words of its creator, John Gruber:

46 | 47 |

The idea is that a Markdown-formatted document should be publishable as-is, as plain text, without looking like it’s been marked up with tags or formatting instructions.

48 | 49 |

Syntax Guide

50 | 51 |

Strong and Emphasize

52 | 53 |
*emphasize*    **strong**
 54 | _emphasize_    __strong__
 55 | 
56 | 57 |

Shortcuts

58 | 59 | 67 | 68 | 69 |

Links

70 | 71 |

Inline links:

72 | 73 |
[link text](http://url.com/ "title")
 74 | [link text](http://url.com/)
 75 | <http://url.com>
 76 | 
77 | 78 |

Reference-style links:

79 | 80 |
[link text][id]
 81 | 
 82 |     [id]: http://url.com "title"
 83 | 
84 | 85 |

Shortcuts

86 | 87 | 92 | 93 | 94 |

Images

95 | 96 |

Inline images:

97 | 98 |
![alt text](http://path/to/img.jpg "title")
 99 | ![alt text](http://path/to/img.jpg)
100 | 
101 | 102 |

Reference-style links:

103 | 104 |
![alt text][id]
105 | 
106 |     [id]: http://path/to/img.jpg "title"
107 | 
108 | 109 |

Shortcuts

110 | 111 | 116 | 117 | 118 |

Headers

119 | 120 |

Atx-style headers:

121 | 122 |
# h1
123 | ## h2
124 | ### h3
125 | …
126 | 
127 | 128 |

Closing # are optional.

129 | 130 |
# h1 #
131 | ## h2 ##
132 | …
133 | 
134 | 135 |

Lists

136 | 137 |

Ordered list without paragraphs:

138 | 139 |
1. foo
140 | 2. bar
141 | 
142 | 143 |

Unordered list with paragraphs:

144 | 145 |
* A list item.
146 | 
147 |   With multiple paragraphs.
148 | 
149 | * bar
150 | 
151 | 152 |

You can nest them:

153 | 154 |
* Abacus
155 |   * anser
156 | * Bubbles
157 |   1. bunk
158 |   2. bupkis
159 |      * bar
160 |   3. burper
161 | * Cunning
162 | 
163 | 164 |

Shortcuts

165 | 166 | 174 | 175 | 176 |

Blockquotes

177 | 178 |
> Email-style angle brackets
179 | > are used for blockquotes.
180 | 
181 | > > And, they can be nested.
182 | 
183 | > #### Headers in blockquotes
184 | > 
185 | > * You can quote a list.
186 | > * Etc.
187 | 
188 | 189 |

Shortcuts

190 | 191 | 196 | 197 | 198 |

Code Spans

199 | 200 |
`<code>` spans are delimited
201 | by backticks.
202 | 
203 | You can include literal backticks
204 | like `` `this` ``.
205 | 
206 | 207 |

Code Blocks

208 | 209 |

Indent at least 4 spaces or 1 tab.

210 | 211 |
This is a normal paragraph
212 | 
213 |     this is code block
214 | 
215 | 216 |

Horizontal Rules

217 | 218 |

Three or more dashes for asterisks.

219 | 220 |
---
221 | 
222 | * * *
223 | 
224 | - - - - 
225 | 
226 | 227 |

Manual Line Breaks

228 | 229 |

End a line with two or more spaces:

230 | 231 |
Roses are red, [space][space]
232 | Violets are blue. [space][space]
233 | 
234 |
235 | 236 | 237 | -------------------------------------------------------------------------------- /docs/markdown.md: -------------------------------------------------------------------------------- 1 | # Markdown 2 | 3 | ---- 4 | 5 | Markdown is a text formatting syntax inspired on plain text email. In the words of its creator, [John Gruber][]: 6 | 7 | > The idea is that a Markdown-formatted document should be publishable as-is, as plain text, without looking like it’s been marked up with tags or formatting instructions. 8 | 9 | [John Gruber]: http://daringfireball.net/ 10 | 11 | 12 | ## Syntax Guide 13 | 14 | ### Strong and Emphasize 15 | 16 | ``` 17 | *emphasize* **strong** 18 | _emphasize_ __strong__ 19 | ``` 20 | 21 | **Shortcuts** 22 | 23 | - Add/remove bold: 24 | 25 | ⌘-B for Mac / Ctrl-B for Windows and Linux 26 | 27 | - Add/remove italic: 28 | 29 | ⌘-I for Mac / Ctrl-I for windows and Linux 30 | 31 | 32 | ### Links 33 | 34 | Inline links: 35 | 36 | ``` 37 | [link text](http://url.com/ "title") 38 | [link text](http://url.com/) 39 | 40 | ``` 41 | 42 | Reference-style links: 43 | 44 | ``` 45 | [link text][id] 46 | 47 | [id]: http://url.com "title" 48 | ``` 49 | 50 | **Shortcuts** 51 | 52 | - Add link: 53 | 54 | ⌘-K for Mac / Ctrl-K for Windows and Linux 55 | 56 | 57 | ### Images 58 | 59 | Inline images: 60 | 61 | ``` 62 | ![alt text](http://path/to/img.jpg "title") 63 | ![alt text](http://path/to/img.jpg) 64 | ``` 65 | 66 | Reference-style links: 67 | 68 | ``` 69 | ![alt text][id] 70 | 71 | [id]: http://path/to/img.jpg "title" 72 | ``` 73 | 74 | **Shortcuts** 75 | 76 | - Add image: 77 | 78 | ⌥-⌘-I for Mac / Alt-Ctrl-I for Windows and Linux 79 | 80 | 81 | ### Headers 82 | 83 | Atx-style headers: 84 | 85 | ``` 86 | # h1 87 | ## h2 88 | ### h3 89 | … 90 | ``` 91 | 92 | Closing # are optional. 93 | 94 | ``` 95 | # h1 # 96 | ## h2 ## 97 | … 98 | ``` 99 | 100 | 101 | ### Lists 102 | 103 | Ordered list without paragraphs: 104 | 105 | ``` 106 | 1. foo 107 | 2. bar 108 | ``` 109 | 110 | Unordered list with paragraphs: 111 | 112 | ``` 113 | * A list item. 114 | 115 | With multiple paragraphs. 116 | 117 | * bar 118 | ``` 119 | 120 | You can nest them: 121 | 122 | ``` 123 | * Abacus 124 | * anser 125 | * Bubbles 126 | 1. bunk 127 | 2. bupkis 128 | * bar 129 | 3. burper 130 | * Cunning 131 | ``` 132 | 133 | **Shortcuts** 134 | 135 | - Add/remove unordered list: 136 | 137 | ⌘-L for Mac / Ctrl-L for Windows and Linux 138 | 139 | - Add/remove ordered list: 140 | 141 | ⌥-⌘-L for Mac / Alt-Ctrl-L for Windows and Linux 142 | 143 | 144 | ### Blockquotes 145 | 146 | ``` 147 | > Email-style angle brackets 148 | > are used for blockquotes. 149 | 150 | > > And, they can be nested. 151 | 152 | > #### Headers in blockquotes 153 | > 154 | > * You can quote a list. 155 | > * Etc. 156 | ``` 157 | 158 | **Shortcuts** 159 | 160 | - Add/remove blockquote: 161 | 162 | ⌘-’ for Mac / Ctrl-’ for Windows and Linux 163 | 164 | 165 | ### Code Spans 166 | 167 | ``` 168 | `` spans are delimited 169 | by backticks. 170 | 171 | You can include literal backticks 172 | like `` `this` ``. 173 | ``` 174 | 175 | ### Code Blocks 176 | 177 | Indent at least 4 spaces or 1 tab. 178 | 179 | ``` 180 | This is a normal paragraph 181 | 182 | this is code block 183 | ``` 184 | 185 | 186 | ### Horizontal Rules 187 | 188 | Three or more dashes for asterisks. 189 | 190 | ``` 191 | --- 192 | 193 | * * * 194 | 195 | - - - - 196 | ``` 197 | 198 | ### Manual Line Breaks 199 | 200 | End a line with two or more spaces: 201 | 202 | ``` 203 | Roses are red, [space][space] 204 | Violets are blue. [space][space] 205 | ``` 206 | -------------------------------------------------------------------------------- /docs/marked.js: -------------------------------------------------------------------------------- 1 | /** 2 | * marked - a markdown parser 3 | * Copyright (c) 2011-2013, Christopher Jeffrey. (MIT Licensed) 4 | * https://github.com/chjj/marked 5 | */ 6 | 7 | ;(function() { 8 | 9 | /** 10 | * Block-Level Grammar 11 | */ 12 | 13 | var block = { 14 | newline: /^\n+/, 15 | code: /^( {4}[^\n]+\n*)+/, 16 | fences: noop, 17 | hr: /^( *[-*_]){3,} *(?:\n+|$)/, 18 | heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/, 19 | nptable: noop, 20 | lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/, 21 | blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/, 22 | list: /^( *)(bull) [\s\S]+?(?:hr|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, 23 | html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/, 24 | def: /^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/, 25 | table: noop, 26 | paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/, 27 | text: /^[^\n]+/ 28 | }; 29 | 30 | block.bullet = /(?:[*+-]|\d+\.)/; 31 | block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/; 32 | block.item = replace(block.item, 'gm') 33 | (/bull/g, block.bullet) 34 | (); 35 | 36 | block.list = replace(block.list) 37 | (/bull/g, block.bullet) 38 | ('hr', /\n+(?=(?: *[-*_]){3,} *(?:\n+|$))/) 39 | (); 40 | 41 | block._tag = '(?!(?:' 42 | + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code' 43 | + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo' 44 | + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|@)\\b'; 45 | 46 | block.html = replace(block.html) 47 | ('comment', //) 48 | ('closed', /<(tag)[\s\S]+?<\/\1>/) 49 | ('closing', /])*?>/) 50 | (/tag/g, block._tag) 51 | (); 52 | 53 | block.paragraph = replace(block.paragraph) 54 | ('hr', block.hr) 55 | ('heading', block.heading) 56 | ('lheading', block.lheading) 57 | ('blockquote', block.blockquote) 58 | ('tag', '<' + block._tag) 59 | ('def', block.def) 60 | (); 61 | 62 | /** 63 | * Normal Block Grammar 64 | */ 65 | 66 | block.normal = merge({}, block); 67 | 68 | /** 69 | * GFM Block Grammar 70 | */ 71 | 72 | block.gfm = merge({}, block.normal, { 73 | fences: /^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/, 74 | paragraph: /^/ 75 | }); 76 | 77 | block.gfm.paragraph = replace(block.paragraph) 78 | ('(?!', '(?!' 79 | + block.gfm.fences.source.replace('\\1', '\\2') + '|' 80 | + block.list.source.replace('\\1', '\\3') + '|') 81 | (); 82 | 83 | /** 84 | * GFM + Tables Block Grammar 85 | */ 86 | 87 | block.tables = merge({}, block.gfm, { 88 | nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/, 89 | table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/ 90 | }); 91 | 92 | /** 93 | * Block Lexer 94 | */ 95 | 96 | function Lexer(options) { 97 | this.tokens = []; 98 | this.tokens.links = {}; 99 | this.options = options || marked.defaults; 100 | this.rules = block.normal; 101 | 102 | if (this.options.gfm) { 103 | if (this.options.tables) { 104 | this.rules = block.tables; 105 | } else { 106 | this.rules = block.gfm; 107 | } 108 | } 109 | } 110 | 111 | /** 112 | * Expose Block Rules 113 | */ 114 | 115 | Lexer.rules = block; 116 | 117 | /** 118 | * Static Lex Method 119 | */ 120 | 121 | Lexer.lex = function(src, options) { 122 | var lexer = new Lexer(options); 123 | return lexer.lex(src); 124 | }; 125 | 126 | /** 127 | * Preprocessing 128 | */ 129 | 130 | Lexer.prototype.lex = function(src) { 131 | src = src 132 | .replace(/\r\n|\r/g, '\n') 133 | .replace(/\t/g, ' ') 134 | .replace(/\u00a0/g, ' ') 135 | .replace(/\u2424/g, '\n'); 136 | 137 | return this.token(src, true); 138 | }; 139 | 140 | /** 141 | * Lexing 142 | */ 143 | 144 | Lexer.prototype.token = function(src, top) { 145 | var src = src.replace(/^ +$/gm, '') 146 | , next 147 | , loose 148 | , cap 149 | , bull 150 | , b 151 | , item 152 | , space 153 | , i 154 | , l; 155 | 156 | while (src) { 157 | // newline 158 | if (cap = this.rules.newline.exec(src)) { 159 | src = src.substring(cap[0].length); 160 | if (cap[0].length > 1) { 161 | this.tokens.push({ 162 | type: 'space' 163 | }); 164 | } 165 | } 166 | 167 | // code 168 | if (cap = this.rules.code.exec(src)) { 169 | src = src.substring(cap[0].length); 170 | cap = cap[0].replace(/^ {4}/gm, ''); 171 | this.tokens.push({ 172 | type: 'code', 173 | text: !this.options.pedantic 174 | ? cap.replace(/\n+$/, '') 175 | : cap 176 | }); 177 | continue; 178 | } 179 | 180 | // fences (gfm) 181 | if (cap = this.rules.fences.exec(src)) { 182 | src = src.substring(cap[0].length); 183 | this.tokens.push({ 184 | type: 'code', 185 | lang: cap[2], 186 | text: cap[3] 187 | }); 188 | continue; 189 | } 190 | 191 | // heading 192 | if (cap = this.rules.heading.exec(src)) { 193 | src = src.substring(cap[0].length); 194 | this.tokens.push({ 195 | type: 'heading', 196 | depth: cap[1].length, 197 | text: cap[2] 198 | }); 199 | continue; 200 | } 201 | 202 | // table no leading pipe (gfm) 203 | if (top && (cap = this.rules.nptable.exec(src))) { 204 | src = src.substring(cap[0].length); 205 | 206 | item = { 207 | type: 'table', 208 | header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), 209 | align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), 210 | cells: cap[3].replace(/\n$/, '').split('\n') 211 | }; 212 | 213 | for (i = 0; i < item.align.length; i++) { 214 | if (/^ *-+: *$/.test(item.align[i])) { 215 | item.align[i] = 'right'; 216 | } else if (/^ *:-+: *$/.test(item.align[i])) { 217 | item.align[i] = 'center'; 218 | } else if (/^ *:-+ *$/.test(item.align[i])) { 219 | item.align[i] = 'left'; 220 | } else { 221 | item.align[i] = null; 222 | } 223 | } 224 | 225 | for (i = 0; i < item.cells.length; i++) { 226 | item.cells[i] = item.cells[i].split(/ *\| */); 227 | } 228 | 229 | this.tokens.push(item); 230 | 231 | continue; 232 | } 233 | 234 | // lheading 235 | if (cap = this.rules.lheading.exec(src)) { 236 | src = src.substring(cap[0].length); 237 | this.tokens.push({ 238 | type: 'heading', 239 | depth: cap[2] === '=' ? 1 : 2, 240 | text: cap[1] 241 | }); 242 | continue; 243 | } 244 | 245 | // hr 246 | if (cap = this.rules.hr.exec(src)) { 247 | src = src.substring(cap[0].length); 248 | this.tokens.push({ 249 | type: 'hr' 250 | }); 251 | continue; 252 | } 253 | 254 | // blockquote 255 | if (cap = this.rules.blockquote.exec(src)) { 256 | src = src.substring(cap[0].length); 257 | 258 | this.tokens.push({ 259 | type: 'blockquote_start' 260 | }); 261 | 262 | cap = cap[0].replace(/^ *> ?/gm, ''); 263 | 264 | // Pass `top` to keep the current 265 | // "toplevel" state. This is exactly 266 | // how markdown.pl works. 267 | this.token(cap, top); 268 | 269 | this.tokens.push({ 270 | type: 'blockquote_end' 271 | }); 272 | 273 | continue; 274 | } 275 | 276 | // list 277 | if (cap = this.rules.list.exec(src)) { 278 | src = src.substring(cap[0].length); 279 | bull = cap[2]; 280 | 281 | this.tokens.push({ 282 | type: 'list_start', 283 | ordered: bull.length > 1 284 | }); 285 | 286 | // Get each top-level item. 287 | cap = cap[0].match(this.rules.item); 288 | 289 | next = false; 290 | l = cap.length; 291 | i = 0; 292 | 293 | for (; i < l; i++) { 294 | item = cap[i]; 295 | 296 | // Remove the list item's bullet 297 | // so it is seen as the next token. 298 | space = item.length; 299 | item = item.replace(/^ *([*+-]|\d+\.) +/, ''); 300 | 301 | // Outdent whatever the 302 | // list item contains. Hacky. 303 | if (~item.indexOf('\n ')) { 304 | space -= item.length; 305 | item = !this.options.pedantic 306 | ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') 307 | : item.replace(/^ {1,4}/gm, ''); 308 | } 309 | 310 | // Determine whether the next list item belongs here. 311 | // Backpedal if it does not belong in this list. 312 | if (this.options.smartLists && i !== l - 1) { 313 | b = block.bullet.exec(cap[i + 1])[0]; 314 | if (bull !== b && !(bull.length > 1 && b.length > 1)) { 315 | src = cap.slice(i + 1).join('\n') + src; 316 | i = l - 1; 317 | } 318 | } 319 | 320 | // Determine whether item is loose or not. 321 | // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ 322 | // for discount behavior. 323 | loose = next || /\n\n(?!\s*$)/.test(item); 324 | if (i !== l - 1) { 325 | next = item.charAt(item.length - 1) === '\n'; 326 | if (!loose) loose = next; 327 | } 328 | 329 | this.tokens.push({ 330 | type: loose 331 | ? 'loose_item_start' 332 | : 'list_item_start' 333 | }); 334 | 335 | // Recurse. 336 | this.token(item, false); 337 | 338 | this.tokens.push({ 339 | type: 'list_item_end' 340 | }); 341 | } 342 | 343 | this.tokens.push({ 344 | type: 'list_end' 345 | }); 346 | 347 | continue; 348 | } 349 | 350 | // html 351 | if (cap = this.rules.html.exec(src)) { 352 | src = src.substring(cap[0].length); 353 | this.tokens.push({ 354 | type: this.options.sanitize 355 | ? 'paragraph' 356 | : 'html', 357 | pre: cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style', 358 | text: cap[0] 359 | }); 360 | continue; 361 | } 362 | 363 | // def 364 | if (top && (cap = this.rules.def.exec(src))) { 365 | src = src.substring(cap[0].length); 366 | this.tokens.links[cap[1].toLowerCase()] = { 367 | href: cap[2], 368 | title: cap[3] 369 | }; 370 | continue; 371 | } 372 | 373 | // table (gfm) 374 | if (top && (cap = this.rules.table.exec(src))) { 375 | src = src.substring(cap[0].length); 376 | 377 | item = { 378 | type: 'table', 379 | header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), 380 | align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), 381 | cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n') 382 | }; 383 | 384 | for (i = 0; i < item.align.length; i++) { 385 | if (/^ *-+: *$/.test(item.align[i])) { 386 | item.align[i] = 'right'; 387 | } else if (/^ *:-+: *$/.test(item.align[i])) { 388 | item.align[i] = 'center'; 389 | } else if (/^ *:-+ *$/.test(item.align[i])) { 390 | item.align[i] = 'left'; 391 | } else { 392 | item.align[i] = null; 393 | } 394 | } 395 | 396 | for (i = 0; i < item.cells.length; i++) { 397 | item.cells[i] = item.cells[i] 398 | .replace(/^ *\| *| *\| *$/g, '') 399 | .split(/ *\| */); 400 | } 401 | 402 | this.tokens.push(item); 403 | 404 | continue; 405 | } 406 | 407 | // top-level paragraph 408 | if (top && (cap = this.rules.paragraph.exec(src))) { 409 | src = src.substring(cap[0].length); 410 | this.tokens.push({ 411 | type: 'paragraph', 412 | text: cap[1].charAt(cap[1].length - 1) === '\n' 413 | ? cap[1].slice(0, -1) 414 | : cap[1] 415 | }); 416 | continue; 417 | } 418 | 419 | // text 420 | if (cap = this.rules.text.exec(src)) { 421 | // Top-level should never reach here. 422 | src = src.substring(cap[0].length); 423 | this.tokens.push({ 424 | type: 'text', 425 | text: cap[0] 426 | }); 427 | continue; 428 | } 429 | 430 | if (src) { 431 | throw new 432 | Error('Infinite loop on byte: ' + src.charCodeAt(0)); 433 | } 434 | } 435 | 436 | return this.tokens; 437 | }; 438 | 439 | /** 440 | * Inline-Level Grammar 441 | */ 442 | 443 | var inline = { 444 | escape: /^\\([\\`*{}\[\]()#+\-.!_>])/, 445 | autolink: /^<([^ >]+(@|:\/)[^ >]+)>/, 446 | url: noop, 447 | tag: /^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/, 448 | link: /^!?\[(inside)\]\(href\)/, 449 | reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/, 450 | nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/, 451 | strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/, 452 | em: /^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/, 453 | code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/, 454 | br: /^ {2,}\n(?!\s*$)/, 455 | del: noop, 456 | text: /^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/; 461 | 462 | inline.link = replace(inline.link) 463 | ('inside', inline._inside) 464 | ('href', inline._href) 465 | (); 466 | 467 | inline.reflink = replace(inline.reflink) 468 | ('inside', inline._inside) 469 | (); 470 | 471 | /** 472 | * Normal Inline Grammar 473 | */ 474 | 475 | inline.normal = merge({}, inline); 476 | 477 | /** 478 | * Pedantic Inline Grammar 479 | */ 480 | 481 | inline.pedantic = merge({}, inline.normal, { 482 | strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, 483 | em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/ 484 | }); 485 | 486 | /** 487 | * GFM Inline Grammar 488 | */ 489 | 490 | inline.gfm = merge({}, inline.normal, { 491 | escape: replace(inline.escape)('])', '~|])')(), 492 | url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/, 493 | del: /^~~(?=\S)([\s\S]*?\S)~~/, 494 | text: replace(inline.text) 495 | (']|', '~]|') 496 | ('|', '|https?://|') 497 | () 498 | }); 499 | 500 | /** 501 | * GFM + Line Breaks Inline Grammar 502 | */ 503 | 504 | inline.breaks = merge({}, inline.gfm, { 505 | br: replace(inline.br)('{2,}', '*')(), 506 | text: replace(inline.gfm.text)('{2,}', '*')() 507 | }); 508 | 509 | /** 510 | * Inline Lexer & Compiler 511 | */ 512 | 513 | function InlineLexer(links, options) { 514 | this.options = options || marked.defaults; 515 | this.links = links; 516 | this.rules = inline.normal; 517 | 518 | if (!this.links) { 519 | throw new 520 | Error('Tokens array requires a `links` property.'); 521 | } 522 | 523 | if (this.options.gfm) { 524 | if (this.options.breaks) { 525 | this.rules = inline.breaks; 526 | } else { 527 | this.rules = inline.gfm; 528 | } 529 | } else if (this.options.pedantic) { 530 | this.rules = inline.pedantic; 531 | } 532 | } 533 | 534 | /** 535 | * Expose Inline Rules 536 | */ 537 | 538 | InlineLexer.rules = inline; 539 | 540 | /** 541 | * Static Lexing/Compiling Method 542 | */ 543 | 544 | InlineLexer.output = function(src, links, options) { 545 | var inline = new InlineLexer(links, options); 546 | return inline.output(src); 547 | }; 548 | 549 | /** 550 | * Lexing/Compiling 551 | */ 552 | 553 | InlineLexer.prototype.output = function(src) { 554 | var out = '' 555 | , link 556 | , text 557 | , href 558 | , cap; 559 | 560 | while (src) { 561 | // escape 562 | if (cap = this.rules.escape.exec(src)) { 563 | src = src.substring(cap[0].length); 564 | out += cap[1]; 565 | continue; 566 | } 567 | 568 | // autolink 569 | if (cap = this.rules.autolink.exec(src)) { 570 | src = src.substring(cap[0].length); 571 | if (cap[2] === '@') { 572 | text = cap[1].charAt(6) === ':' 573 | ? this.mangle(cap[1].substring(7)) 574 | : this.mangle(cap[1]); 575 | href = this.mangle('mailto:') + text; 576 | } else { 577 | text = escape(cap[1]); 578 | href = text; 579 | } 580 | out += '' 583 | + text 584 | + ''; 585 | continue; 586 | } 587 | 588 | // url (gfm) 589 | if (cap = this.rules.url.exec(src)) { 590 | src = src.substring(cap[0].length); 591 | text = escape(cap[1]); 592 | href = text; 593 | out += '' 596 | + text 597 | + ''; 598 | continue; 599 | } 600 | 601 | // tag 602 | if (cap = this.rules.tag.exec(src)) { 603 | src = src.substring(cap[0].length); 604 | out += this.options.sanitize 605 | ? escape(cap[0]) 606 | : cap[0]; 607 | continue; 608 | } 609 | 610 | // link 611 | if (cap = this.rules.link.exec(src)) { 612 | src = src.substring(cap[0].length); 613 | out += this.outputLink(cap, { 614 | href: cap[2], 615 | title: cap[3] 616 | }); 617 | continue; 618 | } 619 | 620 | // reflink, nolink 621 | if ((cap = this.rules.reflink.exec(src)) 622 | || (cap = this.rules.nolink.exec(src))) { 623 | src = src.substring(cap[0].length); 624 | link = (cap[2] || cap[1]).replace(/\s+/g, ' '); 625 | link = this.links[link.toLowerCase()]; 626 | if (!link || !link.href) { 627 | out += cap[0].charAt(0); 628 | src = cap[0].substring(1) + src; 629 | continue; 630 | } 631 | out += this.outputLink(cap, link); 632 | continue; 633 | } 634 | 635 | // strong 636 | if (cap = this.rules.strong.exec(src)) { 637 | src = src.substring(cap[0].length); 638 | out += '' 639 | + this.output(cap[2] || cap[1]) 640 | + ''; 641 | continue; 642 | } 643 | 644 | // em 645 | if (cap = this.rules.em.exec(src)) { 646 | src = src.substring(cap[0].length); 647 | out += '' 648 | + this.output(cap[2] || cap[1]) 649 | + ''; 650 | continue; 651 | } 652 | 653 | // code 654 | if (cap = this.rules.code.exec(src)) { 655 | src = src.substring(cap[0].length); 656 | out += '' 657 | + escape(cap[2], true) 658 | + ''; 659 | continue; 660 | } 661 | 662 | // br 663 | if (cap = this.rules.br.exec(src)) { 664 | src = src.substring(cap[0].length); 665 | out += '
'; 666 | continue; 667 | } 668 | 669 | // del (gfm) 670 | if (cap = this.rules.del.exec(src)) { 671 | src = src.substring(cap[0].length); 672 | out += '' 673 | + this.output(cap[1]) 674 | + ''; 675 | continue; 676 | } 677 | 678 | // text 679 | if (cap = this.rules.text.exec(src)) { 680 | src = src.substring(cap[0].length); 681 | out += escape(this.smartypants(cap[0])); 682 | continue; 683 | } 684 | 685 | if (src) { 686 | throw new 687 | Error('Infinite loop on byte: ' + src.charCodeAt(0)); 688 | } 689 | } 690 | 691 | return out; 692 | }; 693 | 694 | /** 695 | * Compile Link 696 | */ 697 | 698 | InlineLexer.prototype.outputLink = function(cap, link) { 699 | if (cap[0].charAt(0) !== '!') { 700 | return '' 709 | + this.output(cap[1]) 710 | + ''; 711 | } else { 712 | return ''
 715 |       + escape(cap[1])
 716 |       + ''; 723 | } 724 | }; 725 | 726 | /** 727 | * Smartypants Transformations 728 | */ 729 | 730 | InlineLexer.prototype.smartypants = function(text) { 731 | if (!this.options.smartypants) return text; 732 | return text 733 | // em-dashes 734 | .replace(/--/g, '\u2014') 735 | // opening singles 736 | .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018') 737 | // closing singles & apostrophes 738 | .replace(/'/g, '\u2019') 739 | // opening doubles 740 | .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c') 741 | // closing doubles 742 | .replace(/"/g, '\u201d') 743 | // ellipses 744 | .replace(/\.{3}/g, '\u2026'); 745 | }; 746 | 747 | /** 748 | * Mangle Links 749 | */ 750 | 751 | InlineLexer.prototype.mangle = function(text) { 752 | var out = '' 753 | , l = text.length 754 | , i = 0 755 | , ch; 756 | 757 | for (; i < l; i++) { 758 | ch = text.charCodeAt(i); 759 | if (Math.random() > 0.5) { 760 | ch = 'x' + ch.toString(16); 761 | } 762 | out += '&#' + ch + ';'; 763 | } 764 | 765 | return out; 766 | }; 767 | 768 | /** 769 | * Parsing & Compiling 770 | */ 771 | 772 | function Parser(options) { 773 | this.tokens = []; 774 | this.token = null; 775 | this.options = options || marked.defaults; 776 | } 777 | 778 | /** 779 | * Static Parse Method 780 | */ 781 | 782 | Parser.parse = function(src, options) { 783 | var parser = new Parser(options); 784 | return parser.parse(src); 785 | }; 786 | 787 | /** 788 | * Parse Loop 789 | */ 790 | 791 | Parser.prototype.parse = function(src) { 792 | this.inline = new InlineLexer(src.links, this.options); 793 | this.tokens = src.reverse(); 794 | 795 | var out = ''; 796 | while (this.next()) { 797 | out += this.tok(); 798 | } 799 | 800 | return out; 801 | }; 802 | 803 | /** 804 | * Next Token 805 | */ 806 | 807 | Parser.prototype.next = function() { 808 | return this.token = this.tokens.pop(); 809 | }; 810 | 811 | /** 812 | * Preview Next Token 813 | */ 814 | 815 | Parser.prototype.peek = function() { 816 | return this.tokens[this.tokens.length - 1] || 0; 817 | }; 818 | 819 | /** 820 | * Parse Text Tokens 821 | */ 822 | 823 | Parser.prototype.parseText = function() { 824 | var body = this.token.text; 825 | 826 | while (this.peek().type === 'text') { 827 | body += '\n' + this.next().text; 828 | } 829 | 830 | return this.inline.output(body); 831 | }; 832 | 833 | /** 834 | * Parse Current Token 835 | */ 836 | 837 | Parser.prototype.tok = function() { 838 | switch (this.token.type) { 839 | case 'space': { 840 | return ''; 841 | } 842 | case 'hr': { 843 | return '
\n'; 844 | } 845 | case 'heading': { 846 | return '' 851 | + this.inline.output(this.token.text) 852 | + '\n'; 855 | } 856 | case 'code': { 857 | if (this.options.highlight) { 858 | var code = this.options.highlight(this.token.text, this.token.lang); 859 | if (code != null && code !== this.token.text) { 860 | this.token.escaped = true; 861 | this.token.text = code; 862 | } 863 | } 864 | 865 | if (!this.token.escaped) { 866 | this.token.text = escape(this.token.text, true); 867 | } 868 | 869 | return '
'
 877 |         + this.token.text
 878 |         + '
\n'; 879 | } 880 | case 'table': { 881 | var body = '' 882 | , heading 883 | , i 884 | , row 885 | , cell 886 | , j; 887 | 888 | // header 889 | body += '\n\n'; 890 | for (i = 0; i < this.token.header.length; i++) { 891 | heading = this.inline.output(this.token.header[i]); 892 | body += '\n'; 897 | } 898 | body += '\n\n'; 899 | 900 | // body 901 | body += '\n' 902 | for (i = 0; i < this.token.cells.length; i++) { 903 | row = this.token.cells[i]; 904 | body += '\n'; 905 | for (j = 0; j < row.length; j++) { 906 | cell = this.inline.output(row[j]); 907 | body += '\n'; 912 | } 913 | body += '\n'; 914 | } 915 | body += '\n'; 916 | 917 | return '\n' 918 | + body 919 | + '
\n'; 920 | } 921 | case 'blockquote_start': { 922 | var body = ''; 923 | 924 | while (this.next().type !== 'blockquote_end') { 925 | body += this.tok(); 926 | } 927 | 928 | return '
\n' 929 | + body 930 | + '
\n'; 931 | } 932 | case 'list_start': { 933 | var type = this.token.ordered ? 'ol' : 'ul' 934 | , body = ''; 935 | 936 | while (this.next().type !== 'list_end') { 937 | body += this.tok(); 938 | } 939 | 940 | return '<' 941 | + type 942 | + '>\n' 943 | + body 944 | + '\n'; 947 | } 948 | case 'list_item_start': { 949 | var body = ''; 950 | 951 | while (this.next().type !== 'list_item_end') { 952 | body += this.token.type === 'text' 953 | ? this.parseText() 954 | : this.tok(); 955 | } 956 | 957 | return '
  • ' 958 | + body 959 | + '
  • \n'; 960 | } 961 | case 'loose_item_start': { 962 | var body = ''; 963 | 964 | while (this.next().type !== 'list_item_end') { 965 | body += this.tok(); 966 | } 967 | 968 | return '
  • ' 969 | + body 970 | + '
  • \n'; 971 | } 972 | case 'html': { 973 | return !this.token.pre && !this.options.pedantic 974 | ? this.inline.output(this.token.text) 975 | : this.token.text; 976 | } 977 | case 'paragraph': { 978 | return '

    ' 979 | + this.inline.output(this.token.text) 980 | + '

    \n'; 981 | } 982 | case 'text': { 983 | return '

    ' 984 | + this.parseText() 985 | + '

    \n'; 986 | } 987 | } 988 | }; 989 | 990 | /** 991 | * Helpers 992 | */ 993 | 994 | function escape(html, encode) { 995 | return html 996 | .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') 997 | .replace(//g, '>') 999 | .replace(/"/g, '"') 1000 | .replace(/'/g, '''); 1001 | } 1002 | 1003 | function replace(regex, opt) { 1004 | regex = regex.source; 1005 | opt = opt || ''; 1006 | return function self(name, val) { 1007 | if (!name) return new RegExp(regex, opt); 1008 | val = val.source || val; 1009 | val = val.replace(/(^|[^\[])\^/g, '$1'); 1010 | regex = regex.replace(name, val); 1011 | return self; 1012 | }; 1013 | } 1014 | 1015 | function noop() {} 1016 | noop.exec = noop; 1017 | 1018 | function merge(obj) { 1019 | var i = 1 1020 | , target 1021 | , key; 1022 | 1023 | for (; i < arguments.length; i++) { 1024 | target = arguments[i]; 1025 | for (key in target) { 1026 | if (Object.prototype.hasOwnProperty.call(target, key)) { 1027 | obj[key] = target[key]; 1028 | } 1029 | } 1030 | } 1031 | 1032 | return obj; 1033 | } 1034 | 1035 | /** 1036 | * Marked 1037 | */ 1038 | 1039 | function marked(src, opt, callback) { 1040 | if (callback || typeof opt === 'function') { 1041 | if (!callback) { 1042 | callback = opt; 1043 | opt = null; 1044 | } 1045 | 1046 | opt = merge({}, marked.defaults, opt || {}); 1047 | 1048 | var highlight = opt.highlight 1049 | , tokens 1050 | , pending 1051 | , i = 0; 1052 | 1053 | try { 1054 | tokens = Lexer.lex(src, opt) 1055 | } catch (e) { 1056 | return callback(e); 1057 | } 1058 | 1059 | pending = tokens.length; 1060 | 1061 | var done = function() { 1062 | var out, err; 1063 | 1064 | try { 1065 | out = Parser.parse(tokens, opt); 1066 | } catch (e) { 1067 | err = e; 1068 | } 1069 | 1070 | opt.highlight = highlight; 1071 | 1072 | return err 1073 | ? callback(err) 1074 | : callback(null, out); 1075 | }; 1076 | 1077 | if (!highlight || highlight.length < 3) { 1078 | return done(); 1079 | } 1080 | 1081 | delete opt.highlight; 1082 | 1083 | if (!pending) return done(); 1084 | 1085 | for (; i < tokens.length; i++) { 1086 | (function(token) { 1087 | if (token.type !== 'code') { 1088 | return --pending || done(); 1089 | } 1090 | return highlight(token.text, token.lang, function(err, code) { 1091 | if (code == null || code === token.text) { 1092 | return --pending || done(); 1093 | } 1094 | token.text = code; 1095 | token.escaped = true; 1096 | --pending || done(); 1097 | }); 1098 | })(tokens[i]); 1099 | } 1100 | 1101 | return; 1102 | } 1103 | try { 1104 | if (opt) opt = merge({}, marked.defaults, opt); 1105 | return Parser.parse(Lexer.lex(src, opt), opt); 1106 | } catch (e) { 1107 | e.message += '\nPlease report this to https://github.com/chjj/marked.'; 1108 | if ((opt || marked.defaults).silent) { 1109 | return '

    An error occured:

    '
    1110 |         + escape(e.message + '', true)
    1111 |         + '
    '; 1112 | } 1113 | throw e; 1114 | } 1115 | } 1116 | 1117 | /** 1118 | * Options 1119 | */ 1120 | 1121 | marked.options = 1122 | marked.setOptions = function(opt) { 1123 | merge(marked.defaults, opt); 1124 | return marked; 1125 | }; 1126 | 1127 | marked.defaults = { 1128 | gfm: true, 1129 | tables: true, 1130 | breaks: false, 1131 | pedantic: false, 1132 | sanitize: false, 1133 | smartLists: false, 1134 | silent: false, 1135 | highlight: null, 1136 | langPrefix: 'lang-', 1137 | smartypants: false 1138 | }; 1139 | 1140 | /** 1141 | * Expose 1142 | */ 1143 | 1144 | marked.Parser = Parser; 1145 | marked.parser = Parser.parse; 1146 | 1147 | marked.Lexer = Lexer; 1148 | marked.lexer = Lexer.lex; 1149 | 1150 | marked.InlineLexer = InlineLexer; 1151 | marked.inlineLexer = InlineLexer.output; 1152 | 1153 | marked.parse = marked; 1154 | 1155 | if (typeof exports === 'object') { 1156 | module.exports = marked; 1157 | } else if (typeof define === 'function' && define.amd) { 1158 | define(function() { return marked; }); 1159 | } else { 1160 | this.marked = marked; 1161 | } 1162 | 1163 | }).call(function() { 1164 | return this || (typeof window !== 'undefined' ? window : global); 1165 | }()); 1166 | -------------------------------------------------------------------------------- /docs/yue.css: -------------------------------------------------------------------------------- 1 | /** 2 | * yue.css 3 | * 4 | * yue.css is designed for readable content. 5 | * 6 | * Copyright (c) 2013 by Hsiaoming Yang. 7 | */ 8 | 9 | .yue { 10 | font: 400 18px/1.62 "Georgia", "Xin Gothic", "Hiragino Sans GB", "WenQuanYi Micro Hei", "Microsoft YaHei", "SimSun", sans-serif; 11 | color: #333332; 12 | } 13 | .yue ::selection { 14 | background-color: rgba(0,0,0,0.2); 15 | } 16 | .yue h1, 17 | .yue h2, 18 | .yue h3, 19 | .yue h4, 20 | .yue h5, 21 | .yue h6 { 22 | color: #222223; 23 | } 24 | .yue h1 { 25 | font-size: 1.8em; 26 | margin: 0.67em 0; 27 | } 28 | .yue > h1 { 29 | margin-top: 0; 30 | font-size: 2em; 31 | } 32 | .yue h2 { 33 | font-size: 1.5em; 34 | margin: 0.83em 0; 35 | } 36 | .yue h3 { 37 | font-size: 1.17em; 38 | margin: 1em 0; 39 | } 40 | .yue h4, 41 | .yue h5, 42 | .yue h6 { 43 | font-size: 1em; 44 | margin: 1.6em 0 1em 0; 45 | } 46 | .yue h6 { 47 | font-weight: 500; 48 | } 49 | .yue p { 50 | margin-top: 0; 51 | margin-bottom: 1.64em; 52 | } 53 | .yue a { 54 | color: #111; 55 | word-wrap: break-word; 56 | } 57 | .yue a:hover { 58 | color: #555; 59 | } 60 | .yue strong, 61 | .yue b { 62 | font-weight: 700; 63 | color: #222; 64 | } 65 | .yue em, 66 | .yue i { 67 | font-style: italic; 68 | color: #222; 69 | } 70 | .yue img { 71 | max-width: 98%; 72 | margin: 0.2em 0; 73 | } 74 | .yue hr { 75 | border: 0 none; 76 | margin-bottom: 1em; 77 | } 78 | .yue hr:after { 79 | color: #9f9f95; 80 | font-size: 1.1em; 81 | display: block; 82 | content: "* * *"; 83 | text-align: center; 84 | letter-spacing: 0.6em; 85 | } 86 | .yue blockquote { 87 | padding-left: 20px; 88 | border-left: 4px solid #dadada; 89 | margin: 0 0 1.64em -24px; 90 | color: #666664; 91 | } 92 | .yue ul, 93 | .yue ol { 94 | margin: 0 0 24px 6px; 95 | padding-left: 16px; 96 | } 97 | .yue ul { 98 | list-style-type: square; 99 | } 100 | .yue ol { 101 | list-style-type: decimal; 102 | } 103 | .yue li { 104 | margin-bottom: 0.2em; 105 | } 106 | .yue li ul, 107 | .yue li ol { 108 | margin-top: 0; 109 | margin-bottom: 0; 110 | margin-left: 14px; 111 | } 112 | .yue li ul { 113 | list-style-type: disc; 114 | } 115 | .yue li ul ul { 116 | list-style-type: circle; 117 | } 118 | .yue li p { 119 | margin: 0.4em 0 0.6em; 120 | } 121 | .yue .unstyled { 122 | list-style-type: none; 123 | margin: 0; 124 | padding: 0; 125 | } 126 | .yue code, 127 | .yue tt { 128 | color: #808080; 129 | font-size: 0.96em; 130 | background-color: #f9f9f7; 131 | padding: 1px 2px; 132 | border: 1px solid #dadada; 133 | border-radius: 3px; 134 | font-family: "Inconsolata", "Menlo", monospace; 135 | } 136 | .yue pre { 137 | padding: 7px; 138 | border: 1px solid #dadada; 139 | border-radius: 4px; 140 | overflow: auto; 141 | line-height: 1.5; 142 | font-size: 0.96em; 143 | font-family: "Inconsolata", "Menlo", monospace; 144 | color: #4c4c4c; 145 | background-color: #f9f9f7; 146 | } 147 | .yue pre code, 148 | .yue pre tt { 149 | color: #4c4c4c; 150 | border: none; 151 | background-color: none; 152 | padding: 0; 153 | } 154 | .yue table { 155 | border-collapse: collapse; 156 | border-spacing: 0; 157 | margin-bottom: 1.5em; 158 | font-size: 0.96em; 159 | } 160 | .yue th, 161 | .yue td { 162 | text-align: left; 163 | padding: 4px 8px 4px 10px; 164 | border: 1px solid #dadada; 165 | } 166 | .yue td { 167 | vertical-align: top; 168 | } 169 | .yue tr:nth-child(even) { 170 | background-color: #efefee; 171 | } 172 | -------------------------------------------------------------------------------- /editor.css: -------------------------------------------------------------------------------- 1 | .CodeMirror { 2 | height: 450px; 3 | } 4 | :-webkit-full-screen { 5 | background: #f9f9f5; 6 | padding: 0.5em 1em; 7 | width: 100%; 8 | height: 100%; 9 | } 10 | :-moz-full-screen { 11 | padding: 0.5em 1em; 12 | background: #f9f9f5; 13 | width: 100%; 14 | height: 100%; 15 | } 16 | .editor-wrapper { 17 | font: 16px/1.62 "Helvetica Neue", "Xin Gothic", "Hiragino Sans GB", "WenQuanYi Micro Hei", "Microsoft YaHei", sans-serif; 18 | color: #2c3e50; 19 | } 20 | /* this is the title */ 21 | .editor-wrapper input.title { 22 | font: 18px "Helvetica Neue", "Xin Gothic", "Hiragino Sans GB", "WenQuanYi Micro Hei", "Microsoft YaHei", sans-serif; 23 | background: transparent; 24 | padding: 4px; 25 | width: 100%; 26 | border: none; 27 | outline: none; 28 | opacity: 0.6; 29 | } 30 | .editor-toolbar { 31 | position: relative; 32 | padding: 0 8px; 33 | opacity: 0.6; 34 | -webkit-user-select: none; 35 | -moz-user-select: none; 36 | -ms-user-select: none; 37 | -o-user-select: none; 38 | user-select: none; 39 | } 40 | .editor-toolbar:before, .editor-toolbar:after { 41 | display: block; 42 | content: ' '; 43 | height: 1px; 44 | background-color: #bdc3c7; 45 | background: -moz-linear-gradient(45deg, #f9f9f9, #bdc3c7, #f9f9f9); 46 | background: -webkit-linear-gradient(45deg, #f9f9f9, #bdc3c7, #f9f9f9); 47 | background: -ms-linear-gradient(45deg, #f9f9f9, #bdc3c7, #f9f9f9); 48 | background: linear-gradient(45deg, #f9f9f9, #bdc3c7, #f9f9f9); 49 | } 50 | .editor-toolbar:before { 51 | margin-bottom: 8px; 52 | } 53 | .editor-toolbar:after { 54 | margin-top: 8px; 55 | } 56 | .editor-wrapper input.title:hover, .editor-wrapper input.title:focus, .editor-toolbar:hover { 57 | opacity: 0.8; 58 | } 59 | .editor-toolbar a { 60 | display: inline-block; 61 | text-align: center; 62 | text-decoration: none !important; 63 | color: #2c3e50 !important; 64 | width: 24px; 65 | height: 24px; 66 | margin: 2px; 67 | border: 1px solid transparent; 68 | border-radius: 3px; 69 | cursor: pointer; 70 | } 71 | .editor-toolbar a:hover, .editor-toolbar a.active { 72 | background: #fcfcfc; 73 | border-color: #95a5a6; 74 | } 75 | .editor-toolbar a:before { 76 | line-height: 24px; 77 | } 78 | .editor-toolbar i.separator { 79 | display: inline-block; 80 | width: 0; 81 | border-left: 1px solid #d9d9d9; 82 | border-right: 1px solid white; 83 | color: transparent; 84 | text-indent: -10px; 85 | margin: 0 6px; 86 | } 87 | .editor-toolbar a.icon-fullscreen { 88 | float: right; 89 | } 90 | .editor-statusbar { 91 | border-top: 1px solid #ece9e9; 92 | padding: 8px 10px; 93 | font-size: 12px; 94 | color: #959694; 95 | text-align: right; 96 | } 97 | .editor-statusbar span { 98 | display: inline-block; 99 | min-width: 4em; 100 | margin-left: 1em; 101 | } 102 | .editor-statusbar .lines:before { 103 | content: 'lines: '; 104 | } 105 | .editor-statusbar .words:before { 106 | content: 'words: '; 107 | } 108 | .editor-preview { 109 | position: absolute; 110 | width: 100%; 111 | height: 100%; 112 | top: 0; 113 | left: 100%; 114 | background: #f9f9f5; 115 | z-index: 9999; 116 | overflow: auto; 117 | -webkit-transition: left 0.2s ease; 118 | -moz-transition: left 0.2s ease; 119 | -ms-transition: left 0.2s ease; 120 | transition: left 0.2s ease; 121 | } 122 | .editor-preview-active { 123 | left: 0; 124 | } 125 | .editor-preview > p { 126 | margin-top: 0; 127 | } 128 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "family": "lepture", 3 | "name": "editor", 4 | "version": "0.1.0", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/lepture/editor.git" 8 | }, 9 | "bugs": { 10 | "url": "https://github.com/lepture/editor/issues" 11 | }, 12 | "dependencies": {}, 13 | "devDependencies": { 14 | "connect-livereload": "*", 15 | "grunt-contrib-connect": "*", 16 | "grunt-contrib-watch": "*" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /paper.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | height: 300px; 5 | } 6 | .CodeMirror-scroll { 7 | /* Set scrolling behaviour here */ 8 | overflow: auto; 9 | } 10 | 11 | /* PADDING */ 12 | 13 | .CodeMirror-lines { 14 | padding: 4px 0; /* Vertical padding around content */ 15 | } 16 | .CodeMirror pre { 17 | padding: 0 4px; /* Horizontal padding of content */ 18 | } 19 | 20 | .CodeMirror-scrollbar-filler { 21 | background-color: white; /* The little square between H and V scrollbars */ 22 | } 23 | 24 | /* CURSOR */ 25 | .CodeMirror div.CodeMirror-cursor { 26 | border-left: 1px solid black; 27 | z-index: 3; 28 | } 29 | /* Shown when moving in bi-directional text */ 30 | .CodeMirror div.CodeMirror-secondarycursor { 31 | border-left: 1px solid silver; 32 | } 33 | .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { 34 | width: auto; 35 | border: 0; 36 | background: #7e7; 37 | z-index: 1; 38 | } 39 | /* Can style cursor different in overwrite (non-insert) mode */ 40 | .CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {} 41 | 42 | /* DEFAULT THEME */ 43 | 44 | .cm-s-paper .cm-keyword {color: #555;} 45 | .cm-s-paper .cm-atom {color: #7f8c8d;} 46 | .cm-s-paper .cm-number {color: #7f8c8d;} 47 | .cm-s-paper .cm-def {color: #00f;} 48 | .cm-s-paper .cm-variable {color: black;} 49 | .cm-s-paper .cm-variable-2 {color: #555;} 50 | .cm-s-paper .cm-variable-3 {color: #085;} 51 | .cm-s-paper .cm-property {color: black;} 52 | .cm-s-paper .cm-operator {color: black;} 53 | .cm-s-paper .cm-comment {color: #959595;} 54 | .cm-s-paper .cm-string {color: #7f8c8d;} 55 | .cm-s-paper .cm-string-2 {color: #f50;} 56 | .cm-s-paper .cm-meta {color: #555;} 57 | .cm-s-paper .cm-error {color: #f00;} 58 | .cm-s-paper .cm-qualifier {color: #555;} 59 | .cm-s-paper .cm-builtin {color: #555;} 60 | .cm-s-paper .cm-bracket {color: #997;} 61 | .cm-s-paper .cm-tag {color: #7f8c8d;} 62 | .cm-s-paper .cm-attribute {color: #7f8c8d;} 63 | .cm-s-paper .cm-header {color: #000;} 64 | .cm-s-paper .cm-quote {color: #888;} 65 | .cm-s-paper .cm-hr {color: #999;} 66 | .cm-s-paper .cm-link {color: #7f8c8d;} 67 | 68 | .cm-negative {color: #d44;} 69 | .cm-positive {color: #292;} 70 | .cm-header, .cm-strong {font-weight: bold;} 71 | .cm-em {font-style: italic;} 72 | .cm-link {text-decoration: underline;} 73 | 74 | .cm-invalidchar {color: #f00;} 75 | 76 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 77 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 78 | 79 | 80 | /* STOP */ 81 | 82 | /* The rest of this file contains styles related to the mechanics of 83 | the editor. You probably shouldn't touch them. */ 84 | 85 | .CodeMirror { 86 | position: relative; 87 | overflow: hidden; 88 | } 89 | 90 | .CodeMirror-scroll { 91 | /* 30px is the magic margin used to hide the element's real scrollbars */ 92 | /* See overflow: hidden in .CodeMirror, and the paddings in .CodeMirror-sizer */ 93 | margin-bottom: -30px; margin-right: -30px; 94 | padding-bottom: 30px; padding-right: 30px; 95 | height: 100%; 96 | outline: none; /* Prevent dragging from highlighting the element */ 97 | position: relative; 98 | } 99 | .CodeMirror-sizer { 100 | position: relative; 101 | } 102 | 103 | /* The fake, visible scrollbars. Used to force redraw during scrolling 104 | before actuall scrolling happens, thus preventing shaking and 105 | flickering artifacts. */ 106 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler { 107 | position: absolute; 108 | z-index: 6; 109 | display: none; 110 | } 111 | .CodeMirror-vscrollbar { 112 | right: 0; top: 0; 113 | overflow-x: hidden; 114 | overflow-y: scroll; 115 | } 116 | .CodeMirror-hscrollbar { 117 | bottom: 0; left: 0; 118 | overflow-y: hidden; 119 | overflow-x: scroll; 120 | } 121 | .CodeMirror-scrollbar-filler { 122 | right: 0; bottom: 0; 123 | z-index: 6; 124 | } 125 | 126 | .CodeMirror-lines { 127 | cursor: text; 128 | } 129 | .CodeMirror pre { 130 | /* Reset some styles that the rest of the page might have set */ 131 | -moz-border-radius: 0; -webkit-border-radius: 0; -o-border-radius: 0; border-radius: 0; 132 | border-width: 0; 133 | background: transparent; 134 | font-family: inherit; 135 | font-size: inherit; 136 | margin: 0; 137 | white-space: pre-wrap; 138 | word-wrap: normal; 139 | line-height: inherit; 140 | color: inherit; 141 | z-index: 2; 142 | position: relative; 143 | overflow: visible; 144 | } 145 | .CodeMirror-wrap pre { 146 | word-wrap: break-word; 147 | white-space: pre-wrap; 148 | word-break: normal; 149 | } 150 | .CodeMirror-linebackground { 151 | position: absolute; 152 | left: 0; right: 0; top: 0; bottom: 0; 153 | z-index: 0; 154 | } 155 | 156 | .CodeMirror-linewidget { 157 | position: relative; 158 | z-index: 2; 159 | overflow: auto; 160 | } 161 | 162 | .CodeMirror-widget { 163 | display: inline-block; 164 | } 165 | 166 | .CodeMirror-wrap .CodeMirror-scroll { 167 | overflow-x: hidden; 168 | } 169 | 170 | .CodeMirror-measure { 171 | position: absolute; 172 | width: 100%; height: 0px; 173 | overflow: hidden; 174 | visibility: hidden; 175 | } 176 | .CodeMirror-measure pre { position: static; } 177 | 178 | .CodeMirror div.CodeMirror-cursor { 179 | position: absolute; 180 | visibility: hidden; 181 | border-right: none; 182 | width: 0; 183 | } 184 | .CodeMirror-focused div.CodeMirror-cursor { 185 | visibility: visible; 186 | } 187 | 188 | .CodeMirror-selected { background: #d9d9d9; } 189 | .CodeMirror-focused .CodeMirror-selected { background: #BDC3C7; } 190 | 191 | .cm-searching { 192 | background: #ffa; 193 | background: rgba(255, 255, 0, .4); 194 | } 195 | 196 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 197 | .CodeMirror span { *vertical-align: text-bottom; } 198 | 199 | @media print { 200 | /* Hide the cursor when printing */ 201 | .CodeMirror div.CodeMirror-cursor { 202 | visibility: hidden; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/editor.js: -------------------------------------------------------------------------------- 1 | var toolbar = [ 2 | {name: 'bold', action: toggleBold}, 3 | {name: 'italic', action: toggleItalic}, 4 | {name: 'code', action: toggleCodeBlock}, 5 | '|', 6 | 7 | {name: 'quote', action: toggleBlockquote}, 8 | {name: 'unordered-list', action: toggleUnOrderedList}, 9 | {name: 'ordered-list', action: toggleOrderedList}, 10 | '|', 11 | 12 | {name: 'link', action: drawLink}, 13 | {name: 'image', action: drawImage}, 14 | '|', 15 | 16 | {name: 'info', action: 'http://lab.lepture.com/editor/markdown'}, 17 | {name: 'preview', action: togglePreview}, 18 | {name: 'fullscreen', action: toggleFullScreen} 19 | ]; 20 | 21 | /** 22 | * Interface of Editor. 23 | */ 24 | function Editor(options) { 25 | options = options || {}; 26 | 27 | if (options.element) { 28 | this.element = options.element; 29 | } 30 | 31 | options.toolbar = options.toolbar || Editor.toolbar; 32 | // you can customize toolbar with object 33 | // [{name: 'bold', shortcut: 'Ctrl-B', className: 'icon-bold'}] 34 | 35 | if (!options.hasOwnProperty('status')) { 36 | options.status = ['lines', 'words', 'cursor']; 37 | } 38 | 39 | this.options = options; 40 | 41 | // If user has passed an element, it should auto rendered 42 | if (this.element) { 43 | this.render(); 44 | } 45 | } 46 | 47 | /** 48 | * Default toolbar elements. 49 | */ 50 | Editor.toolbar = toolbar; 51 | 52 | /** 53 | * Default markdown render. 54 | */ 55 | Editor.markdown = function(text) { 56 | if (window.marked) { 57 | // use marked as markdown parser 58 | return marked(text); 59 | } 60 | }; 61 | 62 | /** 63 | * Render editor to the given element. 64 | */ 65 | Editor.prototype.render = function(el) { 66 | if (!el) { 67 | el = this.element || document.getElementsByTagName('textarea')[0]; 68 | } 69 | 70 | if (this._rendered && this._rendered === el) { 71 | // Already rendered. 72 | return; 73 | } 74 | 75 | this.element = el; 76 | var options = this.options; 77 | 78 | var self = this; 79 | var keyMaps = {}; 80 | 81 | for (var key in shortcuts) { 82 | (function(key) { 83 | keyMaps[fixShortcut(key)] = function(cm) { 84 | shortcuts[key](self); 85 | }; 86 | })(key); 87 | } 88 | 89 | keyMaps["Enter"] = "newlineAndIndentContinueMarkdownList"; 90 | keyMaps['Tab'] = 'tabAndIndentContinueMarkdownList'; 91 | keyMaps['Shift-Tab'] = 'shiftTabAndIndentContinueMarkdownList'; 92 | 93 | this.codemirror = CodeMirror.fromTextArea(el, { 94 | mode: 'markdown', 95 | theme: 'paper', 96 | tabSize: '2', 97 | indentWithTabs: true, 98 | lineNumbers: false, 99 | autofocus: true, 100 | extraKeys: keyMaps 101 | }); 102 | 103 | if (options.toolbar !== false) { 104 | this.createToolbar(); 105 | } 106 | if (options.status !== false) { 107 | this.createStatusbar(); 108 | } 109 | 110 | this._rendered = this.element; 111 | }; 112 | 113 | Editor.prototype.createToolbar = function(items) { 114 | items = items || this.options.toolbar; 115 | 116 | if (!items || items.length === 0) { 117 | return; 118 | } 119 | 120 | var bar = document.createElement('div'); 121 | bar.className = 'editor-toolbar'; 122 | 123 | var self = this; 124 | 125 | var el; 126 | self.toolbar = {}; 127 | 128 | for (var i = 0; i < items.length; i++) { 129 | (function(item) { 130 | var el; 131 | if (item.name) { 132 | el = createIcon(item.name, item); 133 | } else if (item === '|') { 134 | el = createSep(); 135 | } else { 136 | el = createIcon(item); 137 | } 138 | 139 | // bind events, special for info 140 | if (item.action) { 141 | if (typeof item.action === 'function') { 142 | el.onclick = function(e) { 143 | item.action(self); 144 | }; 145 | } else if (typeof item.action === 'string') { 146 | el.href = item.action; 147 | el.target = '_blank'; 148 | } 149 | } 150 | self.toolbar[item.name || item] = el; 151 | bar.appendChild(el); 152 | })(items[i]); 153 | } 154 | 155 | var cm = this.codemirror; 156 | cm.on('cursorActivity', function() { 157 | var stat = getState(cm); 158 | 159 | for (var key in self.toolbar) { 160 | (function(key) { 161 | var el = self.toolbar[key]; 162 | if (stat[key]) { 163 | el.className += ' active'; 164 | } else { 165 | el.className = el.className.replace(/\s*active\s*/g, ''); 166 | } 167 | })(key); 168 | } 169 | }); 170 | 171 | var cmWrapper = cm.getWrapperElement(); 172 | cmWrapper.parentNode.insertBefore(bar, cmWrapper); 173 | return bar; 174 | }; 175 | 176 | Editor.prototype.createStatusbar = function(status) { 177 | status = status || this.options.status; 178 | 179 | if (!status || status.length === 0) return; 180 | 181 | var bar = document.createElement('div'); 182 | bar.className = 'editor-statusbar'; 183 | 184 | var pos, cm = this.codemirror; 185 | for (var i = 0; i < status.length; i++) { 186 | (function(name) { 187 | var el = document.createElement('span'); 188 | el.className = name; 189 | if (name === 'words') { 190 | el.innerHTML = '0'; 191 | cm.on('update', function() { 192 | el.innerHTML = wordCount(cm.getValue()); 193 | }); 194 | } else if (name === 'lines') { 195 | el.innerHTML = '0'; 196 | cm.on('update', function() { 197 | el.innerHTML = cm.lineCount(); 198 | }); 199 | } else if (name === 'cursor') { 200 | el.innerHTML = '0:0'; 201 | cm.on('cursorActivity', function() { 202 | pos = cm.getCursor(); 203 | el.innerHTML = pos.line + ':' + pos.ch; 204 | }); 205 | } 206 | bar.appendChild(el); 207 | })(status[i]); 208 | } 209 | var cmWrapper = this.codemirror.getWrapperElement(); 210 | cmWrapper.parentNode.insertBefore(bar, cmWrapper.nextSibling); 211 | return bar; 212 | }; 213 | 214 | /** 215 | * Get or set the text content. 216 | */ 217 | Editor.prototype.value = function(val) { 218 | if (val) { 219 | this.codemirror.getDoc().setValue(val); 220 | return this; 221 | } else { 222 | return this.codemirror.getValue(); 223 | } 224 | }; 225 | 226 | 227 | /** 228 | * Bind static methods for exports. 229 | */ 230 | Editor.toggleBold = toggleBold; 231 | Editor.toggleItalic = toggleItalic; 232 | Editor.toggleBlockquote = toggleBlockquote; 233 | Editor.toggleUnOrderedList = toggleUnOrderedList; 234 | Editor.toggleOrderedList = toggleOrderedList; 235 | Editor.drawLink = drawLink; 236 | Editor.drawImage = drawImage; 237 | Editor.undo = undo; 238 | Editor.redo = redo; 239 | Editor.togglePreview = togglePreview; 240 | Editor.toggleFullScreen = toggleFullScreen; 241 | 242 | /** 243 | * Bind instance methods for exports. 244 | */ 245 | Editor.prototype.toggleBold = function() { 246 | toggleBold(this); 247 | }; 248 | Editor.prototype.toggleItalic = function() { 249 | toggleItalic(this); 250 | }; 251 | Editor.prototype.toggleBlockquote = function() { 252 | toggleBlockquote(this); 253 | }; 254 | Editor.prototype.toggleUnOrderedList = function() { 255 | toggleUnOrderedList(this); 256 | }; 257 | Editor.prototype.toggleOrderedList = function() { 258 | toggleOrderedList(this); 259 | }; 260 | Editor.prototype.drawLink = function() { 261 | drawLink(this); 262 | }; 263 | Editor.prototype.drawImage = function() { 264 | drawImage(this); 265 | }; 266 | Editor.prototype.undo = function() { 267 | undo(this); 268 | }; 269 | Editor.prototype.redo = function() { 270 | redo(this); 271 | }; 272 | Editor.prototype.togglePreview = function() { 273 | togglePreview(this); 274 | }; 275 | Editor.prototype.toggleFullScreen = function() { 276 | toggleFullScreen(this); 277 | }; 278 | -------------------------------------------------------------------------------- /src/intro.js: -------------------------------------------------------------------------------- 1 | 2 | var isMac = /Mac/.test(navigator.platform); 3 | 4 | var shortcuts = { 5 | 'Cmd-B': toggleBold, 6 | 'Cmd-I': toggleItalic, 7 | 'Cmd-K': drawLink, 8 | 'Cmd-Alt-I': drawImage, 9 | "Cmd-'": toggleBlockquote, 10 | 'Cmd-Alt-L': toggleOrderedList, 11 | 'Cmd-L': toggleUnOrderedList 12 | }; 13 | 14 | 15 | /** 16 | * Fix shortcut. Mac use Command, others use Ctrl. 17 | */ 18 | function fixShortcut(name) { 19 | if (isMac) { 20 | name = name.replace('Ctrl', 'Cmd'); 21 | } else { 22 | name = name.replace('Cmd', 'Ctrl'); 23 | } 24 | return name; 25 | } 26 | 27 | 28 | /** 29 | * Create icon element for toolbar. 30 | */ 31 | function createIcon(name, options) { 32 | options = options || {}; 33 | var el = document.createElement('a'); 34 | 35 | var shortcut = options.shortcut || shortcuts[name]; 36 | if (shortcut) { 37 | shortcut = fixShortcut(shortcut); 38 | el.title = shortcut; 39 | el.title = el.title.replace('Cmd', '⌘'); 40 | if (isMac) { 41 | el.title = el.title.replace('Alt', '⌥'); 42 | } 43 | } 44 | 45 | el.className = options.className || 'editor-icon-' + name; 46 | return el; 47 | } 48 | 49 | function createSep() { 50 | el = document.createElement('i'); 51 | el.className = 'separator'; 52 | el.innerHTML = '|'; 53 | return el; 54 | } 55 | 56 | 57 | /** 58 | * The state of CodeMirror at the given position. 59 | */ 60 | function getState(cm, pos) { 61 | pos = pos || cm.getCursor('start'); 62 | var stat = cm.getTokenAt(pos); 63 | if (!stat.type) return {}; 64 | 65 | var types = stat.type.split(' '); 66 | 67 | var ret = {}, data, text; 68 | for (var i = 0; i < types.length; i++) { 69 | data = types[i]; 70 | if (data === 'strong') { 71 | ret.bold = true; 72 | } else if (data === 'variable-2') { 73 | text = cm.getLine(pos.line); 74 | if (/^\s*\d+\.\s/.test(text)) { 75 | ret['ordered-list'] = true; 76 | } else { 77 | ret['unordered-list'] = true; 78 | } 79 | } else if (data === 'atom') { 80 | ret.quote = true; 81 | } else if (data === 'em') { 82 | ret.italic = true; 83 | } 84 | } 85 | return ret; 86 | } 87 | 88 | 89 | /** 90 | * Toggle full screen of the editor. 91 | */ 92 | function toggleFullScreen(editor) { 93 | var el = editor.codemirror.getWrapperElement(); 94 | 95 | // https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode 96 | var doc = document; 97 | var isFull = doc.fullScreen || doc.mozFullScreen || doc.webkitIsFullScreen; 98 | var request = function() { 99 | if (el.requestFullScreen) { 100 | el.requestFullScreen(); 101 | } else if (el.mozRequestFullScreen) { 102 | el.mozRequestFullScreen(); 103 | } else if (el.webkitRequestFullScreen) { 104 | el.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT); 105 | } 106 | }; 107 | var cancel = function() { 108 | if (doc.cancelFullScreen) { 109 | doc.cancelFullScreen(); 110 | } else if (doc.mozCancelFullScreen) { 111 | doc.mozCancelFullScreen(); 112 | } else if (doc.webkitCancelFullScreen) { 113 | doc.webkitCancelFullScreen(); 114 | } 115 | }; 116 | if (!isFull) { 117 | request(); 118 | } else if (cancel) { 119 | cancel(); 120 | } 121 | } 122 | 123 | 124 | /** 125 | * Action for toggling bold. 126 | */ 127 | function toggleBold(editor) { 128 | _toggleBlock(editor, 'bold', '**'); 129 | } 130 | 131 | 132 | /** 133 | * Action for toggling italic. 134 | */ 135 | function toggleItalic(editor) { 136 | _toggleBlock(editor, 'italic', '*'); 137 | } 138 | 139 | /** 140 | * Action for toggling code block. 141 | */ 142 | function toggleCodeBlock(editor) { 143 | _toggleBlock(editor, 'code', '```\r\n', '\r\n```'); 144 | } 145 | 146 | /** 147 | * Action for toggling blockquote. 148 | */ 149 | function toggleBlockquote(editor) { 150 | var cm = editor.codemirror; 151 | _toggleLine(cm, 'quote'); 152 | } 153 | 154 | 155 | 156 | /** 157 | * Action for toggling ul. 158 | */ 159 | function toggleUnOrderedList(editor) { 160 | var cm = editor.codemirror; 161 | _toggleLine(cm, 'unordered-list'); 162 | } 163 | 164 | 165 | /** 166 | * Action for toggling ol. 167 | */ 168 | function toggleOrderedList(editor) { 169 | var cm = editor.codemirror; 170 | _toggleLine(cm, 'ordered-list'); 171 | } 172 | 173 | 174 | /** 175 | * Action for drawing a link. 176 | */ 177 | function drawLink(editor) { 178 | var cm = editor.codemirror; 179 | var stat = getState(cm); 180 | _replaceSelection(cm, stat.link, '[', '](http://)'); 181 | } 182 | 183 | 184 | /** 185 | * Action for drawing an img. 186 | */ 187 | function drawImage(editor) { 188 | var cm = editor.codemirror; 189 | var stat = getState(cm); 190 | _replaceSelection(cm, stat.image, '![', '](http://)'); 191 | } 192 | 193 | 194 | /** 195 | * Undo action. 196 | */ 197 | function undo(editor) { 198 | var cm = editor.codemirror; 199 | cm.undo(); 200 | cm.focus(); 201 | } 202 | 203 | 204 | /** 205 | * Redo action. 206 | */ 207 | function redo(editor) { 208 | var cm = editor.codemirror; 209 | cm.redo(); 210 | cm.focus(); 211 | } 212 | 213 | /** 214 | * Preview action. 215 | */ 216 | function togglePreview(editor) { 217 | var toolbar = editor.toolbar.preview; 218 | var parse = editor.constructor.markdown; 219 | var cm = editor.codemirror; 220 | var wrapper = cm.getWrapperElement(); 221 | var preview = wrapper.lastChild; 222 | if (!/editor-preview/.test(preview.className)) { 223 | preview = document.createElement('div'); 224 | preview.className = 'editor-preview'; 225 | wrapper.appendChild(preview); 226 | } 227 | if (/editor-preview-active/.test(preview.className)) { 228 | preview.className = preview.className.replace( 229 | /\s*editor-preview-active\s*/g, '' 230 | ); 231 | toolbar.className = toolbar.className.replace(/\s*active\s*/g, ''); 232 | } else { 233 | /* When the preview button is clicked for the first time, 234 | * give some time for the transition from editor.css to fire and the view to slide from right to left, 235 | * instead of just appearing. 236 | */ 237 | setTimeout(function() {preview.className += ' editor-preview-active'}, 1); 238 | toolbar.className += ' active'; 239 | } 240 | var text = cm.getValue(); 241 | preview.innerHTML = parse(text); 242 | } 243 | 244 | function _replaceSelection(cm, active, start, end) { 245 | var text; 246 | var startPoint = cm.getCursor('start'); 247 | var endPoint = cm.getCursor('end'); 248 | if (active) { 249 | text = cm.getLine(startPoint.line); 250 | start = text.slice(0, startPoint.ch); 251 | end = text.slice(startPoint.ch); 252 | cm.setLine(startPoint.line, start + end); 253 | } else { 254 | text = cm.getSelection(); 255 | cm.replaceSelection(start + text + end); 256 | 257 | startPoint.ch += start.length; 258 | endPoint.ch += start.length; 259 | } 260 | cm.setSelection(startPoint, endPoint); 261 | cm.focus(); 262 | } 263 | 264 | 265 | function _toggleLine(cm, name) { 266 | var stat = getState(cm); 267 | var startPoint = cm.getCursor('start'); 268 | var endPoint = cm.getCursor('end'); 269 | var repl = { 270 | quote: /^(\s*)\>\s+/, 271 | 'unordered-list': /^(\s*)(\*|\-|\+)\s+/, 272 | 'ordered-list': /^(\s*)\d+\.\s+/ 273 | }; 274 | var map = { 275 | quote: '> ', 276 | 'unordered-list': '* ', 277 | 'ordered-list': '1. ' 278 | }; 279 | for (var i = startPoint.line; i <= endPoint.line; i++) { 280 | (function(i) { 281 | var text = cm.getLine(i); 282 | if (stat[name]) { 283 | text = text.replace(repl[name], '$1'); 284 | } else { 285 | text = map[name] + text; 286 | } 287 | cm.setLine(i, text); 288 | })(i); 289 | } 290 | cm.focus(); 291 | } 292 | 293 | function _toggleBlock(editor, type, start_chars, end_chars){ 294 | end_chars = (typeof end_chars === 'undefined') ? start_chars : end_chars; 295 | var cm = editor.codemirror; 296 | var stat = getState(cm); 297 | 298 | var text; 299 | var start = start_chars; 300 | var end = end_chars; 301 | 302 | var startPoint = cm.getCursor('start'); 303 | var endPoint = cm.getCursor('end'); 304 | if (stat[type]) { 305 | text = cm.getLine(startPoint.line); 306 | start = text.slice(0, startPoint.ch); 307 | end = text.slice(startPoint.ch); 308 | startRegex = new RegExp("/^(.*)?(\*|\_){" + start_chars.length + "}(\S+.*)?$/", "g") 309 | start = start.replace(startRegex, '$1$3'); 310 | endRegex = new RegExp("/^(.*\S+)?(\*|\_){" + end_chars.length + "}(\s+.*)?$/", "g") 311 | end = end.replace(endRegex, '$1$3'); 312 | startPoint.ch -= start_chars.length; 313 | endPoint.ch -= end_chars.length; 314 | cm.setLine(startPoint.line, start + end); 315 | } else { 316 | text = cm.getSelection(); 317 | cm.replaceSelection(start + text + end); 318 | 319 | startPoint.ch += start_chars.length; 320 | endPoint.ch += end_chars.length; 321 | } 322 | cm.setSelection(startPoint, endPoint); 323 | cm.focus(); 324 | } 325 | 326 | 327 | /* The right word count in respect for CJK. */ 328 | function wordCount(data) { 329 | var pattern = /[a-zA-Z0-9_\u0392-\u03c9]+|[\u4E00-\u9FFF\u3400-\u4dbf\uf900-\ufaff\u3040-\u309f\uac00-\ud7af]+/g; 330 | var m = data.match(pattern); 331 | var count = 0; 332 | if( m === null ) return count; 333 | for (var i = 0; i < m.length; i++) { 334 | if (m[i].charCodeAt(0) >= 0x4E00) { 335 | count += m[i].length; 336 | } else { 337 | count += 1; 338 | } 339 | } 340 | return count; 341 | } 342 | -------------------------------------------------------------------------------- /vendor/continuelist.js: -------------------------------------------------------------------------------- 1 | const listRE = /^(\s*)([*+-]|(\d+)\.)([\w+(\s+\w+)]|[\s*])/, 2 | emptyListRE = /^(\s*)([*+-]|(\d+)\.)(\s*)$/, 3 | unorderedBullets = '*+-'; 4 | 5 | var inListState = function(cm, pos){ 6 | return cm.getStateAfter(pos.line).list || null; 7 | }; 8 | 9 | var inListOrNot = function(cm){ 10 | var pos = cm.getCursor(); 11 | return inListState(cm, pos); 12 | }; 13 | 14 | CodeMirror.commands.shiftTabAndIndentContinueMarkdownList = function(cm){ 15 | var inList = inListOrNot(cm); 16 | 17 | if(inList !== null){ 18 | cm.execCommand('insertTab'); 19 | return; 20 | } 21 | 22 | cm.execCommand('indentLess'); 23 | }; 24 | 25 | CodeMirror.commands.tabAndIndentContinueMarkdownList = function(cm){ 26 | var inList = inListOrNot(cm); 27 | 28 | if(inList !== null){ 29 | cm.execCommand('insertTab'); 30 | return; 31 | } 32 | 33 | cm.execCommand('indentMore'); 34 | }; 35 | 36 | CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm){ 37 | var pos, tok, match, emptyMatch, inList; 38 | 39 | pos = cm.getCursor(); 40 | tok = cm.getTokenAt(pos); 41 | emptyMatch = cm.getLine(pos.line).match(emptyListRE); 42 | inList = inListState(cm, pos); 43 | 44 | if (!inList && emptyMatch){ 45 | cm.replaceRange("", {line: pos.line , ch:tok.start}, {line:pos.line , ch:tok.end}); 46 | cm.execCommand('delLineLeft'); 47 | cm.execCommand('newlineAndIndent'); 48 | return; 49 | } 50 | 51 | if (!inList || !(match = cm.getLine(pos.line).match(listRE))) { 52 | cm.execCommand('newlineAndIndent'); 53 | return; 54 | } 55 | 56 | var indent = match[1], after = " "; 57 | var bullet = unorderedBullets.indexOf(match[2]) >= 0 58 | ? match[2] 59 | : (parseInt(match[3], 10) + 1) + '.'; 60 | 61 | cm.replaceSelection('\n' + indent + bullet + after, 'end'); 62 | }; 63 | -------------------------------------------------------------------------------- /vendor/icomoon/fonts/icomoon.dev.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | This is a custom SVG font generated by IcoMoon. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 28 | 29 | 33 | 34 | 35 | 36 | 37 | 39 | 41 | 43 | 44 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /vendor/icomoon/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lepture/editor/7cc8fe036ed0b6b90b8eb0894590fc15a6cb1abf/vendor/icomoon/fonts/icomoon.eot -------------------------------------------------------------------------------- /vendor/icomoon/fonts/icomoon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | This is a custom SVG font generated by IcoMoon. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 28 | 29 | 33 | 34 | 35 | 36 | 37 | 39 | 41 | 43 | 44 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /vendor/icomoon/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lepture/editor/7cc8fe036ed0b6b90b8eb0894590fc15a6cb1abf/vendor/icomoon/fonts/icomoon.ttf -------------------------------------------------------------------------------- /vendor/icomoon/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lepture/editor/7cc8fe036ed0b6b90b8eb0894590fc15a6cb1abf/vendor/icomoon/fonts/icomoon.woff -------------------------------------------------------------------------------- /vendor/icomoon/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Your Font/Glyphs 5 | 6 | 7 | 83 | 84 | 85 |
    86 |
    87 |
    88 |

    Your font contains the following glyphs

    89 |

    The generated SVG font can be imported back to IcoMoon for modification.

    90 |
    91 |
    92 | 93 | 94 |
    95 |
    96 | 97 | 98 |
    99 |
    100 | 101 | 102 |
    103 |
    104 | 105 | 106 |
    107 |
    108 | 109 | 110 |
    111 |
    112 | 113 | 114 |
    115 |
    116 | 117 | 118 |
    119 |
    120 | 121 | 122 |
    123 |
    124 | 125 | 126 |
    127 |
    128 | 129 | 130 |
    131 |
    132 | 133 | 134 |
    135 |
    136 | 137 | 138 |
    139 |
    140 | 141 | 142 |
    143 |
    144 | 145 | 146 |
    147 |
    148 | 149 | 150 |
    151 |
    152 | 153 | 154 |
    155 |
    156 | 157 | 158 |
    159 |
    160 |
    161 |
    162 |
    163 |

    Class Names

    164 |
    165 | 166 | 167 |  icon-bold 168 | 169 | 170 | 171 |  icon-italic 172 | 173 | 174 | 175 |  icon-quotes-left 176 | 177 | 178 | 179 |  icon-list 180 | 181 | 182 | 183 |  icon-numbered-list 184 | 185 | 186 | 187 |  icon-link 188 | 189 | 190 | 191 |  icon-image 192 | 193 | 194 | 195 |  icon-play 196 | 197 | 198 | 199 |  icon-music 200 | 201 | 202 | 203 |  icon-contract 204 | 205 | 206 | 207 |  icon-expand 208 | 209 | 210 | 211 |  icon-question 212 | 213 | 214 | 215 |  icon-info 216 | 217 | 218 | 219 |  icon-undo 220 | 221 | 222 | 223 |  icon-redo 224 | 225 | 226 | 227 |  icon-code 228 | 229 | 230 | 231 |  icon-eye 232 | 233 |
    234 | 237 |
    238 | 246 | 247 | -------------------------------------------------------------------------------- /vendor/icomoon/license.txt: -------------------------------------------------------------------------------- 1 | Icon Set: IcoMoon - Free -- http://keyamoon.com/icomoon/ 2 | License: CC BY 3.0 -- http://creativecommons.org/licenses/by/3.0/ -------------------------------------------------------------------------------- /vendor/icomoon/lte-ie7.js: -------------------------------------------------------------------------------- 1 | /* Load this script using conditional IE comments if you need to support IE 7 and IE 6. */ 2 | 3 | window.onload = function() { 4 | function addIcon(el, entity) { 5 | var html = el.innerHTML; 6 | el.innerHTML = '' + entity + '' + html; 7 | } 8 | var icons = { 9 | 'icon-bold' : '', 10 | 'icon-italic' : '', 11 | 'icon-quotes-left' : '', 12 | 'icon-list' : '', 13 | 'icon-numbered-list' : '', 14 | 'icon-link' : '', 15 | 'icon-image' : '', 16 | 'icon-play' : '', 17 | 'icon-music' : '', 18 | 'icon-contract' : '', 19 | 'icon-expand' : '', 20 | 'icon-question' : '', 21 | 'icon-info' : '', 22 | 'icon-undo' : '', 23 | 'icon-redo' : '', 24 | 'icon-code' : '', 25 | 'icon-eye' : '' 26 | }, 27 | els = document.getElementsByTagName('*'), 28 | i, attr, html, c, el; 29 | for (i = 0; i < els.length; i += 1) { 30 | el = els[i]; 31 | attr = el.getAttribute('data-icon'); 32 | if (attr) { 33 | addIcon(el, attr); 34 | } 35 | c = el.className; 36 | c = c.match(/icon-[^\s'"]+/); 37 | if (c && icons[c[0]]) { 38 | addIcon(el, icons[c[0]]); 39 | } 40 | } 41 | }; -------------------------------------------------------------------------------- /vendor/icomoon/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'icomoon'; 3 | src:url('fonts/icomoon.eot'); 4 | src:url('fonts/icomoon.eot?#iefix') format('embedded-opentype'), 5 | url('fonts/icomoon.woff') format('woff'), 6 | url('fonts/icomoon.ttf') format('truetype'), 7 | url('fonts/icomoon.svg#icomoon') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | /* Use the following CSS code if you want to use data attributes for inserting your icons */ 13 | [data-icon]:before { 14 | font-family: 'icomoon'; 15 | content: attr(data-icon); 16 | speak: none; 17 | font-weight: normal; 18 | font-variant: normal; 19 | text-transform: none; 20 | line-height: 1; 21 | -webkit-font-smoothing: antialiased; 22 | } 23 | 24 | /* Use the following CSS code if you want to have a class per icon */ 25 | /* 26 | Instead of a list of all class selectors, 27 | you can use the generic selector below, but it's slower: 28 | [class*="editor-icon-"] { 29 | */ 30 | .editor-icon-bold, .editor-icon-italic, .editor-icon-quote, .editor-icon-unordered-list, .editor-icon-ordered-list, .editor-icon-link, .editor-icon-image, .editor-icon-play, .editor-icon-music, .editor-icon-contract, .editor-icon-fullscreen, .editor-icon-question, .editor-icon-info, .editor-icon-undo, .editor-icon-redo, .editor-icon-code, .editor-icon-preview { 31 | font-family: 'icomoon'; 32 | speak: none; 33 | font-style: normal; 34 | font-weight: normal; 35 | font-variant: normal; 36 | text-transform: none; 37 | line-height: 1; 38 | -webkit-font-smoothing: antialiased; 39 | } 40 | .editor-icon-bold:before { 41 | content: "\e000"; 42 | } 43 | .editor-icon-italic:before { 44 | content: "\e001"; 45 | } 46 | .editor-icon-quote:before { 47 | content: "\e003"; 48 | } 49 | .editor-icon-unordered-list:before { 50 | content: "\e004"; 51 | } 52 | .editor-icon-ordered-list:before { 53 | content: "\e005"; 54 | } 55 | .editor-icon-link:before { 56 | content: "\e006"; 57 | } 58 | .editor-icon-image:before { 59 | content: "\e007"; 60 | } 61 | .editor-icon-play:before { 62 | content: "\e008"; 63 | } 64 | .editor-icon-music:before { 65 | content: "\e009"; 66 | } 67 | .editor-icon-contract:before { 68 | content: "\e00a"; 69 | } 70 | .editor-icon-fullscreen:before { 71 | content: "\e00b"; 72 | } 73 | .editor-icon-question:before { 74 | content: "\e00c"; 75 | } 76 | .editor-icon-info:before { 77 | content: "\e00d"; 78 | } 79 | .editor-icon-undo:before { 80 | content: "\e00e"; 81 | } 82 | .editor-icon-redo:before { 83 | content: "\e00f"; 84 | } 85 | .editor-icon-code:before { 86 | content: "\e011"; 87 | } 88 | .editor-icon-preview:before { 89 | content: "\e002"; 90 | } 91 | -------------------------------------------------------------------------------- /vendor/markdown.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { 2 | 3 | var htmlFound = CodeMirror.modes.hasOwnProperty("xml"); 4 | var htmlMode = CodeMirror.getMode(cmCfg, htmlFound ? {name: "xml", htmlMode: true} : "text/plain"); 5 | var aliases = { 6 | html: "htmlmixed", 7 | js: "javascript", 8 | json: "application/json", 9 | c: "text/x-csrc", 10 | "c++": "text/x-c++src", 11 | java: "text/x-java", 12 | csharp: "text/x-csharp", 13 | "c#": "text/x-csharp", 14 | scala: "text/x-scala" 15 | }; 16 | 17 | var getMode = (function () { 18 | var i, modes = {}, mimes = {}, mime; 19 | 20 | var list = []; 21 | for (var m in CodeMirror.modes) 22 | if (CodeMirror.modes.propertyIsEnumerable(m)) list.push(m); 23 | for (i = 0; i < list.length; i++) { 24 | modes[list[i]] = list[i]; 25 | } 26 | var mimesList = []; 27 | for (var m in CodeMirror.mimeModes) 28 | if (CodeMirror.mimeModes.propertyIsEnumerable(m)) 29 | mimesList.push({mime: m, mode: CodeMirror.mimeModes[m]}); 30 | for (i = 0; i < mimesList.length; i++) { 31 | mime = mimesList[i].mime; 32 | mimes[mime] = mimesList[i].mime; 33 | } 34 | 35 | for (var a in aliases) { 36 | if (aliases[a] in modes || aliases[a] in mimes) 37 | modes[a] = aliases[a]; 38 | } 39 | 40 | return function (lang) { 41 | return modes[lang] ? CodeMirror.getMode(cmCfg, modes[lang]) : null; 42 | }; 43 | }()); 44 | 45 | // Should underscores in words open/close em/strong? 46 | if (modeCfg.underscoresBreakWords === undefined) 47 | modeCfg.underscoresBreakWords = true; 48 | 49 | // Turn on fenced code blocks? ("```" to start/end) 50 | if (modeCfg.fencedCodeBlocks === undefined) modeCfg.fencedCodeBlocks = false; 51 | 52 | // Turn on task lists? ("- [ ] " and "- [x] ") 53 | if (modeCfg.taskLists === undefined) modeCfg.taskLists = false; 54 | 55 | var codeDepth = 0; 56 | 57 | var header = 'header' 58 | , code = 'comment' 59 | , quote1 = 'atom' 60 | , quote2 = 'number' 61 | , list1 = 'variable-2' 62 | , list2 = 'variable-3' 63 | , list3 = 'keyword' 64 | , hr = 'hr' 65 | , image = 'tag' 66 | , linkinline = 'link' 67 | , linkemail = 'link' 68 | , linktext = 'link' 69 | , linkhref = 'string' 70 | , em = 'em' 71 | , strong = 'strong' 72 | , strike = 'strike'; 73 | 74 | var hrRE = /^([*\-=_])(?:\s*\1){4,}\s*$/ 75 | , ulRE = /^[*\-+]\s+/ 76 | , olRE = /^[0-9]+\.\s+/ 77 | , taskListRE = /^\[(x| )\](?=\s)/ // Must follow ulRE or olRE 78 | , headerRE = /^(?:\={1,}|-{1,})$/ 79 | , textRE = /^[^!\[\]*_~\\<>` "'(]+/; 80 | 81 | function switchInline(stream, state, f) { 82 | state.f = state.inline = f; 83 | return f(stream, state); 84 | } 85 | 86 | function switchBlock(stream, state, f) { 87 | state.f = state.block = f; 88 | return f(stream, state); 89 | } 90 | 91 | 92 | // Blocks 93 | 94 | function blankLine(state) { 95 | // Reset linkTitle state 96 | state.linkTitle = false; 97 | // Reset EM state 98 | state.em = false; 99 | // Reset STRONG state 100 | state.strong = false; 101 | // Reset STRIKE state 102 | state.strike = false; 103 | 104 | // Reset state.quote 105 | state.quote = 0; 106 | if (!htmlFound && state.f == htmlBlock) { 107 | state.f = inlineNormal; 108 | state.block = blockNormal; 109 | } 110 | // Reset state.trailingSpace 111 | state.trailingSpace = 0; 112 | state.trailingSpaceNewLine = false; 113 | // Mark this line as blank 114 | state.thisLineHasContent = false; 115 | return null; 116 | } 117 | 118 | function blockNormal(stream, state) { 119 | 120 | var prevLineIsList = (state.list !== false); 121 | if (state.list !== false && state.indentationDiff >= 0) { // Continued list 122 | if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block 123 | state.indentation -= state.indentationDiff; 124 | } 125 | state.list = null; 126 | } else if (state.list !== false && state.indentation > 0) { 127 | state.list = null; 128 | state.listDepth = Math.floor(state.indentation / 4); 129 | } else if (state.list !== false) { // No longer a list 130 | state.list = false; 131 | state.listDepth = 0; 132 | } 133 | 134 | if (state.indentationDiff >= 4) { 135 | state.indentation -= 4; 136 | stream.skipToEnd(); 137 | return code; 138 | } else if (stream.eatSpace()) { 139 | return null; 140 | } else if (stream.peek() === '#' || (state.prevLineHasContent && stream.match(headerRE)) ) { 141 | state.header = true; 142 | } else if (stream.eat('>')) { 143 | state.indentation++; 144 | state.quote = 1; 145 | stream.eatSpace(); 146 | while (stream.eat('>')) { 147 | stream.eatSpace(); 148 | state.quote++; 149 | } 150 | } else if (stream.peek() === '[') { 151 | return switchInline(stream, state, footnoteLink); 152 | } else if (stream.match(hrRE, true)) { 153 | return hr; 154 | } else if ((!state.prevLineHasContent || prevLineIsList) && (stream.match(ulRE, true) || stream.match(olRE, true))) { 155 | state.indentation += 4; 156 | state.list = true; 157 | state.listDepth++; 158 | if (modeCfg.taskLists && stream.match(taskListRE, false)) { 159 | state.taskList = true; 160 | } 161 | } else if (modeCfg.fencedCodeBlocks && stream.match(/^```([\w+#]*)/, true)) { 162 | // try switching mode 163 | state.localMode = getMode(RegExp.$1); 164 | if (state.localMode) state.localState = state.localMode.startState(); 165 | switchBlock(stream, state, local); 166 | return code; 167 | } 168 | 169 | return switchInline(stream, state, state.inline); 170 | } 171 | 172 | function htmlBlock(stream, state) { 173 | var style = htmlMode.token(stream, state.htmlState); 174 | if (htmlFound && style === 'tag' && state.htmlState.type !== 'openTag' && !state.htmlState.context) { 175 | state.f = inlineNormal; 176 | state.block = blockNormal; 177 | } 178 | if (state.md_inside && stream.current().indexOf(">")!=-1) { 179 | state.f = inlineNormal; 180 | state.block = blockNormal; 181 | state.htmlState.context = undefined; 182 | } 183 | return style; 184 | } 185 | 186 | function local(stream, state) { 187 | if (stream.sol() && stream.match(/^```/, true)) { 188 | state.localMode = state.localState = null; 189 | state.f = inlineNormal; 190 | state.block = blockNormal; 191 | return code; 192 | } else if (state.localMode) { 193 | return state.localMode.token(stream, state.localState); 194 | } else { 195 | stream.skipToEnd(); 196 | return code; 197 | } 198 | } 199 | 200 | // Inline 201 | function getType(state) { 202 | var styles = []; 203 | 204 | if (state.taskOpen) { return "meta"; } 205 | if (state.taskClosed) { return "property"; } 206 | 207 | if (state.strong) { styles.push(strong); } 208 | if (state.strike) { styles.push(strike); } 209 | if (state.em) { styles.push(em); } 210 | 211 | if (state.linkText) { styles.push(linktext); } 212 | 213 | if (state.code) { styles.push(code); } 214 | 215 | if (state.header) { styles.push(header); } 216 | if (state.quote) { styles.push(state.quote % 2 ? quote1 : quote2); } 217 | if (state.list !== false) { 218 | var listMod = (state.listDepth - 1) % 3; 219 | if (!listMod) { 220 | styles.push(list1); 221 | } else if (listMod === 1) { 222 | styles.push(list2); 223 | } else { 224 | styles.push(list3); 225 | } 226 | } 227 | 228 | if (state.trailingSpaceNewLine) { 229 | styles.push("trailing-space-new-line"); 230 | } else if (state.trailingSpace) { 231 | styles.push("trailing-space-" + (state.trailingSpace % 2 ? "a" : "b")); 232 | } 233 | 234 | return styles.length ? styles.join(' ') : null; 235 | } 236 | 237 | function handleText(stream, state) { 238 | if (stream.match(textRE, true)) { 239 | return getType(state); 240 | } 241 | return undefined; 242 | } 243 | 244 | function inlineNormal(stream, state) { 245 | var style = state.text(stream, state); 246 | if (typeof style !== 'undefined') 247 | return style; 248 | 249 | if (state.list) { // List marker (*, +, -, 1., etc) 250 | state.list = null; 251 | return getType(state); 252 | } 253 | 254 | if (state.taskList) { 255 | var taskOpen = stream.match(taskListRE, true)[1] !== "x"; 256 | if (taskOpen) state.taskOpen = true; 257 | else state.taskClosed = true; 258 | state.taskList = false; 259 | return getType(state); 260 | } 261 | 262 | state.taskOpen = false; 263 | state.taskClosed = false; 264 | 265 | var ch = stream.next(); 266 | 267 | if (ch === '\\') { 268 | stream.next(); 269 | return getType(state); 270 | } 271 | 272 | // Matches link titles present on next line 273 | if (state.linkTitle) { 274 | state.linkTitle = false; 275 | var matchCh = ch; 276 | if (ch === '(') { 277 | matchCh = ')'; 278 | } 279 | matchCh = (matchCh+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); 280 | var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh; 281 | if (stream.match(new RegExp(regex), true)) { 282 | return linkhref; 283 | } 284 | } 285 | 286 | // If this block is changed, it may need to be updated in GFM mode 287 | if (ch === '`') { 288 | var t = getType(state); 289 | var before = stream.pos; 290 | stream.eatWhile('`'); 291 | var difference = 1 + stream.pos - before; 292 | if (!state.code) { 293 | codeDepth = difference; 294 | state.code = true; 295 | return getType(state); 296 | } else { 297 | if (difference === codeDepth) { // Must be exact 298 | state.code = false; 299 | return t; 300 | } 301 | return getType(state); 302 | } 303 | } else if (state.code) { 304 | return getType(state); 305 | } 306 | 307 | if (ch === '!' && stream.match(/\[[^\]]*\] ?(?:\(|\[)/, false)) { 308 | stream.match(/\[[^\]]*\]/); 309 | state.inline = state.f = linkHref; 310 | return image; 311 | } 312 | 313 | if (ch === '[' && stream.match(/.*\](\(| ?\[)/, false)) { 314 | state.linkText = true; 315 | return getType(state); 316 | } 317 | 318 | if (ch === ']' && state.linkText) { 319 | var type = getType(state); 320 | state.linkText = false; 321 | state.inline = state.f = linkHref; 322 | return type; 323 | } 324 | 325 | if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, false)) { 326 | return switchInline(stream, state, inlineElement(linkinline, '>')); 327 | } 328 | 329 | if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, false)) { 330 | return switchInline(stream, state, inlineElement(linkemail, '>')); 331 | } 332 | 333 | if (ch === '<' && stream.match(/^\w/, false)) { 334 | if (stream.string.indexOf(">")!=-1) { 335 | var atts = stream.string.substring(1,stream.string.indexOf(">")); 336 | if (/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(atts)) { 337 | state.md_inside = true; 338 | } 339 | } 340 | stream.backUp(1); 341 | return switchBlock(stream, state, htmlBlock); 342 | } 343 | 344 | if (ch === '<' && stream.match(/^\/\w*?>/)) { 345 | state.md_inside = false; 346 | return "tag"; 347 | } 348 | 349 | var ignoreUnderscore = false; 350 | if (!modeCfg.underscoresBreakWords) { 351 | if (ch === '_' && stream.peek() !== '_' && stream.match(/(\w)/, false)) { 352 | var prevPos = stream.pos - 2; 353 | if (prevPos >= 0) { 354 | var prevCh = stream.string.charAt(prevPos); 355 | if (prevCh !== '_' && prevCh.match(/(\w)/, false)) { 356 | ignoreUnderscore = true; 357 | } 358 | } 359 | } 360 | } 361 | var t = getType(state); 362 | if (ch === '*' || (ch === '_' && !ignoreUnderscore)) { 363 | if (state.strong === ch && stream.eat(ch) && stream.peek(ch)) { // Remove STRONG 364 | state.strong = false; 365 | return t; 366 | } else if (!state.strong && stream.eat(ch) && stream.peek(ch)) { // Add STRONG 367 | state.strong = ch; 368 | return getType(state); 369 | } else if (state.em === ch) { // Remove EM 370 | state.em = false; 371 | return t; 372 | } else if (!state.em) { // Add EM 373 | state.em = ch; 374 | return getType(state); 375 | } 376 | } else if (ch === '~'){ 377 | if (state.strike === ch && stream.eat(ch)) { // Remove SRTIKE 378 | state.strike = false; 379 | return t; 380 | } else if (!state.strike && stream.eat(ch)) { // Add STRIKE 381 | state.strike = ch; 382 | return getType(state); 383 | } 384 | } else if (ch === ' ') { 385 | if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces 386 | if (stream.peek() === ' ') { // Surrounded by spaces, ignore 387 | return getType(state); 388 | } else { // Not surrounded by spaces, back up pointer 389 | stream.backUp(1); 390 | } 391 | } 392 | } 393 | 394 | if (ch === ' ') { 395 | if (stream.match(/ +$/, false)) { 396 | state.trailingSpace++; 397 | } else if (state.trailingSpace) { 398 | state.trailingSpaceNewLine = true; 399 | } 400 | } 401 | 402 | return getType(state); 403 | } 404 | 405 | function linkHref(stream, state) { 406 | // Check if space, and return NULL if so (to avoid marking the space) 407 | if(stream.eatSpace()){ 408 | return null; 409 | } 410 | var ch = stream.next(); 411 | if (ch === '(' || ch === '[') { 412 | return switchInline(stream, state, inlineElement(linkhref, ch === '(' ? ')' : ']')); 413 | } 414 | return 'error'; 415 | } 416 | 417 | function footnoteLink(stream, state) { 418 | if (stream.match(/^[^\]]*\]:/, true)) { 419 | state.f = footnoteUrl; 420 | return linktext; 421 | } 422 | return switchInline(stream, state, inlineNormal); 423 | } 424 | 425 | function footnoteUrl(stream, state) { 426 | // Check if space, and return NULL if so (to avoid marking the space) 427 | if(stream.eatSpace()){ 428 | return null; 429 | } 430 | // Match URL 431 | stream.match(/^[^\s]+/, true); 432 | // Check for link title 433 | if (stream.peek() === undefined) { // End of line, set flag to check next line 434 | state.linkTitle = true; 435 | } else { // More content on line, check if link title 436 | stream.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true); 437 | } 438 | state.f = state.inline = inlineNormal; 439 | return linkhref; 440 | } 441 | 442 | var savedInlineRE = []; 443 | function inlineRE(endChar) { 444 | if (!savedInlineRE[endChar]) { 445 | // Escape endChar for RegExp (taken from http://stackoverflow.com/a/494122/526741) 446 | endChar = (endChar+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); 447 | // Match any non-endChar, escaped character, as well as the closing 448 | // endChar. 449 | savedInlineRE[endChar] = new RegExp('^(?:[^\\\\]|\\\\.)*?(' + endChar + ')'); 450 | } 451 | return savedInlineRE[endChar]; 452 | } 453 | 454 | function inlineElement(type, endChar, next) { 455 | next = next || inlineNormal; 456 | return function(stream, state) { 457 | stream.match(inlineRE(endChar)); 458 | state.inline = state.f = next; 459 | return type; 460 | }; 461 | } 462 | 463 | return { 464 | startState: function() { 465 | return { 466 | f: blockNormal, 467 | 468 | prevLineHasContent: false, 469 | thisLineHasContent: false, 470 | 471 | block: blockNormal, 472 | htmlState: CodeMirror.startState(htmlMode), 473 | indentation: 0, 474 | 475 | inline: inlineNormal, 476 | text: handleText, 477 | 478 | linkText: false, 479 | linkTitle: false, 480 | em: false, 481 | strong: false, 482 | strike: false, 483 | header: false, 484 | taskList: false, 485 | list: false, 486 | listDepth: 0, 487 | quote: 0, 488 | trailingSpace: 0, 489 | trailingSpaceNewLine: false 490 | }; 491 | }, 492 | 493 | copyState: function(s) { 494 | return { 495 | f: s.f, 496 | 497 | prevLineHasContent: s.prevLineHasContent, 498 | thisLineHasContent: s.thisLineHasContent, 499 | 500 | block: s.block, 501 | htmlState: CodeMirror.copyState(htmlMode, s.htmlState), 502 | indentation: s.indentation, 503 | 504 | localMode: s.localMode, 505 | localState: s.localMode ? CodeMirror.copyState(s.localMode, s.localState) : null, 506 | 507 | inline: s.inline, 508 | text: s.text, 509 | linkTitle: s.linkTitle, 510 | em: s.em, 511 | strong: s.strong, 512 | strike: s.strike, 513 | header: s.header, 514 | taskList: s.taskList, 515 | list: s.list, 516 | listDepth: s.listDepth, 517 | quote: s.quote, 518 | trailingSpace: s.trailingSpace, 519 | trailingSpaceNewLine: s.trailingSpaceNewLine, 520 | md_inside: s.md_inside 521 | }; 522 | }, 523 | 524 | token: function(stream, state) { 525 | if (stream.sol()) { 526 | if (stream.match(/^\s*$/, true)) { 527 | state.prevLineHasContent = false; 528 | return blankLine(state); 529 | } else { 530 | state.prevLineHasContent = state.thisLineHasContent; 531 | state.thisLineHasContent = true; 532 | } 533 | 534 | // Reset state.header 535 | state.header = false; 536 | 537 | // Reset state.taskList 538 | state.taskList = false; 539 | 540 | // Reset state.code 541 | state.code = false; 542 | 543 | // Reset state.trailingSpace 544 | state.trailingSpace = 0; 545 | state.trailingSpaceNewLine = false; 546 | 547 | state.f = state.block; 548 | var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, ' ').length; 549 | var difference = Math.floor((indentation - state.indentation) / 4) * 4; 550 | if (difference > 4) difference = 4; 551 | var adjustedIndentation = state.indentation + difference; 552 | state.indentationDiff = adjustedIndentation - state.indentation; 553 | state.indentation = adjustedIndentation; 554 | if (indentation > 0) return null; 555 | } 556 | return state.f(stream, state); 557 | }, 558 | 559 | blankLine: blankLine, 560 | 561 | getType: getType 562 | }; 563 | 564 | }, "xml"); 565 | 566 | CodeMirror.defineMIME("text/x-markdown", "markdown"); 567 | -------------------------------------------------------------------------------- /vendor/xml.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("xml", function(config, parserConfig) { 2 | var indentUnit = config.indentUnit; 3 | var multilineTagIndentFactor = parserConfig.multilineTagIndentFactor || 1; 4 | var multilineTagIndentPastTag = parserConfig.multilineTagIndentPastTag || true; 5 | 6 | var Kludges = parserConfig.htmlMode ? { 7 | autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true, 8 | 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true, 9 | 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true, 10 | 'track': true, 'wbr': true}, 11 | implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true, 12 | 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true, 13 | 'th': true, 'tr': true}, 14 | contextGrabbers: { 15 | 'dd': {'dd': true, 'dt': true}, 16 | 'dt': {'dd': true, 'dt': true}, 17 | 'li': {'li': true}, 18 | 'option': {'option': true, 'optgroup': true}, 19 | 'optgroup': {'optgroup': true}, 20 | 'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true, 21 | 'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true, 22 | 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true, 23 | 'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true, 24 | 'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true}, 25 | 'rp': {'rp': true, 'rt': true}, 26 | 'rt': {'rp': true, 'rt': true}, 27 | 'tbody': {'tbody': true, 'tfoot': true}, 28 | 'td': {'td': true, 'th': true}, 29 | 'tfoot': {'tbody': true}, 30 | 'th': {'td': true, 'th': true}, 31 | 'thead': {'tbody': true, 'tfoot': true}, 32 | 'tr': {'tr': true} 33 | }, 34 | doNotIndent: {"pre": true}, 35 | allowUnquoted: true, 36 | allowMissing: true 37 | } : { 38 | autoSelfClosers: {}, 39 | implicitlyClosed: {}, 40 | contextGrabbers: {}, 41 | doNotIndent: {}, 42 | allowUnquoted: false, 43 | allowMissing: false 44 | }; 45 | var alignCDATA = parserConfig.alignCDATA; 46 | 47 | // Return variables for tokenizers 48 | var tagName, type; 49 | 50 | function inText(stream, state) { 51 | function chain(parser) { 52 | state.tokenize = parser; 53 | return parser(stream, state); 54 | } 55 | 56 | var ch = stream.next(); 57 | if (ch == "<") { 58 | if (stream.eat("!")) { 59 | if (stream.eat("[")) { 60 | if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>")); 61 | else return null; 62 | } else if (stream.match("--")) { 63 | return chain(inBlock("comment", "-->")); 64 | } else if (stream.match("DOCTYPE", true, true)) { 65 | stream.eatWhile(/[\w\._\-]/); 66 | return chain(doctype(1)); 67 | } else { 68 | return null; 69 | } 70 | } else if (stream.eat("?")) { 71 | stream.eatWhile(/[\w\._\-]/); 72 | state.tokenize = inBlock("meta", "?>"); 73 | return "meta"; 74 | } else { 75 | var isClose = stream.eat("/"); 76 | tagName = ""; 77 | var c; 78 | while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c; 79 | if (!tagName) return "error"; 80 | type = isClose ? "closeTag" : "openTag"; 81 | state.tokenize = inTag; 82 | return "tag"; 83 | } 84 | } else if (ch == "&") { 85 | var ok; 86 | if (stream.eat("#")) { 87 | if (stream.eat("x")) { 88 | ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";"); 89 | } else { 90 | ok = stream.eatWhile(/[\d]/) && stream.eat(";"); 91 | } 92 | } else { 93 | ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";"); 94 | } 95 | return ok ? "atom" : "error"; 96 | } else { 97 | stream.eatWhile(/[^&<]/); 98 | return null; 99 | } 100 | } 101 | 102 | function inTag(stream, state) { 103 | var ch = stream.next(); 104 | if (ch == ">" || (ch == "/" && stream.eat(">"))) { 105 | state.tokenize = inText; 106 | type = ch == ">" ? "endTag" : "selfcloseTag"; 107 | return "tag"; 108 | } else if (ch == "=") { 109 | type = "equals"; 110 | return null; 111 | } else if (ch == "<") { 112 | return "error"; 113 | } else if (/[\'\"]/.test(ch)) { 114 | state.tokenize = inAttribute(ch); 115 | state.stringStartCol = stream.column(); 116 | return state.tokenize(stream, state); 117 | } else { 118 | stream.eatWhile(/[^\s\u00a0=<>\"\']/); 119 | return "word"; 120 | } 121 | } 122 | 123 | function inAttribute(quote) { 124 | var closure = function(stream, state) { 125 | while (!stream.eol()) { 126 | if (stream.next() == quote) { 127 | state.tokenize = inTag; 128 | break; 129 | } 130 | } 131 | return "string"; 132 | }; 133 | closure.isInAttribute = true; 134 | return closure; 135 | } 136 | 137 | function inBlock(style, terminator) { 138 | return function(stream, state) { 139 | while (!stream.eol()) { 140 | if (stream.match(terminator)) { 141 | state.tokenize = inText; 142 | break; 143 | } 144 | stream.next(); 145 | } 146 | return style; 147 | }; 148 | } 149 | function doctype(depth) { 150 | return function(stream, state) { 151 | var ch; 152 | while ((ch = stream.next()) != null) { 153 | if (ch == "<") { 154 | state.tokenize = doctype(depth + 1); 155 | return state.tokenize(stream, state); 156 | } else if (ch == ">") { 157 | if (depth == 1) { 158 | state.tokenize = inText; 159 | break; 160 | } else { 161 | state.tokenize = doctype(depth - 1); 162 | return state.tokenize(stream, state); 163 | } 164 | } 165 | } 166 | return "meta"; 167 | }; 168 | } 169 | 170 | var curState, curStream, setStyle; 171 | function pass() { 172 | for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]); 173 | } 174 | function cont() { 175 | pass.apply(null, arguments); 176 | return true; 177 | } 178 | 179 | function pushContext(tagName, startOfLine) { 180 | var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent); 181 | curState.context = { 182 | prev: curState.context, 183 | tagName: tagName, 184 | indent: curState.indented, 185 | startOfLine: startOfLine, 186 | noIndent: noIndent 187 | }; 188 | } 189 | function popContext() { 190 | if (curState.context) curState.context = curState.context.prev; 191 | } 192 | 193 | function element(type) { 194 | if (type == "openTag") { 195 | curState.tagName = tagName; 196 | curState.tagStart = curStream.column(); 197 | return cont(attributes, endtag(curState.startOfLine)); 198 | } else if (type == "closeTag") { 199 | var err = false; 200 | if (curState.context) { 201 | if (curState.context.tagName != tagName) { 202 | if (Kludges.implicitlyClosed.hasOwnProperty(curState.context.tagName.toLowerCase())) { 203 | popContext(); 204 | } 205 | err = !curState.context || curState.context.tagName != tagName; 206 | } 207 | } else { 208 | err = true; 209 | } 210 | if (err) setStyle = "error"; 211 | return cont(endclosetag(err)); 212 | } 213 | return cont(); 214 | } 215 | function endtag(startOfLine) { 216 | return function(type) { 217 | var tagName = curState.tagName; 218 | curState.tagName = curState.tagStart = null; 219 | if (type == "selfcloseTag" || 220 | (type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(tagName.toLowerCase()))) { 221 | maybePopContext(tagName.toLowerCase()); 222 | return cont(); 223 | } 224 | if (type == "endTag") { 225 | maybePopContext(tagName.toLowerCase()); 226 | pushContext(tagName, startOfLine); 227 | return cont(); 228 | } 229 | return cont(); 230 | }; 231 | } 232 | function endclosetag(err) { 233 | return function(type) { 234 | if (err) setStyle = "error"; 235 | if (type == "endTag") { popContext(); return cont(); } 236 | setStyle = "error"; 237 | return cont(arguments.callee); 238 | }; 239 | } 240 | function maybePopContext(nextTagName) { 241 | var parentTagName; 242 | while (true) { 243 | if (!curState.context) { 244 | return; 245 | } 246 | parentTagName = curState.context.tagName.toLowerCase(); 247 | if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) || 248 | !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) { 249 | return; 250 | } 251 | popContext(); 252 | } 253 | } 254 | 255 | function attributes(type) { 256 | if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);} 257 | if (type == "endTag" || type == "selfcloseTag") return pass(); 258 | setStyle = "error"; 259 | return cont(attributes); 260 | } 261 | function attribute(type) { 262 | if (type == "equals") return cont(attvalue, attributes); 263 | if (!Kludges.allowMissing) setStyle = "error"; 264 | else if (type == "word") setStyle = "attribute"; 265 | return (type == "endTag" || type == "selfcloseTag") ? pass() : cont(); 266 | } 267 | function attvalue(type) { 268 | if (type == "string") return cont(attvaluemaybe); 269 | if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();} 270 | setStyle = "error"; 271 | return (type == "endTag" || type == "selfCloseTag") ? pass() : cont(); 272 | } 273 | function attvaluemaybe(type) { 274 | if (type == "string") return cont(attvaluemaybe); 275 | else return pass(); 276 | } 277 | 278 | return { 279 | startState: function() { 280 | return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, tagStart: null, context: null}; 281 | }, 282 | 283 | token: function(stream, state) { 284 | if (!state.tagName && stream.sol()) { 285 | state.startOfLine = true; 286 | state.indented = stream.indentation(); 287 | } 288 | if (stream.eatSpace()) return null; 289 | 290 | setStyle = type = tagName = null; 291 | var style = state.tokenize(stream, state); 292 | state.type = type; 293 | if ((style || type) && style != "comment") { 294 | curState = state; curStream = stream; 295 | while (true) { 296 | var comb = state.cc.pop() || element; 297 | if (comb(type || style)) break; 298 | } 299 | } 300 | state.startOfLine = false; 301 | return setStyle || style; 302 | }, 303 | 304 | indent: function(state, textAfter, fullLine) { 305 | var context = state.context; 306 | // Indent multi-line strings (e.g. css). 307 | if (state.tokenize.isInAttribute) { 308 | return state.stringStartCol + 1; 309 | } 310 | if ((state.tokenize != inTag && state.tokenize != inText) || 311 | context && context.noIndent) 312 | return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0; 313 | // Indent the starts of attribute names. 314 | if (state.tagName) { 315 | if (multilineTagIndentPastTag) 316 | return state.tagStart + state.tagName.length + 2; 317 | else 318 | return state.tagStart + indentUnit * multilineTagIndentFactor; 319 | } 320 | if (alignCDATA && /", 332 | 333 | configuration: parserConfig.htmlMode ? "html" : "xml", 334 | helperType: parserConfig.htmlMode ? "html" : "xml" 335 | }; 336 | }); 337 | 338 | CodeMirror.defineMIME("text/xml", "xml"); 339 | CodeMirror.defineMIME("application/xml", "xml"); 340 | if (!CodeMirror.mimeModes.hasOwnProperty("text/html")) 341 | CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true}); 342 | --------------------------------------------------------------------------------