├── .gitignore ├── static ├── img │ └── hr.png ├── index.html └── style.css ├── snippets └── mit.txt ├── package.json ├── .jshintrc ├── LICENSE ├── browser ├── index.js └── codemirror.js ├── bin └── morkdown.js ├── README.md └── lib └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | static/codemirror/codemirror.css 3 | static/codemirror/theme/ 4 | -------------------------------------------------------------------------------- /static/img/hr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rvagg/archived-morkdown/HEAD/static/img/hr.png -------------------------------------------------------------------------------- /snippets/mit.txt: -------------------------------------------------------------------------------- 1 | (MIT) 2 | 3 | Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "morkdown", 3 | "version": "2.4.5", 4 | "description": "A markdown editor built on Chrome & Node", 5 | "main": "lib/server.js", 6 | "dependencies": { 7 | "delayed": "~0.0.0", 8 | "concat-stream": "~1.0.1", 9 | "domready": "~0.2.13", 10 | "hyperquest": "~0.1.8", 11 | "browserify": "~2.35.1", 12 | "brucedown": "~0.1.1", 13 | "after": "~0.8.1", 14 | "st": "~0.2.2", 15 | "he": "~0.4.1", 16 | "codemirror": "~3.19.0", 17 | "optimist": "~0.6.0", 18 | "bl": "~0.5.0", 19 | "shoe": "~0.0.15", 20 | "through": "~2.3.4", 21 | "become": "~1.1.0" 22 | }, 23 | "bin": { 24 | "morkdown": "./bin/morkdown.js" 25 | }, 26 | "keywords": [ 27 | "markdown" 28 | ], 29 | "repository": { 30 | "type": "git", 31 | "url": "https://github.com/rvagg/morkdown.git" 32 | }, 33 | "preferGlobal": true, 34 | "authors": [ 35 | "Rod Vagg (https://github.com/rvagg)", 36 | "Ian Duffy (https://github.com/imduffy15)", 37 | "ralphtheninja (https://github.com/ralphtheninja)" 38 | ], 39 | "scripts": { 40 | "install": "mkdir -p static/codemirror/; cp node_modules/codemirror/lib/codemirror.css static/codemirror/ && cp -a node_modules/codemirror/theme/ static/codemirror/theme/" 41 | }, 42 | "license": "MIT" 43 | } 44 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ "alert" ] 3 | , "bitwise": false 4 | , "camelcase": false 5 | , "curly": false 6 | , "eqeqeq": false 7 | , "forin": false 8 | , "immed": false 9 | , "latedef": false 10 | , "noarg": true 11 | , "noempty": true 12 | , "nonew": true 13 | , "plusplus": false 14 | , "quotmark": true 15 | , "regexp": false 16 | , "undef": true 17 | , "unused": true 18 | , "strict": false 19 | , "trailing": true 20 | , "maxlen": 120 21 | , "asi": true 22 | , "boss": true 23 | , "debug": true 24 | , "eqnull": true 25 | , "esnext": true 26 | , "evil": true 27 | , "expr": true 28 | , "funcscope": false 29 | , "globalstrict": false 30 | , "iterator": false 31 | , "lastsemic": true 32 | , "laxbreak": true 33 | , "laxcomma": true 34 | , "loopfunc": true 35 | , "multistr": false 36 | , "onecase": false 37 | , "proto": false 38 | , "regexdash": false 39 | , "scripturl": true 40 | , "smarttabs": false 41 | , "shadow": false 42 | , "sub": true 43 | , "supernew": false 44 | , "validthis": true 45 | , "browser": true 46 | , "couch": false 47 | , "devel": false 48 | , "dojo": false 49 | , "mootools": false 50 | , "node": true 51 | , "nonstandard": true 52 | , "prototypejs": false 53 | , "rhino": false 54 | , "worker": true 55 | , "wsh": false 56 | , "nomen": false 57 | , "onevar": true 58 | , "passfail": false 59 | } -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Morkdown [{file}] 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 |
{output}
33 | 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012, Rod Vagg (the "Original Author") 2 | All rights reserved. 3 | 4 | MIT +no-false-attribs License 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the "Software"), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | Distributions of all or part of the Software intended to be used 19 | by the recipients as they would use the unmodified Software, 20 | containing modifications that substantially alter, remove, or 21 | disable functionality of the Software, outside of the documented 22 | configuration mechanisms provided by the Software, shall be 23 | modified such that the Original Author's bug reporting email 24 | addresses and urls are either replaced with the contact information 25 | of the parties responsible for the changes, or removed entirely. 26 | 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 28 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 29 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 30 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 31 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 32 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 33 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 34 | OTHER DEALINGS IN THE SOFTWARE. 35 | 36 | 37 | Except where noted, this license applies to any and all software 38 | programs and associated documentation files created by the 39 | Original Author, when distributed with the Software. -------------------------------------------------------------------------------- /browser/index.js: -------------------------------------------------------------------------------- 1 | var ready = require('domready') 2 | , request = require('hyperquest') 3 | , cumulativeDelayed = require('delayed').cumulativeDelayed 4 | , concat = require('concat-stream') 5 | , shoe = require('shoe') 6 | , through = require('through') 7 | , become = require('become') 8 | 9 | , $codeMirror 10 | , $output 11 | , inTransit = false 12 | , lastText 13 | 14 | require('./codemirror') 15 | 16 | var handleResponse = function (res) { 17 | inTransit = false 18 | 19 | if (res == 'stale') 20 | return // ignore 21 | 22 | res = JSON.parse(res) 23 | 24 | if (res.error) 25 | return alert(res.error) 26 | 27 | become($output, res.content ? res.content : '', {inner: true}) 28 | } 29 | 30 | var transmit = cumulativeDelayed(function () { 31 | if (inTransit) 32 | return 33 | 34 | var content = $codeMirror.getValue() 35 | , req 36 | 37 | if (lastText == content) 38 | return 39 | 40 | lastText = content 41 | 42 | inTransit = true 43 | req = request.post('/content') 44 | req.setHeader('Content-Type', 'application/json') 45 | req.pipe(concat(handleResponse)) 46 | req.write(JSON.stringify({ 47 | ts : Date.now(), 48 | content : content 49 | })) 50 | req.end() 51 | }, 0.3) 52 | 53 | ready(function () { 54 | var $input = document.querySelector('#input') 55 | , $textarea 56 | , stream 57 | 58 | $output = document.querySelector('#output > div') 59 | 60 | if ($input.style && $input.style.display && $input.style.display == 'none') { 61 | // pushing changes to client when file changed locally 62 | stream = shoe('/output') 63 | stream.pipe(through(handleResponse)) 64 | } 65 | else { 66 | // input from browser to server (save file) and result back to client 67 | $textarea = document.querySelector('#input > textarea') 68 | $codeMirror = CodeMirror.fromTextArea($textarea, { 69 | mode : 'gfm' 70 | , lineNumbers : true 71 | , theme : document.body.getAttribute('data-theme') || 'neat' 72 | , lineWrapping : true 73 | , tabSize : 2 74 | , autofocus : true 75 | }); 76 | lastText = $codeMirror.getValue() 77 | $codeMirror.on('change', transmit) 78 | } 79 | }) 80 | -------------------------------------------------------------------------------- /bin/morkdown.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var me = require('..') 4 | , spawn = require('child_process').spawn 5 | , os = require('os') 6 | , fs = require('fs') 7 | , path = require('path') 8 | , argv = (function () { 9 | var argv = require('optimist').argv 10 | , def, p 11 | try { 12 | def = JSON.parse(fs.readFileSync(path.join(process.env.HOME, '.morkdownrc'))) 13 | for (p in argv) 14 | def[p] = argv[p] 15 | argv = def 16 | } catch (e) {} 17 | return argv 18 | }()) 19 | , watching = argv.w 20 | , file = watching ? argv.w : argv._[0] 21 | , theme = argv.theme 22 | 23 | , bin = 'google-chrome' 24 | , darwinBin = [ 25 | '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' 26 | , '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary' 27 | , '/opt/homebrew-cask/Caskroom/google-chrome/stable-channel/Google Chrome.app/Contents/MacOS/Google Chrome' 28 | , '/opt/homebrew-cask/Caskroom/google-chrome/latest/Google Chrome.app/Contents/MacOS/Google Chrome' 29 | ] 30 | , linuxBin = [ 31 | '/usr/bin/google-chrome' 32 | , '/usr/bin/chromium-browser' 33 | , '/usr/bin/chromium' 34 | ] 35 | , args = [ 36 | null 37 | , '--disk-cache-size 0' 38 | , '--no-proxy-server' 39 | ] 40 | 41 | if (file && fs.existsSync(file) && !fs.statSync(file).isFile()) { 42 | console.error('File [' + file + '] is not a regular file') 43 | file = null 44 | } 45 | 46 | if (file && !fs.existsSync(file)) 47 | fs.writeFileSync(file, '', 'utf8') 48 | 49 | if (!file) { 50 | console.error('Usage: morkdown ') 51 | process.exit(-1) 52 | } 53 | 54 | if (os.platform() == 'darwin') { 55 | bin = darwinBin.reduce(function (p, c) { 56 | if (p) 57 | return p 58 | return fs.existsSync(c) && c 59 | }, null) 60 | 61 | if (!bin) 62 | throw(new Error('Chrome or Canary were not found')) 63 | } 64 | 65 | if (os.platform() == 'linux') { 66 | bin = linuxBin.reduce(function (p, c) { 67 | if (p) 68 | return p 69 | return fs.existsSync(c) && c 70 | }, null) 71 | 72 | if (!bin) 73 | throw(new Error('Chrome or Chromium were not found')) 74 | } 75 | 76 | me(file, theme, watching).listen(0, 'localhost', function (err) { 77 | if (err) 78 | throw err 79 | 80 | // put the address on the end of the first argument, the URL 81 | args[0] = '--app=http://' + this.address().address + ':' + String(this.address().port) 82 | 83 | if (process.env.HOME) 84 | args.push('--user-data-dir=' + path.join(process.env.HOME, '.md')) 85 | 86 | spawn(bin, args) 87 | .on('exit', process.exit.bind(process, 0)) 88 | .stderr.pipe(process.stderr) 89 | }) 90 | -------------------------------------------------------------------------------- /browser/codemirror.js: -------------------------------------------------------------------------------- 1 | global.CodeMirror = require('codemirror') && window.CodeMirror 2 | require('codemirror/addon/mode/overlay') 3 | require('codemirror/addon/dialog/dialog') 4 | require('codemirror/addon/search/searchcursor') 5 | require('codemirror/addon/search/search') 6 | require('codemirror/addon/search/match-highlighter') 7 | require('codemirror/addon/edit/closebrackets') 8 | require('codemirror/addon/edit/matchbrackets') 9 | require('codemirror/addon/edit/continuelist') 10 | require('codemirror/addon/edit/closetag') 11 | 12 | require('codemirror/mode/yaml/yaml') 13 | require('codemirror/mode/commonlisp/commonlisp') 14 | require('codemirror/mode/turtle/turtle') 15 | require('codemirror/mode/d/d') 16 | require('codemirror/mode/ruby/ruby') 17 | require('codemirror/mode/ocaml/ocaml') 18 | require('codemirror/mode/coffeescript/coffeescript') 19 | require('codemirror/mode/z80/z80') 20 | require('codemirror/mode/gfm/gfm') 21 | require('codemirror/mode/pig/pig') 22 | require('codemirror/mode/smalltalk/smalltalk') 23 | require('codemirror/mode/lua/lua') 24 | require('codemirror/mode/rst/rst') 25 | require('codemirror/mode/haskell/haskell') 26 | require('codemirror/mode/python/python') 27 | require('codemirror/mode/xquery/xquery') 28 | require('codemirror/mode/erlang/erlang') 29 | require('codemirror/mode/mirc/mirc') 30 | require('codemirror/mode/q/q') 31 | require('codemirror/mode/velocity/velocity') 32 | require('codemirror/mode/jinja2/jinja2') 33 | require('codemirror/mode/sieve/sieve') 34 | require('codemirror/mode/perl/perl') 35 | require('codemirror/mode/htmlembedded/htmlembedded') 36 | require('codemirror/mode/stex/stex') 37 | require('codemirror/mode/rpm/changes/changes') 38 | require('codemirror/mode/rpm/spec/spec') 39 | require('codemirror/mode/http/http') 40 | require('codemirror/mode/tiki/tiki') 41 | require('codemirror/mode/clike/clike') 42 | require('codemirror/mode/scheme/scheme') 43 | require('codemirror/mode/vb/vb') 44 | require('codemirror/mode/sparql/sparql') 45 | require('codemirror/mode/sql/sql') 46 | require('codemirror/mode/livescript/livescript') 47 | require('codemirror/mode/sass/sass') 48 | require('codemirror/mode/ntriples/ntriples') 49 | require('codemirror/mode/markdown/markdown') 50 | require('codemirror/mode/ecl/ecl') 51 | require('codemirror/mode/tiddlywiki/tiddlywiki') 52 | require('codemirror/mode/clojure/clojure') 53 | require('codemirror/mode/properties/properties') 54 | require('codemirror/mode/shell/shell') 55 | require('codemirror/mode/haml/haml') 56 | require('codemirror/mode/groovy/groovy') 57 | require('codemirror/mode/meta') 58 | require('codemirror/mode/vbscript/vbscript') 59 | require('codemirror/mode/rust/rust') 60 | require('codemirror/mode/diff/diff') 61 | require('codemirror/mode/r/r') 62 | require('codemirror/mode/pascal/pascal') 63 | require('codemirror/mode/less/less') 64 | require('codemirror/mode/cobol/cobol') 65 | require('codemirror/mode/tcl/tcl') 66 | require('codemirror/mode/gas/gas') 67 | require('codemirror/mode/go/go') 68 | require('codemirror/mode/xml/xml') 69 | require('codemirror/mode/verilog/verilog') 70 | require('codemirror/mode/css/css') 71 | require('codemirror/mode/apl/apl') 72 | require('codemirror/mode/php/php') 73 | require('codemirror/mode/javascript/javascript') 74 | require('codemirror/mode/smarty/smarty') 75 | require('codemirror/mode/htmlmixed/htmlmixed') 76 | require('codemirror/mode/asterisk/asterisk') 77 | require('codemirror/mode/haxe/haxe') -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Morkdown 2 | 3 | ***A beautifully simple editor for Markdown documents*** 4 | 5 | [![NPM](https://nodei.co/npm/morkdown.png?compact=true)](https://nodei.co/npm/morkdown/) 6 | 7 | Morkdown is primarily designed to render [GitHub Flavored Markdown](http://github.github.com/github-flavored-markdown/) (GFM), so it's ideal for your **README.md**. When rendering the Markdown, it uses the same syntax highlighter as GitHub (the Python [Pygments](http://pygments.org/) library) and the styling is near identical to GitHub. Markdown content is parsed using [marked][marked](https://github.com/chjj/marked)(via [brucedown](https://github.com/rvagg/node-brucedown/)), a JavaScript Markdown parser capable of parsing GFM. 8 | 9 | ![WOW!!!](https://github.com/rvagg/morkdown/blob/gh-pages/screenshot-1.png) 10 | 11 | *Morkdown editing the [LevelUP](https://github.com/rvagg/node-levelup) README* 12 | 13 | Morkdown is a **Google Chrome App** coupled to a Node server and uses [CodeMirror](http://codemirror.net) for the editor panel. 14 | 15 | ![GFM you say?](https://github.com/rvagg/morkdown/blob/gh-pages/screenshot-2.png) 16 | 17 | *Morkdown editing the [LevelUP](https://github.com/rvagg/node-levelup) README with the "monokai" theme* 18 | 19 | Morkdown will **automatically save** your document as you edit it. 20 | 21 | ## Themes 22 | 23 | Morkdown is packaged with the standard CodeMirror themes, you can switch to a different theme with the `--theme ` commandline argument: 24 | 25 | * ambiance 26 | * ambiance-mobile 27 | * blackboard 28 | * cobalt 29 | * eclipse 30 | * elegant 31 | * erlang-dark 32 | * lesser-dark 33 | * midnight 34 | * monokai 35 | * neat 36 | * night 37 | * rubyblue 38 | * solarized 39 | * twilight 40 | * vibrant-ink 41 | * xq-dark 42 | * xq-light 43 | 44 | The default theme is **neat** but you can set your own default theme by saving a JSON file in your home directory named *`.morkdownrc`* with the following content: `{ "theme": "themename" }`. (My theme of choice is *monokai*). 45 | 46 | ## Installing & Using 47 | 48 | You'll need Google Chrome of course, plus you'll need a python interpreter on your system to get the syntax highlighting working—which should be fine for Linux and Mac users. Getting it running on Windows might be a little tricky (but presumably not impossible!). 49 | 50 | You can install from [npm](http://npmjs.org), the Node.js package manager, with **`npm install -g morkdown`** (you may need to `sudo` that depending on your setup). Once installed you can simply run the **`morkdown `** command and you're away! 51 | 52 | If you want to use an editor of your choice, launch morkdown with a *watch flag*, e.g. **`morkdown -w `** and morkdown will re-render the output in the browser when the file is saved locally. 53 | 54 | ## Contributors 55 | 56 | **morkdown** is brought to you by the following hackers: 57 | 58 | * [Rod Vagg](https://github.com/rvagg) 59 | * [Ian Duffy](https://github.com/imduffy15) 60 | * [ralphtheninja](https://github.com/ralphtheninja) 61 | 62 | ## Licence & Copyright 63 | 64 | Morkdown is Copyright (c) 2013 Rod Vagg <@rvagg> and licenced under the MIT licence. All rights not explicitly granted in the MIT license are reserved. See the included LICENSE file for more details. 65 | 66 | Morkdown contains remnants of **[me](http://github.com/juliangruber/me/)**, which is Copyright (c) 2013 Julian Gruber and licenced under the MIT licence.rubyblue.css 67 | -------------------------------------------------------------------------------- /lib/server.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | , http = require('http') 3 | , st = require('st') 4 | , browserify = require('browserify') 5 | , fs = require('fs') 6 | , brucedown = require('brucedown') 7 | , after = require('after') 8 | , bl = require('bl') 9 | , he = require('he') 10 | , shoe = require('shoe') 11 | 12 | , staticPath = path.join(__dirname, '../static') 13 | 14 | function writeHead (res, contentType) { 15 | res.writeHead(200, { 16 | 'Content-Type': contentType 17 | , 'Cache-Control': 'no-cache' 18 | }) 19 | } 20 | 21 | // constructor 22 | function Morkdown (file, theme, watching) { 23 | if (!(this instanceof Morkdown)) return new Morkdown(file, theme, watching) 24 | 25 | // file we're editing 26 | this.file = file 27 | this.theme = theme || 'neat' 28 | 29 | // static resources 30 | this.morkdownStatic = st({ 31 | path : staticPath 32 | , url : '/' 33 | , cache : false 34 | , dot : true 35 | , passthrough : true 36 | }) 37 | 38 | // static resources 39 | this.projectStatic = st({ 40 | path : path.dirname(path.resolve(file)) 41 | , url : '/' 42 | , cache : false 43 | , dot : true 44 | , passthrough : false 45 | }) 46 | 47 | // hide input and expand output if we are watching changes to file 48 | this.watching = watching 49 | this.inputStyle = this.watching ? 'style="display: none;"' : '' 50 | this.outputStyle = this.watching ? 'style="left: 0%"' : '' 51 | 52 | this.server = http.createServer(function (req, res) { 53 | // first try to handle it locally, then handler() will defer to st if needed 54 | this.handler(req, res) 55 | }.bind(this)) 56 | } 57 | 58 | // exportable 59 | Morkdown.prototype.listen = function () { 60 | var sock 61 | 62 | this.server.listen.apply(this.server, arguments) 63 | 64 | if (!this.watching) 65 | return 66 | 67 | sock = shoe(function (stream) { 68 | var opts = { persistent: true, interval: 100 } 69 | fs.watchFile(this.file, opts, function () { 70 | fs.readFile(this.file, function (err, data) { 71 | brucedown(data, function (err, content) { 72 | stream.write(JSON.stringify({ content: content })) 73 | }) 74 | }) 75 | }.bind(this)) 76 | 77 | stream.on('end', function () { 78 | fs.unwatchFile(this.file) 79 | }.bind(this)) 80 | 81 | }.bind(this)) 82 | 83 | sock.install(this.server, '/output') 84 | } 85 | 86 | // handle /bundle.js requests 87 | Morkdown.prototype.handle_bundle_js = function(req, res) { 88 | writeHead(res, 'text/javascript') 89 | browserify(path.join(__dirname, '../browser/index.js')) 90 | .bundle() 91 | .pipe(res) 92 | } 93 | 94 | // handle / & /index.html requests 95 | Morkdown.prototype.handle_ = 96 | Morkdown.prototype.handle_index_html = function (req, res) { 97 | // read index.html, read file, render file as markdown 98 | // replace {input} and {output} in index.html with our 99 | // content, serve to the client 100 | var index, input, output 101 | 102 | , done = after(2, function (err) { 103 | if (err) { 104 | writeHead(res, 'application/json') 105 | return res.end(JSON.stringify({ error: err })) 106 | } 107 | 108 | // replace order matters here since {output} appears last in the file, we don't 109 | // want to replace "{output}" if it happens to exist in the input 110 | index = index 111 | .replace('{theme}', this.theme) 112 | .replace('{output}', output) 113 | .replace('{input}', input) 114 | .replace('{file}', this.file) 115 | .replace('{input-style}', this.inputStyle) 116 | .replace('{output-style}', this.outputStyle) 117 | 118 | writeHead(res, 'text/html') 119 | res.end(index) 120 | }.bind(this)) 121 | 122 | fs.readFile(path.join(staticPath, 'index.html'), 'utf8', function (err, _index) { 123 | index = _index 124 | done(err) 125 | }) 126 | 127 | fs.readFile(this.file, 'utf8', function (err, _input) { 128 | if (err) 129 | return done(err) 130 | 131 | brucedown(_input ,function (err, _output) { 132 | if (err) 133 | return done(err) 134 | 135 | input = he.encode(_input) 136 | output = _output 137 | done() 138 | }) 139 | }) 140 | } 141 | 142 | // handle /content calls (assume it's a post that we can collect from) 143 | Morkdown.prototype.handle_content = function (req, res) { 144 | req.pipe(bl(function (err, body) { 145 | if (err) 146 | return console.error(err) // whaaa?? 147 | 148 | // write file (save) and render markdown & return 149 | 150 | var content, done 151 | 152 | body = JSON.parse(body.toString()) 153 | 154 | // out of time request, ignore it 155 | if (this.lastTransmission && body.ts < this.lastTransmission) { 156 | writeHead(res, 'text/plain') 157 | return res.end('stale') 158 | } 159 | 160 | done = after(2, function (err) { 161 | var responseBody = {} 162 | if (err) { 163 | responseBody.error = err.message || err 164 | console.error(err.stack) 165 | } else { 166 | responseBody.content = content 167 | } 168 | 169 | writeHead(res, 'application/json') 170 | res.end(JSON.stringify(responseBody)) 171 | }) 172 | 173 | fs.writeFile(this.file, body.content, done) 174 | 175 | brucedown(body.content, function (err, _content) { 176 | content = _content 177 | done(err) 178 | }) 179 | }.bind(this))) 180 | } 181 | 182 | // basic router that maps urls to this.handle_X function calls where 183 | // X is the name of the request stripped of non-alphabetic characters 184 | // and . replaced with _ 185 | Morkdown.prototype.handler = function(req, res) { 186 | var fn = 'handle_' + String(req.url) 187 | .toLowerCase() 188 | .replace(/[^a-z\.]/g, '') 189 | .replace(/\./g, '_') 190 | 191 | if (typeof this[fn] == 'function') 192 | return this[fn](req, res) 193 | 194 | // no match, defer to st mount point which will do 404 if required 195 | this.morkdownStatic(req, res, function () { 196 | this.projectStatic(req, res) 197 | }.bind(this)) 198 | } 199 | 200 | module.exports = Morkdown 201 | -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | font: 14px 'Helvetica Neue', Helvetica, arial, freesans, clean, sans-serif; 3 | } 4 | 5 | #input, #output { 6 | height: 100%; 7 | } 8 | 9 | #input { 10 | right: 50%; 11 | padding-left: 20px; 12 | top: 0; 13 | left: 0; 14 | bottom: 0; 15 | position: absolute; 16 | } 17 | 18 | #output { 19 | position: absolute; 20 | top: 0; 21 | right: 0; 22 | bottom: 0; 23 | left: 50%; 24 | padding-right: 20px; 25 | overflow: auto; 26 | } 27 | 28 | #output > div { 29 | padding: 10px; 30 | line-height: 1.6; 31 | color: #333; 32 | } 33 | 34 | #input > textarea, #input > .CodeMirror { 35 | position: absolute; 36 | top: 0; 37 | bottom: 0; 38 | left: 0; 39 | right: 0; 40 | padding: 10px; 41 | xbackground-color: #f5f5f5; 42 | font-family: 'UbuntuMono', monospace; 43 | font-size: 14px; 44 | height: auto; 45 | line-height: 1.3; 46 | overflow-y: scroll; 47 | } 48 | 49 | #input textarea:focus { 50 | outline: 0; 51 | } 52 | 53 | #output > *:first-child { 54 | margin-top: 0 !important; 55 | padding-top: 0 !important; 56 | } 57 | 58 | #output a, #output a:visited { 59 | color: #4183c4; 60 | text-decoration: none; 61 | } 62 | 63 | #output a:hover { 64 | text-decoration: underline; 65 | } 66 | 67 | #output p, #output blockquote, #output ul, #output ol, #output dl, #output table, #output pre { 68 | margin: 15px 0; 69 | } 70 | 71 | #output .markdown-body h1 72 | , #output .markdown-body h2 73 | , #output .markdown-body h3 74 | , #output .markdown-body h4 75 | , #output .markdown-body h5 76 | , #output .markdown-body h6 { 77 | margin: 20px 0 10px; 78 | padding: 0; 79 | font-weight: bold; 80 | } 81 | 82 | #output h1 { 83 | font-size: 28px; 84 | color: #000; 85 | } 86 | 87 | #output h2 { 88 | font-size: 24px; 89 | border-bottom: 1px solid #ccc; 90 | color: #000; 91 | } 92 | 93 | #output img { 94 | max-width: 100%; 95 | } 96 | 97 | #output hr { 98 | background: transparent url("/img/hr.png") repeat-x 0 0; 99 | border: 0 none; 100 | color: #ccc; 101 | height: 4px; 102 | padding: 0; 103 | } 104 | 105 | #output table { 106 | border-collapse: collapse; 107 | border-spacing: 0; 108 | } 109 | 110 | #output tr:nth-child(2n) { 111 | background-color: #f8f8f8; 112 | } 113 | 114 | .markdown-body tr { 115 | border-top: 1px solid #ccc; 116 | background-color: #fff; 117 | } 118 | 119 | #output td, #output th { 120 | border: 1px solid #ccc; 121 | padding: 6px 13px; 122 | } 123 | 124 | #output th { 125 | font-weight: bold; 126 | } 127 | 128 | #output blockquote { 129 | border-left: 4px solid #ddd; 130 | padding: 0 15px; 131 | color: #777; 132 | } 133 | 134 | #output blockquote > :last-child, #output blockquote > :first-child { 135 | margin-bottom: 0px; 136 | } 137 | 138 | pre, code { 139 | font-family: 'UbuntuMono', monospace; 140 | white-space: nowrap; 141 | margin: 0 2px; 142 | padding: 0px 5px; 143 | border: 1px solid #eaeaea; 144 | background-color: #f8f8f8; 145 | border-radius: 3px; 146 | } 147 | 148 | pre > code { 149 | white-space: pre; 150 | } 151 | 152 | pre { 153 | overflow-x: auto; 154 | white-space: pre; 155 | padding: 10px; 156 | line-height: 150%; 157 | background-color: #f8f8f8; 158 | border-color: #ccc; 159 | } 160 | 161 | pre code, pre tt { 162 | margin: 0; 163 | padding: 0; 164 | border: 0; 165 | background-color: transparent; 166 | border: none; 167 | } 168 | 169 | .highlight .c 170 | , .highlight .cm 171 | , .highlight .cp 172 | , .highlight .c1 { 173 | color:#999988; 174 | font-style:italic; 175 | } 176 | 177 | .highlight .err { 178 | color:#a61717; 179 | background-color:#e3d2d2 180 | } 181 | 182 | .highlight .o 183 | , .highlight .gs 184 | , .highlight .kc 185 | , .highlight .kd 186 | , .highlight .kn 187 | , .highlight .kp 188 | , .highlight .kr { 189 | font-weight:bold 190 | } 191 | 192 | .highlight .cs { 193 | color:#999999; 194 | font-weight:bold; 195 | font-style:italic 196 | } 197 | 198 | .highlight .gd { 199 | color:#000000; 200 | background-color:#ffdddd 201 | } 202 | 203 | .highlight .gd .x { 204 | color:#000000; 205 | background-color:#ffaaaa 206 | } 207 | 208 | .highlight .ge { 209 | font-style:italic 210 | } 211 | 212 | .highlight .gr 213 | , .highlight .gt { 214 | color:#aa0000 215 | } 216 | 217 | .highlight .gh 218 | , .highlight .bp { 219 | color:#999999 220 | } 221 | 222 | .highlight .gi { 223 | color:#000000; 224 | background-color:#ddffdd 225 | } 226 | 227 | .highlight .gi .x { 228 | color:#000000; 229 | background-color:#aaffaa 230 | } 231 | 232 | .highlight .go { 233 | color:#888888 234 | } 235 | 236 | .highlight .gp 237 | , .highlight .nn { 238 | color:#555555 239 | } 240 | 241 | 242 | .highlight .gu { 243 | color:#800080; 244 | font-weight:bold 245 | } 246 | 247 | 248 | .highlight .kt { 249 | color:#445588; 250 | font-weight:bold 251 | } 252 | 253 | .highlight .m 254 | , .highlight .mf 255 | , .highlight .mh 256 | , .highlight .mi 257 | , .highlight .mo 258 | , .highlight .il { 259 | color:#009999 260 | } 261 | 262 | .highlight .s 263 | , .highlight .sb 264 | , .highlight .sc 265 | , .highlight .sd 266 | , .highlight .s2 267 | , .highlight .se 268 | , .highlight .sh 269 | , .highlight .si 270 | , .highlight .sx 271 | , .highlight .s1 { 272 | color:#d14 273 | } 274 | 275 | .highlight .n { 276 | color:#333333 277 | } 278 | 279 | .highlight .na 280 | , .highlight .no 281 | , .highlight .nv 282 | , .highlight .vc 283 | , .highlight .vg 284 | , .highlight .vi 285 | , .highlight .nb { 286 | color:#0086B3 287 | } 288 | 289 | .highlight .nc { 290 | color:#445588; 291 | font-weight:bold 292 | } 293 | 294 | .highlight .ni { 295 | color:#800080 296 | } 297 | 298 | .highlight .ne 299 | , .highlight .nf { 300 | color:#990000; 301 | font-weight:bold 302 | } 303 | 304 | .highlight .nt { 305 | color:#000080 306 | } 307 | 308 | .highlight .ow { 309 | font-weight:bold 310 | } 311 | 312 | .highlight .w { 313 | color:#bbbbbb 314 | } 315 | 316 | .highlight .sr { 317 | color:#009926 318 | } 319 | 320 | .highlight .ss { 321 | color:#990073 322 | } 323 | 324 | .highlight .gc { 325 | color:#999; 326 | background-color:#EAF2F5 327 | } 328 | 329 | @media print { 330 | #input { 331 | display: none; 332 | } 333 | 334 | #output { 335 | display: block; 336 | position: static; 337 | left: 0%; 338 | right: 0%; 339 | top: 0%; 340 | width: 100%; 341 | height: 100%; 342 | } 343 | } 344 | --------------------------------------------------------------------------------