[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\"env\"]\n}\n"
  },
  {
    "path": ".eslintignore",
    "content": "# Built files\ndist\n"
  },
  {
    "path": ".eslintrc.yml",
    "content": "root: true\nparser: \"babel-eslint\"\nextends:\n  - \"eslint:recommended\"\n  - \"plugin:prettier/recommended\"\nrules:\n  no-console: 0\nenv:\n  node: true\n  es6: true\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directory\n# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git\nnode_modules\n\n# Temporary files for tests\n/test/temp\n\n# Built files\n/dist\n"
  },
  {
    "path": ".npmignore",
    "content": ".DS_Store\nnode_modules\n/.eslintignore\n/.eslintrc\n/.gitignore\n/.travis.yml\n/test/temp\n/resource\n\n# Built JS and CSS files\n/dist/build.js\n/dist/build.css\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nnode_js:\n- 6\n- 8\n- node\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015-2018 Jun\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "README.md",
    "content": "<h1><img src='media/logo.png' alt='logo' width='200'/></h1>\n\n> We need a better Markdown previewer.\n\n[![travis](https://travis-ci.org/utatti/pen.svg)](https://travis-ci.org/utatti/pen)\n\n`pen` is a Markdown previewer written in JavaScript, aiming to *just work*.\n\nThere are literally tons of Markdown previewers out there. I love some of them,\nI even made [one](https://github.com/utatti/orange-cat) of them. Nevertheless,\nwe always need a better one, don't we?\n\nUsing `pen` is super simple, we don't need to install any special editor or\nlaunch any GUI application. `pen` is just a tidy command-line tool. You can use\nyour favourite editor and browser. No manual refresh is even needed.\n\nAlso, the previewer renders the content using [React](https://facebook.github.io/react/).\nIt means that it will not re-render entire DOM when the document is updated.\nThis is a huge advantage because images or other media won't be reloaded for\nthe DOM update.\n\nI personally love to use `pen`, and I hope you love it too. :black_nib:\n\n## Demo\n\nHere is a short demo showing how awesome `pen` is.\n\n![demo](https://cloud.githubusercontent.com/assets/1013641/9977359/21b79f66-5f3f-11e5-860a-cf19b2287009.gif)\n\nThe following demo shows `pen` incrementally updates only modified part using\n[React](https://facebook.github.io/react/) and its [Reconciliation](https://reactjs.org/docs/reconciliation.html).\n\n![incremental update](https://cloud.githubusercontent.com/assets/1013641/11914823/896591ba-a6cd-11e5-94ee-05e3ab50413b.gif)\n\n## Requirement\n\n`pen` uses [Node.js >= 4.0](https://nodejs.org/en/docs/es6/). It may not work\non earlier versions.\n\n## Install\n\nUsing [npm](http://npmjs.com):\n\n```shell\nnpm i -g pen\n```\n\nYou can try using `pen` with `npx`:\n\n```shell\nnpx pen\n```\n\n## Usage\n\nTo use `pen`, simply run the `pen` command.\n\n```shell\npen README.md\n```\n\nThe command above will launch a `pen` server and open the file in your default\nbrowser. The server will listen to a 6060 port by default. To be honest, you\ndon't even need to launch it with a filename. You can manually open\nhttp://localhost:6060/README.md, or any other files in the same directory.\n\nTo stop the server, enter `^C`.\n\nFor the further details of the `pen` command, please enter `pen -h` or `pen\n--help`.\n\n### Pandoc\n\nPen uses [markdown-it](https://github.com/markdown-it/markdown-it) as Markdown\nparser by default, but it also supports Pandoc. Please provide [a proper Pandoc\nformat](http://pandoc.org/MANUAL.html#general-options) for the value.\n\n```shell\npen --pandoc gfm README.md\n```\n\n## Contribution\n\nI welcome every contribution on `pen`. You may start from forking and cloning\nthis repo.\n\n```shell\ngit clone git@github.com:your_username/pen.git\ncd pen\n\n# Install dependencies\nnpm i\n\n# Lint, build, and test pen codes at once\nnpm test\n```\n\nTo build frontend scripts:\n\n```shell\nnpm run build\n```\n\nTo lint with [ESLint](http://eslint.org):\n\n```shell\nnpm run lint\n```\n\nTo test with [Mocha](http://mochajs.org)\n\n```shell\nnpm run mocha\n```\n\n## License\n\nPen is released under the [MIT License](LICENSE).\n"
  },
  {
    "path": "bin/pen",
    "content": "#!/usr/bin/env node\nrequire('../index');\n"
  },
  {
    "path": "index.js",
    "content": "\"use strict\";\n\nconst open = require(\"opn\");\nconst Server = require(\"./src/server\");\nconst argv = require(\"./src/argv\");\n\nlet server = new Server(process.cwd());\nserver.listen(argv.port, () => {\n  console.log(`listening ${argv.port} ...`);\n\n  argv._.forEach(file =>\n    open(`http://localhost:${argv.port}/${file}`).catch(() => {})\n  );\n});\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"pen\",\n  \"version\": \"2.2.0\",\n  \"description\": \"A better Markdown previewer\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"npm run lint && npm run build && npm run mocha\",\n    \"lint\": \"eslint --ext .js --ignore-path .gitignore .\",\n    \"lintfix\": \"eslint --fix --ext .js --ignore-path .gitignore .\",\n    \"build\": \"NODE_ENV=production webpack -p\",\n    \"mocha\": \"mocha -r babel-core/register -r babel-polyfill -r test/setup.js test/**/test-*\",\n    \"release\": \"npm run build && npm publish --access=public\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/utatti/pen.git\"\n  },\n  \"keywords\": [\n    \"markdown\",\n    \"previewer\"\n  ],\n  \"author\": \"Jun <noraesae+dev@gmail.com>\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/utatti/pen/issues\"\n  },\n  \"homepage\": \"https://github.com/utatti/pen\",\n  \"devDependencies\": {\n    \"babel-core\": \"^6.26.3\",\n    \"babel-eslint\": \"^8.2.3\",\n    \"babel-loader\": \"^7.1.2\",\n    \"babel-polyfill\": \"^6.26.0\",\n    \"babel-preset-env\": \"^1.7.0\",\n    \"css-loader\": \"^0.28.7\",\n    \"eslint\": \"^4.19.1\",\n    \"eslint-config-prettier\": \"^2.9.0\",\n    \"eslint-plugin-prettier\": \"^2.6.0\",\n    \"eslint-plugin-react\": \"^7.7.0\",\n    \"extract-text-webpack-plugin\": \"^3.0.2\",\n    \"github-markdown-css\": \"^2.9.0\",\n    \"html-webpack-inline-source-plugin\": \"^0.0.9\",\n    \"html-webpack-plugin\": \"^2.30.1\",\n    \"jsdom\": \"^9.4.2\",\n    \"json-loader\": \"^0.5.7\",\n    \"mocha\": \"^5.2.0\",\n    \"prettier\": \"^1.12.1\",\n    \"react\": \"^16.2.0\",\n    \"react-dom\": \"^16.2.0\",\n    \"react-render-html\": \"^0.6.0\",\n    \"request\": \"^2.87.0\",\n    \"rimraf\": \"^2.5.4\",\n    \"style-loader\": \"^0.19.0\",\n    \"webpack\": \"^3.8.1\",\n    \"webpack-fail-plugin\": \"^2.0.0\"\n  },\n  \"dependencies\": {\n    \"highlight.js\": \"^9.9.0\",\n    \"markdown-it\": \"^8.2.2\",\n    \"markdown-it-anchor\": \"^5.0.2\",\n    \"markdown-it-checkbox\": \"^1.1.0\",\n    \"markdown-it-emoji\": \"^1.2.0\",\n    \"markdown-it-highlightjs\": \"^2.0.0\",\n    \"opn\": \"^4.0.2\",\n    \"prop-types\": \"^15.6.0\",\n    \"simple-pandoc\": \"^0.1.0\",\n    \"websocket\": \"^1.0.26\",\n    \"yargs\": \"^6.6.0\"\n  },\n  \"preferGlobal\": true,\n  \"bin\": {\n    \"pen\": \"./bin/pen\"\n  }\n}\n"
  },
  {
    "path": "src/argv.js",
    "content": "\"use strict\";\n\nconst DEFAULT_PORT = 6060;\n\nmodule.exports = require(\"yargs\")\n  .usage(\"Usage: $0 [options] [file]\")\n  .option(\"p\", {\n    alias: \"port\",\n    default: DEFAULT_PORT,\n    describe: \"Set a custom port\"\n  })\n  .option(\"pandoc\", {\n    type: \"string\",\n    describe:\n      \"Use local pandoc as markdown converter. Provide target format as the value.\"\n  })\n  .help(\"h\")\n  .alias(\"h\", \"help\").argv;\n"
  },
  {
    "path": "src/frontend/.eslintrc.yml",
    "content": "parserOptions:\n  sourceType: module\nextends:\n  - \"eslint:recommended\"\n  - \"plugin:react/recommended\"\nenv:\n  browser: true\n"
  },
  {
    "path": "src/frontend/html-renderer.js",
    "content": "import React from \"react\";\nimport renderHTML from \"react-render-html\";\nimport SocketClient from \"./socket-client\";\nimport PropTypes from \"prop-types\";\n\nclass HTMLRenderer extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = { html: \"\" };\n  }\n\n  componentDidMount() {\n    this.socketClient = new SocketClient(this.props.location);\n    this.socketClient.onData(html => this.setState({ html }));\n  }\n\n  componentDidUpdate() {\n    if (this.props.onUpdate) {\n      this.props.onUpdate();\n    }\n  }\n\n  render() {\n    return React.createElement(\"div\", null, renderHTML(this.state.html));\n  }\n}\n\nHTMLRenderer.propTypes = {\n  location: PropTypes.shape({\n    host: PropTypes.string.isRequired,\n    pathname: PropTypes.string.isRequired\n  }).isRequired,\n  onUpdate: PropTypes.func\n};\n\nexport default HTMLRenderer;\n"
  },
  {
    "path": "src/frontend/main.js",
    "content": "// Non-js dependencies\nimport \"github-markdown-css/github-markdown.css\";\nimport \"highlight.js/styles/foundation.css\";\nimport \"./style.css\";\n\nimport React from \"react\";\nimport ReactDOM from \"react-dom\";\nimport HTMLRenderer from \"./html-renderer\";\n\nconst app = document.createElement(\"div\");\napp.setAttribute(\"id\", \"app\");\napp.setAttribute(\"class\", \"markdown-body\");\ndocument.body.appendChild(app);\n\n// Set title to Markdown filename\nconst pathTokens = location.pathname.split(\"/\");\ndocument.title = pathTokens[pathTokens.length - 1];\n\nReactDOM.render(React.createElement(HTMLRenderer, { location }), app);\n"
  },
  {
    "path": "src/frontend/socket-client.js",
    "content": "import { w3cwebsocket as WebSocket } from \"websocket\";\n\nexport default class SocketClient {\n  constructor(location) {\n    this.host = location.host;\n    this.pathname = location.pathname;\n\n    const url = `ws://${this.host}${this.pathname}`;\n\n    this._socket = new WebSocket(url);\n    this._socket.onmessage = event => {\n      this.triggerOnData(event.data);\n    };\n\n    this._dataCallback = null;\n  }\n\n  onData(callback) {\n    this._dataCallback = callback;\n    return this;\n  }\n\n  triggerOnData(data) {\n    if (this._dataCallback) {\n      this._dataCallback(data);\n    }\n  }\n}\n"
  },
  {
    "path": "src/frontend/style.css",
    "content": "#app {\n  max-width: 790px;\n  margin: 30px auto;\n}\n\n/* checkbox */\ninput[type='checkbox'] {\n  vertical-align: middle;\n  margin: 0 0.5em 0.25em 0;\n}\nli:has(> input[type='checkbox']) {\n  list-style-type:none;\n}\nli>input[type='checkbox'] {\n  margin: 0 0.7em 0.25em -1.6em;\n}\n"
  },
  {
    "path": "src/markdown-socket.js",
    "content": "\"use strict\";\n\nconst MarkdownWatcher = require(\"./markdown-watcher\");\nconst path = require(\"path\");\nconst WebSocketServer = require(\"websocket\").server;\n\nclass MarkdownSocket {\n  constructor(rootPath) {\n    this.rootPath = rootPath;\n    this._server = null;\n    this.pathname = null;\n  }\n\n  listenTo(httpServer) {\n    this._server = new WebSocketServer();\n    this._server.mount({ httpServer: httpServer });\n    this._server.on(\"request\", this.onRequest.bind(this));\n    this._server.on(\"connect\", this.onConnect.bind(this));\n  }\n\n  onRequest(request) {\n    const extname = path.extname(request.resource);\n\n    if (extname !== \".md\" && extname !== \".markdown\") {\n      request.reject();\n      return;\n    }\n\n    this.pathname = request.resource;\n    request.accept(null, request.origin);\n  }\n\n  onConnect(connection) {\n    const decodedPath = decodeURIComponent(this.pathname);\n    const watcher = new MarkdownWatcher(path.join(this.rootPath, decodedPath));\n    watcher.onData(data => connection.send(data));\n    watcher.onError(err => {\n      if (err.code === \"ENOENT\") {\n        // if there is no file, ignore and send 'no file'\n        connection.send(\"Not found\");\n        return;\n      }\n      throw err;\n    });\n\n    connection.on(\"close\", () => {\n      watcher.stop();\n    });\n  }\n\n  close() {\n    this._server.closeAllConnections();\n  }\n}\n\nmodule.exports = MarkdownSocket;\n"
  },
  {
    "path": "src/markdown-watcher.js",
    "content": "\"use strict\";\n\nconst convert = require(\"./markdown\");\nconst Watcher = require(\"./watcher\");\n\nclass MarkdownWatcher extends Watcher {\n  onData(callback) {\n    this._dataCallback = data => convert(data.toString()).then(callback);\n    return this;\n  }\n}\n\nmodule.exports = MarkdownWatcher;\n"
  },
  {
    "path": "src/markdown.js",
    "content": "\"use strict\";\n\nconst argv = require(\"./argv\");\nconst mdit = require(\"markdown-it\");\nconst pandoc = require(\"simple-pandoc\");\n\nconst singleton = creator => {\n  let obj;\n  return () => obj || (obj = creator());\n};\n\nconst md = singleton(() =>\n  mdit({ html: true, linkify: true })\n    .use(require(\"markdown-it-highlightjs\"))\n    .use(require(\"markdown-it-emoji\"))\n    .use(require(\"markdown-it-checkbox\"))\n    .use(require(\"markdown-it-anchor\"))\n);\n\nconst pd = singleton(() => pandoc(argv.pandoc, \"html\"));\n\nmodule.exports = markdown =>\n  argv.pandoc ? pd()(markdown) : Promise.resolve(md().render(markdown));\n"
  },
  {
    "path": "src/server.js",
    "content": "\"use strict\";\n\nconst fs = require(\"fs\");\nconst http = require(\"http\");\nconst path = require(\"path\");\nconst urllib = require(\"url\");\nconst MarkdownSocket = require(\"./markdown-socket\");\n\nfunction isMarkdown(path) {\n  const lowerCasedPath = path.toLowerCase();\n  return lowerCasedPath.endsWith(\".md\") || lowerCasedPath.endsWith(\".markdown\");\n}\n\nclass Server {\n  constructor(rootPath) {\n    this.rootPath = rootPath;\n    this._server = http.createServer(this.handler.bind(this));\n\n    this._ws = new MarkdownSocket(this.rootPath);\n    this._ws.listenTo(this._server);\n  }\n\n  listen(port, cb) {\n    this._server.listen(port, cb);\n  }\n\n  close(cb) {\n    this._ws.close();\n    this._server.close(cb);\n  }\n\n  handler(req, res) {\n    const url = urllib.parse(req.url);\n\n    if (isMarkdown(url.pathname)) {\n      this.handleAsMarkdown(res);\n    } else {\n      this.handleAsStatic(url.pathname, res);\n    }\n  }\n\n  handleAsMarkdown(res) {\n    res.setHeader(\"Content-Type\", \"text/html\");\n    const indexHTMLPath = path.join(__dirname, \"../dist/index.html\");\n    fs.createReadStream(indexHTMLPath).pipe(res);\n  }\n\n  handleAsStatic(pathname, res) {\n    const fullPath = path.join(this.rootPath, pathname);\n\n    try {\n      const stat = fs.statSync(fullPath);\n      if (stat.isDirectory()) {\n        if (!pathname.endsWith(\"/\")) {\n          res.writeHead(302, { Location: pathname + \"/\" });\n          res.end();\n          return;\n        }\n\n        const fileList = fs.readdirSync(fullPath).filter(isMarkdown);\n        res.setHeader(\"Content-Type\", \"text/html\");\n        res.end(fileList.map(f => `<a href='${f}'>${f}</a>`).join(\" \"));\n      } else {\n        fs.createReadStream(fullPath).pipe(res);\n      }\n    } catch (err) {\n      if (err.code === \"ENOENT\") {\n        res.statusCode = 404;\n        res.end(\"Not found\");\n      } else {\n        throw err;\n      }\n    }\n  }\n}\n\nmodule.exports = Server;\n"
  },
  {
    "path": "src/watcher.js",
    "content": "\"use strict\";\n\nconst fs = require(\"fs\");\n\nconst WatchInterval = 200; // milliseconds\n\nclass Watcher {\n  constructor(p) {\n    this.path = p;\n    this._watchLoop = null;\n    this._dataCallback = null;\n    this._errorCallback = null;\n    this._previousData = null;\n\n    this.start();\n  }\n\n  start() {\n    if (this._watchLoop) {\n      clearInterval(this._watchLoop);\n    }\n\n    setTimeout(() => this.watch(), 0); // for the first execution\n    this._watchLoop = setInterval(() => this.watch(), WatchInterval);\n  }\n\n  stop() {\n    clearInterval(this._watchLoop);\n    this._watchLoop = null;\n  }\n\n  watch() {\n    fs.readFile(this.path, (error, data) => {\n      if (error) {\n        this.triggerOnError(error);\n        this.stop();\n      } else {\n        if (!this._previousData || data.compare(this._previousData) !== 0) {\n          this.triggerOnData(data);\n          this._previousData = data;\n        }\n      }\n    });\n  }\n\n  onData(callback) {\n    this._dataCallback = callback;\n    return this;\n  }\n\n  onError(callback) {\n    this._errorCallback = callback;\n    return this;\n  }\n\n  triggerOnData(data) {\n    if (this._dataCallback) {\n      this._dataCallback(data);\n    }\n  }\n\n  triggerOnError(error) {\n    if (this._errorCallback) {\n      this._errorCallback(error);\n    } else {\n      throw error;\n    }\n  }\n}\n\nmodule.exports = Watcher;\n"
  },
  {
    "path": "test/.eslintrc.yml",
    "content": "parserOptions:\n  sourceType: module\nextends:\n  - \"eslint:recommended\"\n  - \"plugin:react/recommended\"\nrules:\n  no-console: 1\n\nenv:\n  mocha: true\n"
  },
  {
    "path": "test/lib/helper.js",
    "content": "import fs from \"fs\";\nimport path from \"path\";\nimport rimraf from \"rimraf\";\n\nconst root = path.join(__dirname, \"../temp\");\n\nfunction filePath(p) {\n  return path.join(root, p);\n}\n\nconst helper = {\n  createFile(p, initialContent) {\n    fs.writeFileSync(filePath(p), initialContent);\n  },\n  makeDirectory(p) {\n    try {\n      fs.mkdirSync(filePath(p));\n    } catch (e) {\n      if (e.code !== \"EEXIST\") {\n        throw e;\n      }\n    }\n  },\n  path(p) {\n    return path.join(root, p);\n  },\n  createRootDirectory() {\n    try {\n      fs.mkdirSync(root);\n    } catch (e) {\n      // do nothing\n    }\n  },\n  clean() {\n    rimraf.sync(path.join(root, \"*\"));\n  }\n};\n\nhelper.createRootDirectory();\nhelper.clean();\n\nexport default helper;\n"
  },
  {
    "path": "test/setup.js",
    "content": "import jsdom from \"jsdom\";\n\n// test setup for browser mocking\nglobal.document = jsdom.jsdom(\"<!doctype html><html><body></body></html>\");\nglobal.window = global.document.defaultView;\nglobal.navigator = { userAgent: \"node.js\" };\n"
  },
  {
    "path": "test/test-build-html.js",
    "content": "import assert from \"assert\";\nimport path from \"path\";\nimport fs from \"fs\";\n\ndescribe(\"built HTML\", () => {\n  const indexHTMLPath = path.join(__dirname, \"../dist/index.html\");\n\n  it(\"exists\", () => {\n    assert.ok(fs.readFileSync(indexHTMLPath));\n  });\n\n  it(\"contains a style tag\", () => {\n    const html = fs.readFileSync(indexHTMLPath);\n    assert.ok(/<style.+>/.test(html));\n  });\n\n  it(\"contains a script tag\", () => {\n    const html = fs.readFileSync(indexHTMLPath);\n    assert.ok(/<script.+>/.test(html));\n  });\n});\n"
  },
  {
    "path": "test/test-html-renderer.js",
    "content": "import assert from \"assert\";\nimport fs from \"fs\";\nimport helper from \"./lib/helper\";\nimport HTMLRenderer from \"../src/frontend/html-renderer\";\nimport http from \"http\";\nimport MarkdownSocket from \"../src/markdown-socket\";\nimport React from \"react\";\nimport ReactTestUtils from \"react-dom/test-utils\";\n\nfunction getRenderedHTML(rendered) {\n  const div = ReactTestUtils.findRenderedDOMComponentWithTag(rendered, \"div\");\n  return div.innerHTML.replace(/ data-react[-\\w]+=\"[^\"]+\"/g, \"\");\n}\n\ndescribe(\"HTMLRenderer\", () => {\n  let server;\n  let mdSocket;\n\n  beforeEach(done => {\n    helper.makeDirectory(\"md-root\");\n    helper.createFile(\"md-root/test.md\", \"# hello\");\n    server = http.createServer((req, res) => res.end(\"hello\"));\n    mdSocket = new MarkdownSocket(helper.path(\"md-root\"));\n    mdSocket.listenTo(server);\n    server.listen(1234, done);\n  });\n\n  afterEach(done => {\n    helper.clean();\n    mdSocket.close();\n    server.close(done);\n  });\n\n  it(\"renders HTML parsed from Markdown with using Virtual DOM\", done => {\n    let rendered;\n    let renderer = React.createElement(HTMLRenderer, {\n      location: {\n        host: \"localhost:1234\",\n        pathname: \"/test.md\"\n      },\n      onUpdate() {\n        assert.equal(getRenderedHTML(rendered), '<h1 id=\"hello\">hello</h1>\\n');\n        done();\n      }\n    });\n    rendered = ReactTestUtils.renderIntoDocument(renderer);\n  });\n\n  it(\"re-renders whenever the file is updated\", done => {\n    const callback = err => {\n      if (err) {\n        done(err);\n      }\n    };\n\n    let called = 0;\n    let rendered;\n    let renderer = React.createElement(HTMLRenderer, {\n      location: {\n        host: \"localhost:1234\",\n        pathname: \"/test.md\"\n      },\n      onUpdate() {\n        let html = getRenderedHTML(rendered);\n        switch (called) {\n          case 0:\n            assert.equal(html, '<h1 id=\"hello\">hello</h1>\\n');\n            fs.writeFile(\n              helper.path(\"md-root/test.md\"),\n              \"```js\\nvar a=10;\\n```\",\n              callback\n            );\n            break;\n          case 1:\n            assert.equal(\n              html,\n              '<pre><code class=\"hljs language-js\"><span class=\"hljs-keyword\">var</span> a=<span class=\"hljs-number\">10</span>;\\n</code></pre>\\n'\n            );\n            fs.writeFile(\n              helper.path(\"md-root/test.md\"),\n              \"* nested\\n  * nnested\\n    * nnnested\",\n              callback\n            );\n            break;\n          case 2:\n            assert.equal(\n              html,\n              \"<ul>\\n<li>nested\\n<ul>\\n<li>nnested\\n<ul>\\n<li>nnnested</li>\\n</ul>\\n</li>\\n</ul>\\n</li>\\n</ul>\\n\"\n            );\n            done();\n            break;\n        }\n        called += 1;\n      }\n    });\n    rendered = ReactTestUtils.renderIntoDocument(renderer);\n  });\n});\n"
  },
  {
    "path": "test/test-index.js",
    "content": "import assert from \"assert\";\nimport helper from \"./lib/helper\";\nimport path from \"path\";\nimport request from \"request\";\nimport { spawn } from \"child_process\";\n\ndescribe(\"index\", () => {\n  let proc;\n  const cwd = process.cwd();\n  const indexScriptPath = path.join(cwd, \"index.js\");\n\n  beforeEach(() => {\n    helper.makeDirectory(\"server-root\");\n    helper.createFile(\"server-root/test1.txt\", \"hello\");\n    process.chdir(helper.path(\"server-root\"));\n  });\n\n  afterEach(done => {\n    proc.on(\"close\", done);\n    proc.kill();\n    helper.clean();\n    process.chdir(cwd);\n  });\n\n  it(\"runs a server listening to a port\", done => {\n    proc = spawn(\"node\", [indexScriptPath]);\n    proc.stdout.on(\"data\", data => {\n      assert.equal(data.toString(), \"listening 6060 ...\\n\");\n      request.get(\"http://localhost:6060/test1.txt\", (err, res, body) => {\n        if (err) {\n          done(err);\n          return;\n        }\n\n        assert.equal(res.statusCode, 200);\n        assert.equal(body, \"hello\");\n        done();\n      });\n    });\n  });\n\n  it(\"runs a server listening to a custom port\", done => {\n    proc = spawn(\"node\", [indexScriptPath, \"-p\", \"1234\"]);\n    proc.stdout.on(\"data\", data => {\n      assert.equal(data.toString(), \"listening 1234 ...\\n\");\n      request.get(\"http://localhost:1234/test1.txt\", (err, res, body) => {\n        if (err) {\n          done(err);\n          return;\n        }\n\n        assert.equal(res.statusCode, 200);\n        assert.equal(body, \"hello\");\n        done();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/test-markdown-socket.js",
    "content": "import assert from \"assert\";\nimport fs from \"fs\";\nimport helper from \"./lib/helper\";\nimport http from \"http\";\nimport MarkdownSocket from \"../src/markdown-socket\";\nimport { w3cwebsocket as WebSocket } from \"websocket\";\n\ndescribe(\"MarkdownSocket\", () => {\n  let server;\n  let mdSocket;\n\n  beforeEach(done => {\n    helper.makeDirectory(\"md-root\");\n    helper.createFile(\"md-root/test.md\", \"# hello\");\n    server = http.createServer((req, res) => {\n      res.end(\"hello\");\n    });\n    mdSocket = new MarkdownSocket(helper.path(\"md-root\"));\n    mdSocket.listenTo(server);\n    server.listen(1234, done);\n  });\n\n  afterEach(done => {\n    helper.clean();\n    mdSocket.close();\n    server.close(done);\n  });\n\n  it(\"handles a websocket connection\", done => {\n    let client = new WebSocket(\"ws://localhost:1234/test.md\");\n\n    client.onopen = () => {\n      done();\n    };\n  });\n\n  it(\"cannot handle a non markdown connection\", done => {\n    let client = new WebSocket(\"ws://localhost:1234\");\n\n    client.onerror = () => {\n      done();\n    };\n  });\n\n  it(\"opens a Markdown file and sends the parsed HTML\", done => {\n    let client = new WebSocket(\"ws://localhost:1234/test.md\");\n\n    client.onmessage = message => {\n      assert.equal(message.data, '<h1 id=\"hello\">hello</h1>\\n');\n      done();\n    };\n  });\n\n  it(\"sends parsed HTML data again when the file is updated\", done => {\n    const callback = err => {\n      if (err) {\n        done(err);\n      }\n    };\n\n    let called = 0;\n    let client = new WebSocket(\"ws://localhost:1234/test.md\");\n    client.onmessage = message => {\n      switch (called) {\n        case 0:\n          assert.equal(message.data, '<h1 id=\"hello\">hello</h1>\\n');\n          fs.writeFile(\n            helper.path(\"md-root/test.md\"),\n            \"```js\\nvar a=10;\\n```\",\n            callback\n          );\n          break;\n        case 1:\n          assert.equal(\n            message.data,\n            '<pre><code class=\"hljs language-js\"><span class=\"hljs-keyword\">var</span> a=<span class=\"hljs-number\">10</span>;\\n</code></pre>\\n'\n          );\n          fs.writeFile(\n            helper.path(\"md-root/test.md\"),\n            \"* nested\\n  * nnested\\n    * nnnested\",\n            callback\n          );\n          break;\n        case 2:\n          assert.equal(\n            message.data,\n            \"<ul>\\n<li>nested\\n<ul>\\n<li>nnested\\n<ul>\\n<li>nnnested</li>\\n</ul>\\n</li>\\n</ul>\\n</li>\\n</ul>\\n\"\n          );\n          done();\n          break;\n      }\n      called += 1;\n    };\n  });\n\n  it(\"ignores when there is no file for the path\", done => {\n    let client = new WebSocket(\"ws://localhost:1234/no-file.md\");\n    client.onmessage = message => {\n      assert.equal(message.data, \"Not found\");\n      done();\n    };\n  });\n});\n"
  },
  {
    "path": "test/test-markdown-watcher.js",
    "content": "import assert from \"assert\";\nimport fs from \"fs\";\nimport helper from \"./lib/helper\";\nimport MarkdownWatcher from \"../src/markdown-watcher\";\n\ndescribe(\"MarkdownWatcher\", () => {\n  let watcher;\n\n  beforeEach(() => {\n    helper.createFile(\"watcher-temp.md\", \"# hello\");\n  });\n\n  afterEach(() => {\n    watcher.stop();\n    helper.clean();\n  });\n\n  it(\"reads a Markdown file and send parsed HTML data\", done => {\n    watcher = new MarkdownWatcher(helper.path(\"watcher-temp.md\"));\n    watcher\n      .onData(data => {\n        assert.equal(data, '<h1 id=\"hello\">hello</h1>\\n');\n        done();\n      })\n      .onError(done);\n  });\n\n  it(\"send parsed HTML data again when the file is updated\", done => {\n    const callback = err => {\n      if (err) {\n        done(err);\n      }\n    };\n\n    let called = 0;\n    watcher = new MarkdownWatcher(helper.path(\"watcher-temp.md\"));\n    watcher\n      .onData(data => {\n        switch (called) {\n          case 0:\n            assert.equal(data, '<h1 id=\"hello\">hello</h1>\\n');\n            fs.writeFile(\n              helper.path(\"watcher-temp.md\"),\n              \"```js\\nvar a=10;\\n```\",\n              callback\n            );\n            break;\n          case 1:\n            assert.equal(\n              data,\n              '<pre><code class=\"hljs language-js\"><span class=\"hljs-keyword\">var</span> a=<span class=\"hljs-number\">10</span>;\\n</code></pre>\\n'\n            );\n            fs.writeFile(\n              helper.path(\"watcher-temp.md\"),\n              \"* nested\\n  * nnested\\n    * nnnested\",\n              callback\n            );\n            break;\n          case 2:\n            assert.equal(\n              data,\n              \"<ul>\\n<li>nested\\n<ul>\\n<li>nnested\\n<ul>\\n<li>nnnested</li>\\n</ul>\\n</li>\\n</ul>\\n</li>\\n</ul>\\n\"\n            );\n            done();\n            break;\n        }\n        called += 1;\n      })\n      .onError(done);\n  });\n});\n"
  },
  {
    "path": "test/test-server.js",
    "content": "import assert from \"assert\";\nimport helper from \"./lib/helper\";\nimport request from \"request\";\nimport Server from \"../src/server\";\nimport { w3cwebsocket as WebSocket } from \"websocket\";\n\nconst TestPort = 1234;\n\ndescribe(\"Server\", () => {\n  let server;\n\n  beforeEach(() => {\n    helper.makeDirectory(\"server-root\");\n    helper.createFile(\"server-root/test1.txt\", \"hello\");\n    helper.createFile(\"server-root/test2.txt\", \"world\");\n    helper.createFile(\"server-root/test.md\", \"# hello\");\n    helper.createFile(\"server-root/test2.md\", \"# hello\");\n    helper.createFile(\"server-root/test3.MD\", \"# hello\");\n    helper.createFile(\"server-root/test4.markdown\", \"# hello\");\n  });\n\n  afterEach(() => {\n    server.close();\n    helper.clean();\n  });\n\n  it(\"creates a file server on a given path\", done => {\n    server = new Server(helper.path(\"server-root\"));\n    server.listen(TestPort);\n\n    let url = `http://localhost:${TestPort}/test1.txt`;\n    request.get(url, (err, res, body) => {\n      if (err) {\n        done(err);\n        return;\n      }\n\n      assert.equal(res.statusCode, 200);\n      assert.equal(body, \"hello\");\n\n      let url = `http://localhost:${TestPort}/test2.txt`;\n      request.get(url, (err, res, body) => {\n        if (err) {\n          done(err);\n          return;\n        }\n\n        assert.equal(res.statusCode, 200);\n        assert.equal(body, \"world\");\n        done();\n      });\n    });\n  });\n\n  it(\"fails when there is no file\", done => {\n    server = new Server(helper.path(\"server-root\"));\n    server.listen(TestPort);\n\n    let url = `http://localhost:${TestPort}/test3.txt`;\n    request.get(url, (err, res, body) => {\n      if (err) {\n        done(err);\n        return;\n      }\n\n      assert.equal(res.statusCode, 404);\n      assert.equal(body, \"Not found\");\n      done();\n    });\n  });\n\n  it(\"shows a list of Markdown files for directories\", done => {\n    server = new Server(helper.path(\"server-root\"));\n    server.listen(TestPort);\n\n    let url = `http://localhost:${TestPort}/`;\n    request.get(url, (err, res, body) => {\n      if (err) {\n        done(err);\n        return;\n      }\n\n      assert.equal(res.statusCode, 200);\n      assert.equal(\n        body,\n        \"<a href='test.md'>test.md</a> <a href='test2.md'>test2.md</a> <a href='test3.MD'>test3.MD</a> <a href='test4.markdown'>test4.markdown</a>\"\n      );\n      done();\n    });\n  });\n\n  function previewTest(filename) {\n    return new Promise((resolve, reject) => {\n      request.get(\n        `http://localhost:${TestPort}/${filename}`,\n        (err, res, body) => {\n          if (err) {\n            reject(err);\n            return;\n          }\n\n          assert.equal(res.statusCode, 200);\n          assert.equal(res.headers[\"content-type\"], \"text/html\");\n          assert.ok(/<style.+>/.test(body));\n          assert.ok(/<script.+>/.test(body));\n          resolve();\n        }\n      );\n    });\n  }\n\n  it(\"shows a preview page for Markdown files\", async () => {\n    server = new Server(helper.path(\"server-root\"));\n    server.listen(TestPort);\n\n    await previewTest(\"test.md\");\n    await previewTest(\"test2.md\");\n    await previewTest(\"test3.MD\");\n    await previewTest(\"test4.markdown\");\n  });\n\n  it(\"receives a websocket connection\", done => {\n    server = new Server(helper.path(\"server-root\"));\n    server.listen(TestPort);\n\n    let url = `ws://localhost:${TestPort}/test.md`;\n    let ws = new WebSocket(url);\n    ws.onmessage = message => {\n      assert.equal(message.data, '<h1 id=\"hello\">hello</h1>\\n');\n      done();\n    };\n  });\n});\n"
  },
  {
    "path": "test/test-socket-client.js",
    "content": "import assert from \"assert\";\nimport fs from \"fs\";\nimport helper from \"./lib/helper\";\nimport http from \"http\";\nimport MarkdownSocket from \"../src/markdown-socket\";\nimport SocketClient from \"../src/frontend/socket-client\";\n\ndescribe(\"SocketClient\", () => {\n  let server;\n  let mdSocket;\n\n  beforeEach(done => {\n    helper.makeDirectory(\"md-root\");\n    helper.createFile(\"md-root/test.md\", \"# hello\");\n    server = http.createServer((req, res) => res.end(\"hello\"));\n    mdSocket = new MarkdownSocket(helper.path(\"md-root\"));\n    mdSocket.listenTo(server);\n    server.listen(1234, done);\n  });\n\n  afterEach(done => {\n    helper.clean();\n    mdSocket.close();\n    server.close(done);\n  });\n\n  it(\"receives HTML data sent from a Markdown socket server\", done => {\n    let client = new SocketClient({\n      host: \"localhost:1234\",\n      pathname: \"/test.md\"\n    });\n    client.onData(html => {\n      assert.equal(html, '<h1 id=\"hello\">hello</h1>\\n');\n      done();\n    });\n  });\n\n  it(\"receives the data whenever the file is updated\", done => {\n    const callback = err => {\n      if (err) {\n        done(err);\n      }\n    };\n\n    let called = 0;\n    let client = new SocketClient({\n      host: \"localhost:1234\",\n      pathname: \"/test.md\"\n    });\n    client.onData(html => {\n      switch (called) {\n        case 0:\n          assert.equal(html, '<h1 id=\"hello\">hello</h1>\\n');\n          fs.writeFile(\n            helper.path(\"md-root/test.md\"),\n            \"```js\\nvar a=10;\\n```\",\n            callback\n          );\n          break;\n        case 1:\n          assert.equal(\n            html,\n            '<pre><code class=\"hljs language-js\"><span class=\"hljs-keyword\">var</span> a=<span class=\"hljs-number\">10</span>;\\n</code></pre>\\n'\n          );\n          fs.writeFile(\n            helper.path(\"md-root/test.md\"),\n            \"* nested\\n  * nnested\\n    * nnnested\",\n            callback\n          );\n          break;\n        case 2:\n          assert.equal(\n            html,\n            \"<ul>\\n<li>nested\\n<ul>\\n<li>nnested\\n<ul>\\n<li>nnnested</li>\\n</ul>\\n</li>\\n</ul>\\n</li>\\n</ul>\\n\"\n          );\n          done();\n          break;\n      }\n      called += 1;\n    });\n  });\n});\n"
  },
  {
    "path": "test/test-watcher.js",
    "content": "import assert from \"assert\";\nimport fs from \"fs\";\nimport helper from \"./lib/helper\";\nimport Watcher from \"../src/watcher\";\n\ndescribe(\"Watcher\", () => {\n  let watcher;\n\n  beforeEach(() => {\n    helper.createFile(\"test테스트テスト.txt\", \"hello\");\n  });\n\n  afterEach(() => {\n    watcher.stop();\n    helper.clean();\n  });\n\n  it(\"reads a file\", done => {\n    watcher = new Watcher(helper.path(\"test테스트テスト.txt\"));\n    watcher\n      .onData(data => {\n        assert.equal(data.toString(), \"hello\");\n        done();\n      })\n      .onError(done);\n  });\n\n  it(\"cannot read a wrong file\", done => {\n    let watcher = new Watcher(helper.path(\"watcher-wrong-temp\"));\n    watcher\n      .onData(() => {\n        done(\"there shouldn't be a file!\");\n      })\n      .onError(error => {\n        assert.equal(error.code, \"ENOENT\");\n        done();\n      });\n  });\n\n  it(\"send the data again when the file is updated\", done => {\n    const callback = err => {\n      if (err) {\n        done(err);\n      }\n    };\n\n    let called = 0;\n    watcher = new Watcher(helper.path(\"test테스트テスト.txt\"));\n    watcher\n      .onData(data => {\n        switch (called) {\n          case 0:\n            assert.equal(data.toString(), \"hello\");\n            fs.writeFile(\n              helper.path(\"test테스트テスト.txt\"),\n              \"world\",\n              callback\n            );\n            break;\n          case 1:\n            assert.equal(data.toString(), \"world\");\n            fs.writeFile(helper.path(\"test테스트テスト.txt\"), \"pen!\", callback);\n            break;\n          case 2:\n            assert.equal(data.toString(), \"pen!\");\n            done();\n            break;\n        }\n        called += 1;\n      })\n      .onError(done);\n  });\n});\n"
  },
  {
    "path": "webpack.config.js",
    "content": "\"use strict\";\nconst ExtractTextPlugin = require(\"extract-text-webpack-plugin\");\nconst failPlugin = require(\"webpack-fail-plugin\");\nconst HTMLInlineSourcePlugin = require(\"html-webpack-inline-source-plugin\");\nconst HTMLPlugin = require(\"html-webpack-plugin\");\nconst path = require(\"path\");\nconst webpack = require(\"webpack\");\n\n// Always enabled plugins\nconst plugins = [\n  // Extract CSS files to the 'bundle.css'.\n  new ExtractTextPlugin(\"build.css\"),\n  new HTMLPlugin({\n    title: \"Pen\",\n    inlineSource: \".(js|css)$\"\n  }),\n  new HTMLInlineSourcePlugin(),\n  // This plugin should be always required. See https://github.com/webpack/webpack/issues/708\n  failPlugin\n];\n\n// Production only plugins\nif (process.env.NODE_ENV === \"production\") {\n  plugins.push(\n    // Pass the 'NODE_ENV=production' environment variable to the child processes.\n    new webpack.DefinePlugin({\n      \"process.env\": { NODE_ENV: JSON.stringify(\"production\") }\n    })\n  );\n}\n\n// Configs\nmodule.exports = {\n  entry: \"./main.js\",\n  context: path.resolve(__dirname, \"src/frontend\"),\n  output: {\n    filename: \"build.js\",\n    path: path.resolve(__dirname, \"dist\")\n  },\n  module: {\n    loaders: [\n      {\n        test: /\\.css$/,\n        use: ExtractTextPlugin.extract({\n          fallback: \"style-loader\",\n          use: \"css-loader\"\n        })\n      },\n      {\n        test: /\\.json$/,\n        use: \"json-loader\"\n      },\n      {\n        test: /\\.js$/,\n        exclude: /node_modules/,\n        use: \"babel-loader\"\n      }\n    ]\n  },\n  plugins\n};\n"
  }
]